GDAL源码剖析(十一)之OGR投影说明_gdal源码剖析(十一)之ogr投影说明-CSDN博客
一、简介
本文参考英文地址:http://www.gdal.org/ogr/osr_tutorial.html。
OGRSpatialReference类和OGRCoordinateTransformation类主要用来提供定义坐标系统(投影和水准面)和转换坐标。这两个类都基于OpenGIS的坐标转换说明,并且使用Well Known Text格式来进行表述坐标系统。
一些关于OpenGIS坐标系统的资料,以及空间参考坐标抽象模型文件可以在OGC(Open Geospatial Consortium)的网站上找到。GeoTIFF投影转换列表(GeoTIFF Projections Transform List)网页可以更好的帮助你理解WKT的规则,同时EPSG的网站也是很有用的资料。
二、定义地理坐标系统
坐标系统使用OGRSpatialReference类来进行封装。这里提供了数种初始化OGRSpatialReference类的方式。这里有两类主要的坐标系统,第一种是地理坐标系统(位置信息使用经纬度来表示的),第二种是投影坐标系统(比如UTM-通用横轴墨卡托投影,位置信息使用米或者英尺来表示)。
一个地理坐标系统包含的信息有一个大地基准(里面含有一个使用长半轴和扁率的倒数来表示的托球体),一个中央经线(通常是本初子午线,也就是0度经线Greenwich), 此外还有一个角度的度量单位,使用度而不是弧度。如果含有这些信息,就可以构造一个有效的地理坐标系统。
OGRSpatialReference oSRS;
oSRS.SetGeogCS( "Mygeographic coordinate system",
"WGS_1984",
"My WGS84 Spheroid",
SRS_WGS84_SEMIMAJOR, SRS_WGS84_INVFLATTENING,
"Greenwich", 0.0,
"degree", SRS_UA_DEGREE_CONV );
在上面的代码中,名称为“My geographic coordinate system”,“My WGS84 Spheroid”,“Greenwich”和“degree”的并不是关键词,这些主要是用来给用户进行说明的。然而,“WGS_1984”是一个定义大地基准的关键词,注意:这里的大地基准必须是一个有效的大地基准!(这句话的意思,前面的那些字符串就是随便指定的,用来显示的,后面的WGS_1984这个位置的字符串,必须是一个有效的,不能随便命名,具体后面会说到)。
OGRSpatialReference可以使用一些常用的字符串来进行建立一个常用的坐标系统,这些字符串包括“NAD27”、“NAD83”,“WGS72”和“WGS84”等,使用的函数是SetWellKnownGeogCS(),使用方式见下面。
oSRS.SetWellKnownGeogCS( "WGS84" );
而且,还可以使用EPSG数据库定义的GCS代码来定义一个地理坐标系统,如:
oSRS.SetWellKnownGeogCS( "EPSG:4326" );
为了方便的和其他库进行关联,这里可以转换为OpenGIS的WKT格式。同样OGRSpatialReference可以使用一个WKT来进行初始化,或者将里面的信息导出为WKT格式。
char *pszWKT =NULL;
SRS.SetWellKnownGeogCS( "WGS84" );
oSRS.exportToWkt( &pszWKT );
printf( "%s\n", pszWKT );
上面的语句会输出下面的内容:
GEOGCS["WGS84",DATUM["WGS_1984",SPHEROID["WGS84",6378137,298.257223563,AUTHORITY["EPSG",7030]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG",6326]],PRIMEM["Greenwich",0,AUTHORITY["EPSG",8901]],UNIT["DMSH",0.0174532925199433,AUTHORITY["EPSG",9108]],AXIS["Lat",NORTH],AXIS["Long",EAST],AUTHORITY["EPSG",4326]]
将上面的字符串格式调整成更好看的样子:
GEOGCS["WGS 84",
DATUM["WGS_1984",
SPHEROID["WGS 84",6378137,298.257223563,
AUTHORITY["EPSG",7030]],
TOWGS84[0,0,0,0,0,0,0],
AUTHORITY["EPSG",6326]],
PRIMEM["Greenwich",0,AUTHORITY["EPSG",8901]],
UNIT["DMSH",0.0174532925199433,AUTHORITY["EPSG",9108]],
AXIS["Lat",NORTH],
AXIS["Long",EAST],
AUTHORITY["EPSG",4326]]
函数OGRSpatialReference::importFromWkt()可以从一个WKT定义的坐标系统来构造一个OGRSpatialReference类对象。
三、定义投影坐标系统
一个投影坐标系统(比如UTM,兰伯特等角圆锥投影等)需要建立在一个地理坐标系统之上,在投影坐标系统中,坐标点使用米或者英尺等长度单位来表示,同时也可以用经纬度的角度坐标来表示。下面将定义一个UTM的第17带的投影坐标系统,基于WGS84的大地基准椭球体。
OGRSpatialReference oSRS;
oSRS.SetProjCS( "UTM 17(WGS84) in northern hemisphere." );
oSRS.SetWellKnownGeogCS("WGS84" );
oSRS.SetUTM( 17, TRUE );
首先调用SetProjCS()函数设置投影坐标系统的名称,然后使用函数SetWellKnownGeogCS()指定地理坐标系统,最后调用函数SetUTM()设置投影转换参数信息。完成这些工作之后就定义了一个有效的投影坐标系统。这里必须要注意定义OGRSpatialReference的顺序!
上面定义的投影使用WKT表示的形式如下,注意UTM17会使用横轴墨卡托的分带投影参数来表示。
PROJCS["UTM 17 (WGS84) in northernhemisphere.",
GEOGCS["WGS 84",
DATUM["WGS_1984",
SPHEROID["WGS 84",6378137,298.257223563,
AUTHORITY["EPSG",7030]],
TOWGS84[0,0,0,0,0,0,0],
AUTHORITY["EPSG",6326]],
PRIMEM["Greenwich",0,AUTHORITY["EPSG",8901]],
UNIT["DMSH",0.0174532925199433,AUTHORITY["EPSG",9108]],
AXIS["Lat",NORTH],
AXIS["Long",EAST],
AUTHORITY["EPSG",4326]],
PROJECTION["Transverse_Mercator"],
PARAMETER["latitude_of_origin",0],
PARAMETER["central_meridian",-81],
PARAMETER["scale_factor",0.9996],
PARAMETER["false_easting",500000],
PARAMETER["false_northing",0]]
这里提供了很多设置投影坐标的方法,包括SetTM()(横轴墨卡托投影), SetLCC()(兰伯特等角圆锥投影)和SetMercator()。
四、获取坐标系统信息
一旦一个OGRSpatialReference对象进行创建,那么就可以获取里面的各种信息,比如可以使用OGRSpatialReference::IsProjected()和OGRSpatialReference::IsGeographic()方法来判断坐标系统是地理坐标系统还是投影坐标系统。使用函数OGRSpatialReference::GetSemiMajor()、OGRSpatialReference::GetSemiMinor()和OGRSpatialReference::GetInvFlattening()方法可以用来获取椭球体信息,分别是椭球体的长半轴,短半轴以及扁率的倒数。使用OGRSpatialReference::GetAttrValue()方法可以用来获取PROJCS、GEOGCS、DATUM、SPHEROID和PROJECTION的名称字符串。使用OGRSpatialReference::GetProjParm()方法可以获取投影参数信息。使用OGRSpatialReference::GetLinearUnits()方法可以获取长度单位类型,并将其转换为米。
下面的代码是一个获取坐标系统信息的示例,摘自ogr_srs_proj4.cpp文件中。使用GetAttrValue()获取投影名称,使用GetProjParm()获取投影参数信息。GetAttrValue()函数的第一个值节点就是WKT字符串的描述信息。投影参数的宏定义(比如SRS_PP_CENTRAL_MERIDIAN)和函数GetProjParm()一起使用,可以用来获取投影的参数。更多的使用方式可以参考文件ogrspatialreference.cpp中的相关代码。
const char *pszProjection = poSRS->GetAttrValue("PROJECTION");
if( pszProjection == NULL )
{
if( poSRS->IsGeographic() )
sprintf(szProj4+strlen(szProj4), "+proj=longlat " );
else
sprintf(szProj4+strlen(szProj4), "unknown " );
}
else if(EQUAL(pszProjection,SRS_PT_CYLINDRICAL_EQUAL_AREA) )
{
sprintf(szProj4+strlen(szProj4),
"+proj=cea +lon_0=%.9f +lat_ts=%.9f +x_0=%.3f +y_0=%.3f ",
poSRS->GetProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0),
poSRS->GetProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0),
poSRS->GetProjParm(SRS_PP_FALSE_EASTING,0.0),
poSRS->GetProjParm(SRS_PP_FALSE_NORTHING,0.0));
}
...
const char *pszProjection = poSRS->GetAttrValue("PROJECTION");
if( pszProjection == NULL )
{
if( poSRS->IsGeographic() )
sprintf(szProj4+strlen(szProj4), "+proj=longlat " );
else
sprintf(szProj4+strlen(szProj4), "unknown " );
}
else if(EQUAL(pszProjection,SRS_PT_CYLINDRICAL_EQUAL_AREA) )
{
sprintf(szProj4+strlen(szProj4),
"+proj=cea +lon_0=%.9f +lat_ts=%.9f +x_0=%.3f +y_0=%.3f ",
poSRS->GetProjParm(SRS_PP_CENTRAL_MERIDIAN,0.0),
poSRS->GetProjParm(SRS_PP_STANDARD_PARALLEL_1,0.0),
poSRS->GetProjParm(SRS_PP_FALSE_EASTING,0.0),
poSRS->GetProjParm(SRS_PP_FALSE_NORTHING,0.0));
}
...
五、坐标转换
OGRCoordinateTransformation类可以用来在不同的坐标系统中进行坐标转换。可以使用函数OGRCreateCoordinateTransformation()创建一个新的坐标转换对象,然后使用OGRCoordinateTransformation::Transform()方法来进行坐标转换。
OGRSpatialReference oSourceSRS,oTargetSRS;
OGRCoordinateTransformation *poCT;
double x, y;
oSourceSRS.importFromEPSG(atoi(papszArgv[i+1]) );
oTargetSRS.importFromEPSG(atoi(papszArgv[i+2]) );
poCT = OGRCreateCoordinateTransformation(&oSourceSRS,
&oTargetSRS );
x = atof( papszArgv[i+3] );
y = atof( papszArgv[i+4] );
if( poCT == NULL || !poCT->Transform( 1, &x,&y ) )
printf("Transformation failed.\n" );
else
printf("(%f,%f) -> (%f,%f)\n",
atof(papszArgv[i+3] ),
atof(papszArgv[i+4] ),
x, y );
一对点转换失败的原因有,首先,OGRCreateCoordinateTransformation()函数执行失败,通常的原因是不知道指定的两个投影之间的转换关系。这个可能是试用了PROJ.4库不支持的投影,不同的椭球体之间的转换关系没有定义,或者是其中的一个坐标系统没有定义完全。如果函数OGRCreateCoordinateTransformation() 执行失败,那么其返回值将是NULL。
OGRCoordinateTransformation::Transform() 方法本身页可能执行失败。这个可能的原因也有上面的问题,或者是转换的点里面有一个以上没有定义的数字。函数Transform()执行成功是返回TRUE,如果有一个点转换失败都会返回FALSE。
坐标转换也可以支持三维点的坐标转换,会自动根据不同的椭球地的基准面调整高程值。这个可以用来在不同的垂直基准面进行坐标转换。如果没有Z值,系统会认为所有的点都是在水准面上。
下面的例子演示了如果从一个投影坐标系统中的点转换为该投影中的地理坐标系统中的点,将米表示的坐标转换为经纬度表示的坐标。
OGRSpatialReference oUTM, *poLatLong;
OGRCoordinateTransformation *poTransform;
oUTM.SetProjCS("UTM 17 /WGS84");
oUTM.SetWellKnownGeogCS("WGS84" );
oUTM.SetUTM( 17 );
poLatLong = oUTM.CloneGeogCS();
poTransform = OGRCreateCoordinateTransformation( &oUTM,poLatLong );
if( poTransform == NULL )
{
...
}
...
if( !poTransform->Transform( nPoints, x,y, z ) )
...
六、其他语言接口
OGR的空间参考提供了一个C语言的接口,定义在ogr_srs_api.h文件中,Python的接口定义在osr.py文件中。所有的接口名称和C++的接口都很相似,但是C和Python中有些方法没有进行提供。
1、C语言接口
typedef void *OGRSpatialReferenceH;
typedef void *OGRCoordinateTransformationH;
OGRSpatialReferenceH OSRNewSpatialReference( const char *);
void OSRDestroySpatialReference( OGRSpatialReferenceH );
int OSRReference( OGRSpatialReferenceH );
int OSRDereference( OGRSpatialReferenceH );
OGRErr OSRImportFromEPSG( OGRSpatialReferenceH, int );
OGRErr OSRImportFromWkt( OGRSpatialReferenceH, char ** );
OGRErr OSRExportToWkt( OGRSpatialReferenceH, char ** );
OGRErr OSRSetAttrValue( OGRSpatialReferenceH hSRS, const char * pszNodePath,
const char *pszNewNodeValue );
const char *OSRGetAttrValue( OGRSpatialReferenceH hSRS,
const char * pszName, intiChild);
OGRErr OSRSetLinearUnits( OGRSpatialReferenceH, const char *, double );
double OSRGetLinearUnits( OGRSpatialReferenceH, char ** );
int OSRIsGeographic( OGRSpatialReferenceH );
int OSRIsProjected( OGRSpatialReferenceH );
int OSRIsSameGeogCS( OGRSpatialReferenceH, OGRSpatialReferenceH );
int OSRIsSame( OGRSpatialReferenceH, OGRSpatialReferenceH );
OGRErr OSRSetProjCS( OGRSpatialReferenceH hSRS, const char * pszName );
OGRErr OSRSetWellKnownGeogCS( OGRSpatialReferenceH hSRS,
const char * pszName );
OGRErr OSRSetGeogCS( OGRSpatialReferenceH hSRS,
const char * pszGeogName,
const char *pszDatumName,
const char *pszEllipsoidName,
double dfSemiMajor,double dfInvFlattening,
const char * pszPMName ,
double dfPMOffset ,
const char * pszUnits,
double dfConvertToRadians);
double OSRGetSemiMajor( OGRSpatialReferenceH, OGRErr * );
double OSRGetSemiMinor( OGRSpatialReferenceH, OGRErr * );
double OSRGetInvFlattening( OGRSpatialReferenceH, OGRErr * );
OGRErr OSRSetAuthority( OGRSpatialReferenceH hSRS,
const char *pszTargetKey,
const char *pszAuthority,
int nCode );
OGRErr OSRSetProjParm( OGRSpatialReferenceH, const char *, double );
double OSRGetProjParm( OGRSpatialReferenceH hSRS,
const char *pszParmName,
double dfDefault,
OGRErr * );
OGRErr OSRSetUTM( OGRSpatialReferenceH hSRS, int nZone, int bNorth );
int OSRGetUTMZone( OGRSpatialReferenceH hSRS, int *pbNorth );
OGRCoordinateTransformationH
OCTNewCoordinateTransformation( OGRSpatialReferenceH hSourceSRS,
OGRSpatialReferenceHhTargetSRS );
void OCTDestroyCoordinateTransformation( OGRCoordinateTransformationH );
int OCTTransform(OGRCoordinateTransformationH hCT,
int nCount, double *x, double*y, double *z );
2、Python语言接口
class osr.SpatialReference
def __init__(self,obj=None):
def ImportFromWkt( self, wkt ):
def ExportToWkt(self):
def ImportFromEPSG(self,code):
def IsGeographic(self):
def IsProjected(self):
def GetAttrValue(self, name, child = 0):
def SetAttrValue(self, name, value):
def SetWellKnownGeogCS(self, name):
def SetProjCS(self, name = "unnamed" ):
def IsSameGeogCS(self, other):
def IsSame(self, other):
def SetLinearUnits(self, units_name, to_meters ):
def SetUTM(self, zone, is_north = 1):
class CoordinateTransformation:
def __init__(self,source,target):
def TransformPoint(self, x, y, z = 0):
def TransformPoints(self, points):
七、内部实现
OGRCoordinateTransformation依赖于PROJ.4库。所以要使用坐标转换的内容,GDAL必须在编译的时候绑定PROJ4才可以用来进行坐标转换。如果要使用GDAL的坐标转换,重投影相关的算法,就必须要有PROJ4库的支持,否则会转换失败。关于PROJ4的编译和GDAL绑定PROJ4的内容,请参考之前的博文。