Python:如何对FY3D TSHS的数据集进行重投影并输出为TIFF文件以及批量镶嵌插值?

news2024/11/27 5:32:18

完整代码见 Github:https://github.com/ChaoQiezi/read_fy3d_tshs,由于代码中注释较为详细,因此博客中部分操作一笔带过。

01 FY3D的HDF转TIFF

1.1 数据集说明

FY3D TSHS数据集是二级产品(TSHS即MWTS/MWHS 融合大气温湿度廓线/稳定度指数/位势高度产品);
文件格式为HDF5
空间分辨率为33KM(星下点)
范围为全球区域;(FY3D为极轨卫星,因此对于得到的单个数据集并没有完全覆盖全球区域),类似下方(其余地区均未扫描到):

在这里插入图片描述

提取数据涉及的多个数据集:

在这里插入图片描述

位于DATA/MWHS_Ch_BT和DATA/MWTS_Ch_BT 路径下的两个数据集分别为MWTS 通道亮温MWHS 通道亮温,这是需要提取的数据集;
另外位于GEO/Latitude 和GEO/Longitude 路径下的两个数据集分别经纬度数据集,用于确定像元的地理位置以便后续进行重投影;

1.1.1 MWTS 通道亮温和MWHS 通道亮温数据集的说明

这是关于数据集的基本属性说明:

在这里插入图片描述

在这里插入图片描述

在产品说明书中关于数据集的说明如下:

在这里插入图片描述

可以发现,其共有三个维度,譬如MWHS_Ch_BT数据集的Shape为(15, 1212, 90),其表示极轨卫星的传感器15个通道(即波段数)在每条扫描线总共 90 个观测像元的MWHS亮温值,此处扫描线共有1212个。对于MWTS数据集类似格式。(这意味着三个维度中并没有空间上的关系)

1.1.2 经纬度数据集的说明

在这里插入图片描述
在这里插入图片描述

这是官方产品说明对于经纬度数据集的介绍:

在这里插入图片描述

可以发现,经纬度数据集的Shape均为(1212, 90),这正好对应前文提及的两个数据集的所有像元(除去波段数),其表示每条1212条扫描线上的90个观测像元的经纬度。

1.2 读取HDF5文件数据集

def read_h5(hdf_path, ds_path, scale=True):
    """
    读取指定数据集并进行预处理
    :param hdf_path: 数据集所在HDF文件的绝对路径
    :param ds_path: 数据集在HDF文件内的路径
    :return: 返回处理好的数据集
    """

    with h5py.File(hdf_path) as f:
        # 读取目标数据集属矩阵和相关属性
        ds = f[ds_path]
        ds_values = np.float32(ds[:])  # 获取数据集
        valid_range = ds.attrs['valid_range']  # 获取有效范围
        slope = ds.attrs['Slope'][0]  # 获取斜率(类似scale_factor)
        intercept = ds.attrs['Intercept'][0]  # 获取截距(类似add_offset)

        """"
        原始数据集可能存在缩放(可能是为存储空间全部存为整数(需要通过斜率和截距再还原为原来的值,或者是需要进行单位换算甚至物理量的计算例如
        最常见的DN值转大气层表观反射率等(这多出现于一级产品的辐射定标, 但二级产品可能因为单位换算等也可能出现));
        如果原始数据集不存在缩放, 那么Slope属性和Intercept属性分别为1和0;
        这里为了确保所有迭代的HDF文件的数据集均正常得到, 这里依旧进行slope和intercept的读取和计算(尽管可能冗余)
        """

    # 目标数据集的预处理(无效值, 范围限定等)
    ds_values[(ds_values < valid_range[0]) | (ds_values > valid_range[1])] = np.nan
    if scale:
        ds_values = ds_values * slope + intercept  # 还原缩放
        """
        Note: 这里之所以选择是否进行缩放, 原因为经纬度数据集中的slope为1, intercept也为1, 但是进行缩放后超出地理范围1°即出现了90.928
        对于纬度。其它类似, 因此认为这里可能存在问题如果进行缩放, 所以对于经纬度数据集这里不进行缩放"""

    return ds_values

