在目前很多相机都支持在拍照的时候保留GPS信息,这些信息一般都是存储在jpg图像的exif信息中。下面内容主要说明如何使用GDAL库来读取jpg图像中的GPS信息并解析经纬度坐标。
首先,还是最常用的工具,gdalinfo,来查看这个GPS信息到底在什么地方。下面是使用gdalinfo输出的信息,图1为截图。
Driver: JPEG/JPEG JFIFFiles: C:\Users\LiMinlu\Desktop\DSCN8806.JPGSize is 4608, 3456Coordinate 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=YCbCrCorner 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=JPEGBand 2 Block=4608x1 Type=Byte, ColorInterp=Green Image Structure Metadata: COMPRESSION=JPEGBand 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开头的含义可以参考这个()。通过这个页面可以得知,我们要获取的坐标就是下面这几个元数据里面的值,标识的含义见后面。
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需要注意的是,上面的代码中有句“split(vSplitValue, strValue, is_any_of("( )"), token_compress_on); //使用( )拆分字符串”,按理说这句应该会把括弧和空格去掉,我本机执行的时候确实去掉了,但是在解析后的字符串vector中的收尾增加了两个空字符串,所以下面只好从1开始了。解析后的输出如图2所示。#include 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 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 vSplitValue; split(vSplitValue, strValue, is_any_of("( )"), token_compress_on); //使用( )拆分字符串 if(vSplitValue.size() != 3) dHgt = 0; else dHgt = lexical_cast (vSplitValue[1]); } else if(strName == "EXIF_GPSLongitude") //获取经度 { vector vSplitValue; split(vSplitValue, strValue, is_any_of("( )"), token_compress_on); //使用( )拆分字符串 if(vSplitValue.size() != 5) dLon = 0; else dLon = lexical_cast (vSplitValue[1]) + lexical_cast (vSplitValue[2]) / 60.0 + lexical_cast (vSplitValue[3]) / 3600.0; } else if(strName == "EXIF_GPSLatitude") //获取纬度 { vector vSplitValue; split(vSplitValue, strValue, is_any_of("( )"), token_compress_on); //使用( )拆分字符串 if(vSplitValue.size() != 5) dLat = 0; else dLat = lexical_cast (vSplitValue[1]) + lexical_cast (vSplitValue[2]) / 60.0 + lexical_cast (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;}
图2 解析的坐标信息