GDAL栅格数据处理教程

本文结合实例详细讲解了如何使用GDAL操作栅格数据,包括栅格数据的读写,以及一些常用的栅格数据处理操作,如坐标变换、裁剪、镶嵌、插值等。关注公众号GeodataAnalysis,回复20230220获取示例数据和代码,公众号二维码见文末。

GDAL(Geospatial Data Abstraction Library)是能够读写大量的栅格数据格式的开源地理空间库,其使用面向对象的C++语言编写,这令该库在支持百余种栅格数据格式的同时,还具有很高的执行效率。 GDAL同时还提供了多种主流编程语言的绑定,除了C和C++语言之外,用户还可以在Perl、Python、Ruby、Java、C#等语言中调用GDAL,这令GDAL的应用变得非常广泛,几乎在所有地理空间工具中都能看到它的身影,很多软件都使用它作为底层数据处理的库,其中包括:ArcGIS、Google Earth、OpenEV、GRASS GIS、OSSIM、Quantum GIS、MapServer、World Wind等。

1 导入GDAL

在Python中使用GDAL,只需要导入gdal模块即可。 在早期的版本(1.5以前)中,GDAL是使用 import gdal 语句导入。但是后来GDAL成为OSGEO的子项目后,对代码进行了重新组织。 实现了Python的新的命名空间osgeo,并将gdalogr都包含在这个名称空间之下。因此对于1.6以后的版本,推荐使用from osgeo import gdal语句导入。

为了保持兼容性,可以使用下面的语句来导入GDAL:

try:
    import gdal
except:
    from osgeo import gdal

2 查看GDAL支持的栅格数据格式

下面的代码用于列出GDAL支持的所有的数据驱动,每个驱动对应一种栅格数据格式。第一行用于获取当前系统支持的数据驱动的个数,第三行使用gdal.GetDriver()函数来获得驱动,第四行则打印了驱动的名称。驱动有ShortName与LongName,ShortName与栅格数据格式在GDAL中定义的编码是一致的,而LongName则可以看成是描述性的文字。

drv_count = gdal.GetDriverCount()
print('数据驱动的数量为{},分别是:'.format(drv_count))
for idx in range(drv_count):
    driver = gdal.GetDriver(idx) # 获取索引值为idx的驱动
    print( "%10s: %s" % (driver.ShortName, driver.LongName))
数据驱动的数量为216,分别是:
       VRT: Virtual Raster
   DERIVED: Derived datasets using VRT pixel functions
     GTiff: GeoTIFF
      ... ... ...
      ENVI: ENVI .hdr Labelled
      EHdr: ESRI .hdr Labelled
      ISCE: ISCE raster
      HTTP: HTTP Fetching Wrapper

对于不同的运行环境,以及安装的GDAL的版本与编译选项的不同,程序的结果也不尽相同。因此为避免同一驱动在不同运行环境下索引值不同造成的问题,一般情况下不推荐使用gdal.GetDriver()这个函数来获取驱动,应当使用gdal.GetDriverByName()函数获取驱动,该函数的参数为数据驱动的ShortName。例如要获取GeoTiff格式的数据驱动,可以使用如下代码:

driver = gdal.GetDriverByName('GTiff')
if not driver is None:
    print('获取驱动成功')
else:
    print('获取驱动失败')

3 读取栅格数据集的信息

关注公众号GeodataAnalysis,回复20230220获取示例数据和代码,公众号二维码见文末。

接下来以GeoTiff格式的栅格数据文件为例,讲解如何使用GDAL读取栅格数据的基本信息,首先使用如下代码打开该文件(应使用绝对路径,本文使用相对路径只是作为演示):

dataset = gdal.Open(r"./LT51190381984137HAJ00.tif")

gdal.Open用于一个文件并返回为一个GDAL可操作的Python对象,下面来看一下都能对其进行怎样的操作。Python提供了dir()内省函数,可以快速查看一下当前对象可用的操作。如要查询其中某个操作的用法介绍,则可以用help函数,例如help(dataset.ReadAsArray)

dir(dataset)
['AbortSQL',
 'AddBand',
 'AddFieldDomain',
 'AdviseRead',
 'BeginAsyncReader',
 'BuildOverviews',
 ... ...
 'this',
 'thisown']

下面看一下如何获取文件的一些基本信息,以及需要用到的的一些函数与属性。

  • dataset.GetMetadata() :用于获取栅格的元数据信息。
  • dataset.GetDescription() :用于获取栅格的描述信息。
  • dataset.RasterXSize :用于获取栅格数据的宽度(X方向(经度方向)上的像素个数)
  • dataset.RasterYSize :用于获取栅格数据的高度(Y方向(维度方向)上的像素个数)
  • dataset.GetProjection() :用于获取栅格数据的投影信息。
  • dataset.RasterCount :用于获取栅格数据集的波段数。
  • dataset.GetRasterBand() :用于获取栅格数据的波段,进而读取各个波段的信息。

客观地说,GDAL对元数据的支持并不好,它并没有直接的元数据处理接口,更没有实现元数据的相关标准。但是,GDAL也提供了一些函数,可以读取影像的一些信息,从而方便对数据进行处理。GDAL一般是以字典的形式对元数据进行组织的,但是对于不同的栅格数据类型,元数据的类型与键值可能都不一样。即使是同一栅格数据类型,元数据信息也不尽相同。下面以提供的这个GeoTIFF文件为例,查看其元数据信息:

dataset.GetMetadata()
{'Band_1': 'Layer (Band 1:LT51190381984137HAJ00_B1.TIF)',
 'Band_2': 'Layer (Band 1:LT51190381984137HAJ00_B2.TIF)',
 'Band_3': 'Layer (Band 1:LT51190381984137HAJ00_B3.TIF)',
 'Band_4': 'Layer (Band 1:LT51190381984137HAJ00_B4.TIF)',
 'Band_5': 'Layer (Band 1:LT51190381984137HAJ00_B5.TIF)',
 'Band_6': 'Layer (Band 1:LT51190381984137HAJ00_B6.TIF)',
 'Band_7': 'Layer (Band 1:LT51190381984137HAJ00_B7.TIF)'}

同样的,不同数据集有不同的描述信息,这里的描述信息是栅格数据的路径:

dataset.GetDescription()
'./LT51190381984137HAJ00.tif'

栅格数据的大小指出了影像以像元为单位的宽度与高度,查看方式如下:

dataset.RasterXSize, dataset.RasterYSize
(7931, 7161)

下面看一下如果从栅格数据集中获取其投影与空间参考信息,首先使用GetGeoTransform()方法获取地理仿射变换参数。对于遥感影像来说,它需要在地理空间中进行定位。在GDAL中,这有两种方式,其中一种是使用六个参数坐标转换模型。这个模型的具体实现在不同的软件中是不一样的。在GDAL中,这六个参数分别是左上角坐标,像元在X、Y方向的像元大小和旋转角度。要注意,Y方向的像元大小为负值。

GT = dataset.GetGeoTransform()
GT
(135885.0, 30.0, -0.0, 3621915.0, -0.0, -30.0)

其中,GT[2]和 GT[4] 表示图像的旋转系数,在“向北”的图像中,二者都为为0。 GT[1]和GT[5]分别是图像X方向和Y方向的分辨率(X方向的分辨率为整数,Y方向的分辨率为负数)。 (GT[0], GT[3]) 是栅格左上角像素的地理坐标,分别是经度和纬度。

获取数据集的投影信息需要使用GetProjection()函数,该函数返回数据集投影的字符串格式的WKT文本。

dataset.GetProjection()
'PROJCS["UTM_Zone_51N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",123],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1],AXIS["Easting",EAST],AXIS["Northing",NORTH]]'

栅格数据集是由多个数据构成的,在GDAL中,每一个波段,都是一个数据集; 不仅如此,栅格数据集还可能包含有子数据集,每子数据集又可能包含有多个波段。下面查看一下打开的数据集的波段个数,这是一个由7个波段构成的Landsat遥感影像,所以返回的RasterCount的值为7。

dataset.RasterCount
7

这里应当注意的是,对于不同的数据集,查看其波段个数的方法也是不同的。如对HDF格式的栅格数据而言,直接查看其RasterCount返回的结果不一定是其波段数。这是由于它的数据是以子数据集组织的, 要获取其相关的数据的信息, 需要继续访问其子数据集。

GetRasterBand()函数,可以获得GDAL可操作的Python对象,该对象即栅格的波段,函数的参数为使用波段的索引值。注意!这里我们获取了Landsat影响的第一个波段(红光波段),这里的波段获取和通常的列表或数组的获取不一样,开始是1而不是0。

band = dataset.GetRasterBand(1) # 读取第一个波段

与操作数据集一样,GDAL同样提供了查看波段基本信息的函数。

dir(band)
['AdviseRead',
 'AsMDArray',
 'Checksum',
 'ComputeBandStats',
 'ComputeRasterMinMax',
 'ComputeStatistics',
 'CreateMaskBand',
 'DataType',
 ... ...
 'this',
 'thisown']

下面介绍一下常用的操作,用来获取波段的属性信息:

band.XSize, band.YSize

执行以上代码得到了波段图像的宽和高(像元为单位),由于示例数据为GeoTIFF格式的栅格数据,其各个波段的行列数相同,因此返回结果与数据集对象的RasterXSize和RasterYSize的返回结果相同。

band.DataType, band.GetNoDataValue()
(1, None)

执行以上代码获取波段的数据类型和NoData值,由于该波段不存在NoData值,所以返回结果为None。栅格数据用像元的值表示地理区域中实体的信息,对于其中不存在有效数据或数据缺失的像元,便可以将像元值设为NoData。NoData通常使用数据集中在其他位置不会作为有效值的值进行存储,一般是栅格数据类型所能表示的最大值或最小值。例如,对于反映中国全球海拔的栅格数据,可以用2位的整型表示(不要求精确到小数的情况下),2位的整型数据的取值范围为-32768~32767,同时我们知道世界上海拔最高的珠穆朗玛峰的海拔为8848米,那么便可以把栅格数据中的NoData值设为比8848大的任何数。波段的数据类型用整型数字表示,不同的整数对应不同的数据类型,具体数据类型定义在gdalconst模块里,gdalconst与整型数字的对应值:

  • 未知或未指定类型 gdalconst.GDT_Unknown 0
  • 8位无符整型 gdalconst.GDT_Byte 1
  • 16位无符整型 gdalconst.GDT_UInt16 2
  • 16位整型 gdalconst.GDT_Int16 3
  • 32位无符整型 gdalconst.GDT_UInt32 4
  • 32位整型值 gdalconst.GDT_Int32 5
  • 32位浮点型 gdalconst.GDT_Float32 6
  • 64位浮点型 gdalconst.GDT_Float64 7
  • 16位复数整型 gdalconst.GDT_CInt16 8
  • 32位复数整型 gdalconst.GDT_CInt32 9
  • 32位复数浮点型 gdalconst.GDT_CFloat32 10
  • 64位复数浮点型 gdalconst.GDT_CFloat64 11
band.GetMaximum(), band.GetMinimum(), band.ComputeRasterMinMax()
(255.0, 0.0, (0.0, 255.0))

.GetMaximum().GetMinimum()函数用于返回本波段数值中最大和最小的值,某些文件格式不会有固定的最大最小值(此时返回结果为None),但我们可以通过函数ComputeRasterMinMax()计算得到本波段数值中最小和最大的值。需要注意的是,这里的最大最小值不包括上述的NoData值。

4 访问栅格数据集的数据

关注公众号GeodataAnalysis,回复20230220获取示例数据和代码,公众号二维码见文末。

通过上一节介绍的方法,可以访问遥感影像的描述性信息,可以概括地知道影像的投影信息、波段数、 影像大小等一些信息。 但是为了对遥感影像进行处理,需要进一步访问遥感影像中的数据, 即影像中像元的灰度值。GDAL提供了下面两个函数来访问影像的数值:

  • ReadRaster() :以二进制的形式读取图像数据(不常用)。
  • ReadAsArray() :以numpy数组的形式读取图像数据(常用)。

ReadAsArray是一个非常重要的函数,它可以直接读取图像的数据并转为Numpy数组,下面介绍一下该函数常用的几个参数的含义:

  • xoff,yoff :指定想要读取的部分原点位置在整张图像中距离全图原点的位置(以像元为单位)。
  • xsize,ysize : 指定要读取部分图像的矩形的长和宽(以像元为单位)。
  • buf_type :可以对读出的数据的类型进行转换(比如原图数据类型是int,可以转换为float)。
  • band_list :适应多波段的情况。可以指定要读取的波段。

下面通过几个例子看一下GDAL如何获取GeoTIFF文件中的数据,以下代码在Ipython中运行:

In [1]: import numpy as np
In [2]: from osgeo import gdal
In [3]: dataset = gdal.Open(r"./LT51190381984137HAJ00.tif")
In [4]: a = dataset.ReadAsArray() 
In [5]: type(a) 
Out[5]: numpy.ndarray
In [6]: a.dtype 
Out[6]: dtype('uint8')
In [7]: a.shape 
Out[7]: (7, 7161, 7931)

GDAL还能实现分块读取,并且只读取指定的波段。在下面的代码中,第四行读取了栅格数据集中心像元的X方向和Y方向各3个像元的3×3的区域的所有波段的数据并转为数组,在第七行输出了表示第一个波段的数组。第十九行相较于第七行多了一个band_list参数,表示读取第一个波段,紧接着输出了读取结果,可以看到与之前的输出结果是一样的。

In [8]: center_x, center_y = int(dataset.RasterXSize/2), 
                            int(dataset.RasterYSize/2)
In [9]: center_x, center_y # 栅格数据集中心像元的坐标
Out[9]: (3965, 3580)
In [10]: a = dataset.ReadAsArray(center_x, center_y, 3, 3)
In [11]: a.shape
Out[11]: (7, 3, 3)
In [12]: a[0]
Out[12]:
array([[ 99,  92,  89],
       [ 92,  94,  94],
       [ 90,  92,  98],], dtype=uint8)
In [13]: a = dataset.ReadAsArray(center_x, center_y, 
								 3, 3, band_list=[1])
In [14]: a
Out[14]:
array([[ 99,  92,  89],
       [ 92,  94,  94],
       [ 90,  92,  98],], dtype=uint8)

5 使用GDAL创建影像

创建影像的前提是要知道创建什么格式的影像,而后生成对应的数据驱动, 如下面的代码所示(以下代码均在IPython中运行):

In [1]: from osgeo import gdal
In [2]: driver = gdal.GetDriverByName('GTiff')

如果不是使用一个已有的影像文件来创建新的影像,就需要用到数据驱动的Create方法创建一个栅格数据集对象。在数据处理过程中,这种是主要的方法,它可以把建立在内存中的虚拟数据集输出为实际的栅格文件。这也就是栅格数据持久化的概念,将内存中的数据模型(除了数据本身外,还有投影、元数据信息等信息)转换为存储模型。

在下面的代码中,我们首先打开了之前提供的作为示例的Landsat影像。之后用前Create方法创建了一个宽度和高度与之前打开的Landsat影像相同且数据类型为32位浮点型的栅格数据集。Create方法共有五个必选参数,分别是要创建的栅格数据集的路径,栅格数据集的宽度(以像元为单位),栅格数据集的高度(以像元为单位),栅格数据集的波段个数,以及栅格数据及的数据类型。需要注意的是,使用Create方法创建栅格文件时,应使用绝对路径,本文使用相对路径只是作为演示。

In [3]: dataset = gdal.Open(r"./LT51190381984137HAJ00.tif")
In [4]: dst_ds = driver.Create('./ndvi.tif', 
							   dataset.RasterXSize, 
							   dataset.RasterYSize, 
							   1, gdal.GDT_Float32)

创建数据集之后便可以向数据集中写入数据,但在此之前应当清楚,对一个描述地理信息的栅格数据而言,还需要必不可少的投影信息和空间参考信息。在下面的代码中,首先调用栅格数据集的SetGeoTransform方法设置空间参考,传入的参数类型应当是一个可迭代对象(如列表、元组等),内容参考章节3.1.3介绍的仿射变换六参数。之后调用栅格数据集的SetProjection方法设置投影信息,传入的参数类型为字符串,内容是目标投影的WKT文本,二者返回值为0表示设置成功。

In [5]: dst_ds.SetGeoTransform(dataset.GetGeoTransform())
Out[5]: 0
In [6]: dst_ds.SetProjection(dataset.GetProjection())
Out[6]: 0

最后向栅格数据集中写入数据即可,首先读取Landsat影像的第三和第四波段(红光和近红外波段),而后计算归一化植被指数(计算方法为:(近红外-红光)/(近红外+红光)),此时会输出警告,这是因为数组中作为分母的部分有0值。由NDVI的计算公式可知,分母为0时分子也为0(Landsat影像所有波段的值均大于等于0),根据numpy的运算规则,0除0的结果为np.nan。而后读取了新建的数据集的第一个波段,并写入了表示归一化植被指数的numpy数组。之后导入numpy模块,将这个波段的NoData值设为np.nan,即将计算归一化植被指数时分母为0的像元设为空值。最后使用数据集的FlushCache将数据写入磁盘,如此便完成了影像的创建和存储。

In [7]: r_nr_array = dataset.ReadAsArray(band_list=[3, 4])
In [8]: r_nr_array = r_nr_array.astype(np.float32) # 转为32位浮点型,之后除的时候会变换类型,且与创建栅格数据集时定义的数据类型保持一致
In [9]: r_nr_array.shape
Out[9]: (2, 7161, 7931)
In [10]: ndvi = (r_nr_array[1] - r_nr_array[0])/(r_nr_array[1] + r_nr_array[0])
<ipython-input-12-5993f4ba43dc>:1: RuntimeWarning: divide by zero encountered in true_divide
  ndvi = (r_nr_array[1] - r_nr_array[0])/(r_nr_array[1] + r_nr_array[0])
<ipython-input-12-5993f4ba43dc>:1: RuntimeWarning: invalid value encountered in true_divide
  ndvi = (r_nr_array[1] - r_nr_array[0])/(r_nr_array[1] + r_nr_array[0])
In [11]: band = dst_ds.GetRasterBand(1)
In [12]: band.WriteArray(ndvi)
Out[12]: 0
In [13]: import numpy as np
In [14]: band.SetNoDataValue(np.nan)
Out[14]: 0
In [15]: band.GetNoDataValue()
Out[15]: inf
In [16]: dst_ds.FlushCache()
In [17]: dst_ds = band = None

创建多波段栅格数据的方式与之相同,首先使用Create方法创建一个多波段的数据集,之后使用GetRasterBand函数获取该数据集的不同波段,最后使用WriteArray函数向该波段写入数据即可。写入的数据可以是随机生成的Numpy数组,也可以是从其他数据集中读取的数据。借由Numpy数组的特性,可以轻松实现类似于ArcGIS的栅格计算器的功能。

此外,GDAL还能非常便捷的实现栅格数据的压缩与构建金字塔。要实现栅格数据的压缩存储,只需要在使用Create方法创建栅格数据集时增加一个参数,建立金字塔也只需要在写入数据后使用BuildOverviews方法构建即可。

# options是创建栅格数据的选项,COMPRESS=LZW表示使用LZW压缩算法进行压缩
dst_ds = driver.Create('./ndvi.tif', dataset.RasterXSize,
                        dataset.RasterYSize, 1, gdal.GDT_Float32,
                        options=["COMPRESS=LZW"])
# 设定Imagine风格的pyramids
gdal.SetConfigOption('HFA_USE_RRD', 'YES')
# 强制建立pyramids,重采样方法这里采用的是bilinear,此外还有nearest,cubic等
# overviewlist表示缩放等级
dst_ds.BuildOverviews("bilinear", overviewlist=[2, 4, 8, 16, 32, 64, 128])

