Back in October I wrote a little script that added GPS location information to photos. My idea was to be able to take one photo with my iPhone, which would capture the location in its EXIF metadata, and use the script to transfer that information to all the photos I took with my regular camera at that same location. It works well, and I’ve been using it ever since, but once I had a bunch of photos with location info in them, I needed a tool that would do the converse: show me where they were taken. It turned out to be very easy to write.

The October program is called coordinate, and I use it like this:

coordinate -g iphone-photo.jpg IMG*


This reads the GPS metadata from the iphone-photo.jpg file and writes it to all the files that start with IMG. The new program is called map, and when I call it,

map photo.jpg


it reads the GPS info from the image file and opens Google Maps in my browser with a marker at the photo’s location.

Here’s the code for map:

python:
1:  #!/usr/bin/python
2:
3:  import pyexiv2
4:  import sys
5:  import subprocess
6:
7:  try:
8:    # Open the photo file.
9:    photo = sys.argv[1]
12:
13:    # Read the GPS info.
14:    latref = md['Exif.GPSInfo.GPSLatitudeRef'].value
15:    lat = md['Exif.GPSInfo.GPSLatitude'].value
16:    lonref = md['Exif.GPSInfo.GPSLongitudeRef'].value
17:    lon = md['Exif.GPSInfo.GPSLongitude'].value
18:
19:  except:
20:    print "No GPS info in file %s" % photo
21:    sys.exit()
22:
23:  # Convert the latitude and longitude to signed floating point values.
24:  latitude = float(lat[0]) + float(lat[1])/60 + float(lat[2])/3600
25:  longitude = float(lon[0]) + float(lon[1])/60 + float(lon[2])/3600
26:  if latref == 'S': latitude = -latitude
27:  if lonref == 'W': longitude = -longitude
28:
29:  # Construct the Google Maps query and open it.
30:  query = "http://maps.google.com/maps?q=loc:%.6f,%.6f" % (latitude, longitude)
31:  subprocess.call(['open', query])


It uses the standard sys and subprocess libraries and the distinctly non-standard pyexiv2 library. Installing pyexiv2 isn’t the hardest thing in the world, but it takes more than a simple pip pyexiv2. I have a writeup on how to do it via Homebrew if you’re interested.

The script is easy to read, I think. Lines 9-17 get the name of the file from the command line, open it, and extract the GPS information. If there’s a problem in any of those steps, an exception is raised, and Lines 20-21 print an error message and quit.

Because the latitude and longitude are returned in an odd format (a list of three Fractions, one each for degrees, minutes, and seconds), Lines 24-27 are needed to put them in a format we can use to query Google Maps. The query is then constructed in Line 30, and Line 31 sends it to OS X’s wonderfully useful open command to be opened in the default browser. The Google Maps API automatically drops a marker at the location and centers the map around the marker.

I could extend the query with more parameters to set the zoom level, force the display to satellite, map, or street view, and so on, but I prefer to stick with the defaults.

There are some obvious improvements I could make:

• Add an option to use Bing instead of Google Maps. With aerial photos, Bing’s oblique bird’s eye view is often more useful than Google’s top view. I haven’t looked yet, but I assume Bing can be queried through the URL just like Google Maps can.
• Rewrite it for a library that’s easier to install than pyexiv2. This wouldn’t affect me, but would make it easier for others to use.
• Turn it into a Service. This is a trivial change that would produce a big improvement in usability. With a Service, I could just right-click on an image in the Finder and get the location by choosing an item from the popup menu.

If you’re wondering why I don’t just use iPhoto’s built-in mapping tools, it’s because I don’t use iPhoto. I find it very clunky and slow, and its organization system doesn’t work for the photos I take for my job (which is most of my photos). The map script gives me a quick way to check the location of any photo, regardless of where it’s stored on my computer.

subprocess.call(['/path/to/google-chrome', query])