Pages

Thursday, May 7, 2015

Geolocating Photos in QGIS

Photos taken with GPS-enabled cameras, including smartphones, store location information in the header of the file, in a format called EXIF tags. These tags are largely based on the same header tags used by the TIFF image standard. If you have a directory full of photos, you can easily sort them by date or by name.  But if you want to sort them by location, only a map will do.  In this recipe, we'll use these tags to create locations on a map in QGIS for some photos and provide links to open them. 

Getting ready

You need to make sure you have QGIS installed as well as the Python Imaging Library (PIL) for the Python distribution that QGIS uses.  QGIS should already have PIL included. You can download some sample geotagged images here or you can use any photo taken outdoors with your smartphone.  Unzip these photos and place them in a directory accessible to QGIS.

How to do it...

PIL can parse EXIF tags. We will gather the file names of the photos, parse the location information, convert it to decimal degrees, create the point vector layer, add the photo locations, and add a QGIS action link to the attributes. To do this, we need to perform the following steps:

In the QGIS Python Console, import the libraries that we'll need, including the Image module (PIL) and its ExifTags module for parsing image data and the glob module for doing wildcard file searches:

import glob
import Image
from ExifTags import TAGS


Next, we'll create a function that can parse the header data:

def exif(img):
   exif_data = {}
   try:  
       i = Image.open(img)
       tags = i._getexif()
       for tag, value in tags.items():
         decoded = TAGS.get(tag, tag)
           exif_data[decoded] = value
   except:
       pass
return exif_data

Now, we'll create a function that can convert degrees-minute-seconds to decimal degrees, which is how coordinates are stored in JPEG images:

def dms2dd(d, m, s, i):
   sec = float((m * 60) + s)
   dec = float(sec / 3600)
   deg = float(d + dec)
   if i.upper() == 'W':
       deg = deg * -1
   elif i.upper() == 'S':
       deg = deg * -1
   return float(deg)

Next, we'll define a function to parse the location data from the header data:

def gps(exif):
   lat = None
   lon = None
   if exif['GPSInfo']:      
       # Lat
       coords = exif['GPSInfo']
       i = coords[1]
       d = coords[2][0][0]
       m = coords[2][1][0]
       s = coords[2][2][0]
       lat = dms2dd(d, m ,s, i)
       # Lon
       i = coords[3]
       d = coords[4][0][0]
       m = coords[4][1][0]
       s = coords[4][2][0]
       lon = dms2dd(d, m ,s, i)
return lat, lon

Next, we'll loop through the photos directory, get the file names, parse the location information, and build a simple dictionary to store the information, as follows:

photos = {}
photo_dir = "/Users/joellawhead/qgis_data/photos/"
files = glob.glob(photo_dir + "*.jpg")
for f in files:
   e = exif(f)
   lat, lon = gps(e)
 photos[f] = [lon, lat]

Now, we'll set up the vector layer for editing:

lyr_info = "Point?crs=epsg:4326&field=photo:string(75)"
vectorLyr = QgsVectorLayer(lyr_info, \"Geotagged Photos" , "memory")
vpr = vectorLyr.dataProvider()

We'll add the photo details to the vector layer:

features = []
for pth, p in photos.items():
   lon, lat = p
   pnt = QgsGeometry.fromPoint(QgsPoint(lon,lat))
   f = QgsFeature()
   f.setGeometry(pnt)
   f.setAttributes([pth])
   features.append(f)
vpr.addFeatures(features)
vectorLyr.updateExtents()

Now, we can add the layer to the map and make the active layer:

QgsMapLayerRegistry.instance().addMapLayer(vectorLyr)
iface.setActiveLayer(vectorLyr)
activeLyr = iface.activeLayer()

Finally, we'll add an action that allows you to click on it and open the photo.  Actions are a simple yet powerful feature of QGIS that let you define the result of certain user actions like clicking on features:

actions = activeLyr.actions()
actions.addAction(QgsAction.OpenUrl, "Photos", \'[% "photo" %]')

How it works...

Using the included PIL EXIF parser, getting location information and adding it to a vector layer is relatively straight forward. This action is a default option for opening a URL. However, you can also use Python expressions as actions to perform a variety of tasks. The following screenshot shows an example of the data visualization and photo popup:














There's more...

You can download the complete code sample here. Another plugin called Photo2Shape is available that performs a similar function, but it requires you to install an external EXIF tag parser while this approach uses the parser in PIL included with QGIS.

2 comments:

  1. This post totally does not work. I downloaded your images, and copy all your code, just changing path to image directory to my local directory. But your code give blank layer.

    Any feedback, please write to thanh.nv@me.com

    ReplyDelete
  2. All depends on how you give your path. Secondly I tried out the code it works but the coordinates land in Alaska for my images which I took in Fresno and Madera. Don't know what to do I am new to this. Please if there is any recommendations email me at nikhilsh.92@gmail.com

    ReplyDelete