python-GDAL空间分析编程的整理

记得前段时间,总结了基于python的GIS空间分析类库,大大小小有20多个,司职于不同的领域,当然也有一定的重复,不过对于空间分析部分,总结下来最为主要的还是python-GDAL以及矩阵分析类库numpy,ArcGIS 最新版本已经将numpy囊括到它的geoprocessing里面去了,看来用numpy做空间分析是大势所趋。因为一直有用基于python写Multi-agents based land use modelling 的需求, 所以在空间分析方面有了一点小小的体会和经验,不成系统,希望大家多多探讨交流。

当然,如果要做空间分析,首先要将数据读入,gdal是必须的,要分析才牵扯到numpy做栅格分析,shapely做矢量分析。记得以前还列出了scipy等,按照长尾理论,这些类库用的机会是很少的,所以我在想,如果要开发一整套基于python的空间类库,是不是有必要引入这个庞大的统计分析模块。自己读过UCSB一些牛人写的程序,他们甚至连数据读入和输出都是自己来写,我方是觉得这似乎没有必要,基于GDAL在GIS业界的声誉和强大功能,引入GDAL是不会错的。

要做空间分析,就牵扯到数据输入、处理,输出三个部分。关于数据输入,我情钟与GeoTiff,虽然大牛们用asc格式(AAGrid)比较多,但是GeoTiff的优势是显而易见。至少在gdal的GTiff driver的create和createCopy方法都会支持。

1
2
3
4
5
6
7
8
9
    format = "HFA"
    driver = gdal.GetDriverByName( format )
    metadata = driver.GetMetadata()
    if metadata.has_key(gdal.DCAP_CREATE) \
       and metadata[gdal.DCAP_CREATE] == 'YES':
        print 'Driver %s supports Create() method.' % format
    if metadata.has_key(gdal.DCAP_CREATECOPY) \
       and metadata[gdal.DCAP_CREATECOPY] == 'YES':
        print 'Driver %s supports CreateCopy() method.' % format

其次,在数据读入这里,我建议自己先确定整个分析研究区的情况,比如大小,投影,转换,控制点什么的,这些metadata数据最好是import一个基本数据,比如import base year的土地利用情况。其实对于这点,大家可以思量以前用ArcMap加载数据的情况,如果要设置一个layer的相关信息,包括投影信息,extent信息等,我们都会选择导入项目中和该数据具有相同metadata的数据。通过以上分析,可以看出,如果要建立一个基本研究区,可以通过以下两种方法:

  • 自己制作数据的metadata,相对而言,比较麻烦,
  • 导入相关信息的metadata,方便,不过需要有相关信息,如果没有,只有用前者。

例如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    def init_from_name(self, filename):
        """
        Initialize file_info from filename
        filename -- Name of file to read.
        Returns 1 on success or 0 if the file can't be opened.
        """
        fh = gdal.Open( filename )
        if fh is None:
            return 0
        self.filename = filename
        self.bands = fh.RasterCount
        self.xsize = fh.RasterXSize
        self.ysize = fh.RasterYSize
        self.band_type = fh.GetRasterBand(1).DataType
        self.projection = fh.GetProjection()
        self.geotransform = fh.GetGeoTransform()
        self.ulx = self.geotransform[0]
        self.uly = self.geotransform[3]
        self.lrx = self.ulx + self.geotransform[1] * self.xsize
        self.lry = self.uly + self.geotransform[5] * self.ysize
 
        ct = fh.GetRasterBand(1).GetRasterColorTable()
        if ct is not None:
            self.ct = ct.Clone()
        else:
            self.ct = None
        return 1

这样就有了一个基本研究区概况的类,这样我们做分析的时候,就不用不停的去获取metadata信息,另一个需要注意的是关于nodatavalue,这个以前在用erdas和arcgis做分析的时候并没有太多注意,不过如果要用python编程,nodatavalue的值是个很重要的部分,搞不好,你在做slope分析或者做hillshade的时候,把不需要分析的值加以分析,产生错误的结果。一般情况下,如果是和邻域无关的空间分析还好点,如果和邻域有关,那结果肯定是有问题的。比如说,你要做一个moore准则的CA,如果你不设定nodatavlue那么,这样一来所有做为nodata的部分全部都死亡掉了,然后进入了整个loop。

