7 min read

In this article by Joel Lawhead, author of the book, QGIS Python Programming Cookbook uses the tags to create locations on a map for some photos and provide links to open them.

(For more resources related to this topic, see here.)

Getting ready

You will need to download some sample geotagged photos from https://github.com/GeospatialPython/qgis/blob/gh-pages/photos.zip?raw=true and place them in a directory named photos in your qgis_data directory.

How to do it…

QGIS requires the Python Imaging Library (PIL), which should already be included with your installation. PIL can parse EXIF tags. We will gather the filenames of the photos, parse the location information, convert it to decimal degrees, create the point vector layer, add the photo locations, and add an action link to the attributes. To do this, we need to perform the following steps:

  1. In the QGIS Python Console, import the libraries that we’ll need, including k for parsing image data and the glob module for doing wildcard file searches:
    import glob
    import Image
    from ExifTags import TAGS
  2. 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
  3. 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)
  4. 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
  5. Next, we’ll loop through the photos directory, get the filenames, 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]
  6. 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()
  7. 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()
  8. Now, we can add the layer to the map and make the active layer:
    QgsMapLayerRegistry.instance().addMapLayer(vectorLyr)
    iface.setActiveLayer(vectorLyr)
    activeLyr = iface.activeLayer()
  9. Finally, we’ll add an action that allows you to click on it and open the photo:
    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 straightforward. 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:

QGIS Python Programming Cookbook

There’s more…

Another plugin called Photo2Shape is available, but it requires you to install an external EXIF tag parser.

Image change detection

Change detection allows you to automatically highlight the differences between two images in the same area if they are properly orthorectified. We’ll do a simple difference change detection on two images, which are several years apart, to see the differences in urban development and the natural environment.

Getting ready

You can download the two images from https://github.com/GeospatialPython/qgis/blob/gh-pages/change-detection.zip?raw=true and put them in a directory named change-detection in the rasters directory of your qgis_data directory. Note that the file is 55 megabytes, so it may take several minutes to download.

How to do it…

We’ll use the QGIS raster calculator to subtract the images in order to get the difference, which will highlight significant changes. We’ll also add a color ramp shader to the output in order to visualize the changes. To do this, we need to perform the following steps:

  1. First, we need to import the libraries that we need in to the QGIS console:
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    from qgis.analysis import *
  2. Now, we’ll set up the path names and raster names for our images:
    before = "/Users/joellawhead/qgis_data/rasters/change-detection/before.tif"
    |after = "/Users/joellawhead/qgis_data/rasters/change-detection/after.tif"
    beforeName = "Before"
    afterName = "After"
  3. Next, we’ll establish our images as raster layers:
    beforeRaster = QgsRasterLayer(before, beforeName)
    afterRaster = QgsRasterLayer(after, afterName)
  4. Then, we can build the calculator entries:
    beforeEntry = QgsRasterCalculatorEntry()
    afterEntry = QgsRasterCalculatorEntry()
    beforeEntry.raster = beforeRaster
    afterEntry.raster = afterRaster
    beforeEntry.bandNumber = 1
    afterEntry.bandNumber = 2
    beforeEntry.ref = beforeName + "@1"
    afterEntry.ref = afterName + "@2"
    entries = [afterEntry, beforeEntry]
  5. Now, we’ll set up the simple expression that does the math for remote sensing:
    exp = "%s - %s" % (afterEntry.ref, beforeEntry.ref)
  6. Then, we can set up the output file path, the raster extent, and pixel width and height:
    output = "/Users/joellawhead/qgis_data/rasters/change-detection/change.tif"
    e = beforeRaster.extent()
    w = beforeRaster.width()
    h = beforeRaster.height()
  7. Now, we perform the calculation:
    change = QgsRasterCalculator(exp, output, "GTiff", e, w, h, entries)
    change.processCalculation()
  8. Finally, we’ll load the output as a layer, create the color ramp shader, apply it to the layer, and add it to the map, as shown here:
    lyr = QgsRasterLayer(output, "Change")
    algorithm = QgsContrastEnhancement.StretchToMinimumMaximum
    limits = QgsRaster.ContrastEnhancementMinMax
    lyr.setContrastEnhancement(algorithm, limits)
    s = QgsRasterShader()
    c = QgsColorRampShader()
    c.setColorRampType(QgsColorRampShader.INTERPOLATED)
    i = []
    qri = QgsColorRampShader.ColorRampItem
    i.append(qri(0, QColor(0,0,0,0), 'NODATA'))
    i.append(qri(-101, QColor(123,50,148,255), 'Significant Itensity Decrease'))
    i.append(qri(-42.2395, QColor(194,165,207,255), 'Minor Itensity Decrease'))
    i.append(qri(16.649, QColor(247,247,247,0), 'No Change'))
    i.append(qri(75.5375, QColor(166,219,160,255), 'Minor Itensity Increase'))
    i.append(qri(135, QColor(0,136,55,255), 'Significant Itensity Increase'))
    c.setColorRampItemList(i)
    s.setRasterShaderFunction(c)
    ps = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 1, s)
    lyr.setRenderer(ps)
    QgsMapLayerRegistry.instance().addMapLayer(lyr)

How it works…

If a building is added in the new image, it will be brighter than its surroundings. If a building is removed, the new image will be darker in that area. The same holds true for vegetation, to some extent.

Summary

The concept is simple. We subtract the older image data from the new image data. Concentrating on urban areas tends to be highly reflective and results in higher image pixel values.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here