Skip to content
Victor Goff edited this page Aug 16, 2013 · 21 revisions

Roadmap: Building the command-line app

Idea for the program

A friend recently saw a picture posted on Facebook by a friend and wanted to find out where that picture was taken. The picture was of someone in a park and he could not pinpoint exactly which park it was, but had a general idea. He asked me if there was a way for him to see the location of the photo on a map.

Preamble

Before you can determine the location of a photo, the photo has to be geotagged. Geotagged basically means that the longitude and latitude of the photo has been stored in the photo metadata. The metadata is the invisible part of the photo called EXIF data. Depending on the camera, EXIF data will store the current state of the camera when the photo was taken including date and time, shutter speeds, focal lengths, flash, lens type, location data, etc.

Of course, the only way you’ll see where a picture was taken is if the camera is GPS enabled. If you have a camera that doesn’t have any type of GPS option, then there won’t be any location data in the EXIF data. This is true of most SLR cameras. However, if the photo was taken with a smartphone and location services are enabled, then the GPS coordinates of the phone will be captured when you snap a picture.

Building the app

Install the gem

We need to install a gem to read EXIF data from a given image. We shall use the exifr gem. You can read the details of the "exifr gem"

$ gem install exifr

You can run $ bundle install which will do this for you, as per the Gemfile.

Program exifmap.rb

Let's write some code to extract the latitude and longitude info from an image. We will accept the image name (stored on the computer) on the command line.

# exifmap.rb
require 'exifr'

unless ARGV.length == 1  
  puts "Usage: ruby exifmap.rb path_to_image"  
  exit  
end 

begin 
  photo = EXIFR::JPEG.new(ARGV[0])

  lat = photo.gps.latitude.to_s
  lng = photo.gps.longitude.to_s
  photo_map = "http://maps.googleapis.com/maps/api/staticmap?sensor=false&zoom=5&size=600x300&maptype=roadmap&center=#{lat},#{lng}&markers=color:blue%7Clabel:I%7C#{lat},#{lng}"
  puts photo_map

rescue NoMethodError
  puts "Sorry. Your image has no longitude and latitude info in the photo metadata." 
rescue Exception => e  
  puts e.message
end

Code Explanation

  • We accept the name of the image file (along with the location on the computer) on the command line
  • rescue NoMethodError traps an error in case the image has no latitude, longitude info
  • rescue Exception => e traps other errors like a mis-spelled image name

The Google Static Maps API lets you embed a Google Maps image on your web page without requiring JavaScript or any dynamic page loading. The Google Static Map service creates your map based on URL parameters sent through a standard HTTP request and returns the map as an image you can display on your web page.

We create the string photo_map based on the above API, as follows:

  • The base URL is http://maps.googleapis.com/maps/api/staticmap?sensor=false where applications that determine the user's location via a sensor must pass sensor=true within the Static Maps API request URL. If your application does not use a sensor, pass sensor=false. The sensor parameter is required.

The additional parameters used in photo_map are:

  • zoom (required) defines the zoom level of the map, which determines the magnification level of the map. This parameter takes a numerical value corresponding to the zoom level of the region desired. Zoom levels between 0 (the lowest zoom level, in which the entire world can be seen on one map) to 21+ (down to individual buildings) are possible within the default roadmap maps view.
  • size (required) defines the rectangular dimensions of the map image. This parameter takes a string of the form {horizontal_value}x{vertical_value}. For example, 600x300 defines a map 600 pixels wide by 300 pixels high.
  • maptype (optional) defines the type of map to construct. There are several possible maptype values, including roadmap, satellite, hybrid, and terrain.
  • center (required) defines the center of the map, equidistant from all edges of the map. This parameter takes a location as either a comma-separated {latitude,longitude} pair (e.g. "40.714728,-73.998672") or a string address (e.g. "city hall, new york, ny") identifying a unique location on the face of the earth.
  • markers (optional) define one or more markers to attach to the image at specified locations. This parameter takes a single marker definition with parameters separated by the pipe character (| or %7C).

The markers parameter takes set of value assignments (marker descriptors) of the following format: markers=markerStyles|markerLocation1| markerLocation2|... etc.

The set of markerStyles is declared at the beginning of the markers declaration and consists of zero or more style descriptors separated by the pipe character (|), followed by a set of one or more locations also separated by the pipe character (|).

The marker style descriptors contain the following key/value assignments:

  • size: (optional) specifies the size of marker from the set {tiny, mid, small}. If no size parameter is set, the marker will appear in its default (normal) size.
  • color: (optional) specifies a 24-bit color (example: color=0xFFFFCC) or a predefined color from the set {black, brown, green, purple, yellow, blue, gray, orange, red, white}.
  • label: (optional) specifies a single uppercase alphanumeric character from the set {A-Z, 0-9}.

Each marker descriptor must contain a set of one or more locations defining where to place the marker on the map. These locations may be either specified as latitude/longitude values or as addresses.

Finally, our app displays the URL which should be opened in a browser window to see the map.