gps.py
1: #!/usr/bin/env python
2:
3: r"""gps - a program to update the exif data in images using GPX files
4:
5: This requires:
6: surd.py - surd support (rational numbers)
7: pyexiv2.py - libexiv2 support (for reading/writing EXIF data)
8:
9: To use this:
10: run gps.py in a directory of .jpg and .gpx files
11:
12: the program reads all the gpx files to construct a time dictionary
13: then it reads all the photos and updates the GPS/EXIF data to give
14: the photo the closest location in the time dictionary
15:
16: The program has to guess the difference between GPS time and camera time.
17: GPS time is UTC (GMT) time.
18: Camera time is whateve you set in the camera.
19: This program will write/read a file tzinfo with the delta info.
20: The program guesses that this will be the offset from UTC time
21: for your desktop computer! If the tzinfo exists, it will be read
22: and respected. You can use the tzinfo file to correct for
23: camera inaccuracy, incorrect timezone setting and so.
24:
25: For example for PSD (Pacific Daylight) -07:00, this will be 3600*7 = 25200 (seconds)
26: The Camera clock is 28800 seconds behind ZULU
27: If you are to the EAST of ZULU, tzinfo will be negative.
28:
29: --------------------
30: Revision information:
31: $Id: //depot/bin/gps.py#11 $
32: $Header: //depot/bin/gps.py#11 $
33: $Date: 2008/04/16 $
34: $DateTime: 2008/04/16 00:53:00 $
35: $Change: 107 $
36: $File: //depot/bin/gps.py $
37: $Revision: #11 $
38: $Author: rmills $
39: --------------------
40: """
41:
42: __author__ = "Robin Mills <robin@clanmills.com>"
43: __date__ = "$Date: 2008/04/16 $"
44: __version__ = "$Id: //depot/bin/gps.py#11 $"
45: __credits__ = """Everybody who contributed to Python.
46:
47: Especially: Guido van Rossum for creating the language.
48: And: Mark Lutz for the O'Reilly Books to explain it.
49: And: Ka-Ping Yee for the wonderful module 'pydoc'.
50:
51: Olivier Tilloy for the pyexiv2 python wrapper code
52: Andreas Huggel for the libexiv2 library
53: """
54:
55: import sys
56: import os
57: import glob
58: import surd
59: import pyexiv2
60: from time import altzone,daylight,timezone
61: import time
62: import datetime
63: import xml.dom.minidom
64:
65: ##
66: # Ration number support
67: def R(f):
68: """R(float) - get a Rational number for a float"""
69: s = surd.surd(float(f))
70: return pyexiv2.Rational(s.num,s.denom)
71:
72: def d(angle):
73: """d(any) - get degrees from a number :eg d(33.41) -> 33"""
74: return int(angle)
75:
76: def m(angle):
77: """m(any) - get minutes from a number :eg d(33.41) -> 24"""
78: return int( angle*60 - d(angle)* 60)
79:
80: def s(angle):
81: """s(any) - get seconds from a number :eg s(33.41) -> 36"""
82: return int( angle*3600 - d(angle)*3600 - m(angle)*60 )
83:
84: #
85: ##
86:
87:
88: ##
89: # dictionary search (closest match)
90: def search(dict,target):
91: """search(dict,taget) - search for closest match"""
92: s = sorted(dict.keys())
93: N = len(s)
94: low = 0
95: high = N-1
96:
97: while low < high:
98: mid = (low + high)/2
99: if s[mid] < target:
100: low = mid + 1
101: else:
102: high = mid
103: return s[low]
104: #
105: ##
106:
107: ##
108: # XML functions
109: def getXMLtimez(phototime,delta):
110: """getXMLtimez - convert a datetime to an XML formatted date"""
111: #
112: # phototime = timedate.timedate("2008-03-16 08:52:15")
113: # delta = seconds
114: # -----------------------
115:
116: timedelta = datetime.timedelta(0,delta,0)
117: newtime = phototime + timedelta
118: return newtime.strftime('%Y-%m-%dT%H:%M:%SZ') ;
119:
120: def getText(nodelist):
121: """getText(nodeList) - return the text in nodelist"""
122: rc = ""
123: for node in nodelist:
124: if node.nodeType == node.TEXT_NODE:
125: rc = rc + node.data
126: return rc
127:
128: def getNodeValue(node):
129: """getNodeValue(node) - return the value of a node"""
130: return getText(node.childNodes)
131:
132: def handleTRKPT(trkpt,timedict):
133: """handleTRKPT"""
134: ele = getNodeValue(trkpt.getElementsByTagName("ele")[0])
135: time = getNodeValue(trkpt.getElementsByTagName("time")[0])
136: lat = trkpt.getAttribute("lat")
137: lon = trkpt.getAttribute("lon")
138: # print "lat, lon = %s %s ele,time = %s %s" % ( lat,lon , ele,time )
139: timedict[time] = [ ele,lat,lon ]
140:
141: def handleTRKSEG(trkseg,timedict):
142: """handleTRKSEG"""
143: trkpts = trkseg.getElementsByTagName("trkpt")
144: for trkpt in trkpts:
145: handleTRKPT(trkpt,timedict)
146:
147: def handleTRK(trk,timedict):
148: """handleTRK"""
149: trksegs = trk.getElementsByTagName("trkseg")
150: for trkseg in trksegs:
151: handleTRKSEG(trkseg,timedict)
152:
153: def handleGPX(gpx,timedict):
154: """handleGPSX"""
155: trks = gpx.getElementsByTagName("trk")
156: for trk in trks:
157: handleTRK(trk,timedict)
158: #
159: ##
160:
161: ##
162: # read the tzinfo file to get the time offset (or use the clock)
163: def getDelta(filedict):
164: tzinfo = 'tzinfo'
165: delta = altzone if daylight == 1 else timezone
166:
167: read = False
168: try:
169: f = open(tzinfo,'r')
170: delta = int(f.readline())
171: f.close()
172: read = True
173: except:
174: read= False
175: if not read:
176: try:
177: f = open(tzinfo,'w')
178: f.writelines(str(delta))
179: f.close()
180: read = True
181: except:
182: read = False
183:
184: if read:
185: tzinfo = os.path.abspath(tzinfo)
186: filedict[tzinfo] = tzinfo
187:
188:
189: hours = float(delta) / 3600.0
190: print "delta from tz to UTC = %d:%d:%d hrs" % (d(hours),m(hours),s(hours))
191: return delta
192: #
193: ##
194:
195: ##
196: # the program
197: def gps():
198: """ gps - main entry point for program """
199:
200: filedict = {} # contains file which we find useful (*.gpx and tzinfo)
201: delta = getDelta(filedict)
202: ##
203: # build the timedict from the gpx files in this directory
204: #
205: timedict = {}
206: for path in glob.glob("*.gpx"):
207: file = open(path,"r")
208: data = file.read(os.path.getsize(path))
209: print "reading ",path
210: file.close()
211: dom = xml.dom.minidom.parseString(data)
212:
213: handleGPX(dom,timedict)
214:
215: path = os.path.abspath(path)
216: filedict[path] = path ;
217:
218: print "number of timepoints = ",len(timedict)
219:
220: ##
221: # fild all the images in this this directory
222: if len(timedict):
223: image = 0
224: firstTime = True
225: imagedict = {}
226: for path in glob.glob("*"):
227: path = os.path.abspath(path)
228: if os.path.isfile(path) and not filedict.has_key(path):
229: try:
230: image = pyexiv2.Image(path)
231: image.readMetadata()
232: timestamp = image['Exif.Image.DateTime']
233: xmlDate = getXMLtimez(timestamp,delta)
234: imagedict[xmlDate] = path
235: except:
236: print "*** unable to work with ",path , " ***"
237:
238: for xmlDate in sorted(imagedict.keys()):
239: path = imagedict[xmlDate] ;
240: try:
241: image = pyexiv2.Image(path)
242: image.readMetadata()
243: stamp = str(getXMLtimez(image['Exif.Image.DateTime'],delta))
244:
245: timestamp = search(timedict,stamp)
246: data = timedict[timestamp]
247: ele = float(data[0])
248: lat = float(data[1])
249: lon = float(data[2])
250:
251: latR = 'N'
252: lonR = 'E'
253: eleR = 0
254: if lat < 0:
255: lat = -lat
256: latR= 'S'
257: if lon < 0:
258: lon = -lon
259: lonR= 'W'
260: if ele < 0:
261: ele = -ele
262: eleR= 1
263: if firstTime:
264: print "camera time nearest gps latitude longitude elev photofile"
265: firstTime = False
266: slat = "%02d.%02d'" '%02d"%s' % (d(lat),m(lat),s(lat),latR )
267: slon = "%02d.%02d'" '%02d"%s' % (d(lon),m(lon),s(lon),lonR )
268: sele = "%6.1f" % (ele)
269: print "%s %s %s %s %s %s" % ( stamp,timestamp,slat,slon,sele,path )
270:
271: # get Rational number for ele
272: # don't why R(ele) is causing trouble!
273: # it might be that the denominator is overflowing 32 bits!
274: # and this would also import lat and lon
275: rele = pyexiv2.Rational(int(ele*10.0),10)
276:
277: image['Exif.GPSInfo.GPSAltitude' ] = rele
278: image['Exif.GPSInfo.GPSAltitudeRef' ] = eleR
279: image['Exif.GPSInfo.GPSDateStamp' ] = stamp
280: image['Exif.GPSInfo.GPSLatitude' ] = [R(d(lat)),R(m(lat)),R(s(lat))]
281: image['Exif.GPSInfo.GPSLatitudeRef' ] = latR
282: image['Exif.GPSInfo.GPSLongitude' ] = [R(d(lon)),R(m(lon)),R(s(lon))]
283: image['Exif.GPSInfo.GPSLongitudeRef' ] = lonR
284: image['Exif.GPSInfo.GPSMapDatum' ] = 'WGS-84'
285: image['Exif.GPSInfo.GPSProcessingMethod' ] = '65 83 67 73 73 0 0 0 72 89 66 82 73 68 45 70 73 88 '
286: image['Exif.GPSInfo.GPSTimeStamp' ] = [R(10),R(20),R(30)]
287: image['Exif.GPSInfo.GPSVersionID' ] = '2 2 0 0'
288: image.writeMetadata()
289: except:
290: print "*** problem with ",path , " ***"
291:
292: image = 0
293:
294: # That's all folks!
295: ##
296: #
297: ##
298:
299: if __name__ == '__main__':
300: gps()