Location, location, location
October 10, 2011 at 11:14 PM by Dr. Drang
My regular camera is a Canon G10, a nice little compact that sits between an elementary point-and-shoot and a DSLR. My iPhone 4’s camera is fine if the lighting is good, and there’s the “the best camera is the one with you” factor, but its images really don’t compare to the G10’s. The one big advantage the iPhone has is that its photos have the GPS location baked into the EXIF metadata. I’ve often wished I could pull the GPS info from the phone and stick it into the G10. Last night I wrote a script that kinda sorta does that.
The script is called coordinate, and it works like this: While I’m taking a bunch of photos at a spot with the G10, I take a single photo with the iPhone. When I have all the images transferred to my computer I run
coordinate -g iphone.jpg IMG*
and the GPS data is read from the iPhone image (iphone.jpg
) and copied to all the files from the G10 (IMG*
). Boom.
I know that iPhoto and Flickr have interactive maps for adding location info to photos, but they’re slow and clumsy (especially iPhoto’s) compared to pulling the data directly from another image file.
Update 10/11/11
Ryan Gray in the comments points out that you can copy and paste location information from image to image within iPhoto. A very useful feature (which I never ran across) if you use iPhoto.
The source of the location info doesn’t have to be an iPhone, of course—it doesn’t even have to be your own camera phone. Is someone with you? Ask her to take a photo and email it too you.
And if you happen to have the latitude and longitude, coordinate has options for that, too. For example:
coordinate -n 41:53:03.96 -w 87:37:39.96 IMG*
will put your photos under the Marshall Field’s Macy’s Marshall Field’s clock at State and Randolph.
Coordinate is written in Python and relies on the pyexiv2 library, which binds to the C++ exiv2 library. Installing pyexiv2 on a Mac is kind of a pain in the ass (as described here); there are prebuilt installers for Windows and some flavors of Linux. Here’s the code:
python:
1: #!/usr/bin/env python
2:
3: import pyexiv2
4: import getopt
5: import sys
6: from fractions import Fraction
7: from functools import partial
8:
9: usage = """Usage: coordinate [options] [files]
10:
11: Options:
12: -g filename photo file with GPS coordinates
13: -n ddd:mm:ss.ss north coordinate
14: -s ddd:mm:ss.ss south coordinate
15: -e ddd:mm:ss.ss east coordinate
16: -w ddd:mm:ss.ss west coordinate
17: -h show this help message
18:
19: Add location metadata to each of the listed files. The location
20: can come from either the photo associated with the -g option or
21: with a -n, -s, -e, -w pair given on the command line."""
22:
23: # Functions for manipulating coordinates.
24: def makecoord(coordstring):
25: """Make a coordinate list from a coordinate string.
26:
27: The string is of the form ddd:mm:ss.ss and the list comprises
28: three Fractions."""
29:
30: angle = coordstring.split(':', 2)
31: loc = [ Fraction(x).limit_denominator(1000) for x in angle ]
32: return loc
33:
34: def setcoord(metadata, direction, coordinate):
35: """Set the latitude or longitude coordinate.
36:
37: Latitude is set if direction is 'N' or 'S', longitude if 'E' or 'W'.
38: The coordinate is a list of the form [dd, mm, ss], where the degrees,
39: minutes, and seconds are Fractions."""
40:
41: tags = {'lat': ('Exif.GPSInfo.GPSLatitudeRef', 'Exif.GPSInfo.GPSLatitude'),
42: 'lon': ('Exif.GPSInfo.GPSLongitudeRef', 'Exif.GPSInfo.GPSLongitude')}
43: if direction in ('N', 'S'):
44: coord = 'lat'
45: else:
46: coord = 'lon'
47: metadata[tags[coord][0]] = direction
48: metadata[tags[coord][1]] = coordinate
49:
50:
51: # Get the command line options.
52: try:
53: options, filenames = getopt.getopt(sys.argv[1:], 'g:n:s:e:w:h')
54: except getopt.GetoptError, err:
55: print str(err)
56: sys.exit(2)
57:
58: # Set the option values.
59: gpsphoto = north = south = east = west = False # defaults
60: for o, a in options:
61: if o == '-g':
62: gpsphoto = a
63: elif o == '-n':
64: north = makecoord(a)
65: elif o == '-s':
66: south = makecoord(a)
67: elif o == '-e':
68: east = makecoord(a)
69: elif o == '-w':
70: west = makecoord(a)
71: else:
72: print usage
73: sys.exit()
74:
75: # Valid option combinations.
76: ne = (north and east) and not (south or west or gpsphoto)
77: nw = (north and west) and not (south or east or gpsphoto)
78: se = (south and east) and not (north or west or gpsphoto)
79: sw = (south and west) and not (north or east or gpsphoto)
80: gps = gpsphoto and not (north or south or east or west)
81:
82: if not (ne or nw or se or sw or gps):
83: print "invalid location"
84: sys.exit()
85:
86:
87: # Create the coordinate setter functions.
88: if ne:
89: setlat = partial(setcoord, direction='N', coordinate=north)
90: setlon = partial(setcoord, direction='E', coordinate=east)
91: elif nw:
92: setlat = partial(setcoord, direction='N', coordinate=north)
93: setlon = partial(setcoord, direction='W', coordinate=west)
94: elif se:
95: setlat = partial(setcoord, direction='S', coordinate=south)
96: setlon = partial(setcoord, direction='E', coordinate=east)
97: elif sw:
98: setlat = partial(setcoord, direction='S', coordinate=south)
99: setlon = partial(setcoord, direction='W', coordinate=west)
100: elif gps:
101: basemd = pyexiv2.ImageMetadata(gpsphoto)
102: basemd.read()
103: latref = basemd['Exif.GPSInfo.GPSLatitudeRef']
104: lat = basemd['Exif.GPSInfo.GPSLatitude']
105: lonref = basemd['Exif.GPSInfo.GPSLongitudeRef']
106: lon = basemd['Exif.GPSInfo.GPSLongitude']
107: setlat = partial(setcoord, direction=latref.value, coordinate=lat.value)
108: setlon = partial(setcoord, direction=lonref.value, coordinate=lon.value)
109: else:
110: print "coordinate setter failed"
111: sys.exit()
112:
113: # Cycle through the files.
114: for f in filenames:
115: md = pyexiv2.ImageMetadata(f)
116: md.read()
117: setlat(md)
118: setlon(md)
119: md.write()
Most of coordinate’s 119 lines deal with the options that allow you to enter the coordinates on the command line. I don’t really expect to use it that way very often, but I thought it was important to have that capability.
The makecoord
function on Lines 24-32 takes a string in the form ddd:mm:ss.ss
and turns it into the form needed by pyexiv. It’s a weird format: a list of three items, one each for degrees, minutes, and seconds, each expressed as a Fraction.
The setcoord
function on Lines 34-48 puts the given coordinate information in the appropriate EXIF fields in the metadata. The Exif.GPSInfo.GPSLatitudeRef
field is a string that can be either 'N'
or 'S'
. Similarly, the Exif.GPSInfo.GPSLongitudeRef
can be either 'E'
or 'W'
. The Exif.GPSInfo.GPSLatitude
and Exif.GPSInfo.GPSLongitude
fields are lists of Fractions, as returned by makecoord
.
The command line options are read and interpreted in Lines 59-84. Apart from the help option, -h
, there are five possible command line switches. The -g
switch is used if you’re pulling the GPS info from an existing photo file. The -n
, -s
, -e
, and -w
options are used if you’re entering the coordinate data directly. Lines 76-80 make sure that the options you’ve entered make sense—you can’t, for example, give both a north and a south latitude.
Lines 88-111 are my favorite part of the script because they use a high-level, Lispy feature of Python’s functools library. The library’s partial
feature allows you to use a previously defined function as a template for making a new function. The new function behaves like the template but has fewer arguments because some of the arguments are baked into its definition. Here, the setlat
and setlon
functions are created on the fly from the setcoord
function and the given coordinate information. Both setlat
and setlon
have only one argument: the metadata for the photo file being updated.
The advantage of partial
is that it separates the branching (Lines 118-111) from the loop through the files (Lines 114-119). Without it, the structure of this part of the script would be
for f in filenames:
md = pyexiv2.ImageMetadata(f)
md.read()
if ne:
setcoord north
setcoord east
elif nw:
setcoord north
setcoord west
elif se:
setcoord south
setcoord east
elif sw:
set coord south
set coord west
elif gps:
gpsmd = pyexiv2.ImageMetadata(gpsphoto)
gpsmd.read()
setcoord latitude
setcoord longitude
else:
error handling
md.write()
For situations in which the coordinates are specified on the command line, this isn’t much different from functools
solution. But when the location is taken from a photo—which I expect to be the usual case—this structure opens and reads the photo with the location many times instead of just once. So the use of functools
wasn’t just fun, it was a more efficient solution.
I love this script. It was both fun and surprisingly easy to write once I had the pyexiv2 library in place. And there’s something magical about adding a location to dozens of files at once with just a single short command.