<<TableOfContents: execution failed [Argument "maxdepth" must be an integer value, not "[maxdepth]"] (see also the log)>>

关于空间参考

好啦,现在让我们开始进入地理的世界

一幅地图除了需要画出地物的形状,更重要的是要画出地物的位置。做GIS的头一项就是要做定位。这也就是GIS数据源和普通数据源的区别--它带有空间信息。GDAL和PIL等图像处理库的最大区别也就在此处。

空间信息包括两个部分。一个是坐标系统,还有一个就是栅格坐标和地理坐标之间的转换。

坐标系统是一个很大的概念。包括很多知识点,可以写厚厚的一本书。我们这里也不想多说,毕竟我在这里不是要普及地理知识,我只在这里讲GDAL中涉及的坐标系统概念。因为我自己对坐标系统学习也并不是太深入(我不是测绘专业的),所以,我的一些理解可能是错误的,希望大家能及时指正。 根据GDAL数据模型文档的解释,GDAL数据集类中包括的坐标系统解释是这样的:

GDAL文档的解释

数据集的坐标系统由OpenGIS WKT字符串定义,它包含了:

1、一个总体的坐标系名

2、一个地理图形坐标系统名

3、一个基准面定义

4、一个椭球体的名字。长半轴(semi-major axis)和反扁率(inverse flattening)

5、本初子午线(prime meridian)名和其与格林威治子午线的偏移值

6、投影方法类型(如横轴莫卡托)

7、投影参数列表(如中央经线等)

8、一个单位的名称和其到米和弧度单位的转换参数

9、轴线的名称和顺序

10、在预定义的权威坐标系中的编码(如EPSG)

更多信息请参考OpenGIS WKT坐标系统定义,以及osr教程文档和OGRSpatialReference类的描述文档

在GDAL中,返回坐标系统的函数是GDALDataset::GetProjectionRef()。它返回的坐标系统描述了地理参考坐标,暗含着仿射地理参考转换,这地理参考转换是由GDALDataset::GetGeoTransform()来返回。

由GCPs地理参考坐标描述的坐标系统是由GDALDataset::GetGCPProjection()返回的。

注意,返回的坐标系统字符串“”表示未知的地理参考坐标系统。

仿射地理转换

GDAL数据集有两种模式描述栅格位置(用点/线坐标系)以及地理参考坐标系之间的关系:首要的也是最普遍的是使用仿射转换,另一种则是GCPs(多控制点定位方式)

   1 Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2)
   2 Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5)

假设指北针向上的影像,GT2和GT4参数是0,而GT1是象元宽,GT5是象元高,(GT0,GT3)点位置是影像的左上角。

注意,上面所说的点/线坐标系是从左上角(0,0)点到右下角,也就是坐标轴从左到右增长,从上到下增长的坐标系。点/线位置中心是(0.5,0.5)

GCPs