上述代码用于读取指定HDF5文件的指定数据集的数组/矩阵,scale参数用于是否对数据集进行slope和intercept线性转换。

1.3 重组和重投影

这一部分是整个数据处理的核心。

1.3.1 重组

def reform_ds(ds, lon, lat, reform_range=None):
    """
    重组数组
    :param ds: 目标数据集(三维)
    :param lon: 对应目标数据集的经度数据集()
    :param lat: 对应目标数据集的纬度数据集(二维)
    :param reform_range: 重组范围, (lon_min, lat_max, lon_max, lat_min), 若无则使用全部数据
    :return: 以元组形式依次返回: 重组好的目标数据集, 经度数据集, 纬度数据集(均为二维数组)
    """

    # 裁选指定地理范围的数据集
    if reform_range:
        lon_min, lat_max, lon_max, lat_min = reform_range
        x, y = np.where((lon > lon_min) & (lon < lon_max) & (lat > lat_min) & (lat < lat_max))
        ds = ds[:, x, y]
        lon = lon[x, y]
        lat = lat[x, y]
    else:
        ds = ds.reshape(ds.shape[0], -1)
        lon = lon.flatten()
        lat = lat.flatten()

    # 无效值去除(去除地理位置为无效值的元素)
    valid_pos = ~np.isnan(lat) & ~np.isnan(lon)
    ds = ds[:, valid_pos]
    lon = lon[valid_pos]
    lat = lat[valid_pos]

    # 重组数组的初始化
    bands = []
    for band in ds:
        reform_ds_size = np.int32(np.sqrt(band.size))  # int向下取整
        band = band[:reform_ds_size ** 2].reshape(reform_ds_size, reform_ds_size)
        bands.append(band)
    else:
        lon = lon[:reform_ds_size ** 2].reshape(reform_ds_size, reform_ds_size)
        lat = lat[:reform_ds_size ** 2].reshape(reform_ds_size, reform_ds_size)
    bands = np.array(bands)
    return bands, lon, lat

这部分是对原始数据集(此处理中待重组数据集shape为(15, 1212, 90))进行重组。
重组原理即基于经纬度数据集,依据裁剪范围将满足地理范围内的所有有效像元一维化,然后重新reshape为二维数组,数组行列数均为原维度的平方根。

1.3.2 重投影

