开发者

Getting GPS data from an image's EXIF in C#

I am developing a system that allows for an image to be uploaded to a server using ASP.NET C#. I am processing the image and all is working great. I have managed to find a method that reads the Date Created EXIF data and am parsing it as a DateTime. That works great too.

I am now trying to read GPS data from the EXIF. I am wanting to capture the Latitude and Longitude figures.

I am using this list as a reference to the EXIF data (using the numbers for the property items) http://www.exiv2.org/tags.html

Here is the method to capture the date created (which works).

public DateTime GetDateTaken(Image targetImg)
{
开发者_JAVA百科    DateTime dtaken;

    try
    {
        //Property Item 306 corresponds to the Date Taken
        PropertyItem propItem = targetImg.GetPropertyItem(0x0132);

        //Convert date taken metadata to a DateTime object
        string sdate = Encoding.UTF8.GetString(propItem.Value).Trim();
        string secondhalf = sdate.Substring(sdate.IndexOf(" "), (sdate.Length - sdate.IndexOf(" ")));
        string firsthalf = sdate.Substring(0, 10);
        firsthalf = firsthalf.Replace(":", "-");
        sdate = firsthalf + secondhalf;
        dtaken = DateTime.Parse(sdate);
    }
    catch
    {
        dtaken = DateTime.Parse("1956-01-01 00:00:00.000");
    }
    return dtaken;
}

Below is my attempt at doing the same for GPS..

public float GetLatitude(Image targetImg)
{
    float dtaken;

    try
    {
        //Property Item 0x0002 corresponds to the Date Taken
        PropertyItem propItem = targetImg.GetPropertyItem(2);

        //Convert date taken metadata to a DateTime object
        string sdate = Encoding.UTF8.GetString(propItem.Value).Trim();
        dtaken = float.Parse(sdate);
    }
    catch
    {
        dtaken = 0;
    }
    return dtaken;
}

The value that's coming out and into sdate is "5\0\0\0\0\0\0l\t\0\0d\0\0\0\0\0\0\0\0\0\0"

And that is coming from an image that was taken by an iPhone 4 which does carry the GPS EXIF data.

I know there are classes out there that do this but would prefer to write my own - I am open to suggestions though :-)

Thanks in advance.


According to the link posted above by tomfanning, property item 0x0002 is the latitude expressed as a PropertyTagTypeRational. The rational type is defined as...

Specifies that the value data member is an array of pairs of unsigned long integers. Each pair represents a fraction; the first integer is the numerator and the second integer is the denominator.

You are trying to parse it as a string when it's actually just a series of bytes. According to the above, there should be 3 pairs of 32-bit unsigned integers packed into that byte array, which you can retrieve using the following:

uint degreesNumerator   = BitConverter.ToUInt32(propItem.Value, 0);
uint degreesDenominator = BitConverter.ToUInt32(propItem.Value, 4);
uint minutesNumerator   = BitConverter.ToUInt32(propItem.Value, 8);
uint minutesDenominator = BitConverter.ToUInt32(propItem.Value, 12);
uint secondsNumerator   = BitConverter.ToUInt32(propItem.Value, 16);
uint secondsDenominator = BitConverter.ToUInt32(propItem.Value, 20);

What you do with these values after you've got them is for you to work out :) Here's what the docs say:

Latitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes, and seconds are expressed, the format is dd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is dd/1, mmmm/100, 0/1.


A simple (and fast!) way is to use my open source MetadataExtractor library:

var gps = ImageMetadataReader.ReadMetadata(path)
                             .OfType<GpsDirectory>()
                             .FirstOrDefault();

var location = gps.GetGeoLocation();

Console.WriteLine("Image at {0},{1}", location.Latitude, location.Longitude);

The library is written in pure C# and supports many image formats and decodes data specific to many camera models.

It's available via NuGet or GitHub.


I ran across this looking for a way to get the EXIF GPS data as a set of floats. I've adapted the code from Jon Grant as follows...

public static float? GetLatitude(Image targetImg)
{
    try
    {
        //Property Item 0x0001 - PropertyTagGpsLatitudeRef
        PropertyItem propItemRef = targetImg.GetPropertyItem(1);
        //Property Item 0x0002 - PropertyTagGpsLatitude
        PropertyItem propItemLat = targetImg.GetPropertyItem(2);
        return ExifGpsToFloat(propItemRef, propItemLat);
    }
    catch (ArgumentException)
    {
        return null;
    }
}
public static float? GetLongitude(Image targetImg)
{
    try
    {
        ///Property Item 0x0003 - PropertyTagGpsLongitudeRef
        PropertyItem propItemRef = targetImg.GetPropertyItem(3);
        //Property Item 0x0004 - PropertyTagGpsLongitude
        PropertyItem propItemLong = targetImg.GetPropertyItem(4);
        return ExifGpsToFloat(propItemRef, propItemLong);
    }
    catch (ArgumentException)
    {
        return null;
    }
}
private static float ExifGpsToFloat(PropertyItem propItemRef, PropertyItem propItem)
{
    uint degreesNumerator = BitConverter.ToUInt32(propItem.Value, 0);
    uint degreesDenominator = BitConverter.ToUInt32(propItem.Value, 4);
    float degrees = degreesNumerator / (float)degreesDenominator;

    uint minutesNumerator = BitConverter.ToUInt32(propItem.Value, 8);
    uint minutesDenominator = BitConverter.ToUInt32(propItem.Value, 12);
    float minutes = minutesNumerator / (float)minutesDenominator;

    uint secondsNumerator = BitConverter.ToUInt32(propItem.Value, 16);
    uint secondsDenominator = BitConverter.ToUInt32(propItem.Value, 20);
    float seconds = secondsNumerator / (float)secondsDenominator;

    float coorditate = degrees + (minutes / 60f) + (seconds / 3600f);
    string gpsRef = System.Text.Encoding.ASCII.GetString(new byte[1]  { propItemRef.Value[0] } ); //N, S, E, or W
    if (gpsRef == "S" || gpsRef == "W")
        coorditate = 0 - coorditate;
    return coorditate;
}