数据集可以由一系列控制点来定义空间参考坐标系所有的GCPs共用一个地理参考坐标系(由GDALDataset::GetGCPProjection()返回,每个GCP(由GDAL_GCP定义)包含下面内容:

   1  typedef struct
   2 {
   3     char        *pszId;
   4     char        *pszInfo;
   5     double      dfGCPPixel;
   6     double      dfGCPLine;
   7     double      dfGCPX;
   8     double      dfGCPY;
   9     double      dfGCPZ;
  10 } GDAL_GCP;

pszid是本GCP在数据集中一系列GCP点中惟一的标示字符串,它常常是数字,但不一定总是。有可能在GCP状态中包含机器可分析信息,虽然现在还不行。

(dfGCPPixel,dfGCPLine)位置是栅格中的GCP位置,(dfGCPX,dfGCPY,dfGCPZ)位置是联合的地理参考位置(Z通常是0)

GDAL数据模型没有实现由GCPs...产生坐标系的变化的机制,而是把它留给实际应用。但是1到5阶多项式是通常使用的方法。

通常一个数据集会包含仿射地理变换。和GCPS中的一个或者两个都没有。两个都有很少见。而且无法用权威坐标系定义。

我的理解

上面文档说得很玄,还是让我们看看坐标系统究竟是什么样子。

   1 >>> import gdal
   2 >>> dataset = gdal.Open("e:/gisdata/gtif/spot.tif")
   3 >>> dir(dataset)
   4 ['AddBand', 'AdviseRead', 'BuildOverviews', 'FlushCache', 'GetDescription', 'Get
   5 Driver', 'GetGCPCount', 'GetGCPProjection', 'GetGCPs', 'GetGeoTransform', 'GetMe
   6 tadata', 'GetProjection', 'GetProjectionRef', 'GetRasterBand', 'GetSubDatasets',
   7  'RasterCount', 'RasterXSize', 'RasterYSize', 'ReadAsArray', 'ReadRaster', 'Refr
   8 eshBandInfo', 'SetDescription', 'SetGCPs', 'SetGeoTransform', 'SetMetadata', 'Se
   9 tProjection', 'WriteRaster', '__del__', '__doc__', '__init__', '__module__', '_b
  10 and', '_o']
  11 >>> dataset.GetProjectionRef()
  12 'PROJCS["unnamed",GEOGCS["NAD27",DATUM["North_American_Datum_1927",SPHEROID["Cla
  13 rke 1866",6378206.4,294.9786982139006,AUTHORITY["EPSG","7008"]],AUTHORITY["EPSG"
  14 ,"6267"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPS
  15 G","4267"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],
  16 PARAMETER["central_meridian",-105],PARAMETER["scale_factor",0.9996],PARAMETER["f
  17 alse_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EP
  18 SG","9001"]],AUTHORITY["EPSG","26713"]]'
  19 >>>

WKT

最后那一大串字符串就代表了坐标系统了,没有缩进编排会看晕了(这个东西我第一次看以为是脚本)。GDAL的坐标系统的标示方法是用OpenGIS的WKT字符串表示。当然也混合了EPSG权威编码,关于WKT可以看这里。 它是绝对的标准,完全解释了坐标系统的组成,结构和层次,它是由一些层次嵌套构成的。

   1 Coordinate System WKT
   2 <coordinate system>  = <horz cs> | <geocentric cs> | <vert cs> | <compd cs> | <fitted cs> | <local cs>
   3 <horz cs>            = <geographic cs> | <projected cs>
   4 <projected cs>       = PROJCS["<name>", <geographic cs>, <projection>, {<parameter>,}* <linear unit> {,<twin axes>}{,<authority>}]
   5 <projection>         = PROJECTION["<name>" {,<authority>}]
   6 <geographic cs>      = GEOGCS["<name>", <datum>, <prime meridian>, <angular unit> {,<twin axes>} {,<authority>}]
   7 <datum>              = DATUM["<name>", <spheroid> {,<to wgs84>} {,<authority>}]
   8 <spheroid>           = SPHEROID["<name>", <semi-major axis>, <inverse flattening> {,<authority>}]
   9 <semi-major axis>    = <number>
  10 <inverse flattening> = <number>
  11 <prime meridian>     = PRIMEM["<name>", <longitude> {,<authority>}]
  12 <longitude>          = <number>
  13 <angular unit>       = <unit>
  14 <linear unit>        = <unit>
  15 <unit>               = UNIT["<name>", <conversion factor> {,<authority>}]
  16 <conversion factor>  = <number>
  17 <geocentric cs>      = GEOCCS["<name>", <datum>, <prime meridian>, <linear unit> {,<axis>, <axis>, <axis>} {,<authority>}]
  18 <authority>          = AUTHORITY["<name>", "<code>"]
  19 <vert cs>            = VERT_CS["<name>", <vert datum>, <linear unit>, {<axis>,} {,<authority>}]
  20 <vert datum>         = VERT_DATUM["<name>", <datum type> {,<authority>}]
  21 <datum type>         = <number>
  22 <compd cs>           = COMPD_CS["<name>", <head cs>, <tail cs> {,<authority>}]
  23 <head cs>            = <coordinate system>
  24 <tail cs>            = <coordinate system>
  25 <twin axes>          = <axis>, <axis>
  26 <axis>               = AXIS["<name>", NORTH | SOUTH | EAST | WEST | UP | DOWN | OTHER]
  27 <to wgs84s>          = TOWGS84[<seven param>]
  28 <seven param>        = <dx>, <dy>, <dz>, <ex>, <ey>, <ez>, <ppm>
  29 <dx>                 = <number>
  30 <dy>                 = <number>
  31 <dz>                 = <number>
  32 <ex>                 = <number>
  33 <ey>                 = <number>
  34 <ez>                 = <number>
  35 <ppm>                = <number>
  36 <fitted cs>          = FITTED_CS["<name>", <to base>, <base cs>]
  37 <to base>            = <math transform>
  38 <base cs>            = <coordinate system>
  39 <local cs>           = LOCAL_CS["<name>", <local datum>, <unit>, <axis>, {,<axis>}* {,<authority>}]
  40 <local datum>        = LOCAL_DATUM["<name>", <datum type> {,<authority>}]

这个结构的阅读方法是这样的,所有的尖括号<>中的值都是一个需要被替代的东西(我觉得应该叫做一个类型)。你可以在表的其余中找到对应的类型描述,但是这个类型是不能直接写出来的,也就是说相当于一个链接,也可以看成是一个指针或一个引用,它指向一个对象,但是却不是一个对象,你需要用一个确实的东西来代替它。而且文档的下面有各种类型的解释。在花括号{}中的是可选的项目,表现在实际的坐标表示中,就是可写可不写。等号=是用来表示类型的表达形式。 用 | 分割开的是并列的选项,也就是在实际坐标表示中你只能选用其中的一个。*号表示有数个,多个相同的类型组成。除了上面的其他的符号就代表确实的东西([]是需要实际写入坐标系表示字符串的一部分,不是这个语法表达表中的表达符号,冒号" 逗号, 同样也是实体的一部分),是需要到坐标系中的。最后注意书写顺序从<coordinate system>一条条对应下来,就可以了。比如从<coordinate system>开始,它不是一个东西,而它是<horz cs>等类型的其中一个类型。如果我们判断我们需要的坐标系是一个<horz cs>类型,我们就去<horz cs>中找,<horz cs>是<projected cs>等类型其中一个类型。如果我们判断我们需要的坐标系是一个投影坐标类型,我们就到<projected cs>中找,<projected cs>的内容不再是虚的了,我们就把PROJ[.., .., ..]结构写下来。PROJ["<name>", <geographic cs>, <projection>, {<parameter>,}* <linear unit> {,<twin axes>}{,<authority>}],其中包括了<name>类型,<geographic cs>类型,<projection>类型,数个可选的<parameter>类型,<linear unit>类型,还有可选的<twin axes>和<authority>类型组成的。然后到每个类型中去找对应的结构,直到全部内容都写完为止。需要解释的是<name> <code>是对应的实际的类型的名称和编码。不是在表中有对应。就如上面的例子,基准面名是"North_American_Datum_1927",对应的<datum>类型的<name>类型就用该名称代替。也就是说各种坐标、投影、单位、编码,数值也都算做一个单独的类型。最后直到把那些类型都用实际的东西填满为止。

解释起来很麻烦(汗都说出来了),如果你还是不理解,算了,就当我没说,其实你可以完全不管这些的(但是如果你要写,可以看看这里这里来获取更多有关于坐标系统和其参数的信息,你还可以下载postgresql,如果安装了postgis插件,你就拥有了几乎所有常用的坐标系统的wkt表示)。如果我们不亲自写不同坐标间的转换函数。我们只要关心两个坐标系统是否完全一样,其他都可以不去管它。要是说起不同坐标系统之间转换,又有一大堆的东西要说了,还是以后开个专门的页面写写吧。对于我们来说重要的是另外一个部分--坐标转换。

坐标转换

   1 >>> dataset.GetGCPs()
   2 []
   3 >>> dataset.GetGeoTransform()
   4 (590000.0, 20.0, 0.0, 4928000.0, 0.0, -20.0)
   5 >>>

这是我们主要要对付的。只有通过这几个数据,我们才可以知道自己手中这张图该如何铺展到绘制平面上。上面说过了,栅格坐标到地理坐标之间的转换可以通过两种方式来进行:一种是仿射坐标转换,一种是GCPs。一般来说,仿射坐标转换比较常见。仿射坐标转换就是通过几个值来进行栅格框架到地理框架的映射。上面的例子中出现了6个值。这六个值可以用下面的

   1 Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2)
   2 Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5)

公式来解释,而且数组顺序对应起来了,理解起来也不困难。至于几个参数究竟是什么意思,这里讲得更清楚:

   1 adfGeoTransform[0] /* top left x */
   2 adfGeoTransform[1] /* w-e pixel resolution */
   3 adfGeoTransform[2] /* rotation, 0 if image is "north up" */
   4 adfGeoTransform[3] /* top left y */
   5 adfGeoTransform[4] /* rotation, 0 if image is "north up" */
   6 adfGeoTransform[5] /* n-s pixel resolution */

看起来和ESRI为GeoTiff定义的tfw文件格式很像。至于是否真的是完全对应,我还不敢说。

有了上面的公式,我们就可以把栅格的每个点代入公式来求出其在地球上的实际位置(当然,求出的数值的单位是在坐标系统中定义的那个,而非经纬度坐标,要求经纬度坐标,还要通过一步转换)。当然我们可以不用这么麻烦,只要求出整个图像外框上下左右四个边界就可以定下整个图像的位置,因为图像都是矩形的(那些非矩形的图像其实是有另外一层二值的掩膜图像(mask)叠加而成的,这种图像只有两种值,一种是0,一种是1,1代表按照图像原色显示,0代表不显示)。图像中某点的地理位置可以通过外框四点线性计算。

现在看看我们这张图的边界地理范围:

   1 >>> dataset.GetGeoTransform()
   2 (590000.0, 20.0, 0.0, 4928000.0, 0.0, -20.0)
   3 >>> dataset.RasterXSize
   4 950
   5 >>> dataset.RasterYSize
   6 700
   7 >>>

上边框和左边框都不用算了,是4928000和590000。 求右下角的坐标,代入公式:


右边框 = Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2) = 590000.0 + 950*20.0 + 700*0.0 = 609000.0

下边框 = Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5) = 4928000.0 + 950*0.0 + 700*(-20.0) = 4914000.0


