从C#中的图像EXIF获取GPS数据

时间:2011-02-13 11:05:49

标签: c# gps latitude-longitude exif

我正在开发一个系统,允许使用ASP.NET C#将图像上传到服务器。我正在处理图像,一切都很好。我设法找到一个方法来读取创建日期的EXIF数据,并将其解析为DateTime。这也很有效。

我现在正尝试从EXIF读取GPS数据。我想要捕捉纬度和经度数字。

我使用此列表作为EXIF数据的参考(使用属性项的数字)http://www.exiv2.org/tags.html

以下是捕获创建日期(工作日期)的方法。

public DateTime GetDateTaken(Image targetImg)
{
    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;
}

以下是我尝试为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;
}

出现并进入sdate的值为“5 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0l \ t \ 0 \ 0d \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0"

这是来自iPhone 4的图像,它携带GPS EXIF数据。

我知道有些课程可以做到这一点,但我更愿意自己编写 - 我仍然愿意接受建议: - )

提前致谢。

9 个答案:

答案 0 :(得分:24)

根据tomfanning的link posted above,属性项0x0002是表示为PropertyTagTypeRational的纬度。理性类型是defined as ......

  

指定值数据成员是无符号长整数对的数组。每对代表一个分数;第一个整数是分子,第二个整数是分母。

当它实际上只是一系列字节时,您试图将其解析为字符串。根据以上所述,应该有3对32位无符号整数打包到该字节数组中,您可以使用以下代码检索:

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);

在你获得这些价值观之后你对这些价值观做了什么才能让你解决问题:)以下是文档所说的内容:

  

纬度表示为三个合理值,分别给出度,分和秒。表达度,分和秒时,格式为dd / 1,mm / 1,ss / 1。当使用度数和分钟时,例如,分数的分数最多给出两位小数,格式为dd / 1,mmmm / 100,0 / 1。

答案 1 :(得分:15)

一种简单(快速!)的方法是使用我的开源 MetadataExtractor 库:

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

var location = gps.GetGeoLocation();

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

该库以纯C#编写,支持多种图像格式,并可解码特定于许多相机型号的数据。

可通过NuGetGitHub获取。

答案 2 :(得分:11)

我跑过这里寻找一种方法将EXIF GPS数据作为一组浮点数。我已经改编了Jon Grant的代码,如下所示......

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;
}

答案 3 :(得分:11)

这是代码工作,我发现一些错误与float一起工作。 我希望这有助于某人。

  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;
    }

答案 4 :(得分:4)

我知道这是一个老帖子,但我想提供一个帮助我的答案。

我使用位于此处的ExifLibrary来编写和读取图像文件中的元数据: https://code.google.com/p/exiflibrary/wiki/ExifLibrary

这很简单易用。我希望这有助于其他人。

答案 5 :(得分:3)

首先必须读取指示EXIF数据是大端还是小端格式的字节,这样就不会搞砸所有内容。

然后你需要扫描图像的每个IFD寻找GPSInfo标签(0x25 0x88),如果你没有在任何IFD中找到这个标签意味着图像没有任何GPS信息。如果你确实找到了这个标签,请读取它的4个字节的值,它给你一个偏移到另一个IFD,GPS IFD,在这个IFD中你只需要检索以下标签的值:

0x00 0x02 - 适用于纬度

0x00 0x04 - 经度

0x00 0x06 - 海拔高度

这些值中的每一个都是无符号的有理数。

在这里您可以找到如何做几乎所有事情:http://www.media.mit.edu/pia/Research/deepview/exif.html

答案 6 :(得分:2)

您是否尝试过每http://msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx的标签0x0013-16,它看起来像GPS纬度?

不确定这些与较低编号标签的区别,但值得一试。

以下是他们的描述:

  

0x0013 - 以空值终止的字符串,指定目标点的纬度是北纬还是南纬。 N指定北纬,S指定南纬。

     

0x0014 - 目标点的纬度。纬度表示为三个有理值,分别给出度,分和秒。表达度,分和秒时,格式为dd / 1,mm / 1,ss / 1。当使用度数和分钟时,例如,分数的分数最多给出两位小数,格式为dd / 1,mmmm / 100,0 / 1。

     

0x0015 - 以空值终止的字符串,指定目标点的经度是东经还是西经。 E指定东经度,W指定西经度。

     

0x0016 - 目标点的经度。经度表示为三个理性值,分别给出度,分和秒。表达度,分和秒时,格式为ddd / 1,mm / 1,ss / 1。当使用度数和分钟时,例如,分数的分数最多为两位小数,格式为ddd / 1,mmmm / 100,0 / 1。

答案 7 :(得分:1)

谢谢,代码很好,但至少有一个失败者,

uint minutes = minutesNumerator / minutesDenominator;

当minutesDenominator不等于1时,

没有给出确切的结果,例如在我的exif = 16中,

答案 8 :(得分:0)

如果您使用的是ImageMagick库,那么这很简单。

假设您从图像流开始:

  1. 转换为MagickImage对象
  2. 获取exif信息
  3. 使用上面的@Cesar答案获取坐标

像这样:

     //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;
            }