January 27, 2012 at 9:18 PM by Dr. Drang
Following up on this morning’s post, I’ve expanded my
map script to include an option for using Bing Maps to display the location of a photograph, and I’ve created a Service so I can get a map by right-clicking on a photo in the Finder and choosing “Photo location” from the contextual menu.
Here’s the updated
python: 1: #!/usr/bin/python 2: 3: import pyexiv2 4: import sys 5: import subprocess 6: import getopt 7: 8: usage = """Usage: map [option] file 9: 10: Options: 11: -b use Bing instead of Google Maps 12: -h show this help message 13: 14: Get the GPS data from the given image file and open a map to that 15: location in the user's default browser.""" 16: 17: # Parse the options. 18: try: 19: options, photos = getopt.getopt(sys.argv[1:], 'bh') 20: except getopt.GetoptError, err: 21: print str(err) 22: sys.exit(2) 23: 24: # Set the option values. 25: engine = 'google' # default 26: for o, a in options: 27: if o == '-b': 28: engine = 'bing' 29: else: 30: print usage 31: sys.exit() 32: 33: try: 34: # Open the photo file. 35: md = pyexiv2.ImageMetadata(photos) 36: md.read() 37: except: 38: print usage 39: sys.exit() 40: 41: try: 42: # Read the GPS info. 43: latref = md['Exif.GPSInfo.GPSLatitudeRef'].value 44: lat = md['Exif.GPSInfo.GPSLatitude'].value 45: lonref = md['Exif.GPSInfo.GPSLongitudeRef'].value 46: lon = md['Exif.GPSInfo.GPSLongitude'].value 47: except KeyError: 48: print "No GPS data for %s" % photos 49: sys.exit(1) 50: 51: # Convert the latitude and longitude to signed floating point values. 52: latitude = float(lat) + float(lat)/60 + float(lat)/3600 53: longitude = float(lon) + float(lon)/60 + float(lon)/3600 54: if latref == 'S': latitude = -latitude 55: if lonref == 'W': longitude = -longitude 56: 57: # Construct the Google Maps or Bing Maps query and open it. 58: if engine == 'bing': 59: query = "http://www.bing.com/maps/?v=2&where1=%.6f,%.6f" % (latitude, longitude) 60: else: 61: query = "http://maps.google.com/maps?q=loc:%.6f,%.6f" % (latitude, longitude) 62: subprocess.call(['open', query])
It’s quite a bit longer than before, mostly because of the option handling code in Lines 17-31 and the usage message in Lines 8-15. I’ve also separated the error handling into two
try/except clauses, one for dealing with errors in opening the image file and the other for handling files with no location. (Note also that Line 49 has
map return a nonzero result code if there’s no GPS info. We’ll use that in the service.)
Otherwise, it’s basically the same script. Use
to open a Google Maps view of the photo’s location in your default browser. Use
map -b photo.jpg
to open the location in Bing Maps instead.
The “Photo location” service is defined this way in Automator:
It’s defined only for image files selected in the Finder and runs the following shell script:
bash: 1: for f in "$@" 2: do 3: ~/bin/map "$f" 4: if [ $? -ne 0 ]; then 5: say "No location" 6: fi 7: done
For every selected image file, it runs the
map program. If the
map command in Line 3 returns a nonzero result code,
$?—which it will if the image file has no location metadata—the script will say “No location” in the user’s default voice. If you want to use this service, but find the talking a bit too cute, you can change Line 5 to
bash: 5: osascript -e "beep 1"
and it will sound the system alert if the file has no GPS data. If you’d rather use Bing, slip a
-b after the
map in Line 3. Or you could make two services, one that uses Google and one that uses Bing.
With these improvements, I think I’m done. I have a tool that works the way I want on my computers. I could rewrite
map to use an EXIF library that’s easier to install than
pyexiv2, possibly switching to Perl or Ruby in the process, but since I managed to get
pyexiv2 installed on my machines, I’d get no benefit out of doing so. But if someone else takes this code and rewrites if for another library, I’d love to hear about it.