def data_glt(out_path, src_ds, src_x, src_y, out_res, zoom_scale=6, glt_range=None, windows_size=9):
    """
    基于经纬度数据集对目标数据集进行GLT校正/重投影(WGS84), 并输出为TIFF文件
    :param out_path: 输出tiff文件的路径
    :param src_ds: 目标数据集
    :param src_x: 对应的横轴坐标系(对应地理坐标系的经度数据集)
    :param src_y: 对应的纵轴坐标系(对应地理坐标系的纬度数据集)
    :param out_res: 输出分辨率(单位: 度/°)
    :param zoom_scale:
    :return: None
    """
    if glt_range:
        # lon_min, lat_max, lon_max, lat_min = -180.0, 90.0, 180.0, -90.0
        lon_min, lat_max, lon_max, lat_min = glt_range
    else:
        lon_min, lat_max, lon_max, lat_min = np.nanmin(src_x), np.nanmax(src_y), \
            np.nanmax(src_x), np.nanmin(src_y)

    zoom_lon = zoom(src_x, (zoom_scale, zoom_scale), order=0)  # 0为最近邻插值
    zoom_lat = zoom(src_y, (zoom_scale, zoom_scale), order=0)
    # # 确保插值结果正常
    # zoom_lon[(zoom_lon < -180) | (zoom_lon > 180)] = np.nan
    # zoom_lat[(zoom_lat < -90) | (zoom_lat > 90)] = np.nan
    glt_cols = np.ceil((lon_max - lon_min) / out_res).astype(int)
    glt_rows = np.ceil((lat_max - lat_min) / out_res).astype(int)

    deal_bands = []
    for src_ds_band in src_ds:
        glt_ds = np.full((glt_rows, glt_cols), np.nan)
        glt_lon = np.full((glt_rows, glt_cols), np.nan)
        glt_lat = np.full((glt_rows, glt_cols), np.nan)
        geo_x_ix, geo_y_ix = np.floor((zoom_lon - lon_min) / out_res).astype(int), \
            np.floor((lat_max - zoom_lat) / out_res).astype(int)
        glt_lon[geo_y_ix, geo_x_ix] = zoom_lon
        glt_lat[geo_y_ix, geo_x_ix] = zoom_lat
        glt_x_ix, glt_y_ix = np.floor((src_x - lon_min) / out_res).astype(int), \
            np.floor((lat_max - src_y) / out_res).astype(int)
        glt_ds[glt_y_ix, glt_x_ix] = src_ds_band
        # write_tiff('H:\\Datasets\\Objects\\ReadFY3D\\Output\\py_lon.tiff', [glt_lon],
        #            [lon_min, out_res, 0, lat_max, 0, -out_res])
        # write_tiff('H:\\Datasets\\Objects\\ReadFY3D\\Output\\py_lat.tiff', [glt_lat],
        #            [lon_min, out_res, 0, lat_max, 0, -out_res])

        # # 插值
        # interpolation_ds = np.full_like(glt_ds, fill_value=np.nan)
        # jump_size = windows_size // 2
        # for row_ix in range(jump_size, glt_rows - jump_size):
        #     for col_ix in range(jump_size, glt_cols - jump_size):
        #         if ~np.isnan(glt_ds[row_ix, col_ix]):
        #             interpolation_ds[row_ix, col_ix] = glt_ds[row_ix, col_ix]
        #             continue
        #         # 定义当前窗口的边界
        #         row_start = row_ix - jump_size
        #         row_end = row_ix + jump_size + 1  # +1 因为切片不包含结束索引
        #         col_start = col_ix - jump_size
        #         col_end = col_ix + jump_size + 1
        #         rows, cols = np.ogrid[row_start:row_end, col_start:col_end]
        #         distances = np.sqrt((rows - row_ix) ** 2 + (cols - col_ix) ** 2)
        #         window_ds = glt_ds[(row_ix - jump_size):(row_ix + jump_size + 1),
        #                     (col_ix - jump_size):(col_ix + jump_size + 1)]
        #         if np.sum(~np.isnan(window_ds)) == 0:
        #             continue
        #         distances_sort_pos = np.argsort(distances.flatten())
        #         window_ds_sort = window_ds[np.unravel_index(distances_sort_pos, distances.shape)]
        #         interpolation_ds[row_ix, col_ix] = window_ds_sort[~np.isnan(window_ds_sort)][0]

        deal_bands.append(glt_ds)
        # print('处理波段: {}'.format(len(deal_bands)))
        # if len(deal_bands) == 6:
        #     break
    write_tiff(out_path, deal_bands, [lon_min, out_res, 0, lat_max, 0, -out_res])
    # write_tiff('H:\\Datasets\\Objects\\ReadFY3D\\Output\\py_underlying.tiff', [interpolation_ds], [lon_min, out_res, 0, lat_max, 0, -out_res])
    # write_tiff('H:\\Datasets\\Objects\\ReadFY3D\\Output\\py_lon.tiff', [glt_lon], [x_min, out_res, 0, y_max, 0, -out_res])
    # write_tiff('H:\\Datasets\\Objects\\ReadFY3D\\Output\\py_lat.tiff', [glt_lat], [x_min, out_res, 0, y_max, 0, -out_res])

这是对重组后的数组进行重投影,其基本思路就是对经纬度数据集进行zoom (congrid),将其重采样放大譬如此处为原行列数的六倍。
接着再将zoom后的经纬度数据集按照角点信息套入到输出glt数组中,而对重组后的目标数组直接套入,无需进行zoom操作。
接着对套入的目标数组进行最近邻插值,如果没有进行插值,情况如下:

在这里插入图片描述

进行最近邻插值后(9*9窗口内的最近有效像元为填充值),结果如下:

在这里插入图片描述

