This is a quick note on how to map images in a directory purely from their exif files. An exif file is one which contains the meta data of the image. If the image was taken by a phone of GPS enabled camera, then a location will also be contained within that file. To read that directory on the webserver we need to use a CGI script. For our purposes we will use python.
This is really a very simple concept:
-
- have a folder space on a webserver
- set that server up to to understand .py files as CGI scripts
- have a directory full of images
- have a web page which pulls up a Google Map
- have that map requst the geography of each image (via ajax and the CGI script)
- display the locations of the images on the map
This idea is simple, but requires some fiddling. Firstly you need to make your apache webserver execute python scripts. Thankfully there are numerous resources to help in this. With that in mind I will assume you now have a space on a webserver in which a python script can be executed. next make a directory called ‘images’ and put your images in it.
Next we will need a very simple web page. Ours will just have a single div called “map”
<html> <head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"> </script> <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"> </script> <style> html, body, #map { height: 100%; margin: 0px; padding: 0px; } </style> </head> <body> <div id="map"></div> </body> </html>
And, we’ll need a smidge of JavaScript to display a Google Map, ask the python script for the coordinates from the images’ exif files and then provide listeners for the info boxes.
<script> $.ajax({ url: "get_images.py", type: "GET", dataType: 'json', success: function(data){ initialize_map(data) } }); function initialize_map(data){ var myLatlng = new google.maps.LatLng(-25.363882,131.044922); var mapOptions = { zoom: 4, center: myLatlng }; var bounds = new google.maps.LatLngBounds(); var map = new google.maps.Map(document.getElementById('map'), mapOptions); for (var image in data){ lat = data[image].loc.split(',')[0] lon = data[image].loc.split(',')[1] var pos = new google.maps.LatLng(lat,lon); marker = new google.maps.Marker({ position: pos, map: map }); bounds.extend(pos) attachInfo(marker, data[image].image) function attachInfo(marker, image) { var infowindow = new google.maps.InfoWindow({ content: '<img src="images/' + image + '" width=200/>'}); google.maps.event.addListener(marker, 'click', function() { infowindow.open(map,marker); }); }) } map.fitBounds(bounds); } </script>
This provides a simple ajax using jquery request to the python script. It doesn’t have to do anything clever, it just says “run your get_images.py” script. In return its expecting a json object with image names and locations. The image names returned help build the info windows (by pulling in images to them) and the returned locations add the markers to the map.
The final piece of the puzzle is the python script.
#!/usr/bin/env/python # -*- coding: UTF-8 -*- import cgitb import cgi import os, sys import mimetypes import exifread import json from fractions import Fraction cgitb.enable() print "Content-Type: application/json" print source = "/path/to/your/images" image_locate = {} directory = os.listdir( source ) def _get_if_exist(data, key): if key in data: return data[key] return None def _convert_to_degress(value): """Helper function to convert the GPS coordinates stored in the EXIF to degrees in float format""" d = float(Fraction(str(value.values[0]))) m = float(Fraction(str(value.values[1]))) s = float(Fraction(str(value.values[2]))) return d + (m / 60.0) + (s / 3600.0) def get_lat_lon(exif_data): """Returns the latitude and longitude, if available, from the provided exif_data (obtained through get_exif_data above)""" lat = None lon = None gps_latitude = _get_if_exist(tags, "GPS GPSLatitude") gps_latitude_ref = _get_if_exist(tags, 'GPS GPSLatitudeRef').values gps_longitude = _get_if_exist(tags, 'GPS GPSLongitude') gps_longitude_ref = _get_if_exist(tags, 'GPS GPSLongitudeRef').values if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref: lat = _convert_to_degress(gps_latitude) if gps_latitude_ref != "N": lat = 0 - lat lon = _convert_to_degress(gps_longitude) if gps_longitude_ref != "E": lon = 0 - lon return "%s, %s" % (lat, lon) for file in directory: mt = mimetypes.guess_type(file)[0] if mt: p = os.path.join(source, file) f = open(p, 'rb') tags = exifread.process_file(f) image_locate[file] = get_lat_lon(tags) print json.dumps([{'image': k, 'loc': v} for k,v in image_locate.items()], indent=4)
This file depends on the exifread library which very conveniently does all the heavy lifting in the exif reading. That read all we have to do is drop into our images folder then read through the files, check their mimetypes, if they are images then look for geography. The geography is the converted back from rational number representations of Degrees, Minutes, Seconds to the more versitile Decimal Degrees and finally passed back in a json objet to the javascript and the map.
There is little validation here and this is obviously just proof of concept code, but a useful example I think.
The code above assembles to look like this: