





















































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.)
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.
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:
import glob
import Image
from ExifTags import TAGS
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
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)
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
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]
lyr_info = "Point?crs=epsg:4326&field=photo:string(75)"
vectorLyr = QgsVectorLayer(lyr_info, "Geotagged Photos" , "memory")
vpr = vectorLyr.dataProvider()
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()
QgsMapLayerRegistry.instance().addMapLayer(vectorLyr)
iface.setActiveLayer(vectorLyr)
activeLyr = iface.activeLayer()
actions = activeLyr.actions()
actions.addAction(QgsAction.OpenUrl, "Photos", '[% "photo" %]')
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:
Another plugin called Photo2Shape is available, but it requires you to install an external EXIF tag parser.
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.
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.
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:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from qgis.analysis import *
before = "/Users/joellawhead/qgis_data/rasters/change-detection/before.tif"
|after = "/Users/joellawhead/qgis_data/rasters/change-detection/after.tif"
beforeName = "Before"
afterName = "After"
beforeRaster = QgsRasterLayer(before, beforeName)
afterRaster = QgsRasterLayer(after, afterName)
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]
exp = "%s - %s" % (afterEntry.ref, beforeEntry.ref)
output = "/Users/joellawhead/qgis_data/rasters/change-detection/change.tif"
e = beforeRaster.extent()
w = beforeRaster.width()
h = beforeRaster.height()
change = QgsRasterCalculator(exp, output, "GTiff", e, w, h, entries)
change.processCalculation()
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)
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.
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.
Further resources on this subject: