MODIS Swath数据的几何校正-Python批处理

目录

  • 前言
  • MODIS Swath数据
  • Python实现
  • 后记

前言

我是学遥感的,自己的主力编程工具其实一直是IDL。IDL这语言很小众,听过的人不多,但我确实也拿他做了不少事情,科研和教学都得用。目测未来很长一段时间也脱离不了它,毕竟自己用了快10年,那么多代码,短时间内用其他语言全部重新实现一遍,也不太现实。这几年机器学习很火,自己其实也一直有兴趣,然而能抽出时间去学习是件很难的事情,毕竟不能光看理论而不去动手实践。要实践,恐怕最适合的语言就是Python了。Python我之前并没用过,我觉得想尽快熟悉这门语言的套路,可能就是重写一些之前写过的简单程序,这篇以及以后的博文,其实就是记录一下我学习的过程。顺便可能还能帮助别人解决一些遥感学科内的一些共性技术问题,以后教学上可能也能用,何乐不为。但我写的代码可能一点也不符合软件行业的规范,我只管实现功能,毕竟我也不是专门学计算机的人,大佬轻拍。第一篇文章,就写一写MODIS Swath数据的几何校正和批处理吧,这个可能是很多遥感专业的初学者常遇到的问题之一。

MODIS Swath数据

MODIS的产品数据,如果按存放的形式来讲,我认为可以分为两类,第一类是Swath数据,第二类是Grid数据。Swath数据实际上就是按传感器的实际扫描过程,一行一行地存放的,每一景的空间范围并不固定。MODIS的1级产品和大部分的2级大气产品,都属于Swath数据。Grid数据其实是在Swath数据的基础上做了后处理,在全球分块的基础上,把原有的数据全部归算到了固定空间范围的格网内,且每个像元代表的面积也是相等的。Grid数据常用的投影格式就是正弦投影,MODIS绝大部分的地表产品就是这一形式。由于MODIS传感器自身的特性,在扫描的过程中星下点像元的空间分辨率和边缘区域像元的空间分辨率有较大的差异,越往边缘,像元代表的空间范围就越大(如下图)。但在数据存放的过程中,是按像元挨像元的方式,最终出来的结果,和实际的地表范围相比,看起来就像是变形了。要还原出Swath数据代表的真实空间范围,就必须要对它做几何校正(Georeference)。
MODIS Swath数据的几何校正-Python批处理_第1张图片

Python实现

整体来说,MODIS Swath数据的几何校正,需要它数据里提供的经纬度数据集或者专门的MOD/MYD03数据来实现。简单地说,经纬度数据集里记录了每个像元的实际经纬度,根据这个信息再结合一些最近邻采样的办法,便可以把数据实际空间分辨率不统一的问题初步解决。这个过程其实我之前用IDL的时候手动实现过,具体过程和ENVI里的GLT工具有些类似,但我并没有调用ENVI的GLT接口。然而,既然是用Python,当然就有更简单的办法。
这里用到的是GDAL库,然而这就是我遇到的第一个困难。GDAL库太庞大了,目前来说我只是大概知道一些我需要的东西,接口和关键字太多了,很难全搞清楚。我这里用到了其中的两个API:Translate和Warp。总体来说,Translate在这里面的作用是构建一个用于几何校正的文件,这个和ENVI里的GLT有点像,但又不完全一样。如果有兴趣,可以把我代码段里的os.remove(vrt_file)这句注释掉,这样在数据目录下生成的vrt文件就可以保留,用文本编辑器打开能看到里面都包含了些啥信息,这里就不详细讲了。Warp的作用则是根据vrt里的信息,对目标数据集进行几何校正,中间的一些关键字和输出结果有关,后面再讲。
这个程序里只定义了一个函数,swath_georeference。输入参数是:
in_file:输入swath文件名。
geo_file:输入地理数据文件名。这部分功能其实我没有实现完,目前只是针对文件里本身就带了Longitude和Latitude的Swath文件,所以可以看到下面的in_file和geo_file我输入的同一个。后面再完善单独输入MOD/MYD03的部分。
target_dataset_name:待几何校正的目标数据集名称。这个可以用HDF Explorer、Panoply之类的工具查看,也可以在for subdataset in subdatasets:这句代码下添加print(subdataset)看到每个数据集的信息。
out_file:输出文件名。
out_geo_range:输出的空间范围。如果不指定,就用None,代表输出的是数据实际包含的全部空间范围。
x_res_out:输出的像元分辨率(x方向)。这里用的是经纬度单位,我用的是的MYD04_3K的气溶胶产品,所以设置了一个0.03°,和3 km大概等效的一个分辨率。
y_res_out:输出的像元分辨率(y方向)。这里用的是经纬度单位,同上。
这个代码实际跑的时候,其实只需要对应更改main函数下面的那几行就行了,即input_directory(输入路径)、output_directory(输出路径)、out_file(输出文件名,如果需要的话)、dataset_name(目标数据集名称)、geo_range(输出的经纬度范围,不指定则=None)、x_res(x方向的空间分辨率,°)、y_res(y方向的空间分辨率,°)。
输入参数正确之后,便可开始对input_directory下所有文件的批量处理。

import os
import gdal


def swath_georeference(in_file, geo_file, target_dataset_name, out_file, out_geo_range, x_res_out, y_res_out):
    dataset = gdal.Open(in_file)
    subdatasets = dataset.GetSubDatasets()
    dataset_pos = 0
    target_pos = -1
    for subdataset in subdatasets:
        subdataset_name = subdataset[0]
        if subdataset_name.endswith(target_dataset_name):
            target_pos = dataset_pos
        dataset_pos += 1
    if target_pos != -1:
        target_dataset = dataset.GetSubDatasets()[target_pos][0]
        vrt_file = os.path.splitext(in_file)[0] + '.vrt'
        vrt_out = gdal.Translate(vrt_file, target_dataset,
                                 format='vrt', unscale='true')
        georeferenced_data = gdal.Warp(out_file, vrt_out, multithread=True, outputBounds=out_geo_range,
                                       format='GTiff', geoloc=True, dstSRS="EPSG:4326",
                                       xRes=x_res_out, yRes=y_res_out, dstNodata=0.0, outputType=gdal.GDT_Float32)
        print('The georeference of {} is complete.'.format(in_file))
        os.remove(vrt_file)
    else:
        print('{} has no the target dataset.'.format(in_file))


def main():
    file_prefix = '.hdf'
    input_directory = 'O:/coarse_data/chapter_1/MODIS_2018_mod04_3k/'
    output_directory = 'O:/coarse_data/chapter_1/MODIS_2018_mod04_3k/geo_out/'
    if not os.path.exists(output_directory):
        os.mkdir(output_directory)
    file_list = os.listdir(input_directory)
    for file_i in file_list:
        if file_i.endswith(file_prefix):
            file_name = input_directory + file_i
            out_file = output_directory + os.path.splitext(file_i)[0] + '_geo.tiff'
            dataset_name = 'Image_Optical_Depth_Land_And_Ocean'
            geo_range = None  # [90.0, 55.0, 120.0, 25.0]
            x_res = 0.03
            y_res = 0.03
            swath_georeference(file_name, file_name, dataset_name, out_file, geo_range, x_res, y_res)


if __name__ == '__main__':
    main()

后记

有些地方我也没搞清楚,比如想输出投影格式如UTM这种怎么实现,好像并不是简单的修改dstSRS="EPSG:4326"这一项。目前这段代码只是大概能用,还需要进一步修改完善,毕竟这就是我入坑Python一天之后写出来的,有些东西得慢慢搞清楚,GDAL的API帮助也不太友好。目前只测试了MYD04_3K这个数据,没试过能否直接对1级数据正确处理,但理论上是可以的。Warp这个API里有一个multithread关键字,看说明是控制多线程处理的,但好像我理解有问题,开不开没啥变化,我的CPU使用率毫无波澜。
关于geo_range的设置,我个人认为如果后续是对特定研究区的处理分析,那这里可以直接设定一个经纬度范围,如注释里的[90.0, 55.0, 120.0, 25.0],90.0和55.0代表左上角点的经纬度坐标,120.0和25.0代表右下角点的经纬度坐标,这样几何校正之后的结果都会固定在这一空间范围内,后续要编程做一些均值处理要方便些。如果后续是要对一天的结果做拼接,那可能设置成None更合适。
相比ENVI里的GLT接口,GDAL做几何校正要快多了。如果对IDL调用GLT接口感兴趣,可以看:
如何使用idl做GLT重投影(调用envi接口)
后面我也看情况增加一些IDL做遥感数据处理的内容,我对IDL远比Python熟悉得多。毕竟能看到这篇文章的,多半都是圈内人,IDL还是有用武之地。

你可能感兴趣的:(遥感数据Python处理)