开ArcGIS验证一下,没错。

   1 In projected or local coordinates
   2 Left: 590000.000000
   3 Right: 609000.000000
   4 Top: 4928000.000000
   5 Bottom: 4914000.000000

好了,有了这些,就可以把图像和地理坐标联系起来了。有了地理坐标,就可以把图像在照屏幕上预先定好的坐标系画出来了。

需要注意的一点是,对于GeoTiff数据源来说,坐标系统和仿射变换参数有两种存储方式,一种是直接存储在.tif文件内部,这种存储方式是按照tif内部的的键值对方式来存储的。具体的一些细节可以参看这里。官方的说明pdf可以在这里下载。也可以把图像仅仅存储到tif文件中,而把坐标系统和仿射参数什么的单独提取出来,创建两个文件,一个是prj文件,存放WKT坐标系字符串,一个是tfw文件,存放仿射转换参数。

小插曲:

关于tfw文件格式,网络流传几种说法。一种是这样的:


特别是这几行:

行 说明

1 地图单元中的一个象素在X方向上的X分辨率尺度。

2 平移量。

3 旋转量。

4 地图单元中的一个象素在Y方向上的Y分辨率尺度的负值。

5 象素1,1(左上方)的X地坐标。

6 象素1,1(左上方)的Y地坐标。


