Automatic W-9s with PDFpen » « Blackbird.py and background images

Setting my coordinate

January 30th, 2012 at 10:15 pm by Dr. Drang

Too many posts of my retuning of old scripts. This is another one, but I’ll try to make it the last one for a while.

Today I was going to use my coordinate script to set the location of several dozen photos I took with a standard, non-GPS-equipped camera. I’d taken a photo in the same location with my iPhone, and I intended to use the command

coordinate -g iphone.jpg IMG*

to take the GPS location embedded in the iPhone photo and put it in the other photos. In fact, the only reason I took the iPhone photo was to use it to extract the GPS info. Well, this scheme went agley. Apparently, I took the iPhone photo before its GPS had figured out where it was, so the coordinates in it were useless.

I shifted to my backup plan: get the location from Google Maps and use the less automated form of coordinate, where I include the latitude and longitude explicitly on the command line. Unfortunately, I had written coordinate to expect the input in dd:mm:ss.ss format, not the decimal degrees format that Google gives you when you use its “Drop LatLng Marker” feature.1

spacer

It’s not the hardest thing in the world to convert from decimal degrees to degrees, minutes, and seconds, but it seemed like something coordinate should do for me.

Now it does. Here’s the new 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:  from math import modf
  9:  
 10:  usage = """Usage: coordinate [options] [files]
 11:  
 12:  Options:
 13:    -g filename    photo file with GPS coordinates
 14:    -n angle       north coordinate
 15:    -s angle       south coordinate
 16:    -e angle       east coordinate
 17:    -w angle       west coordinate
 18:    -h             show this help message
 19:  
 20:  Add location metadata to each of the listed files. The location
 21:  can come from either the photo associated with the -g option or
 22:  with a -n, -s, -e, -w pair given on the command line. The angles
 23:  can be in either d.dddddd or dd:mm:ss.sss format."""
 24:  
 25:  # Functions for manipulating coordinates.
 26:  def makecoord(coordstring):
 27:    """Make a coordinate list from a coordinate string.
 28:    
 29:    The string can be either "d.ddddd" or "dd:mm:ss.ss", and the
 30:    list comprises three Fractions."""
 31:    
 32:    if ':' not in coordstring:
 33:      theta = float(coordstring)
 34:      m, d = modf(theta)
 35:      s, m = modf(m*60)
 36:      s = s*60
 37:      angle = [ str(x) for x in [d, m, s] ]
 38:    else:
 39:      angle = coordstring.split(':', 2)
 40:    
 41:    loc = [ Fraction(x).limit_denominator(1000) for x in angle ]
 42:    return loc
 43:    
 44:  def setcoord(metadata, direction, coordinate):
 45:    """Set the latitude or longitude coordinate.
 46:    
 47:    Latitude is set if direction is 'N' or 'S', longitude if 'E' or 'W'.
 48:    The coordinate is a list of the form [dd, mm, ss], where the degrees,
 49:    minutes, and seconds are Fractions."""
 50:    
 51:    tags = {'lat': ('Exif.GPSInfo.GPSLatitudeRef', 'Exif.GPSInfo.GPSLatitude'),
 52:            'lon': ('Exif.GPSInfo.GPSLongitudeRef', 'Exif.GPSInfo.GPSLongitude')}
 53:    if direction in ('N', 'S'):
 54:      coord = 'lat'
 55:    else:
 56:      coord = 'lon'
 57:    metadata[tags[coord][0]] = direction
 58:    metadata[tags[coord][1]] = coordinate
 59:  
 60:  
 61:  # Get the command line options.
 62:  try:
 63:    options, filenames = getopt.getopt(sys.argv[1:], 'g:n:s:e:w:h')
 64:  except getopt.GetoptError, err:
 65:    print str(err)
 66:    sys.exit(2)
 67:  
 68:  # Set the option values.
 69:  gpsphoto = north = south = east = west = False       # defaults
 70:  for o, a in options:
 71:    if o == '-g':
 72:      gpsphoto = a
 73:    elif o == '-n':
 74:      north = makecoord(a)
 75:    elif o == '-s':
 76:      south = makecoord(a)
 77:    elif o == '-e':
 78:      east = makecoord(a)
 79:    elif o == '-w':
 80:      west = makecoord(a)
 81:    else:
 82:      print usage
 83:      sys.exit()
 84:  
 85:  
 86:  # Valid option combinations.
 87:  ne = (north and east) and not (south or west or gpsphoto)
 88:  nw = (north and west) and not (south or east or gpsphoto)
 89:  se = (south and east) and not (north or west or gpsphoto)
 90:  sw = (south and west) and not (north or east or gpsphoto)
 91:  gps = gpsphoto and not (north or south or east or west)
 92:  
 93:  if not (ne or nw or se or sw or gps):
 94:    print "invalid location"
 95:    sys.exit()
 96:  
 97:  
 98:  # Create the coordinate setter functions.
 99:  if ne:
