Convert image GPS coordinates into location information

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.
  1. 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).
  2. 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 onload handler, not immediately after setting src.
  3. 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
  4. 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.
  5. 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.
  6. 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!

Start your free trial

2,500 geocoding API requests/day - No credit card required