clanmills logo

News

Trip to Margate
Malta Vacation
Alan's 50th Birthday
2026 Events
2025 Events

Photos

Search
   All Photo Albums
   Photos 2026
   Photos 2025

Family

All Family
   Family Tree
   Irene
   Margaret & Ian
   Robin
   Weddings

Travel

All Travel
   United Kingdom
   United States
   Europe
   World

Articles

All Articles
   My Book
   Bucket List
   Dieting
   Geotagging
   Garden in 2025
   Our House
   Syncopation

BTR Runs

   Runs 2026
   Runs 2025
   Places 2025
   Runs 2024
   Places 2024
   Places 3D 2024

Robin and Alison

Webmaster:
Robin Mills
robinwmills@gmail.com

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()