100:    setlat = partial(setcoord, direction='N', coordinate=north)
101:    setlon = partial(setcoord, direction='E', coordinate=east)
102:  elif nw:
103:    setlat = partial(setcoord, direction='N', coordinate=north)
104:    setlon = partial(setcoord, direction='W', coordinate=west)
105:  elif se:
106:    setlat = partial(setcoord, direction='S', coordinate=south)
107:    setlon = partial(setcoord, direction='E', coordinate=east)
108:  elif sw:
109:    setlat = partial(setcoord, direction='S', coordinate=south)
110:    setlon = partial(setcoord, direction='W', coordinate=west)
111:  elif gps:
112:    basemd = pyexiv2.ImageMetadata(gpsphoto)
113:    basemd.read()
114:    latref = basemd['Exif.GPSInfo.GPSLatitudeRef']
115:    lat = basemd['Exif.GPSInfo.GPSLatitude']
116:    lonref = basemd['Exif.GPSInfo.GPSLongitudeRef']
117:    lon = basemd['Exif.GPSInfo.GPSLongitude']
118:    setlat = partial(setcoord, direction=latref.value, coordinate=lat.value)
119:    setlon = partial(setcoord, direction=lonref.value, coordinate=lon.value)
120:  else:
121:    print "coordinate setter failed"
122:    sys.exit()
123:  
124:  # Cycle through the files.
125:  for f in filenames:
126:    md = pyexiv2.ImageMetadata(f)
127:    md.read()
128:    setlat(md)
129:    setlon(md)
130:    md.write()

The new parts are in the makecoord function, Lines 26-42. If the coordinate given on the command line doesn’t include a colon, it’s assumed to be in decimal degrees, and Lines 33-37 turn it into a three-item list of strings appropriate for the conversion to Fractions that occurs on Line 41. If the coordinate does include a colon, it’s treated as before on Line 39.

I used a new (to me) function: modf from the math library. It takes a floating point number as its argument and returns the fractional and integer parts as a tuple. Strangely, the fractional part comes before the integer part in the tuple. I assumed it was the other way around when I first wrote Lines 34-35, which led to some surprising results.

Now I can copy the coordinates from a LatLng marker and, with just a little editing on the command line, use them directly in the coordinate command:

coordinate -n 41.87658 -w 87.61912 *.jpg

I should have written the script this way in the first place.


  1. You know about that feature, right? You put the mouse pointer where you want the coordinates and right-click. A popup of options appears, one of which is “Drop LatLng Marker.” The result is the little flag you see in the screenshot.

    Update As Sven says in the comments, this isn’t a standard feature. You need to enable it in the Maps Labs settings (accessed through the gear icon in the upper right corner of the screen when you’re visiting Google Maps). ↩


Tags: exif, photos, programming, python
Post #1751 | Comment via Twitter | --> 2 Comments

2 Responses to “Setting my coordinate”

  1. spacer Sven Lütkemeier says:
    January 31st, 2012 at 1:47 pm

    Just a quick note: “Drop LatLng Marker” was missing in my Google Maps. Had to activate it in “Maps Labs”.

  2. spacer Dr. Drang says:
    January 31st, 2012 at 4:57 pm

    Thanks, Sven, I’ll add a note to the post. I added it to my settings so long ago, I forgot it wasn’t standard.

Leave a Reply


Comments can be in plain text or elementary Markdown. Some comments will be moderated, so don't be surprised if your remarks aren't posted immediately. Comments that just say "Nice post," or something like that, may be flagged as spam and never posted. Abusive comments will be deleted.

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.