Changing coordinates of geotagged photos

This script will take as an input a folder of images, and a shapefile. The shapefile needs to be attributed with the image name, and the lat and long of the point. The script will write to the EXIF tag of the image the lat and long from the attribute table. After execution the image location will be the same as the point location. The script requires the pyexiv2 library and therefore, only works with Python 2.7 and ArcGIS 10.x. I will port it to Python 3 if anyone asks. Make a backup of your data before you run this code!

import pyexiv2, traceback, sys, arcpy
#the folder of images...
inFolder = "C:/gTemp/WriteEXIFtest/sfnooksack_lo"
#The points that show the correct image locations...
inFC = 'C:/gTemp/WriteEXIFtest/sfnooksack_lo.shp'
#the shapefile needs to have attributes with the image name, lat and long (as DD)
fields = ['IMAGE', 'lat', 'long']
try:
    def ddlongtodms(dd):
        #converts decimal degrees to degrees minutes and seconds.
        if dd < 0:
            dd = abs(dd)
            GPSLongitudeRef = 'W'
        else:
            GPSLongitudeRef = 'E'
        minutes,seconds = divmod(dd*3600,60)
        degrees,minutes = divmod(minutes,60)
        return (GPSLongitudeRef,int(degrees),int(minutes),seconds)
    def ddlattodms(dd):
        #converts decimal degrees to degrees minutes and seconds.
        if dd < 0:
            dd = abs(dd)
            GPSLatitudeRef = 'S'
        else:
            GPSLatitudeRef = 'N'
        minutes,seconds = divmod(dd*3600,60)
        degrees,minutes = divmod(minutes,60)
        return (GPSLatitudeRef,int(degrees),int(minutes),seconds)
    with arcpy.da.SearchCursor(inFC, fields) as cursor:
        for row in cursor:
            #in this case the image name does not have a .jpg suffix so I add it here...
            imagePath = inFolder +'/'+row[0]+'.jpg'
            print imagePath
            pointLong = ddlongtodms(row[2])
            pointLat = ddlattodms(row[1])
            print pointLong
            print pointLat
            try:
                metadata = pyexiv2.ImageMetadata(imagePath)
                metadata.read()
                latitude = [pyexiv2.Rational(pointLat[1],1),pyexiv2.Rational(pointLat[2], 1),pyexiv2.Rational(int(pointLat[3]*100000), 100000)]
                longitude = [pyexiv2.Rational(pointLong[1],1),pyexiv2.Rational(pointLong[2], 1),pyexiv2.Rational(int(pointLong[3]*100000), 100000)]
                metadata['Exif.GPSInfo.GPSLatitude'] = latitude
                metadata.write()
                metadata['Exif.GPSInfo.GPSLongitude'] = longitude
                metadata.write()
                metadata['Exif.GPSInfo.GPSLatitudeRef'] = pointLat[0]
                metadata.write()
                metadata['Exif.GPSInfo.GPSLongitudeRef'] = pointLong[0]
                metadata.write()
            except:
                print "something bad happened with this file..."
    print "Done"
except:
    tb = sys.exc_info()[2]
    tbinfo = traceback.format_tb(tb)[0]
    pymsg = "PYTHON ERRORS:\nTraceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
    print pymsg + "\n"

And this should work for Python 3 but the Python interpreter is locked in a walled garden in ArcPro. ESRI may have blocked the ability to add the pyexiv2 library. In that case you may need to make a clone.

import pyexiv2, traceback, sys, arcpy
#the folder of images...
inFolder = "C:/gTemp/WriteEXIFtest/sfnooksack_lo"
#The points that show the correct image locations...
inFC = 'C:/gTemp/WriteEXIFtest/sfnooksack_lo.shp'
#the shapefile needs to have attributes with the image name, lat and long (as DD)
fields = ['IMAGE', 'lat', 'long']
try:
    def ddlongtodms(dd):
        #converts decimal degrees to degrees minutes and seconds.
        if dd < 0:
            dd = abs(dd)
            GPSLongitudeRef = 'W'
        else:
            GPSLongitudeRef = 'E'
        minutes,seconds = divmod(dd*3600,60)
        degrees,minutes = divmod(minutes,60)
        return (GPSLongitudeRef,int(degrees),int(minutes),seconds)
    def ddlattodms(dd):
        #converts decimal degrees to degrees minutes and seconds.
        if dd < 0:
            dd = abs(dd)
            GPSLatitudeRef = 'S'
        else:
            GPSLatitudeRef = 'N'
        minutes,seconds = divmod(dd*3600,60)
        degrees,minutes = divmod(minutes,60)
        return (GPSLatitudeRef,int(degrees),int(minutes),seconds)
    with arcpy.da.SearchCursor(inFC, fields) as cursor:
        for row in cursor:
            #in this case the image name does not have a .jpg suffix so I add it here...
            imagePath = inFolder +'/'+row[0]+'.jpg'
            pointLong = ddlongtodms(row[2])
            pointLat = ddlattodms(row[1])
                        try:
                            metadata = pyexiv2.ImageMetadata(imagePath)
                            metadata.read()
                            latitude = [pyexiv2.Rational(pointLat[1],1),pyexiv2.Rational(pointLat[2], 1),pyexiv2.Rational(int(pointLat[3]*100000), 100000)]
                            longitude = [pyexiv2.Rational(pointLong[1],1),pyexiv2.Rational(pointLong[2], 1),pyexiv2.Rational(int(pointLong[3]*100000), 100000)]
                            metadata['Exif.GPSInfo.GPSLatitude'] = latitude
                            metadata.write()
                            metadata['Exif.GPSInfo.GPSLongitude'] = longitude
                            metadata.write()
                            metadata['Exif.GPSInfo.GPSLatitudeRef'] = pointLat[0]
                            metadata.write()
                            metadata['Exif.GPSInfo.GPSLongitudeRef'] = pointLong[0]
                            metadata.write()
                        except:
                            print ("something bad happened with this file...")
                print ("Done")
            except:
                tb = sys.exc_info()[2]
                tbinfo = traceback.format_tb(tb)[0]
                pymsg = "PYTHON ERRORS:\nTraceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
                print (pymsg + "\n") 

I recomend JOSM with plugins 'photo_geotagging' and 'photoadjust'.

  1. Start JOSM
  2. F12 --> Settings --> Install plugins
  3. Layers --> OpenStreetMap Carto
  4. Drag-and-drop all your photos
  5. Activate photo layer. Click on photo. Shift + Mouse Click: move photo, Ctrl + Mouse Click: set GPSImgDirection tag enter image description here
  6. Photos layer --> Context menu --> Write coordinates to image header --> check "Change file modification time - to previous value"

enter image description here This software has much faster GUI than GeoSetter.