在数据处理中经常会碰到将Numpy数组转为GeoTiff文件存储的情况,因此本文提供了下面的函数,对该操作进行了封装。

def numpy_to_gdal(dtype):
    """convert numpy dtype to gdal dtype"""
    numpy_dtype = ["byte", "uint8", "uint16", "uint32",
        "uint64", "int8", "int16", "int32",
        "int64", "float16", "float32", "float64",
        "cint16", "cint32", "cfloat32", "cfloat64"]
    gdal_dtype = [gdal.GDT_Byte, gdal.GDT_Byte, 
        gdal.GDT_UInt16, gdal.GDT_UInt32, gdal.GDT_Float32, 
        gdal.GDT_Int16, gdal.GDT_Int16, gdal.GDT_Int32, 
        gdal.GDT_Float32, gdal.GDT_Float32, gdal.GDT_Float32, 
        gdal.GDT_Float64, gdal.GDT_CInt16, gdal.GDT_CInt32, 
        gdal.GDT_CFloat32, gdal.GDT_CFloat64]
    return gdal_dtype[numpy_dtype.index(dtype.name)]

def array_to_raster(path, array, proj, gt, 
					driver_name='GTiff', nodata=None):
    """ Saving a 2-d array to raster file. """
    assert len(array.shape) == 2
    driver = gdal.GetDriverByName(driver_name)
    dtype = numpy_to_gdal(array.dtype)
    ds = driver.Create(path,
                       array.shape[1],
                       array.shape[0],
                       1, dtype,
                       options=["COMPRESS=LZW"])
    ds.SetProjection(proj)
    ds.SetGeoTransform(gt)
    band = ds.GetRasterBand(1)
    band.WriteArray(array)
    if None == nodata:
        band.SetNoDataValue(nodata)
    ds = band = None

6 常用栅格数据处理操作

6.1 创建直方图

直方图可以显示一个数据集中数据分布频率的统计结果。在遥感领域,数据集就是图片。对于8比特存储图像信息的数据集,其数据分布就是数据值在0~255之间的像元出现的次数。在RGB图像中,颜色是由数字组成的3位元组表达的,其中(0,0,0)代表黑色,(255,255,255)代表白色。为一张图片生成直方图,水平方向x轴代表区间中256种像素值,竖直方向y轴代表频率值。

下面的代码演示了在Python中创建直方图的代码,其中列表bins表示直方图创建时数据值的范围,若bins = [0, 1, 2]则表示计算图像中值在区间[0, 1)[1, 2)的像元个数并可视化,列表bands用于存放需要创建直方图的波段。

from osgeo import gdal
import matplotlib.pyplot as plt
import numpy as np

ds = gdal.Open(r"./LT51190381984137HAJ00.tif")

bins = np.arange(1, 256)
bands = [1, 2, 3]

for band_num in bands:
    band = ds.GetRasterBand(band_num)
    band_a = band.ReadAsArray().flatten()

    location = np.searchsorted(np.sort(band_a), bins)
    hist = location[1:] - location[:-1]

    plt.bar(bins[1:]-0.5, hist, width=1, 
		    label=f'band {
      
      band_num}', alpha=0.5)
    plt.plot(bins[1:]-0.5, hist, '--')

plt.xlabel('Pixel Value', fontsize=18)
plt.ylabel('Frequency', fontsize=18)
plt.title('Raster Histogram', fontsize=20)
plt.legend()
plt.show()

直方图创建结果如下

6.2 坐标变换和重采样

栅格数据可在不同的坐标系统之间转换。欧洲石油调查组织(European Petroleum Survey Group, EPSG)专门负责对地球上所有的测量坐标系统进行维护,并且给每组坐标系统都赋予了一个编号,如常用的WGS84地理坐标系编号就是EPSG: 4326,而WGS84投影坐标系的编号是EPSG: 3395。在这里,我们介绍一个常用于栅格数据坐标系统之间转换的函数,即gdal.Warp,其主要有三个参数,分别是:

  • destNameOrDestDS :输出栅格的文件名或栅格数据集对象。
  • srcDSOrSrcDSTab :输入栅格的文件名或栅格数据集对象,或包含多个文件名或栅格数据集对象的数组。
  • options :关键字参数,gdal.WarpOptions()的返回值。

