简介:本资源为2022年中国区域1km地表温度(LST)空间分布数据集,基于NASA MOD11A2产品加工而成,面向遥感、地理信息、气候与生态研究等需要长时序地表温度数据的科研人员和学生。原始数据经提取子数据集、拼接、投影栅格、单位换算与裁剪后,按8天合成再平均为年度LST,采用Albers等面积投影(WGS84椭球,中央经线105°,标准纬线25°与47°),空间分辨率1km,时间分辨率年,覆盖中国范围。压缩包共7个文件,约64.55MB,包含开氏与摄氏两套tif栅格、配套txt说明、xml元数据及ovr、tfw辅助文件,便于直接加载与制图。目前已有743人学习下载。数据可直接用于地表温度空间格局分析、城市热岛与气候变化研究,并附有官方引用信息,方便论文规范引用与二次处理。
1. 拿到一个 MODIS 2022 年中国 1km LST 数据集,先搞清楚它到底能干什么
你从群里、网盘或者某个数据分享页面拿到一个压缩包,名字叫「MODIS 2022年中国1km地表温度(LST)空间分布数据集.zip」。解压之前你心里得先有数:这东西大概率是 MOD11A2 或 MYD11A2 经过拼接、裁剪、投影转换之后的成品,空间分辨率 1km,时间范围覆盖 2022 年全年,空间范围是中国。它能直接回答的问题很具体——某块地、某个月、某个季节的地表温度大概是多少,空间上怎么分布。它不能直接回答的问题同样具体——你拿它做城市热岛归因、做逐日干旱监测、做地块级精准农业,精度和时空粒度都不够。这篇文章就是帮你把「MODIS 2022 年中国 1km 地表温度数据集」从压缩包变成能进模型、能出图、能写进报告的东西,顺带把下载、拼接、投影、空值、尺度这几个环节里最容易翻车的地方讲清楚。适合手里已经有这个包、或者正准备去找这个数据的人。
2. MODIS LST 数据集的底层逻辑与选型判断
2.1 MOD11A2 和 MYD11A2 到底选哪个
MODIS 地表温度产品按传感器分两套:Terra 上的 MOD11 系列和 Aqua 上的 MYD11 系列。两者算法同源,但过境时间不同。Terra 大约地方时 10:30 过境,Aqua 大约 13:30 过境。对于中国区域,下午过境那轨更接近一天中的最高温,做城市热岛、干旱、蒸散发的从业者通常优先用 MYD11。但现实是很多现成数据集只做了 MOD11,因为 Terra 的数据积累更长、下载更稳。你拿到手的这个包,如果文件名里带 MOD11 就是 Terra,带 MYD11 就是 Aqua,两个都带就是合成或者双星融合。选型上我的建议很直接:做热环境用 Aqua,做长时序一致性用 Terra,做精度验证两个都跑一遍看差异。
1km 分辨率对应的是 MOD11A2 这个 8 天合成产品,不是 MOD11A1 的逐日 1km。A2 把 8 天里质量最好的像元挑出来做平均,所以它牺牲了逐日变化,换来了更少的云污染和更稳的质量。如果你的标题里写的是 2022 年中国 1km,那基本可以确定底层是 A2 拼接出来的。这一点决定了你不能拿它做逐日温度曲线,只能做旬、月、季尺度。
2.2 从 HDF 到中国区 GeoTIFF 的完整链路
原始 MOD11A2 是 HDF4 格式,每景一个瓦片,正弦投影,全球分幅。要变成「中国 1km」需要四步:下载、拼接、投影转换、裁剪。常见做法是用 Google Earth Engine 直接在线完成,或者用 MODIS Reprojection Tool 本地处理。我一般会走 GEE 路线,因为省去下载几十 GB 原始文件的麻烦,而且投影和裁剪可以一步到位。
// GEE 中提取 2022 年中国区 MOD11A2 LST 并导出 var modis = ee.ImageCollection('MODIS/061/MOD11A2') .filterDate('2022-01-01', '2022-12-31') .select('LST_Day_1km'); // 白天 LST 波段 // 乘以 0.02 转成摄氏度,再减 273.15 var lst = modis.map(function(img) { return img.multiply(0.02).subtract(273.15) .copyProperties(img, ['system:time_start']); }); // 按 8 天合成求年均,也可以改成按月 var annual = lst.mean().clip(chinaBoundary); Export.image.toDrive({ image: annual, description: 'LST_2022_China_1km', scale: 1000, region: chinaBoundary, crs: 'EPSG:4326', maxPixels: 1e13 });这段代码的逻辑是:先按时间过滤出 2022 年全年,选白天 LST 波段,然后逐景做尺度变换。MOD11A2 的 LST 原始值是整数,缩放因子 0.02,单位开尔文,所以乘 0.02 再减 273.15 得到摄氏度。最后求年均并裁剪到中国边界,导出为 1km 分辨率的 GeoTIFF。参数上要注意 scale 必须写 1000,因为 MODIS 正弦投影下 1km 在 WGS84 下不是严格的 1000 米,但 GEE 会自动重采样。crs 用 EPSG:4326 方便后续在 ArcGIS 或 QGIS 里叠加其他图层。
如果你拿到的已经是处理好的 zip,里面大概率是分省的 GeoTIFF 或者一个全国整幅。先别急着进模型,用 QGIS 打开看一眼元数据,确认坐标系、NoData 值、数值范围。我见过太多人直接读进来发现温度是 15000 多,那就是没做尺度变换。
2.3 1km 分辨率在中国区域意味着什么
1km 像元在中国中东部大概对应 1 平方公里,在西部高纬地区因为投影变形会略大。这个尺度下,一个像元里可能混合了水体、植被、建筑、裸土。MODIS LST 的反演算法假设像元内是均质的,所以城市边缘、湖泊岸边、山区这些混合像元区域误差会明显增大。做城市热岛的时候,如果你直接拿 1km LST 去对应气象站点的 2 米气温,空间代表性误差可能比你想的大。常见做法是先用土地覆盖数据做分层,或者用 TsHARP 之类的降尺度方法把 1km 降到 250m 甚至更低。但降尺度本身会引入新的不确定性,这个后面避坑章节会细说。
3. 把 zip 变成可分析数据的实操步骤
3.1 解压后先做三件事:看元数据、看范围、看空值
拿到 zip 解压后,不要直接写代码批量读。先用 QGIS 或 gdalinfo 看三个东西。第一,坐标系是什么,是 EPSG:4326 还是 EPSG:326xx 的 UTM,还是原始的 sinusoidal。第二,空间范围是不是完整覆盖中国,有没有缺省省份或者海域被裁掉。第三,NoData 值是多少,是 -9999 还是 0 还是别的。这三件事决定了你后面所有代码的参数。
# 查看 GeoTIFF 元信息 gdalinfo LST_2022_China_1km.tif # 重点看这几行: # Coordinate System is: ... # Origin = (73.0, 54.0) # Pixel Size = (0.01, -0.01) # NoData Value = -9999gdalinfo 输出里 Coordinate System 告诉你投影,Pixel Size 告诉你实际分辨率,NoData Value 告诉你空值标记。如果 NoData 是 -9999,后面做统计时必须先掩膜掉,否则年均温会被拉到零下几百度。如果 Pixel Size 不是 0.01 度左右,说明这个数据可能被重采样过,分辨率已经不是原始 1km。
3.2 用 Python 批量读取并做尺度变换
确认元数据之后,用 rasterio 或 xarray 读进来。下面这段代码做了四件事:读取、掩膜空值、尺度变换、裁剪到中国边界。
import rasterio import numpy as np import geopandas as gpd from rasterio.mask import mask # 读取 LST 栅格 with rasterio.open('LST_2022_China_1km.tif') as src: lst_raw = src.read(1).astype('float32') nodata = src.nodata transform = src.transform crs = src.crs # 掩膜 NoData lst_raw[lst_raw == nodata] = np.nan # MOD11A2 尺度变换:原始值 * 0.02 - 273.15 lst_c = lst_raw * 0.02 - 273.15 # 读取中国边界并裁剪 china = gpd.read_file('china_boundary.shp').to_crs(crs) with rasterio.open('LST_2022_China_1km.tif') as src: lst_clip, out_transform = mask(src, china.geometry, crop=True) lst_clip = lst_clip[0].astype('float32') lst_clip[lst_clip == nodata] = np.nan lst_clip = lst_clip * 0.02 - 273.15 # 统计有效像元比例 valid_ratio = np.sum(~np.isnan(lst_clip)) / lst_clip.size print(f'有效像元比例: {valid_ratio:.2%}') print(f'温度范围: {np.nanmin(lst_clip):.2f} ~ {np.nanmax(lst_clip):.2f} 摄氏度')这段代码的关键参数是尺度变换公式。MOD11A2 的 LST_Day_1km 波段存储的是开尔文乘以 50 之后的整数,所以乘 0.02 得到开尔文,再减 273.15 得到摄氏度。如果你拿到的数据已经是摄氏度,这一步就要跳过,否则会得到 -270 多度的荒谬值。有效像元比例这个指标很重要,中国区域因为云污染,8 天合成产品在某些月份有效像元可能只有 60% 到 70%,年均之后会好一些,但青藏高原和西南地区仍然偏低。
3.3 按月合成与季节均值计算
年均一张图往往不够用,做农业或者生态的从业者通常需要月尺度。MOD11A2 是 8 天合成,一个月大概有 4 景,直接求月均即可。
import xarray as xr import pandas as pd # 假设你已经把 2022 年所有 8 天合成影像读成了一个 DataArray # 维度是 (time, y, x) lst_da = xr.open_dataarray('LST_2022_8day.nc') # 按月分组求均值 monthly = lst_da.groupby('time.month').mean(dim='time') # 按季节求均值:春季 3-5 月,夏季 6-8 月,秋季 9-11 月,冬季 12-2 月 season_map = {12: 'DJF', 1: 'DJF', 2: 'DJF', 3: 'MAM', 4: 'MAM', 5: 'MAM', 6: 'JJA', 7: 'JJA', 8: 'JJA', 9: 'SON', 10: 'SON', 11: 'SON'} season = lst_da.groupby('time.month').map(lambda g: season_map[g.month]) seasonal = lst_da.groupby(season).mean(dim='time') # 导出月均和季节均值 monthly.to_netcdf('LST_2022_monthly.nc') seasonal.to_netcdf('LST_2022_seasonal.nc')这里用 xarray 的好处是它保留了时间维度和坐标信息,后面做趋势分析或者跟其他数据集对齐的时候不用再手动处理 transform。groupby('time.month') 会自动按月份分组,season_map 把月份映射到季节。注意冬季 DJF 跨年,2022 年 12 月属于 2022 年冬季,但 2022 年 1 月和 2 月属于 2021 年冬季,如果你要做完整的 2022 年冬季,需要把 2021 年 12 月也拉进来。这个细节很多人会忽略,导致冬季均值偏差。
4. 避坑与排查:MODIS LST 处理中最容易翻车的五个地方
4.1 现象:温度值在 15000 左右,不是摄氏度也不是开尔文
原因:没有做尺度变换。MOD11A2 的 LST 波段存储的是整数,实际值 = 存储值 × 0.02,单位开尔文。很多人直接读进来就以为是摄氏度,结果得到 15000 这种离谱数字。
解决:先确认数据来源。如果是原始 MOD11A2 HDF 或者 GEE 导出的未变换数据,必须乘 0.02 再减 273.15。如果已经是处理好的数据集,先看元数据里的 scale factor 和 offset,不要凭经验硬套。
4.2 现象:中国区域有效像元比例只有 50% 到 60%,南方夏季大片空值
原因:MODIS LST 在云覆盖下无法反演,8 天合成虽然挑了质量最好的像元,但持续阴雨区域仍然会缺。中国南方夏季多云,青藏高原部分区域常年云覆盖,所以空值集中在这两个地方。
解决:如果做年均,空值影响相对小;如果做月均或者旬均,建议用相邻时段插值或者用 MYD11 和 MOD11 互补填补。常见做法是双星融合:同一天 Terra 有云 Aqua 可能晴,两者取有效值平均。但要注意两颗星过境时间不同,直接平均会引入偏差,更稳妥的是分别做月均再融合。
4.3 现象:投影转换后面积变形,西部像元被拉伸
原因:MODIS 原始正弦投影在高纬地区变形较大,直接转 WGS84 地理坐标系会导致像元大小不一致。中国西部纬度跨度大,这种变形更明显。
解决:如果做全国尺度统计,用等面积投影,比如 Albers 或者 Lambert。如果只是出图,WGS84 可以接受,但要在图注里说明。做面积相关计算时,务必先投影到等面积坐标系再算,否则西部像元会被低估或高估。
4.4 现象:城市热岛分析中,城区 LST 反而比郊区低
原因:MODIS 1km 像元在城区混合了建筑、道路、绿地、水体,而且城区气溶胶和几何结构会影响反演。更关键的是,如果你用的白天 LST,城区高反照率屋顶可能让反演温度偏低。另外,如果城区像元里包含大量水体或者灌溉绿地,也会拉低均值。
解决:先做土地覆盖分层,把水体、高植被覆盖像元剔除,再比较城区和郊区。或者改用夜间 LST,夜间城市热岛信号更稳定,因为白天太阳辐射的空间差异会掩盖热岛效应。MOD11A2 有白天和夜间两个波段,做热岛优先用夜间。
4.5 现象:用 GEE 导出时 maxPixels 报错或者导出后影像有黑边
原因:GEE 默认 maxPixels 是 1e8,中国区域 1km 全年数据远超这个限制。另外,如果 region 参数用的是 FeatureCollection 而不是 Geometry,导出范围可能不对。
解决:maxPixels 设成 1e13 或者更高。region 参数用 chinaBoundary.geometry() 而不是直接传 FeatureCollection。导出后黑边通常是 NoData 被渲染成 0,在 QGIS 里设置 NoData 值即可,不影响数据本身。
5. 进阶用法:用 MODIS LST 做趋势分析和精度验证
5.1 用 Theil-Sen 斜率做 2022 年逐月升温趋势
拿到月均数据之后,很多人想算趋势。线性回归对异常值敏感,MODIS LST 因为云污染会有一些异常低值,所以我一般用 Theil-Sen 斜率,它取的是所有点对斜率的中位数,稳健得多。
from scipy.stats import theilslopes import numpy as np # 假设 monthly 是 (month, y, x) 的 DataArray # 对每个像元计算 12 个月的 Theil-Sen 斜率 def sen_slope(y): if np.sum(~np.isnan(y)) < 6: return np.nan x = np.arange(len(y)) mask = ~np.isnan(y) slope, _, _, _ = theilslopes(y[mask], x[mask]) return slope # 用 xarray 的 apply_ufunc 逐像元计算 trend = xr.apply_ufunc( sen_slope, monthly, input_core_dims=[['month']], vectorize=True, dask='parallelized', output_dtypes=[float] )这段代码对每个像元的 12 个月序列算 Theil-Sen 斜率,有效月份少于 6 个的像元返回 NaN。输出是一张趋势图,正值表示升温,负值表示降温。2022 年只有一年数据,算出来的趋势更多是年内季节变化,不是年际趋势。要做年际趋势至少需要 10 年以上数据。但这个方法本身可以复用到多年数据上。
5.2 用气象站 2 米气温做交叉验证
MODIS LST 是地表温度,气象站测的是 2 米气温,两者有物理差异,但空间分布上高度相关。验证的时候不要直接比绝对值,而是比距平或者做回归看 R²。
| 验证指标 | 含义 | 可接受范围 |
|---|---|---|
| R² | 决定系数 | 日均 > 0.7,月均 > 0.8 |
| RMSE | 均方根误差 | 日均 3-5°C,月均 2-3°C |
| Bias | 平均偏差 | 绝对值 < 2°C |
| 有效样本数 | 匹配上的站点-像元对 | 至少 30 个站点 |
验证步骤:先把气象站坐标转成和 LST 一样的投影,然后提取每个站点所在像元的 LST 值,再和站点观测做相关。注意时间匹配,MODIS 是 8 天合成,气象站是逐日,要么把站点做 8 天平均,要么把 LST 做月均再比。我一般做月均对比,因为 8 天合成的时间代表性误差比较大。
5.3 一个我踩过的坑:别用 2022 年单年数据做年际变化结论
2022 年夏天中国南方经历了极端高温,如果你只拿这一年的 LST 做趋势分析,会得到整个南方都在显著升温的结论。但这只是单年异常,不是气候趋势。我见过有人拿一年数据写报告说某地升温速率 0.5°C/年,这是典型的单年样本偏差。做趋势至少 10 年,做气候态至少 30 年。如果你手里只有 2022 年,就老老实实做空间分布和季节变化,别碰趋势。
另外,MODIS LST 在 2000 年之后才有,2022 年之前的 MOD11A2 和 MYD11A2 都可以从 GEE 或者 LAADS DAAC 获取。如果你要做 2010 到 2022 年的趋势,把每年的年均 LST 叠起来算 Theil-Sen 斜率就行,代码和上面一样,只是时间维度从 12 个月变成 13 年。
希望帮到你。
本文还有配套的精品资源,点击获取