Search code examples
c#.netwpfwindowswic

WIC/WPF: System.GPS.Longitude and System.GPS.Latitude always return NULL (via BitmapMetadata)


Has anyone successfully read GPS data using WPFs class BitmapMetadata which internally uses WIC (Windows Imaging Component)?

I have an image where both Windows (8.1) Explorer and external tools like XnView show GPS coordinates.

I try to extract these data using the class BitmapMetadata which is available via property Metadata of class BitmapFrame:

var md = (BitmapMetdata)extractedFrame.Metadata;
var altitude = md.GetQuery("System.GPS.Altitude");
var altitudeProxy = md.GetQuery("System.GPS.Altitude.Proxy");
var altitudeRef = md.GetQuery("System.GPS.AltitudeRef");
var longitude = md.GetQuery("System.GPS.Longitude");
var longitudeProxy = md.GetQuery("System.GPS.Longitude.Proxy");
var longitudeRef = md.GetQuery("System.GPS.LongitudeRef");
var latitude = md.GetQuery("System.GPS.Latitude");
var latitudeProxy = md.GetQuery("System.GPS.Latitude.Proxy");
var latitudeRef = md.GetQuery("System.GPS.LatitudeRef");

Results of altitude (System.Double), altitudeRef (System.Byte), longitudeRef (System.String) and latitudeRef (System.String) are all ok and retrieve reasonable data ("510.70", "0", "N", "E").

longitude and latitude should be arrays of System.Double but they are always NULL.

The results using the ".Proxy" suffixes return strange String data of which I am not shure how they are to be parsed and if they are culture invariant or not: The MSDN docs tell something different, nothing about strings; but at least there would be "valid" data for Longitude and Latitude there.

Is it a bug or my fault in missing something?


Solution

  • I used the code here to access the latitude and longitude: http://khason.net/blog/how-to-read-gps-metadata-from-image/ It doesn't handle the N,S,E,W +/- part though. So you will have to use the LongitudeRef and LatitudeRef above to figure out whether to change the sign on your coordinates. Basically:

    JpegBitmapDecoder decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
    BitmapMetadata meta = (BitmapMetadata)decoder.Frames[0].Metadata;
    ulong[] latitude    = meta.GetQuery("/app1/ifd/gps/subifd:{ulong=2}") as ulong[];
    ulong[] longitude   = meta.GetQuery("/app1/ifd/gps/subifd:{ulong=4}") as ulong[];
    double lat          = ConvertCoordinate(latitude);
    double longit       = ConvertCoordinate(longitude);
    
    static double ConvertCoordinate(ulong[] coordinates)
    {
        int lDash   = (int)(coordinates[0] - ((ulong)0x100000000));
        int lF      = (int)(coordinates[1] - ((ulong)0x100000000));
        double lR   = ((double)(coordinates[2] - ((ulong)0x6400000000))) / 100;
        double tRes = (lDash + (((double)lF) / 60)) + (lR / 3600);
        return (Math.Floor((double)(tRes * 1000000)) / 1000000);
    }
    

    EDIT the above only worked for older EXIF formats. Newer formats had more digits on the coordinates so they were converted incorrectly. I had to update Convert Coordinates to this:

    static double ConvertCoordinate(ulong[] coordinates)
    {
        if (coordinates == null)
            return 0;
    
        double degrees = ConvertToUnsignedRational( coordinates[ 0 ] ); 
        double minutes = ConvertToUnsignedRational( coordinates[ 1 ] ); 
        double seconds = ConvertToUnsignedRational( coordinates[ 2 ] ); 
        return degrees + (minutes / 60.0) + (seconds / 3600); 
    
    }
    static double ConvertToUnsignedRational( ulong value ) 
    { 
        return (value & 0xFFFFFFFFL) / (double) ((value & 0xFFFFFFFF00000000L) >> 32); 
    }