5.5. 坐标转化#
坐标转化总是一个令人头疼的问题。不知道你有没有写过坐标转换的程序代码,如果写过就会有体会。那一长串的公式和成群出现的大括号,真是太可怕了!只有写过这种东西,你才会对我们生活的这个家园肃然起敬。才会发现原来地球居然如此复杂而显得如此伟大,人类的数学和地理学家居然是如此地会折腾而显得如此伟大。敬畏会让你从此以后不敢乱丢垃圾和随地吐痰~~呵呵,话说大了。反正我是不敢碰那些符号和公式了。不过既然踏入GIS的门槛,就踏进了坐标的深渊,不得不面对它。算了,还是找一个人家已经做出来的轮子去拼自己的车吧,至于轮子有几根钢线,什么材质,怎么才能搞的比较圆,我就不费神去想了。
有一种叫PROJ的牌子的轮子不知道听说过没有。反正开源界现在最好的有关坐标转换的轮子差不多就是它了。对于C/C++人来说,很幸运,因为这个库可以直接调用。如果是在linux平台下的java人,也很幸运,因为可以用jni来间接调用。如果你是在windows平台下的java人,可能是不幸的。我和我老大折腾了两天都没有编译成功,查了无数的帖子,也没有发现有解决方案(如果你成功了,一定要告诉我哦~~~)。对于业余的python人来说,前两天我还以为我们是不幸的,但是今天我觉得上帝还是偏爱python的,让我找到了[[http://lists.maptools.org/pipermail/proj/2005-October/001751.html|这个]],还有[[http://www.cdc.noaa.gov/people/jeffrey.s.whitaker/python/pyproj.html|这个]]。呜啦~~~
对于pyproj,我也是“不太熟悉的”:-),那我们也就看看osr里面坐标转换是怎么玩的吧。 先翻译[[http://www.gdal.org/ogr/osr_tutorial.html|官方教程]],我翻译不对的地方请回头看英文原版
== 官方的教程 == === 简介 === OGRSpatialReference和OGRCoordinateTransformation类提供了用来描绘坐标系统(投影和基准面)以及坐标系统相互之间转换的服务。这些服务在OpenGIS坐标转换说明文档里有模型,并且有对应的WKT描述。 一些OpenGIS坐标系统和服务的背景资料可以在COM的简单要素(Simple Features for COM)和空间参考系统抽象模型文档(可以从[[http://opengis.org|opengis.org]]获取)中找到。[[http://www.remotesensing.org/geotiff/proj_list|GeoTiff投影变换列表]]也可以辅助地用来理解WKT中的投影公式。[[http://www.epsg.org/|EPSG]]测地学网页也是一个有用的资源。
=== 定义一个地理坐标系 === 地理坐标系被封装进了OGRSpatialReference类。有几种办法来初始化OGRSpatialReference对象以形成一个合法的坐标系统。有两种主要的坐标系统。一种是地理坐标(用经纬度表示)。另一种是投影坐标(如UTM,用米等单位量度来定位)。
一个地理坐标包含基准面(它包含了由长半轴描述的椭球体和反扁率),本初子午线(一般是格林威治),和一个角度单位(一般是度)。下面的代码就初始化了一个地理坐标系。它提供了围绕用户定义名字的地理坐标系的所有信息。
{{{ #! python
- OGRSpatialReference oSRS;
- oSRS.SetGeogCS( “My geographic 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” 经常被用于定义基准面。而且哪些值可以用哪些不行都是有规矩的。不能乱写。
OGRSpatialReference 已经支持一些标准的坐标系统。比如”NAD27”, “NAD83”, “WGS72” and “WGS84”。要建造它们只要用一个函数SetWellKnownGeogCS().
5.5.1. oSRS.SetWellKnownGeogCS( “WGS84” );#
如果EPSG数据库存在的话,所有EPSG中的地理坐标系都可以用GCS编码来表示。
5.5.2. oSRS.SetWellKnownGeogCS( “EPSG:4326” );#
在各种坐标系统表达方式中,WKT是个纽带,通过它,各种表达方式可以互换。一个OGRSpatialReference可以用一个wkt来初始化,或者转换出wkt表达。
{{{ #! python char *pszWKT = NULL; oSRS.SetWellKnownGeogCS( “WGS84” ); oSRS.exportToWkt( &pszWKT ); printf( “%sn”, pszWKT ); }}} 比如这样一个wkt:
{{{ #! python 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]] }}} 或者让它更好看一点吧:
{{{ #! python 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等)基于一个地理坐标系统定义以便于在线性单位和角度位置之间转换。接下来的代码定义了一个在WGS84基准面下的地理坐标系统为基础的UTM17带投影坐标系统。
{{{ #! python
OGRSpatialReference oSRS; oSRS.SetProjCS( “UTM 17 (WGS84) in northern hemisphere.” ); oSRS.SetWellKnownGeogCS( “WGS84” ); oSRS.SetUTM( 17, TRUE );
}}} 调用SetProjCS()设置一个用户定义名字的投影坐标系统并确定系统被投影过。SetWellKnownGeogCS()分配一个地理坐标系统,SetUTM()设置投影转换参数细节。上面的步骤直到这时才能创建一个合法的定义。但是这个对象需要时将会重新自动编排内在表达,以保持合法性。
请尤其注意定义对象时的步骤顺序!!
上面的定义会定义一个像下面一样的WKT。注意UTM117被扩展成横轴莫卡托定义UTM带的参数。
{{{ #! python PROJCS[“UTM 17 (WGS84) in northern hemisphere.”,
- 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() (Transverse Mercator), SetLCC() (Lambert Conformal Conic)和 !SetMercator())
=== 查询坐标系统 === 一旦OGRSpatialReference建立起来了,就可以查询多种信息。用OGRSpatialReference::!IsProjected() 和 OGRSpatialReference::!IsGeographic()可以看他是否是投影坐标还是地理坐标。OGRSpatialReference::!GetSemiMajor(), OGRSpatialReference::!GetSemiMinor() 和OGRSpatialReference::!GetInvFlattening()可以获取椭球体信息。OGRSpatialReference::!GetAttrValue() 可以用来获取PROJCS, GEOGCS, DATUM, SPHEROID, 和PROJECTION 的名字表达字符串。OGRSpatialReference::!GetProjParm()可以用来获取投影参数。OGRSpatialReference::!GetLinearUnits() 可以用来获取线性单位,并转换成米。
下面的代码示范了!GetAttrValue()获取投影的用法,以及!GetProjParm()!GetAttrValue() 的用法。已经定义的投影参数(如SRS_PP_CENTRAL_MERIDIAN)应该在!GetProjParm()获取投影参数的时候使用。ogrspatialreference.cpp中设置不同投影的方法的代码可以用来查找哪个投影用哪个参数。
{{{ #! python 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 类用来在不同坐标系统间转换坐标。新的转换对象由OGRCoordinateTransformation()创建。创建后OGRCoordinateTransformation::Transform() 方法就可以用来转换不同坐标系的点了。
{{{ #! python
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内部暂时不支持,这样两个不一样的基准面就没有已知关系。或者一个投影定义得不适当。如果OGRCreateCoordinateTransformation() 失败了将返回空。
OGRCoordinateTransformation::Transform() 方法本身也可能失败。这可能是因为一个有上面问题积累后形成的问题。或者是一个“因为一个或者多个点因数值上未定义而省略操作”起因的后果。Transform()函数如果成功,返回TRUE,如果某个点转换失败,剩下的点就会以一个错误的状态保持。
坐标转换服务其实可以处理3D点的。并且服务会根据椭球体和基准面上的高程差异调节高程。将来,也有可能会有在不同矢量基准面上的点间转换的应用。如果没有Z值,则假设点都在大地水准面上。
下面的例子显示了如何方便地创建一个地理坐标系统到一个基于同一个地理坐标系统的投影坐标系统。并在两个坐标系统之间转换。
{{{ #! python
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 ) ) …
}}} === API绑定 === C的接口在ogr_srs_api.h中定义,python绑定在osr.py模块中。一些C++的方法在C和python绑定中没有定义。
Python Bindings(C绑定略过)
{{{ #! 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服务的实现是建立在PROJ4库之上,该库是最先由USGS的Gerald Evenden 编写的。
== 我的试验 == === 创建和比较 === {{{ #! python >>> import gdal >>> import osr >>> dataset = gdal.Open(“e:/gisdata/gtif/spot.tif”) }}} 从数据集中获取空间参考并且建立一个!SpatialReference对象
{{{ #! python >>> sr = dataset.GetProjectionRef() >>> sr ‘PROJCS[“unnamed”,GEOGCS[“NAD27”,DATUM[“North_American_Datum_1927”,SPHEROID[“Cla rke 1866”,6378206.4,294.9786982139006,AUTHORITY[“EPSG”,”7008”]],AUTHORITY[“EPSG” ,”6267”]],PRIMEM[“Greenwich”,0],UNIT[“degree”,0.0174532925199433],AUTHORITY[“EPS G”,”4267”]],PROJECTION[“Transverse_Mercator”],PARAMETER[“latitude_of_origin”,0], PARAMETER[“central_meridian”,-105],PARAMETER[“scale_factor”,0.9996],PARAMETER[“f alse_easting”,500000],PARAMETER[“false_northing”,0],UNIT[“metre”,1,AUTHORITY[“EP SG”,”9001”]],AUTHORITY[“EPSG”,”26713”]]’ >>> osrobj = osr.SpatialReference() >>> osrobj.ImportFromWkt(sr) 0 >>> osrobj.ExportToWkt() ‘PROJCS[“unnamed”,GEOGCS[“NAD27”,DATUM[“North_American_Datum_1927”,SPHEROID[“Cla rke 1866”,6378206.4,294.9786982139006,AUTHORITY[“EPSG”,”7008”]],AUTHORITY[“EPSG” ,”6267”]],PRIMEM[“Greenwich”,0],UNIT[“degree”,0.0174532925199433],AUTHORITY[“EPS G”,”4267”]],PROJECTION[“Transverse_Mercator”],PARAMETER[“latitude_of_origin”,0], PARAMETER[“central_meridian”,-105],PARAMETER[“scale_factor”,0.9996],PARAMETER[“f alse_easting”,500000],PARAMETER[“false_northing”,0],UNIT[“metre”,1,AUTHORITY[“EP SG”,”9001”]],AUTHORITY[“EPSG”,”26713”]]’ >>> osrobj.IsGeographic() 0 >>> osrobj.IsProjected() 1 }}} 再获取一个进行比较
{{{ #! python >>> dataset2 = gdal.Open(“e:/gisdata/gtif/uparea.tif”) >>> sr2 = dataset2.GetProjectionRef() >>> sr2 ‘PROJCS[“unnamed”,GEOGCS[“NAD27”,DATUM[“North_American_Datum_1927”,SPHEROID[“Cla rke 1866”,6378206.4,294.9786982139006,AUTHORITY[“EPSG”,”7008”]],AUTHORITY[“EPSG” ,”6267”]],PRIMEM[“Greenwich”,0],UNIT[“degree”,0.0174532925199433],AUTHORITY[“EPS G”,”4267”]],PROJECTION[“Transverse_Mercator”],PARAMETER[“latitude_of_origin”,0], PARAMETER[“central_meridian”,-105],PARAMETER[“scale_factor”,0.9996],PARAMETER[“f alse_easting”,500000],PARAMETER[“false_northing”,0],UNIT[“metre”,1,AUTHORITY[“EP SG”,”9001”]],AUTHORITY[“EPSG”,”26713”]]’ >>> osrobj2 = osr.SpatialReference() >>> osrobj2.ImportFromWkt(sr2) 0 >>> osrobj2.IsSame(osrobj) 1 }}} 创建一个地理坐标系,然后和已有的坐标系统比较
{{{ #! python >>> osrobj3 = osr.SpatialReference() >>> osrobj3.SetWellKnownGeogCS(“WGS84”) 0 >>> osrobj3.IsSame(osrobj2) 0 >>> osrobj3.IsSame(osrobj) 0 >>> osrobj3.ExportToWkt() ‘GEOGCS[“WGS 84”,DATUM[“WGS_1984”,SPHEROID[“WGS 84”,6378137,298.257223563,AUTHOR ITY[“EPSG”,”7030”]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[“EPSG”,”6326”]],PRIMEM[“Gre enwich”,0,AUTHORITY[“EPSG”,”8901”]],UNIT[“degree”,0.0174532925199433,AUTHORITY[” EPSG”,”9108”]],AXIS[“Lat”,NORTH],AXIS[“Long”,EAST],AUTHORITY[“EPSG”,”4326”]]’ >>> osrobj3.IsGeographic() 1 }}} === 地理坐标系和投影坐标系之间的坐标转换 === spot的图像投影坐标范围是这样的
{{{ #! python In projected or local coordinates Left: 590000.000000 Right: 609000.000000 Top: 4928000.000000 Bottom: 4914000.000000 }}} 用!CoordinateTransformation转换结果:
{{{ #! python >>> ct = osr.CoordinateTransformation(osrobj,osrobj3) >>> ct.TransformPoint(590000,4928000) (-103.86789483706976, 44.501658895816213, 2.1979212760925293e-007) >>> ct.TransformPoint(609000,4928000) (-103.6289570318164, 44.499040134967487, 2.2072345018386841e-007) >>> ct.TransformPoint(609000,4914000) (-103.63190060724861, 44.373035580280714, 2.2072345018386841e-007) >>> ct.TransformPoint(590000,4914000) (-103.87032571673741, 44.375642923223602, 2.1979212760925293e-007) >>> }}} ArcGIS里面的地理坐标范围结果是这样的:
{{{ #! python In decimal degrees West: -103.870326 East: -103.628957 North: 44.501659 South: 44.373036 }}} 用proj计算的和ArcGIS有些差距(分别有一个很准,一个差了0.01度)。但是我觉得这种误差应该是允许的。
= 反馈 = 如果您发现我写的东西中有问题,或者您对我写的东西有意见,请登陆[[http://groups.google.com/group/geosings?hl=zh-CN|这个论坛]]。