MODIS Swath (GEOTIFF) 数据的拼接与均值运算-Python实现

目录

  • 前言
  • Python实现
  • 后记

前言

上一篇文章实现了MODIS Swath数据的重投影。一般来说,在完成重投影之后的任务,要么是对多幅图像的拼接,要么是对多幅图像做均值运算。但这两个任务,在一定程度上是可以等同的。均值运算,在不考虑质量控制等因素的前提下,其实就是先把所有影像包含的最大地理范围算出来,然后再在对应的地理位置上,把属于这一个地理位置上的所有像元给加进来,并统计有效的累加次数,最后求平均即可,对于某些位置上只出现过一次的像元,其实就是把值填进去了,最后累加的次数为1,均值就是像元值本身。而拼接,实际上是相同的道理。比如两幅有重合区域的影像,重合的部分有多种处理策略,你可以对重合的部分算均值,也可以用下一幅的结果覆盖上一幅的结果,对于不重合的区域,那也不存在处理策略的问题,直接把值填进去即可,这就是为什么我说拼接和均值运算在一定程度上可以等同的原因。
这篇文章里给的代码,可以根据实际需要使用,拼接和求均值都可以用这个代码完成。当然有一些使用的前提,后面再详细说。

Python实现

这个代码用起来也比较粗暴。整体的逻辑,是从给定的输入路径中搜索geotiff文件(即已经重投影好的结果,是不是MODIS的结果其实无所谓),并给定文件名中能够代表日期的字符串起止位置,之后程序会对文件列表中包含的所有日期进行统计,统计完成后按照日期进行循环处理,最终实现对应日期的数据拼接(均值计算)。可以看到这个程序里用了两个比较关键的数组,一个是data_box_geo_sum,另一个是data_box_geo_num,这两个数组是分别用来存储累加结果和累计有效频次用的,最终两个数组相除就是拼接(均值)结果。以上这些就是这个程序中mosaic_unified_resolution这个函数的功能。具体输入参数及解释如下:
input_directory:输入路径名称。路径下包含了所有待拼接/求均值的geotiff文件。但记得给定时,不要省略路径最后的’/’。
output_directory:输出路径名称。不需要自己创建,程序中会检测路径是否存在并自动创建。但记得给定时,不要省略路径最后的’/’。
file_prefix:geotiff文件的后缀。一般就是’.tiff’或者’.tif’,当然你也可以输入你自己的后缀,只要你确定它确实是那个后缀并且是geotiff文件就行。
doy_start_pos:文件名中能够代表日期的字符串开始位置。
doy_end_pos:文件名中能够代表日期的字符串结束位置。
注意doy_start_pos和doy_end_pos的输入是有讲究的。对于大部分卫星数据来讲,他的文件名里包含的日期信息一般都不止一个。拿任意一个MODIS的气溶胶产品文件举例,如MYD04_3K.A2018121.0545.061.2018121172155.hdf,这个文件里日期信息就有两个A2018121.0545是他的观测起始时间,即2018年的第121天,UTC时间5点45分,2018121172155则是这个产品的生成时间,拆开来就是2018年的第121天,UTC时间17点21分55秒。从这个例子就能看出,如果只是简单地截取2018121这个字符串,给这个函数一个起始位置10结束位置17,那很有可能会把某些有相同特征日期字符串的数据拿进来一起拼接了。因为我这个程序只是简单地在文件名中寻找是否有完整的目标字符串,如果有,那就都作为待处理的目标日期加入file_doy这个列表中,如果doy_start_pos和doy_end_pos选取不当,那可能会加入一些不想要的数据进来。比如这样一种情况:极有可能某个2018120的观测结果,是在2018121这天进行的处理,对于这个程序也会把这一天的文件加进来,如果对天做拼接,那时间上显然就错了。所以可以看到我的main()程序里,特意给的起始位置是9,也就是把“A”这个字符加进来了,这样能避免和后面日期的重复。所以这个doy_start_pos和doy_end_pos怎么取,需要根据实际情况来定。如果现在我们想算2018年所有数据的均值,那可能只需要给个起始位置9结束位置14就行了,这样程序就会把包含有A2018这个字符串的所有数据拿进来进行计算。

import os
import gdal
import math
import numpy


def mosaic_unified_resolution(input_directory, output_directory, file_prefix, doy_start_pos, doy_end_pos):
    if not os.path.exists(output_directory):
        os.mkdir(output_directory)

    file_doy = []
    file_all = os.listdir(input_directory)
    for file_i in file_all:
        doy_temp = file_i[doy_start_pos:doy_end_pos]
        if file_i.endswith(file_prefix) and doy_temp not in file_doy:
            file_doy.append(doy_temp)

    for doy_i in file_doy:
        file_list = []
        for file_i in file_all:
            if file_i.find(doy_i) != -1:
                file_list.append(file_i)
        lon_min = 9999.0
        lon_max = -9999.0
        lat_min = 9999.0
        lat_max = -9999.0
        for file_list_i in file_list:
            data_temp = gdal.Open(input_directory + file_list_i)
            data_col = data_temp.RasterXSize
            data_line = data_temp.RasterYSize
            geo_info = data_temp.GetGeoTransform()
            lon_min_temp = geo_info[0]
            lon_max_temp = lon_min_temp + float(data_col) * geo_info[1]
            lat_max_temp = geo_info[3]
            lat_min_temp = lat_max_temp + float(data_line) * geo_info[5]
            if lon_min_temp < lon_min:
                lon_min = lon_min_temp
            if lon_max_temp > lon_max:
                lon_max = lon_max_temp
            if lat_min_temp < lat_min:
                lat_min = lat_min_temp
            if lat_max_temp > lat_max:
                lat_max = lat_max_temp
        output_resolution_x = geo_info[1]
        output_resolution_y = geo_info[5]
        data_box_geo_col = math.ceil((lon_max - lon_min) / abs(output_resolution_x))
        data_box_geo_line = math.ceil((lat_max - lat_min) / abs(output_resolution_y))
        data_box_geo_sum = numpy.full((data_box_geo_line, data_box_geo_col), 0.0)
        data_box_geo_num = numpy.full((data_box_geo_line, data_box_geo_col), 0.0)
        for file_list_i in file_list:
            data_temp = gdal.Open(input_directory + file_list_i)
            data_geoinfo = data_temp.GetGeoTransform()
            data_array = data_temp.ReadAsArray()
            data_line = data_array.shape[0]
            data_col = data_array.shape[1]
            col_start_pos = int((data_geoinfo[0] - lon_min) / abs(output_resolution_x))
            line_start_pos = int((lat_max - data_geoinfo[3]) / abs(output_resolution_y))
            data_box_geo_sum[line_start_pos:(line_start_pos + data_line),
            col_start_pos:(col_start_pos + data_col)] += data_array
            data_box_geo_num[line_start_pos:(line_start_pos + data_line), col_start_pos:(col_start_pos + data_col)] += (
                    data_array > 0.0)
        data_box_geo_num = (data_box_geo_num > 0.0) * data_box_geo_num + (data_box_geo_num == 0.0) * 1.0
        data_box_geo = data_box_geo_sum / data_box_geo_num
        driver = gdal.GetDriverByName('GTiff')
        out_file = driver.Create(output_directory + doy_i + '.tiff', data_box_geo_col, data_box_geo_line, 1,
                                 gdal.GDT_Float32)
        out_file.GetRasterBand(1).WriteArray(data_box_geo)
        geoinfo_new = [lon_min, output_resolution_x, 0, lat_max, 0, output_resolution_y]
        out_file.SetGeoTransform(geoinfo_new)
        out_file.SetProjection(data_temp.GetProjection())
        print('The mosaic of {} is complete.'.format(doy_i))


def main():
    input_directory = 'O:/coarse_data/chapter_1/MODIS_2018_mod04_3k/geo_out/'
    output_directory = 'O:/coarse_data/chapter_1/MODIS_2018_mod04_3k/geo_out/mosaic/'
    file_prefix = '.tiff'
    doy_start_pos = 9
    doy_end_pos = 17
    mosaic_unified_resolution(input_directory, output_directory, file_prefix, doy_start_pos, doy_end_pos)


if __name__ == '__main__':
    main()

接下来就是程序运行的一些效果,这个是geo_out文件夹下待拼接的文件:
MODIS Swath (GEOTIFF) 数据的拼接与均值运算-Python实现_第1张图片这个是运行程序后mosaic文件夹下生成的结果:
MODIS Swath (GEOTIFF) 数据的拼接与均值运算-Python实现_第2张图片这个是我把截取位置变成9、14之后,算的2018年所有结果的均值:
MODIS Swath (GEOTIFF) 数据的拼接与均值运算-Python实现_第3张图片

后记

这个程序要正确运行,numpy和gdal库必须要有,另外对输入数据是有要求的:(1)输入数据必须是已经做过几何校正或重投影的GeoTIFF数据;(2)每个输入数据的投影和空间分辨率要求统一,比如都是经纬度网格、空间分辨率为0.03°;(3)数据中的无效值只有一个,比如0,而不同时有0和-9999。当然具体的无效数值可以自己直接修改,对应像元有效频次data_box_geo_num计算时data_array > 0.0这个地方。
再强调下这个程序的逻辑是把两幅图像重合的地方进行均值计算,并作为最终拼接的结果,所以拼接和均值计算都可以用这个程序来实现。如果拼接时需要的是下一幅对上一幅的覆盖,那对应的在data_box_geo_sum这个地方就不能做累加,只能直接等于,可能还需要加一些对日期的判断,另外data_box_geo_num也就不需要了。整个程序我觉得还是有可读性,大家可以根据自己的需要进行修改。
另外注意到geo_info[5]这个东西。我这套代码测试的时候,用的是上一篇文章里处理出来的几何校正结果,也就是用GDAL做的。之后再读出来的地理信息,geo_info[5]这个实际上代表的是y方向上的分辨率,但如果print出来就会发现它是个负值,所以最终归算最小纬度的时候用的是lat_max_temp + float(data_line) * geo_info[5]。这个和我写IDL的时候有些不一样,我不清楚是不是所有GeoTIFF文件用GDAL读出来都这样。
其实整个代码有很重的个人写IDL的风格,毕竟才接触Python不到3天时间,有些习惯很难改过来。

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