Azure Function to extract GPS location from images

Simple code to create an azure Function to read Exif data from a text file.

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!