Let’s leverage the
exif and folium Python packages
Most modern GPS-enabled cameras and mobile phones record geolocation information when a photo is taken and this information is stored along with all the other metadata.
There are also plenty of usually web-based applications that allow you to create various visualisations using this metadata. But what if you’re like me and don’t trust your photos to third-party services with unknown levels of data security? Luckily, most of these visualisations are relatively simple to replicate using Python. In this guide, I will show how to create a travel map using photo GPS data.
For this project, you will need a few packages installed in your Python environment. We will use two main packages:
exif will allow us to extract metadata from photos and we’ll use
folium to create a map and add location markers. Finally, we will also use color maps from
numpy for data manipulations. To install all the necessary packages simply execute the following in your terminal:
pip install exif folium pandas numpy matplotlib
We’ll start with importing all the necessary packages. In addition to the ones listed above, we will also import
Pathclass from the
pathlib library, both of which are a part of the standard library.
Extracting and preparing metadata for plotting
Reading metadata with
exifis very easy — we simply need to open a file in a binary format and create an
Imageobject from it. Let’s define a convenience function for this:
Depending on the device a photo can contain dozens of types of metadata information. To see those that specifically relate to geolocation we can load a single photo and print out all the metadata categories that begin with “gps_“.
If a photo has embedded geolocation information you should see the following output:
Some of these categories are relatively easy to understand: gps_latitude, gps_longitude and gps_altitude store latitude, longitude and altitude information. Altitude is stored as elevation above sea level, usually in meters. Latitude and longitude are stored as tuples of degrees, minutes and seconds of arc.
Categories ending with “_ref” contain reference information on how the data should be interpreted. In the case of altitude, it simply informs that the value is altitude above sea level, but for latitude and longitude they contain the information of the reference hemisphere, “E” or ”W” for longitude, and ”N” or ”S” for latitude. These are important when converting the raw data to decimal format since the standard notation has coordinates west of Greenwich and south of the equator as negative.
Since volume doesn’t understand coordinates in degrees/minutes/seconds we’ll need to convert latitudes and longitudes to decimal representation and since we’ll also do it quite a few times we can make life easier and code cleaner by defining a function that will take a tuple of coordinate and a reference value and return its binary representation. For this, we convert minutes and seconds into fractions of a degree by dividing by 60 and 3600, respectively. The sign is then decided based on the reference data:
This function will convert latitudes and longitudes to decimal format and we don’t need to do anything with altitudes. With this, we can define another helper function that will take an
exif.Image object and return a tuple of decimal coordinates in the format (latitude, longitude, altitude).
Finally, we will add another function that will recursively go through all subfolders of a selected folder, read spatial information from all the image files and return a dictionary of file names and coordinates.
Now that this is done we can easily read the location data from all files within the image folder:
Printing the resulting dictionary should result in an output similar to mine:
"timestamp": "2014:04:12 18:14:59"},
"timestamp": "2014:04:12 18:15:36"}, ...
Plotting the data
To simplify future operations we can convert the resulting dictionary to a
pandas DataFrame. We then also convert the timestamp column to a
datetime format and sort values by date in ascending order.
In order to color-code our data points we can get one of the color maps that come as a part of the
matplotlib package. In addition to this, we also need to create a
Normalize object. This object will assign numerical values to individual colors in a
colourmap between 0 and 1 and we’ll be able to call any color in the
colourspace by its numerical value. And for this, we will add a column to our
One small issue is that our colors will be in RGBA format but it doesn’t work with
folium so we will also need to convert the color to its hexadecimal representation which is easily done:
Now let’s finally it’s time to visualize our data!
The main part of the
folium package is
Map object and simply creating an instance via
folium.Map() will result in a map of the entire world.
This is fine but I prefer to have the map automatically zoom to the level that all the markers can be seen. We can achieve this using
fit_bounds() method of the resulting object and providing coordinates of the southwest and northeast corners of the map extents.
We can calculate the coordinates of the southwest corner by taking the maximum latitude and minimum longitude from our data. Similarly, the north-eastern corner coordinates are the minimum latitude and the maximum longitude. In the below code I’ve also adjusted the bounding box by 3 degrees in each direction so that the data fits neatly within the window.
So now finally we’re ready to create a map showing the locations of the photos colour-coded by the date taken.
In my case, the result shows a bunch of locations around Victoria, Australia with purple dots representing earlier pictures and brighter red and yellow dots — newer photos:
Here we only touched on a tiny part of all the opportunities that photo metadata offers. With this data, you can create your own databases of photos allowing you to easily categorise and manage them. You can also expand the use of the geolocation data to being able to search for photos by location within your image folders and many other things are available too.
The complete code for this project can be downloaded from my GitHub at the following location: