在目前很多相机都支持在拍照的时候保留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 解析的坐标信息