3

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?

springy76
  • 3,706
  • 2
  • 24
  • 46

2 Answers2

7

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); 
} 
CodeMonkey
  • 86
  • 1
  • 6
  • @StefanFalk it's an answer, not a question. If you are the downvoter I suggest to revert this since the answer does return reasonable values. – springy76 Jan 21 '15 at 13:44
  • @springy76 I'm sorry! I did that mistake while reviewing and thought this was an actual question! I will undo my downvote but I have to wait until her accepts my edit since SO has locked my vote decision until then. – Stefan Falk Jan 21 '15 at 15:27
  • BTW: There a 2 additional keys for `meta.GetQuery()` which also can return `ulong[]` : "/xmp/exif:GPSLatitude" and "/xmp/exif:GPSLongitude" – springy76 Jan 26 '15 at 16:19
1

It's probably too late to answer, but the .Proxy values have the following format:

MetaDataValue   "16,0.4965000000000145E"    object {string}

means 16 degrees, 0.49 minutes. So you just have to convert it to minutes AND seconds.