The problem
You want to extract GPS coordinates from image or video
metadata (Exif data) and turn the coordinates into useful location
information like addresses.
Background
Digital cameras typically record all sorts of data
about the image in a format known as
Exchangeable image file format,
(commonly known as "Exif").
This data includes things like what type of camera was used to make the
image. If the camera is able to it, for example a camera on
a mobile phone, it may also include information about the location
the image was taken.
The location information is recorded as geographic coordinates:
longitude and latitude.
Converting coordinates into location information
is the process know as reverse geocoding.
You can pass coordinates to the OpenCage geocoding API to reverse
geocode the coordinates and return information about the location.
The solution
The process of geocoding images follows a few basic steps.
- Load an Exif library. You will need a way to extract the raw coordinates from the image. There are many open source libraries available, search for "Exif" in the relevant library collection for your language (CPAN, pypi, rubygems, etc). In javascript, common choices are exif-js (simple, but last released in 2017) or exifr (more actively maintained, Promise-based).
-
Wait until the image is fully loaded,
then use the library to extract
the coordinates. Exif libraries generally can't read metadata from
an image that hasn't finished loading — do this work in the image's
onloadhandler, not immediately after settingsrc. - Check whether the image actually has coordinates. Not every image includes coordinate data: screenshots, downloaded stock photos, and many desktop cameras won't have it. Use the library to extract the coordinates from the image
- Convert the coordinates to decimal format. Exif data stores coordinates in degree/minutes/seconds format. Most software however, including our geocoding API, expects decimal degrees. Conversion is not particularly difficult, see the example below.
-
Send the decimal coordinates to the OpenCage geocoding API
We have SDKs for calling the OpenCage geocoding API for over 40 other programming languages and frameworks. - Examine the API response, extract the information you need
Code example
Here is a detailed javascript example.
The logic will be the same in other languages.
To extract the Exif data from an image we use the open source library
exif.js
library from a CDN via
<img id="img1" src="your-photo.jpg" crossorigin="anonymous" />
<pre id="result"></pre>
<script src="https://cdn.jsdelivr.net/npm/exif-js"></script>
<script>
// turn degree, min, sec format into decimal
function DMS2DD(degrees, minutes, seconds, direction) {
var dd = degrees + (minutes/60) + (seconds/3600);
if (direction == "S" || direction == "W") {
dd = dd * -1;
}
return dd;
}
// reverse geocode a "lat,lng" string via OpenCage geocoding API
async function geocode(coords) {
var apikey = 'YOUR-API-KEY';
var api_url = 'https://api.opencagedata.com/geocode/v1/json'
var request_url = api_url
+ '?'
+ 'key=' + apikey
+ '&q=' + encodeURIComponent(coords)
+ '&pretty=1'
+ '&no_annotations=1'; // turn off annotations
// see full list of required and optional parameters:
// https://opencagedata.com/api#required-params
var response = await fetch(request_url);
var data = await response.json();
// see full list of possible response codes:
// https://opencagedata.com/api#codes
if (response.status === 200 && data.results.length > 0) {
return data.results[0].formatted;
} else {
console.log('unable to geocode! Response code: ' + response.status);
console.log('error msg: ' + data.status.message);
return null;
}
}
// run once the image has actually finished loading -
// Exif data can't reliably be read before then
var img1 = document.getElementById('img1');
img1.addEventListener('load', function() {
EXIF.getData(img1, async function() {
var exifdata = this.exifdata;
// the image may not have coordinates at all
if (!exifdata || exifdata.GPSLatitude == null) {
document.getElementById('result').textContent = 'No GPS data found in this image.';
return;
}
// latitude in decimal
var latDeg = exifdata.GPSLatitude[0].numerator;
var latMin = exifdata.GPSLatitude[1].numerator;
var latSec = exifdata.GPSLatitude[2].numerator;
var latDir = exifdata.GPSLatitudeRef;
var lat = DMS2DD(latDeg, latMin, latSec, latDir);
// longitude in decimal
var lngDeg = exifdata.GPSLongitude[0].numerator;
var lngMin = exifdata.GPSLongitude[1].numerator;
var lngSec = exifdata.GPSLongitude[2].numerator;
var lngDir = exifdata.GPSLongitudeRef;
var lng = DMS2DD(lngDeg, lngMin, lngSec, lngDir);
var coords = lat + ',' + lng;
console.log('coords from image: ' + coords);
var location = await geocode(coords);
document.getElementById('result').textContent = location || 'Unable to determine location.';
});
});
// if the image is already cached/loaded by the time this script runs,
// the 'load' event above may never fire - handle that case too
if (img1.complete) {
img1.dispatchEvent(new Event('load'));
}
</script>
If instead you want users to upload their own photo, the same
EXIF.getData
call works directly on a
<input type="file">
selection. No
img
element needed, since the browser reads the file locally:
<input type="file" id="fileInput" accept="image/*" />
document.getElementById('fileInput').addEventListener('change', function(e) {
var file = e.target.files[0];
if (!file) return;
EXIF.getData(file, async function() {
var exifdata = this.exifdata;
// ... same coordinate extraction and geocode() call as above
});
});
Further reading
Happy geocoding!
2,500 geocoding API requests/day - No credit card required