Here is the code working, I found some errors working with float. I hope this help somebody.

  private static double ExifGpsToDouble (PropertyItem propItemRef, PropertyItem propItem)
    {
        double degreesNumerator = BitConverter.ToUInt32(propItem.Value, 0);
        double degreesDenominator = BitConverter.ToUInt32(propItem.Value, 4);
        double degrees = degreesNumerator / (double)degreesDenominator;

        double minutesNumerator = BitConverter.ToUInt32(propItem.Value, 8);
        double minutesDenominator = BitConverter.ToUInt32(propItem.Value, 12);
        double minutes = minutesNumerator / (double)minutesDenominator;

        double secondsNumerator = BitConverter.ToUInt32(propItem.Value, 16);
        double secondsDenominator = BitConverter.ToUInt32(propItem.Value, 20);
        double seconds = secondsNumerator / (double)secondsDenominator;


        double coorditate = degrees + (minutes / 60d) + (seconds / 3600d);
        string gpsRef = System.Text.Encoding.ASCII.GetString(new byte[1] { propItemRef.Value[0] }); //N, S, E, or W
        if (gpsRef == "S" || gpsRef == "W")
            coorditate = coorditate*-1;     
        return coorditate;
    }


I know this is an old post, but I wanted to provide an answer that helped me.

I used ExifLibrary located on here to both write and read metadata from image files: https://code.google.com/p/exiflibrary/wiki/ExifLibrary

This was simple and easy to use. I hope this helps someone else.


You first have to read the bytes that indicate whether the EXIF data is in big endian or little endian format so you don't mess up everything.

Then you need to scan each IFD of the image looking for the GPSInfo tag (0x25 0x88), if you do NOT find this tag inside any IFD means that the image doesn't have any GPS info. If you do find this tag, read the 4 bytes of it's values, which gives you an offset to another IFD, the GPS IFD, inside this IFD you only need to retrieve the values of the following tags:

0x00 0x02 - For the latitude

0x00 0x04 - For the longitude

0x00 0x06 - For the altitude

Each of these values are unsigned rationals.

Here you can find how to do almost everything: http://www.media.mit.edu/pia/Research/deepview/exif.html


Have you tried tags 0x0013-16 per http://msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx which also looks like GPS latitude?

Not sure what distinguishes these from the lower numbered tags, but worth a try.

Here are their descriptions:

0x0013 - Null-terminated character string that specifies whether the latitude of the destination point is north or south latitude. N specifies north latitude, and S specifies south latitude.

0x0014 - Latitude of the destination point. The latitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes, and seconds are expressed, the format is dd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is dd/1, mmmm/100, 0/1.

0x0015 - Null-terminated character string that specifies whether the longitude of the destination point is east or west longitude. E specifies east longitude, and W specifies west longitude.

0x0016 - Longitude of the destination point. The longitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes, and seconds are expressed, the format is ddd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is ddd/1, mmmm/100, 0/1.


Thanks, the code is fine but at least one failor,

uint minutes = minutesNumerator / minutesDenominator;

give not an exact result, when the minutesDenominator is not equal 1, in example in my exif=16,


If you're using the ImageMagick library, this is very straightforward.

Assuming you start off with an image stream:

  1. Convert to an MagickImage object
  2. Get the exif info
  3. Get the coordinates using @Cesar answer above

Like so:

     //1
using (MagickImage image = new MagickImage(photoBlob))
                    {                  
                        var exifData = image.GetExifProfile();
                        
                        if (exifData != null)
                        {
                            //2  
                            var gpsLong = exifData.GetValue(ExifTag.GPSLongitude);
                            var gpsLongRef = exifData.GetValue(ExifTag.GPSLongitudeRef);
                            var gpsLatitude = exifData.GetValue(ExifTag.GPSLatitude);
                            var gpsLatitudeRef = exifData.GetValue(ExifTag.GPSLatitudeRef);
    
                            var longitude = GetCoordinates(gpsLongRef.ToString(), gpsLong.Value);
                            var latitude = GetCoordinates(gpsLatitudeRef.ToString(), gpsLatitude.Value);
                        }
                    }
        
//3     
      private static double GetCoordinates(string gpsRef, Rational[] rationals)
            {
                double degrees = rationals[0].Numerator / rationals[0].Denominator;
                double minutes = rationals[1].Numerator / rationals[1].Denominator;
                double seconds = rationals[2].Numerator / rationals[2].Denominator;
    
                double coordinate = degrees + (minutes / 60d) + (seconds / 3600d);
                if (gpsRef == "S" || gpsRef == "W")
                    coordinate *= -1;                 
               return coordinate;
            }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