gdal.WarpOptions()用于设置执行gdal.Warp函数时的选项信息,执行坐标变换时需要使用的主要参数如下:

  • format :输出栅格的文件的格式。
  • srcSRS,dstSRS :源数据和目标数据的空间参考,支持EPSG编号和WKT文本。

以下代码用于演示如何使用gdal.Warp函数对landsat数据进行坐标变换。

from osgeo import gdal
ds = gdal.Open(r"./LT51190381984137HAJ00.tif")
reproj_ops = gdal.WarpOptions(format='GTiff', 
                            srcSRS='EPSG:4326', 
                            dstSRS='EPSG:4525') 
dsReprj = gdal.Warp("Reprj.tif", ds, 
					options= reproj_ops)
ds = dsReprj = None

处理栅格数据时,由于数据像元大小不符合要求,或者在进行栅格数据配准后,像元发生倾斜,或者对多个栅格数据进行分析时需要使用相同的栅格分辨率,因此对栅格数据操作时经常要进行重采样操作将其统一到同一分辨率下。对于一个既定的空间分辨率的栅格数据,可以通过重采样操作,将栅格数据重采样成更大的像元,即降低空间分辨率。这个过程会丢失部分原高空间分辨率的细节信息;也可以重采样成更小的像元,但是并不会增加更多的信息。gdal.Warp也常用于栅格数据的重采样,其使用方法与执行坐标变换的方法类似,只需要在gdal.WarpOptions()函数的参数中增加输出栅格的空间分辨率和重采样方法即可,具体参数说明如下:

  • xRes,yRes :输出文件在X和Y方向的分辨率(以目标地理参考单位为单位
  • resampleAlg :重采样方法,near(最邻近法),bilinear(双线性内插法),cubic(三次卷积插值法)等。

以下代码用于演示如何使用gdal.Warp函数对landsat数据进行重采样。

from osgeo import gdal
ds = gdal.Open(r"./LT51190381984137HAJ00.tif")
resp_ops = gdal.WarpOptions(format='GTiff', 
                            srcSRS='EPSG:4326', 
                            dstSRS='EPSG:4525', 
                            xRes=10, yRes=10, 
                            resampleAlg='near')
dsRes = gdal.Warp("Res.tif", ds, options=resp_ops)
ds = dsRes = None

6.3 矢量裁剪和镶嵌

栅格数据的裁剪同样也可使用gdal.Warp函数,但应当注意的是裁剪时要保证矢量边界数据和待裁剪栅格数据的坐标系一致。矢量裁剪的执行过程与坐标变换和重采样类似,仅需要在gdal.WarpOptions()函数增加几个参数:

  • cutlineDSName :用于裁剪的矢量数据的文件名。
  • cropToCutline :将目标数据集的范围裁剪到剪切线的范围。
  • srcNodata,dstNodata :输入波段和输出波段的NoData值。

以下代码用于演示如何使用gdal.Warp函数对landsat数据进行矢量裁剪。

from osgeo import gdal
ds = gdal.Open(r"./LT51190381984137HAJ00.tif")
clip_ops = gdal.WarpOptions(format='GTiff', 
                            srcSRS='EPSG:4525',                                 
                            dstSRS='EPSG:4525', 
                            cutlineDSName=r'./lake.shp', 
                            cropToCutline=True, 
                            srcNodata=None, dstNodata=255)
dsClip = gdal.Warp("Clip.tif", ds, options=clip_ops) 
ds = dsClip = None

gdal.Warp函数还可以快速实现若干个栅格数据的镶嵌。对于输入栅格中重叠的像元,可通过设置gdal.WarpOptions()函数的resampleAlg参数实现不同的处理方法,主要有如下几种方法:

  • near :默认的方法,重叠区域的输出像元值为镶嵌到该位置的最后一个栅格数据集中的值。
  • average :重叠区域的输出像元值为叠置像元的平均值。
  • mode :重叠区域的输出像元值为叠置像元中最常出现的值。
  • max :重叠区域的输出像元值为叠置像元的最大值。
  • min :重叠区域的输出像元值为叠置像元的最大值。
  • sum :重叠区域的输出像元值为叠置像元的总和(从GDAL 3.1开始)。

这里我们以两景landsat数据为例,将两个栅格数据集合并到一个新的栅格数据集,具体代码如下。

from osgeo import gdal

file_path1 = r"./LT51190381984137HAJ00.tif" 
file_path2 = r"./LT51190391985235HAJ00.tif" 
merge_files = [file_path1, file_path2]

merge_options = gdal.WarpOptions(format='GTiff', 
                                resampleAlg='near')
dsMer = gdal.Warp("Merge.tif", merge_files, 
                  options=merge_options)

dsMer = None

6.4 空间插值

这一节的示例数据为./elev/points.shp,该数据为点矢量数据,数据格式为Shapefile,字段elevation记录了每个点的高程。下面的代码分别使用最近邻插值、滑动平均插值、反距离权重插值和线性插值创建DEM,具体代码如下:

首先使用ogr库查看一下该数据的字段名,ogrgdal一样,都在osgeo的命名空间之下。gdal用于处理栅格数据,ogr用于处理矢量数据。

from osgeo import gdal, ogr

pts = ogr.Open("./elev/points.shp", 0)
layer = pts.GetLayer()

for field in layer.schema:
    print(field.name)

空间插值是将点矢量数据转为栅格数据,那么首先必然要明确生成的栅格数据的空间范围和行列数(坐标系与点矢量数据相同)。这就有两种思路:

第一种思路,用户提供需要生成的栅格数据的行列数:这时我们要生成的栅格数据的空间范围即为点矢量数据的的空间范围。

# 用户输入的行列数
xsize, ysize = 500, 1000

# 获取点矢量数据的空间范围
extent = layer.GetExtent()
print(extent)

x_min, x_max, y_min, y_max = extent

第二种思路,用户提供需要生成的栅格数据的分辨率:点矢量数据有其空间范围,我们取这个范围的左上角的点为基准点。然后根据分辨率计算行列数为多少可以正好将点矢量数据完全覆盖,进而确定生成的栅格数据的空间范围。

# 用户输入的分辨率
x_res, y_res = 30, 30

# 获取点矢量数据的空间范围
extent = layer.GetExtent()
print(extent)

x_min, x_max, y_min, y_max = extent
xsize = int((x_max - x_min) / x_res) + 1
ysize = int((y_max - y_min) / y_res) + 1

# 更新空间范围
x_max = x_min + x_res * xsize
y_min = y_max - y_res * ysize

下面的代码演示如何使用GDAL进行空间插值,首先是最近邻插值:

nn = gdal.Grid("nearest.tif", "./elev/points.shp", 
                zfield="elevation", algorithm = "nearest", 
                outputBounds = [x_min, y_max, x_max, y_min], 
                width = xsize, height = ysize)
nn = None

滑动平均插值:

ma = gdal.Grid("average.tif", "./elev/points.shp", 
                zfield="elevation", 
                algorithm = "average:radius1=500:radius2=800:angle=20", 
                outputBounds = [x_min, y_max, x_max, y_min], 
                width = xsize, height = ysize)
ma = None

反距离权重插值:

idw = gdal.Grid("invdist.tif", "points.shp", 
                zfield = "elevation",
                algorithm = "invdist:power=3:radius1=2000:radius2=2000",
                outputBounds = [x_min, y_max, x_max, y_min],
                width = xsize, height = ysize)
idw = None

线性插值:

lin = gdal.Grid("linear.tif", "points.shp", 
                zfield = "elevation",
                algorithm = "linear",
                outputBounds = [x_min, y_max, x_max, y_min],
                width = xsize, height = ysize)
lin = None

学习更多Python & GIS的相关知识,请移步公众号GeodataAnalysis

猜你喜欢

转载自blog.csdn.net/weixin_44785184/article/details/129132836