Monday, 18 March 2013

Putting geotagged photos into Google Earth with Python

The final product. You click on a pin and a balloon with the
image taken at that location pops up.
So I got a bunch of pictures at work that turned out to be Geotagged. "Pretty cool" I thought to myself, "but clearly I need to see these on a map to know where they were taken!" Deciding to leap before actually looking, I missed the fact that Picassa can generate KMZ files for use in Google Earth directly, and instead went about making a script to do it manually. Doing it like this does have the slight advantage of being able to control the output KMZ much more precisely - so you can have bigger pictures or different icons or show different info in the balloons.

To do this, I used a couple of third party libraries for Python. Simplekml is a package that lets you write kml and kmz files. Although there were a couple of things that seemed to be bugs, it worked pretty well.
To extract the GPS coordinates, I needed a package to read exif data. At first I tried to use PIL, but I couldn't get it to work on my work computer (didn't have a c compiler set up correctly) so I ended up using pyexiv2. The page said it was deprecated and I should use Gexiv2 instead. That seemed to add more complications, so I just used pyexiv and it worked fine (note I did this in Python 2.7, if I had used Python 3.x pyexiv2 wouldn't have worked).

This outputs a KMZ file which includes all the pictures zipped up within it. In other words, it's making a copy of all your pictures in a file that could get pretty big if you have lots of them. Anyway, without further ado, here is the script that goes through a given folder and generates a kmz file with the locations of each.

import os, simplekml, datetime as dt, pyexiv2 as pex

path = r'C:\Users\aschmitt\Desktop\testpics'
OutputFile = "Map with Sweetcroft Pictures.kmz"

def GetLatLong(fn):
    metadata = pex.ImageMetadata(fn)
    metadata.read()
    Lat = metadata['Exif.GPSInfo.GPSLatitude'].human_value
    Long = metadata['Exif.GPSInfo.GPSLongitude'].human_value
    Alt = metadata['Exif.GPSInfo.GPSAltitude'].human_value
    DateTime = metadata['Exif.Image.DateTime'].human_value #string
    DT = dt.datetime.strptime(DateTime, '%Y:%m:%d %H:%M:%S') #Datetime object
    return (Lat, Long, Alt, DT)

def FormatLatLong(LatLongString):
    d = LatLongString.find('deg')
    Degrees = float(LatLongString[:d])
    m = LatLongString.find("'")
    Minutes = float(LatLongString[d+4:m])
    s = LatLongString.find('"')
    Seconds = float(LatLongString[m+2:s])
    
    decCoords = Degrees + Minutes/60.0 + Seconds/3600.0
    return decCoords
                  
    
kml = simplekml.Kml()
for (dirpath, dirnames, filenames) in os.walk(path):
    for filename in filenames:
        fullpath = os.path.join(dirpath, filename)
        try:
            Lat, Long, Alt, DT = GetLatLong(fullpath)
        except:
            Lat, Long, Alt, DT = (None, None, None, None)
        print '%s: Lat: %s, Long: %s, Alt: %s' % (fullpath, Lat, Long, Alt)
        if Lat:
            x, y = (FormatLatLong(Lat), FormatLatLong(Long))
            point = kml.newpoint(name = filename , coords = [(y,x)])
            picpath = kml.addfile(fullpath)
            print picpath
            fn = 'files/'+ os.path.splitext(filename)[0] + '.jpg' #Note: will not work if .JPG is used, must be lower case.
            balstylestring = "< ![CDATA[

Date: " + DT.strftime('%a, %B %d, %Y') + "
Time: " + DT.strftime("%I:%M:%S %p") + "

]] >" point.style.balloonstyle.text = balstylestring kml.savekmz(OutputFile, format = False) print kml.kml print 'done!'
Please note that the "balstylestring= "..." should be one line, the Blogger formatter thing screws it up and I can't figure out how to fix it. Also note that I had to put a space on either side of the tag between the < and >, so remove those if you cut and paste it.

1 comment:

  1. This comment has been removed by the author.

    ReplyDelete