但是由于数据集巨大,关于最近邻插值此处进行了注释,将该操作移至后面镶嵌操作之后,数据量大大减少,所花费时间成本也极大降低。

2 镶嵌和最近邻插值

2.1 镶嵌

def img_mosaic(mosaic_paths: list, return_transform: bool = True, mode: str = 'last'):
    """
    该函数用于对列表中的所有TIFF文件进行镶嵌
    :param mosaic_paths: 多个TIFF文件路径组成的字符串列表
    :param return_transform: 是否一同返回仿射变换
    :param mode: 镶嵌模式, 默认是Last(即如果有存在像元重叠, mosaic_paths中靠后影像的像元将覆盖其),
        可选: last, mean, max, min
    :return:
    """

    # 获取镶嵌范围
    x_mins, x_maxs, y_mins, y_maxs = [], [], [], []
    for mosaic_path in mosaic_paths:
        ds = gdal.Open(mosaic_path)
        x_min, x_res, _, y_max, _, y_res_negative = ds.GetGeoTransform()
        x_size, y_size = ds.RasterXSize, ds.RasterYSize
        x_mins.append(x_min)
        x_maxs.append(x_min + x_size * x_res)
        y_mins.append(y_max+ y_size * y_res_negative)
        y_maxs.append(y_max)
    else:
        y_res = -y_res_negative
        band_count = ds.RasterCount
        ds = None
    x_min, x_max, y_min, y_max = min(x_mins), max(x_maxs), min(y_mins), max(y_maxs)

    # 镶嵌
    col = ceil((x_max - x_min) / x_res).astype(int)
    row = ceil((y_max - y_min) / y_res).astype(int)
    mosaic_imgs = []  # 用于存储各个影像
    for ix, mosaic_path in enumerate(mosaic_paths):
        mosaic_img = np.full((band_count, row, col), np.nan)  # 初始化
        ds = gdal.Open(mosaic_path)
        ds_bands = ds.ReadAsArray()
        # 计算当前镶嵌范围
        start_row = floor((y_max - (y_maxs[ix] - x_res / 2)) / y_res).astype(int)
        start_col = floor(((x_mins[ix] + x_res / 2) - x_min) / x_res).astype(int)
        end_row = (start_row + ds_bands.shape[1]).astype(int)
        end_col = (start_col + ds_bands.shape[2]).astype(int)
        mosaic_img[:, start_row:end_row, start_col:end_col] = ds_bands
        mosaic_imgs.append(mosaic_img)

    # 判断镶嵌模式
    if mode == 'last':
        mosaic_img = mosaic_imgs[0].copy()
        for img in mosaic_imgs:
            mask = ~np.isnan(img)
            mosaic_img[mask] = img[mask]
    elif mode == 'mean':
        mosaic_imgs = np.asarray(mosaic_imgs)
        mask = np.isnan(mosaic_imgs)
        mosaic_img = np.ma.array(mosaic_imgs, mask=mask).mean(axis=0).filled(np.nan)
    elif mode == 'max':
        mosaic_imgs = np.asarray(mosaic_imgs)
        mask = np.isnan(mosaic_imgs)
        mosaic_img = np.ma.array(mosaic_imgs, mask=mask).max(axis=0).filled(np.nan)
    elif mode == 'min':
        mosaic_imgs = np.asarray(mosaic_imgs)
        mask = np.isnan(mosaic_imgs)
        mosaic_img = np.ma.array(mosaic_imgs, mask=mask).min(axis=0).filled(np.nan)
    else:
        raise ValueError('不支持的镶嵌模式: {}'.format(mode))

    if return_transform:
        return mosaic_img, [x_min, x_res, 0, y_max, 0, -y_res]

    return mosaic_img

这里的镶嵌不仅仅可以解决相同地理位置的镶嵌,也可以解决不同地理位置的拼接,拼接方式支持最大最小值计算、均值计算、Last等模式。
思路非常简单,就是将输入的每个数据集重新装入统一地理范围的箱子(数组)中,使所有数组的角点信息一致,然后基于数据集个数这一维度进行Mean、Max、Min等计算,得到镶嵌数组。

2.2 最近邻插值

def window_interp(arr, distances):
    if np.sum(~np.isnan(arr)) == 0:
        return np.nan
    # 距离最近的有效像元
    arr = arr.flatten()
    arr_sort = arr[np.argsort(distances)]
    if np.sum(~np.isnan(arr_sort)) == 0:
        return np.nan
    else:
        return arr_sort[~np.isnan(arr_sort)][0]

思路与之前一致,不过重构为函数了。

具体代码见项目:https://github.com/ChaoQiezi/read_fy3d_tshs

这是原始数据集:

在这里插入图片描述

这是目标结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1579834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Facebook广告投放数据API对接流程

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 一、需求背景 App在Facebook、Google等巨头进行广告投放&#xff0c;想要拿到实时广告投放效果数据&#xff0c;如曝光、点击、花费、触…

最少按键次数

题目描述 给你一个字符串 s&#xff0c;由小写英文字母组成。 电话键盘上的按键与 不同 小写英文字母集合相映射&#xff0c;可以通过按压按键来组成单词。例如&#xff0c;按键 2 对应 ["a","b","c"]&#xff0c;我们需要按一次键来输入 &quo…

C语言从入门到实战————文件操作

目录 前言 1. 为什么使用文件&#xff1f; 2. 什么是文件&#xff1f; 2.1 程序文件 2.2 数据文件 2.3 文件名 3. ⼆进制文件和文本文件&#xff1f; 4. 文件的打开和关闭 4.1 流和标准流 4.1.1 流 4.1.2 标准流 4.2 文件指针 4.3 文件的打开和关闭 5. 文…

公寓水电表预付费控制系统

随着现代化管理理念的不断发展&#xff0c;许多公寓管理者开始寻求更加高效、透明的水电计费方式。在此趋势下&#xff0c;公寓水电表预付费控制系统应运而生&#xff0c;成为了一种创新的能源管理解决方案。它通过预付费方式&#xff0c;帮助住户和管理者实现了更加精确和便捷…

Jmeter针对多种响应断言的判断

有时候response返回的结果并非一种&#xff0c;有多种&#xff0c;需要对这几种进行判断的时候需要使用Bean Shell。 &#xff08;1&#xff09;首先获取响应数据 String response prev.getResponseDataAsString(); ResponseCode 响应状态码 responseHeaders 响应头信息 res…

Cyber Weekly #1

赛博新闻 1、弱智吧竟成最佳中文AI训练数据&#xff1f;&#xff01;中科院等&#xff1a;8项测试第一&#xff0c;远超知乎豆瓣小红书 使用弱智吧数据训练的大模型&#xff0c;跑分超过百科、知乎、豆瓣、小红书等平台&#xff0c;甚至是研究团队精心挑选的数据集。弱智吧数…

第十三章 OpenGL ES-RGB、HSV、HSL模型介绍

第十三章 OpenGL ES-RGB、HSV、HSL模型详细介绍 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影矩阵与正交投影矩阵…

vue+springboot实现JWT登录验证

目录 前言概念实际演示路由信息初始访问登录界面登录验证验证过期 vue实现依赖引入main.js获取和设置token工具类登录方法实体登录方法axios请求 router配置 springboot实现依赖引入JWT工具类忽视jwt验证注解拦截器逻辑跨域&调用拦截器配置登录接口&验证token接口 结语…

算法打卡day36|动态规划篇04| 01背包理论基础、416. 分割等和子集

目录 01背包理论基础 01背包问题描述 01背包解法 二维数组 一维数组 算法题 Leetcode 416. 分割等和子集 个人思路 解法 动态规划 01背包理论基础 不同的背包种类&#xff0c;虽然有那么多中南背包&#xff0c;但其中01背包和完全背包是重中之重&#xff1b; 01背包问…

flutter多入口点entrypoint

native中引擎对象本身消耗内存(每个引擎对象约莫消耗42MB内存) 多引擎&#xff1a;native多引擎>启动>flutter多入口点entrypoint>多main函数>多子包元素集>多(子)程序 单引擎(复用)&#xff1a;native单引擎>复用启动>flutter多入口点entrypoint>多m…