对于nodatavalue,我个人的看法是,首先要有一个整个研究区的nodatavlue,这样方便我们输出结果,其次很多情况下,栅格数据不光包括DEM数据,(float数据,一般在-8000~8000之前),slope数据(90以下),AHP法和delphi法得到的打分数据(基于打分标准,可能有float 0~1, float 0~100, byte 0~255, int 0~1000 or 10000),光栅值数据(如果是一个band,那么就是byte 0~255). 这样看来,还是找不到一个合理的nodatavalue,因为大家的nadata的range实在是太多了,比如说用 数据类型的最大或者最小值做nodatavalue的。
GDAL data type minimum maximum
Byte 0 255
UInt16 0 65,535
Int16, CInt16 -32,768 32,767
UInt32 0 4,294,967,295
Int32, CInt32 -2,147,483,648 2,147,483,647
Float32, CFloat32 -3.4E38 3.4E38
Float64, CFloat64 -1.79E308 1.79E308
比如,如果是Byte,就用 0 或者 255, Int16 就用 -32768 或者32767.针对此,我看了gdal的python sampleperrygeo自己推荐的一些实例,(一句题外话,这两个项目的python代码推荐有空大家读一读,还蛮有启发的),最好的方法是Band.GetNoDataValue(),然后输出的时候再Set.NoDataValue()。

刚才扯到了栅格数据的range问题,当然具有具体地理意义的栅格数据的range我没法去设定,但是关于打分数据,我觉得我们可以规定一个范围,比如说0~1,然后数据类型用float,切记,这只是个打分数据,数据输出的时候,可能还要进行分数在加以reclassify。其实用栅格进行处理打分只是一个最基本的问题,相关问题还有进行标准化,离散化,空间运算等。这些时候,就很难控制一个值域。所以我觉得,不能去控制输入数据的值域,我们能做的,就是在读入数据后进行必要的标准化运算。

最后说说输出,PIL,Pylab,gdal,mapnik都有产生图片的方法。总而言之,如果你还要进行地理分析,或者不想引入其他类库,gdal,PIL就够用了。

1
2
3
4
5
6
7
8
9
10
11
12
    # Output the Array
    format = "GTiff"
    driver = gdal.GetDriverByName( format )
    dst_ds = driver.Create( outfile, ds.RasterXSize, ds.RasterYSize, 1, gdal.GDT_Float32 )
    if ds.GetGeoTransform():
        dst_ds.SetGeoTransform(ds.GetGeoTransform())
    if ds.GetMetadata():
        dst_ds.SetMetadata(ds.GetMetadata())
    if ds.GetProjection():
        dst_ds.SetProjection(ds.GetProjection()) 
    dst_ds.GetRasterBand(1).SetNoDataValue( noDataValue )   
    dst_ds.GetRasterBand(1).WriteArray( outArray )

Pylab是用来进行数值分析,和统计分析时使用的。

1
2
3
pylab.imshow(result)
pylab.savefig("/XXX.png")
pylab.draw()

如果你要进行美化渲染并以一种很好的形式表现,mapnik是个不错的选择,现在mapnik已经更新到0.6.0了,变化还是很大的,有空给大家介绍介绍。

最后说说架构,建议大家可以读读几个项目的源代码,比如cage,urbansim,gdal python sample。如果你只是先实现一些小功能,那么看看perrygeo的python tools还是挺有帮助的。 就先说这么多,有空再和大家聊。

Share with:

  • email
  • LinkedIn
  • Twitter
  • Facebook
  • del.icio.us
  • StumbleUpon
  • Reddit
  • Digg
  • 豆瓣

Related posts: