Azure Function to extract GPS location from images
I was doing this in a hackathon and found many pages showing how to do it but they were either a bit older or didn't work well.
Ironically, most of the code I tried ended up with the error
Image is not valid JPEG
now this could've been caused by a few things. Was I sending it incorrectly from Postman, were there missing headers, was I hading the request correctly? In the end, it turned out the Jpeg WAS incorrect. When shared from my phone via the email program, I chose to send the small size (not orignal size) which partially broke the file (for the Exif library at least!)
The Code
Note: this will work from an Azure Function project in Visual Studio. It will not work in the design interface (CSX files).
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using ExifLib;
namespace ExtractGpsFromImage
{
/// <summary>
/// If class returns with NULL cooridnates then there are no cooridnates or an error
/// NOTE: 0,0 IS a valid position
/// Check function log/console for exception details
/// </summary>
public class Result
{
public double? Latitide { get; set; }
public double? Longitude { get; set; }
}
public static class ExtractGpsFromImage
{
[FunctionName("ExtractGpsFromImage")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function processed a request.");
//POST a single file from PostMan (https://www.getpostman.com/) with the body set to file and the
var image = req.Form.Files[0];
using (var reader = new StreamReader(image.OpenReadStream()))
{
return GetCoordinates(reader.BaseStream, log);
}
}
catch (Exception e)
{
log.LogInformation($"EXCEPTION:{e.Message}");
}
log.LogInformation("C# HTTP trigger function finished a request.");
return (ActionResult)new OkObjectResult(new Result());
}
private static IActionResult GetCoordinates(Stream fileSTream, ILogger log)
{
var result = new Result();
try
{
using (var exifReader = new ExifReader(fileSTream))
{
// The Double[] value contains the degrees, minutes and seconds.
double[] latitudeDegreesMinutesSeconds;
double[] longitudeDegressMinutesSeconds;
//this contains a list of metadata https://www.codeproject.com/Articles/27242/ExifTagCollection-An-EXIF-metadata-extraction-libr
exifReader.GetTagValue(ExifTags.GPSLatitude, out latitudeDegreesMinutesSeconds);
exifReader.GetTagValue(ExifTags.GPSLongitude, out longitudeDegressMinutesSeconds);
if (latitudeDegreesMinutesSeconds == null || longitudeDegressMinutesSeconds == null)
{
log.LogInformation("No coordinates available");
}
else
{
ExtractCoordinateParts(log, result, latitudeDegreesMinutesSeconds, longitudeDegressMinutesSeconds);
}
}
}
catch (Exception e)
{
log.LogInformation(e.Message);
}
return (ActionResult)new OkObjectResult(result);
}
private static void ExtractCoordinateParts(ILogger log, Result result, double[] latitudeDegreesMinutesSeconds, double[] longitudeDegressMinutesSeconds)
{
double latitude = 0, longitude = 0;
latitude = latitudeDegreesMinutesSeconds[0] +
latitudeDegreesMinutesSeconds[1] / 60 +
latitudeDegreesMinutesSeconds[2] / 3600;
longitude = longitudeDegressMinutesSeconds[0] +
longitudeDegressMinutesSeconds[1] / 60 +
longitudeDegressMinutesSeconds[2] / 3600;
result.Latitide = latitude;
result.Longitude = longitude;
log.LogInformation($"Latitude: '{latitude}' | Longitude: '{longitude}'");
}
}
}
Postman
I was using the very awesome Postman tool to send the image tot the Azure Function endpoint.
Download Postman.
Open Postman and setup a POST request like this:
1 Set the dropdown to POST
2 Enter the address
3 Ensure Body is chosen
4 Choose form-data
5 Enter a name
6 Upload the image
Click the Send button and it will go off to the endpoint. Superb!