GDAL读取jpg文件中GPS坐标信息

在目前很多相机都支持在拍照的时候保留GPS信息,这些信息一般都是存储在jpg图像的exif信息中。下面内容主要说明如何使用GDAL库来读取jpg图像中的GPS信息并解析经纬度坐标。

首先,还是最常用的工具,gdalinfo,来查看这个GPS信息到底在什么地方。下面是使用gdalinfo输出的信息,图1为截图。

Driver: JPEG/JPEG JFIF
Files: C:\Users\LiMinlu\Desktop\DSCN8806.JPG
Size is 4608, 3456
Coordinate System is `'
Metadata:
  EXIF_ColorSpace=1
  EXIF_ComponentsConfiguration=0x1 0x2 0x3 00
  EXIF_CompressedBitsPerPixel=(2)
  EXIF_Contrast=0
  EXIF_CustomRendered=0
  EXIF_DateTime=2013:03:18 16:06:49
  EXIF_DateTimeDigitized=2013:03:18 16:06:49
  EXIF_DateTimeOriginal=2013:03:18 16:06:49
  EXIF_DigitalZoomRatio=(0)
  EXIF_ExifVersion=0230
  EXIF_ExposureBiasValue=(0)
  EXIF_ExposureMode=0
  EXIF_ExposureProgram=2
  EXIF_ExposureTime=(0.00625)
  EXIF_FileSource=0x3
  EXIF_Flash=24
  EXIF_FlashpixVersion=0100
  EXIF_FNumber=(3.9)
  EXIF_FocalLength=(5)
  EXIF_FocalLengthIn35mmFilm=28
  EXIF_GainControl=4
  EXIF_GPSAltitude=(55.6)
  EXIF_GPSAltitudeRef=00
  EXIF_GPSDateStamp=2013:03:18
  EXIF_GPSImgDirection=(33.96)
  EXIF_GPSImgDirectionRef=T
  EXIF_GPSLatitude=(39) (53) (41.298)
  EXIF_GPSLatitudeRef=N
  EXIF_GPSLongitude=(116) (17) (28.344)
  EXIF_GPSLongitudeRef=E
  EXIF_GPSMapDatum=WGS-84   
  EXIF_GPSSatellites=03
  EXIF_GPSTimeStamp=(8) (5) (41.02)
  EXIF_GPSVersionID=0x2 0x3 00 00
  EXIF_ImageDescription=                               
  EXIF_Interoperability_Index=R98
  EXIF_Interoperability_Version=0x30 0x31 0x30 0x30
  EXIF_ISOSpeedRatings=125
  EXIF_LightSource=0
  EXIF_Make=NIKON
  EXIF_MakerNote=Nikon
  EXIF_MaxApertureValue=(3.9)
  EXIF_MeteringMode=5
  EXIF_Model=COOLPIX AW100s 
  EXIF_Orientation=1
  EXIF_PixelXDimension=4608
  EXIF_PixelYDimension=3456
  EXIF_ResolutionUnit=2
  EXIF_Saturation=0
  EXIF_SceneCaptureType=0
  EXIF_SceneType=0x1
  EXIF_Sharpness=0
  EXIF_Software=COOLPIX AW100sV1.0             
  EXIF_SubjectDistanceRange=2
  EXIF_UserComment=                                                                                                                       
  EXIF_WhiteBalance=0
  EXIF_XResolution=(300)
  EXIF_YCbCrPositioning=2
  EXIF_YResolution=(300)
Image Structure Metadata:
  COMPRESSION=JPEG
  INTERLEAVE=PIXEL
  SOURCE_COLOR_SPACE=YCbCr
Corner Coordinates:
Upper Left  (    0.0,    0.0)
Lower Left  (    0.0, 3456.0)
Upper Right ( 4608.0,    0.0)
Lower Right ( 4608.0, 3456.0)
Center      ( 2304.0, 1728.0)
Band 1 Block=4608x1 Type=Byte, ColorInterp=Red
  Image Structure Metadata:
    COMPRESSION=JPEG
Band 2 Block=4608x1 Type=Byte, ColorInterp=Green
  Image Structure Metadata:
    COMPRESSION=JPEG
Band 3 Block=4608x1 Type=Byte, ColorInterp=Blue
  Image Structure Metadata:
    COMPRESSION=JPEG
图1 GDALINFO 输出的信息
从上面的输出信息可以看出,jpg中存储GPS的信息是以EXIF_GPS***开头的元数据信息里面所存储。知道了存储位置我们就可以写程序来解析了,首先要做的是从这么多一大堆的EXIF信息中提取GPS的信息,代码如下:

	char** papszMetadata = poDataset->GetMetadata( NULL ) ;
	char** papszMetadataGPS = NULL;

	if( CSLCount(papszMetadata) > 0 )
	{
		for(int i = 0; papszMetadata[i] != NULL; i++ )
		{
			if(EQUALN(papszMetadata[i], "EXIF_GPS", 8))
			{
				papszMetadataGPS = CSLAddString( papszMetadataGPS, papszMetadata[i]);
				printf( "  %s\n", papszMetadata[i] );
			}
		}
	}
大概解释一下上面的代码,首先从poDataset 中获取元数据信息;然后判断元数据的个数是否大于0,也就是判断是否存在元数据;最后通过函数EQUALN来提取EXIF_GPS开头的元数据存放在新的字符串数组中。通过上面的代码提取之后的元数据如下:

  EXIF_GPSAltitude=(55.6)
  EXIF_GPSAltitudeRef=00
  EXIF_GPSDateStamp=2013:03:18
  EXIF_GPSImgDirection=(33.96)
  EXIF_GPSImgDirectionRef=T
  EXIF_GPSLatitude=(39) (53) (41.298)
  EXIF_GPSLatitudeRef=N
  EXIF_GPSLongitude=(116) (17) (28.344)
  EXIF_GPSLongitudeRef=E
  EXIF_GPSMapDatum=WGS-84   
  EXIF_GPSSatellites=03
  EXIF_GPSTimeStamp=(8) (5) (41.02)
  EXIF_GPSVersionID=0x2 0x3 00 00
关于这些EXIF_GPS开头的含义可以参考这个页面http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/gps.html)。通过这个页面可以得知,我们要获取的坐标就是下面这几个元数据里面的值,标识的含义见后面。

  EXIF_GPSAltitude=(55.6)              ——海拔
  EXIF_GPSAltitudeRef=00               ——海拔参考值(应该就是水平面高程)
  EXIF_GPSLatitude=(39) (53) (41.298)  ——纬度信息
  EXIF_GPSLatitudeRef=N                ——纬度标识,N为北纬,S为南纬
  EXIF_GPSLongitude=(116) (17) (28.344)——经度信息
  EXIF_GPSLongitudeRef=E               ——经度标识,E为东经,W为西经
  EXIF_GPSMapDatum=WGS-84              ——参考椭球,这个应该都是WGS84吧
其中经度和纬度信息是使用度分秒格式表示。知道了格式及其含义,就可以很方便的解析了。下面是一个解析的函数,使用了boost库中的split函数和lexical_cast函数。

#include "gdal_priv.h"
#include <vector>
#include <string>
using namespace std;

#include "boost/lexical_cast.hpp"
#include "boost/algorithm/string.hpp"
using namespace boost;
using namespace boost::algorithm;

bool ExtractGPSInfo(char** papszMetadata, double &dLon, double &dLat, double &dHgt)
{
	if( CSLCount(papszMetadata) <= 0 )
		return false;

	char** papszMetadataGPS = NULL;
	for(int i = 0; papszMetadata[i] != NULL; i++ )
	{
		if(EQUALN(papszMetadata[i], "EXIF_GPS", 8))
			papszMetadataGPS = CSLAddString( papszMetadataGPS, papszMetadata[i]);
	}

	int iGPSCount = CSLCount(papszMetadataGPS);
	if (iGPSCount <=0)
	{
		CSLDestroy( papszMetadataGPS );
		return false;
	}
	
	bool bIsNorth = true;
	bool bIsEast = true;

	for(int i = 0; papszMetadataGPS[i] != NULL; i++ )
	{
		vector<string> vSplitStr;
		split(vSplitStr, papszMetadataGPS[i], is_any_of("="));	//使用=拆分字符串
		if(vSplitStr.empty() || vSplitStr.size() != 2)
			continue;

		string strName = vSplitStr[0];	//取出标识符
		string strValue = vSplitStr[1];	//取出值
		if(strName.empty() || strValue.empty())
			continue;

		if(strName == "EXIF_GPSAltitude")	//获取海拔
		{
			vector<string> vSplitValue;
			split(vSplitValue, strValue, is_any_of("( )"), token_compress_on);	//使用( )拆分字符串
			if(vSplitValue.size() != 3)
				dHgt = 0;
			else
				dHgt = lexical_cast<double>(vSplitValue[1]);
		}
		else if(strName == "EXIF_GPSLongitude")		//获取经度
		{
			vector<string> vSplitValue;
			split(vSplitValue, strValue, is_any_of("( )"), token_compress_on);	//使用( )拆分字符串
			if(vSplitValue.size() != 5)
				dLon = 0;
			else
				dLon = lexical_cast<double>(vSplitValue[1]) + lexical_cast<double>(vSplitValue[2]) / 60.0 + lexical_cast<double>(vSplitValue[3]) / 3600.0;
		}
		else if(strName == "EXIF_GPSLatitude")	//获取纬度
		{
			vector<string> vSplitValue;
			split(vSplitValue, strValue, is_any_of("( )"), token_compress_on);	//使用( )拆分字符串
			if(vSplitValue.size() != 5)
				dLat = 0;
			else
				dLat = lexical_cast<double>(vSplitValue[1]) + lexical_cast<double>(vSplitValue[2]) / 60.0 + lexical_cast<double>(vSplitValue[3]) / 3600.0;
		}
		else if(strName == "EXIF_GPSLongitude")		//获取经度
		{
			if (strValue == "E")
				bIsEast = true;
			else
				bIsEast = false;
		}
		else if(strName == "EXIF_GPSLatitude")	//获取纬度
		{
			if (strValue == "N")
				bIsNorth = true;
			else
				bIsNorth = false;
		}
	}

	dLon = bIsEast ? dLon : -1.0*dLon;
	dLat = bIsNorth ? dLat : -1.0*dLat;

	return true;
}
需要注意的是,上面的代码中有句“split(vSplitValue, strValue, is_any_of("( )"), token_compress_on); //使用( )拆分字符串”,按理说这句应该会把括弧和空格去掉,我本机执行的时候确实去掉了,但是在解析后的字符串vector中的收尾增加了两个空字符串,所以下面只好从1开始了。解析后的输出如图2所示。


图2 解析的坐标信息

原文地址:https://www.cnblogs.com/xiaowangba/p/6313959.html