这种说法的源头可能是这个,但是他的参数个数是8个 第二种则是在gdal文档中的如下的解释

   1 WLD -- ESRI World File
   2 A world file file is a plain ASCII text file consisting of six values separated by newlines. The format is:
   3  pixel X size
   4  rotation about the Y axis (usually 0.0)
   5  rotation about the X axis (usually 0.0)
   6  negative pixel Y size
   7  X coordinate of upper left pixel center
   8  Y coordinate of upper left pixel center
   9 For example:
  10 60.0000000000
  11 0.0000000000
  12 0.0000000000
  13 -60.0000000000
  14 440750.0000000000
  15 3751290.0000000000
  16 You can construct that file simply by using your favorite text editor.
  17 World file usually has suffix .wld, but sometimes it may has .tfw, tifw, .jgw or other suffixes depending on the image file it comes with.

可以看出参数2和3和第一种的解释不一样。我现在宁可相信GDAL做的第二种解释(如果不是,那著名的grass不是白搞了?),但是这种说法也不一定,因为在gdal中直接解释的是wld文件,别名才是tfw,有没可能tfw有其他官方定义而非ESRI为惟一标准呢?如果去查google,中文和英文的解释会有很多种,越看只能越混乱。幸好大多数情况下是0 -_-! 不管了,先凑合这么用吧。如果谁知道ESRI官方解释,不妨贴上来大家共享)

GCPs

至于GCPs,那就麻烦了。不过玩过arcview或者mapinfo的配准的应该理解起来都没什么问题。要配准一张栅格图(把一个没有空间信息的图片变成有空间信息的栅格数据集),只要在一个已知的坐标系统中定位几个控制点,然后输入这几个点的地理位置。软件通过读取这几个点在图像中的位置,以及输入的这几个点对应的地理位置,建立一个拟合方程,用方程来描述其他点在这个坐标系统中的地理位置。当然,在不同投影不同坐标系不同软件中拟合方程处理可能不同。所以拟合出来的坐标可能有一些差异。这个过程不好试验也不好验证,手上也没有相关数据,所以在这里就不多说了。

反馈

如果您发现我写的东西中有问题,或者您对我写的东西有意见,请登陆这个论坛

lilin/gdal-spatialref (last edited 2009-12-25 07:10:15 by localhost)