六、企业级架构缓存篇之memcached

一、memcached概述 1、网站架构优化流程&#xff1a; LNMP架构中网站应用访问流程&#xff1a; 浏览器 (app) → web 服务器 → 后端服务 (php) → 数据库 (mysql) 访问流程越多&#xff0c;访问速度越慢&#xff0c;出现问题的几率也越大。 网站访问流程优化思路&#xff1…

HarmonyOS 开发-PixelMap深拷贝案例

介绍 在图片开发过程中经常会涉及到PixelMap的深拷贝&#xff0c;本例通过使用PixelMap的readPixelsToBuffer方法来实现深拷贝。在创建源PixelMap的时候&#xff0c;需要将解码参数设置为BGRA_8888&#xff0c;而在深拷贝创建目标PixelMap的时候需要将解码参数设置为RGBA_8888…

首发!澳门地区OSGB数据V0.2版免费分享

之前分享了澳门OSGB V0.1版数据(独家&#xff0c;澳门地区OSGB数据免费分享&#xff01;)&#xff0c;得到了众多读者的认同&#xff0c;也有读者在数据使用过程中发现的问题&#xff0c;向我进行了反馈与交流&#xff0c;基于此&#xff0c;我在V0.1版本的基础上进行了修正与更…

vue3+Ts+Ant Design Vue +天地图组件封装

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vue3TsAntDesign-Vue组件天地图组件的封装 示例图 首先,在index.html引入天地图资源,vue3选择v4版本 <script src"http://api.tianditu.gov.cn/api?…

JVM 内存溢出排查

说明&#xff1a;记录一次JVM内存溢出的排查过程&#xff1b; 场景 项目开发完成后&#xff0c;首次提交到测试环境。测试、产品同事反馈页面先是操作响应慢&#xff0c;抛出超时异常&#xff0c;最后直接无法使用。查看日志后得知是内存溢出。 重启服务后&#xff0c;我对前…

2024,还没掌握 JS 发布者、订阅者模式吗

面试中经常出现问到如何实现JS 发布者、订阅者模式。下面是ES5实现发布订阅模式。 1、直接上代码。 function EventEmitter() {this.events {}; }; // 订阅者 EventEmitter.prototype.on function(ename, callback) {if (!this.events[ename]) {// 初始化创建订阅&#xff…

算法训练营第二十一天(二叉树part7)

算法训练营第二十一天&#xff08;二叉树part7&#xff09; 530.二叉搜索树的最小绝对差 力扣题目链接(opens new window) 题目 给你一棵所有节点为非负值的二叉搜索树&#xff0c;请你计算树中任意两节点的差的绝对值的最小值。 示例&#xff1a; 提示&#xff1a;树中至…

9.枚举类与注解

文章目录 1. 枚举类1.1 自定义枚举类1.2 enum关键字定义枚举类1.3 Enum类主要方法 2. 注解2.1 注解作用2.1.1 生成文档相关注解2.1.2 JDK内置基本注解-在编译时进行格式检查2.1.3 跟踪代码依赖性&#xff0c;实现替代配置文件功能 2.2 自定义Annotation2.3 JDK中的元注解2.3.1 …

Python零基础从小白打怪升级中~~~~~~~流程控制语句

第三节&#xff1a;Python的流程控制语法 一、Python条件语句的语法 if 条件1:条件1成立执⾏的代码一条件1成⽴执⾏的代码二...... elif 条件2&#xff1a;条件2成立执⾏的代码三条件2成立执⾏的代码四...... ...... else:以上条件都不成⽴&#xff0c;执行的代码五以上条件都…

海外盲盒系统开发,盲盒出口成为企业新机遇

随着盲盒的兴起&#xff0c;国内消费市场形成了万物皆可盲盒的态势。并且&#xff0c;盲盒自带社交属性&#xff0c;也成为了年轻人的社交神器。 除了在国内&#xff0c;盲盒在海外也掀起了一股热潮&#xff0c;呈现出了爆发式增长形势&#xff0c;国内热门盲盒企业也出口到了…