简介:该数据集提供2020年中国区域1km空间分辨率的地表温度(LST)栅格成果,面向遥感、地理信息、气候与生态环境等方向的研究人员和学生,可用于地表热环境分析、城市热岛研究、干旱监测及模型输入等场景。数据源自NASA MODIS MOD11A2产品,经提取子数据集、拼接、投影转换、单位换算与裁剪后,按8天合成再年均得到年尺度LST,采用Albers等积投影与WGS84椭球,中央经线105°、标准纬线25°与47°。压缩包共11个文件,约66.16MB,包含2个tif栅格(开氏与摄氏两套温度)、2个tfw坐标文件、4个xml元数据、2个txt说明及1个ovr金字塔文件,便于直接加载与快速渲染。已有3545人学习下载,适合需要现成中国区年LST底图、希望省去预处理环节并快速开展空间分析的用户。
1. MODIS 2020年中国1km地表温度数据集:从下载到出图的完整路径
2020年夏季,长江中下游持续高温,不少做城市热岛、农业干旱监测的同行都在找一套能覆盖全国、时间连续、空间分辨率够用的地表温度数据。MODIS 2020年中国1km地表温度(LST)空间分布数据集,正是冲着这个需求来的:它把MOD11A1/MYD11A1这类每日1km产品,经过拼接、裁剪、单位换算、无效值剔除后,整理成一份可直接用于全国尺度分析的栅格数据。适合谁?做遥感入门的学生、做城市热环境的研究者、做旱情监测的业务人员,以及需要一份基准LST底图来对比其他年份的工程师。它解决的核心问题不是“有没有数据”,而是“怎么把原始HDF变成能算均值、能出图、能进GIS的干净栅格”。
2. 先搞懂MODIS LST的物理含义与2020年数据选型
2.1 LST不是气温,MOD11A1和MYD11A1到底选哪个
很多新手拿到MODIS LST第一反应是“这不就是气温吗”,翻车往往从这里开始。LST是地表皮肤温度,是卫星传感器在热红外波段反演出来的辐射温度,代表的是地表(土壤、植被冠层、建筑屋顶)向外辐射的能量所对应的温度。它和气象站百叶箱里测的1.5米气温不是一回事:晴天正午,裸土LST可能比气温高20℃以上;夜间植被覆盖好的区域,LST又可能低于气温。做城市热岛,你要的是LST;做气象分析,你要的是气温。两者混用,结论直接崩。
MODIS提供LST的主要产品是MOD11(Terra星)和MYD11(Aqua星),空间分辨率1km,时间分辨率每日。2020年数据选型时,常见做法是:
- 只做白天、只做全国年均,MOD11A1够用,过境时间约地方时10:30,适合看白天热特征。
- 要捕捉夜间热岛或昼夜温差,必须同时用MOD11A1和MYD11A1,Aqua过境约13:30和01:30,夜间信息更完整。
- 如果做月合成,直接找MOD11A2(8天合成)或MOD11C3(月合成)更省事,但空间分辨率还是1km,时间分辨率换空间连续性。
我一般会先明确分析目标是“白天热”还是“昼夜热”,再决定下哪一套。2020年这个年份本身没有特殊性,但它是很多研究对比2021、2022的基准年,所以数据完整性比单年精度更重要。
2.2 1km分辨率在中国区域的实际覆盖与投影问题
MODIS 1km产品原始投影是正弦投影(Sinusoidal),全球分块,中国区域大概落在h26v04、h26v05、h27v04、h27v05、h28v04、h28v05这几个瓦片里。直接拼接后,中国地图会呈现明显的正弦投影变形,东西向拉伸,南北向压缩。如果你要算面积、做行政区统计,必须重投影到Albers等面积投影或WGS84经纬度。
这里有个血泪经验:很多人拼接完直接裁剪中国边界,发现边界和底图对不上,以为是数据错了,其实是投影没转。正确顺序是:原始HDF → 拼接瓦片 → 重投影 → 按中国边界裁剪 → 单位换算。顺序错了,后面全白干。
另外,1km分辨率在中国西部和东部表现不一样。东部城市密集区,1km像元里可能混合了建筑、道路、绿地,LST是混合温度;西部荒漠区,1km像元相对均一。做城市热岛时,1km分辨率对中小城市偏粗,对大城市群够用。如果你要做街区尺度,得换Landsat 8/9的热红外(100m重采样到30m),但那是另一套流程。
2.3 2020年数据版本与质量波段怎么读
MODIS LST产品每个HDF文件里包含多个数据集:LST_Day_1km、LST_Night_1km、QC_Day、QC_Night、View_Angle等。2020年常见版本是061(Collection 6.1),相比055在反演算法和QC波段上有调整。你不需要记住所有版本差异,但必须会读QC波段。
QC_Day是一个8位整数,不同位代表不同质量信息。常见做法是只保留QC=0(最高质量)或QC≤1(好质量)的像元,其余设为无效。如果你不做QC过滤,云污染、气溶胶、视角过大的像元会混进来,全国均值直接偏高或偏低。
单位换算也是必踩的坑:LST_Day_1km的存储值是整数,比例因子0.02,单位开尔文。真实LST = 存储值 × 0.02。如果你忘了乘0.02,得到的是几万度的“温度”,出图全黑。转摄氏度再减273.15。这一步在代码里必须写死,不能靠记忆。
3. 用Python把2020年MODIS HDF处理成中国1km LST栅格
3.1 环境准备与依赖库安装
处理MODIS数据,Python生态里最稳的组合是GDAL + rasterio + numpy + geopandas。GDAL负责读HDF和重投影,rasterio做栅格读写,geopandas处理中国边界矢量。安装时建议用conda,避免GDAL编译问题。
conda create -n modis_lst python=3.9 conda activate modis_lst conda install -c conda-forge gdal rasterio geopandas numpy逻辑说明:GDAL是底层栅格库,rasterio是它的Python封装,geopandas依赖GDAL和shapely。用conda-forge频道能保证版本兼容。参数上,python=3.9是稳妥选择,3.10以上部分GDAL版本有兼容问题。如果你已经装了ArcGIS或QGIS,系统里可能有GDAL,但Python绑定不一定全,建议独立环境。
3.2 批量读取HDF并提取LST子数据集
MODIS HDF是分层结构,一个文件里多个子数据集。用rasterio打开时,需要指定子数据集编号或名称。下面这段代码批量读取2020年某天的MOD11A1瓦片,提取LST_Day_1km和QC_Day。
import rasterio import numpy as np import glob import os def read_modis_lst(hdf_path): """读取单个MODIS HDF,返回LST和QC数组""" with rasterio.open(hdf_path) as src: # 列出所有子数据集 subdatasets = src.subdatasets lst_ds = None qc_ds = None for ds in subdatasets: if 'LST_Day_1km' in ds: lst_ds = ds if 'QC_Day' in ds: qc_ds = ds # 读取LST with rasterio.open(lst_ds) as lst_src: lst = lst_src.read(1).astype(np.float32) profile = lst_src.profile # 读取QC with rasterio.open(qc_ds) as qc_src: qc = qc_src.read(1).astype(np.uint8) return lst, qc, profile # 示例:处理一个瓦片 hdf_file = 'MOD11A1.A2020200.h27v05.061.2020202034534.hdf' lst, qc, profile = read_modis_lst(hdf_file) print('LST shape:', lst.shape, 'QC shape:', qc.shape)逻辑说明:src.subdatasets返回HDF内所有子数据集的路径字符串,通过关键字匹配找到LST和QC。rasterio.open子数据集路径后,read(1)读第一波段。参数上,astype(np.float32)是为了后续乘0.02不丢精度,QC用uint8因为原始就是8位。profile保存了地理变换和投影信息,后面写文件要用。
注意:不同版本HDF子数据集命名可能略有差异,061版本通常是“LST_Day_1km”和“QC_Day”,如果匹配不到,打印subdatasets看一眼。
3.3 拼接、重投影与按中国边界裁剪
单个瓦片处理完,需要把中国区域涉及的瓦片拼起来,再重投影到Albers,最后用中国边界裁剪。下面用rasterio的merge和warp功能。
from rasterio.merge import merge from rasterio.warp import calculate_default_transform, reproject, Resampling import geopandas as gpd from rasterio.mask import mask def merge_tiles(hdf_list): """拼接多个瓦片的LST""" src_files = [] for hdf in hdf_list: lst, qc, profile = read_modis_lst(hdf) # 这里简化:实际应把lst写成临时tif再merge # 为演示,假设已有临时tif列表 # 实际流程:先各自转tif,再merge pass def reproject_to_albers(src_path, dst_path): """重投影到Albers等面积投影""" dst_crs = '+proj=aea +lat_1=25 +lat_2=47 +lat_0=0 +lon_0=105 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs' with rasterio.open(src_path) as src: transform, width, height = calculate_default_transform( src.crs, dst_crs, src.width, src.height, *src.bounds) kwargs = src.meta.copy() kwargs.update({ 'crs': dst_crs, 'transform': transform, 'width': width, 'height': height }) with rasterio.open(dst_path, 'w', **kwargs) as dst: for i in range(1, src.count + 1): reproject( source=rasterio.band(src, i), destination=rasterio.band(dst, i), src_transform=src.transform, src_crs=src.crs, dst_transform=transform, dst_crs=dst_crs, resampling=Resampling.bilinear ) def clip_to_china(src_path, shp_path, dst_path): """按中国边界裁剪""" gdf = gpd.read_file(shp_path) with rasterio.open(src_path) as src: out_image, out_transform = mask(src, gdf.geometry, crop=True) out_meta = src.meta.copy() out_meta.update({ 'height': out_image.shape[1], 'width': out_image.shape[2], 'transform': out_transform }) with rasterio.open(dst_path, 'w', **out_meta) as dst: dst.write(out_image)逻辑说明:重投影用Albers等面积投影,参数lat_1=25、lat_2=47是中国常用双标准纬线,lon_0=105是中央经线。重采样用双线性,LST是连续量,双线性比最近邻更平滑。裁剪用geopandas读中国边界shp,mask函数按几何范围裁。参数上,crop=True表示裁剪到几何边界,不是外接矩形。
注意:中国边界shp要提前准备好,坐标系必须和重投影后的栅格一致,否则mask会报错或裁出空白。
3.4 单位换算、QC过滤与无效值处理
拼接裁剪完,最后一步是单位换算和QC过滤。这一步不做,数据不能用。
def clean_lst(lst, qc): """单位换算 + QC过滤 + 无效值处理""" # 单位换算:存储值 × 0.02 = 开尔文 lst_k = lst * 0.02 # QC过滤:只保留QC=0(最高质量) lst_k[qc != 0] = np.nan # 无效值:原始填充值0或-1 lst_k[lst_k < 200] = np.nan lst_k[lst_k > 350] = np.nan # 转摄氏度 lst_c = lst_k - 273.15 return lst_c # 应用 lst_clean = clean_lst(lst, qc) print('有效像元比例:', np.sum(~np.isnan(lst_clean)) / lst_clean.size)逻辑说明:先乘0.02转开尔文,再按QC过滤。QC=0是最高质量,QC=1是好质量但可能有少量云。如果你要最大覆盖,可以放宽到QC≤1。无效值处理用物理范围卡:LST开尔文低于200K或高于350K都不合理。最后转摄氏度。参数上,200和350是经验阈值,中国区域LST一般在220K到330K之间。
注意:如果你做的是夜间LST,QC_Night的过滤逻辑一样,但阈值可以略调,夜间极端低温可能到210K。
4. 避坑与排查:MODIS LST处理中最容易翻车的5个地方
4.1 现象:拼接后中国地图变形严重,边界对不上
原因:原始MODIS是正弦投影,直接拼接不重投影,中国区域会被拉伸。解决:拼接后立即重投影到Albers或WGS84,再裁剪。顺序不能反。
4.2 现象:LST值几万度,出图全黑或全白
原因:忘了乘比例因子0.02,或者把填充值当有效值。解决:检查代码里有没有lst * 0.02,有没有把0和-1设为NaN。出图前先打印np.nanmin和np.nanmax,正常范围是-50到60摄氏度。
4.3 现象:全国均值明显偏高,夏季超过50℃
原因:没做QC过滤,云污染像元混入。云在热红外波段表现为低温,但云边缘混合像元可能异常。解决:用QC_Day过滤,只保留QC=0或QC≤1。如果均值还是高,检查是不是把夜间LST当白天用了。
4.4 现象:裁剪后边界外有值,或者边界内大片空白
原因:中国边界shp和栅格坐标系不一致,或者shp本身有拓扑错误。解决:用gdf.crs检查shp坐标系,用src.crs检查栅格坐标系,不一致先统一。shp拓扑错误用gdf.buffer(0)修复。
4.5 现象:处理速度极慢,一天数据跑几小时
原因:逐像元循环,或者没做分块处理。解决:用numpy向量化操作,避免for循环。大区域处理用rasterio的窗口读取,分块处理。如果内存不够,用rioxarray的chunk功能。
5. 进阶:用2020年LST做全国城市热岛强度快速验证
5.1 城市热岛强度的定义与计算
城市热岛强度(SUHI)常用定义是城市建成区LST均值减去郊区参考LST均值。2020年数据做这个,关键是城市边界和郊区缓冲区的划定。我一般用中国城市行政边界shp,城市核心区取建成区,郊区取城市边界外10-20km缓冲环。
import geopandas as gpd import rasterio import numpy as np from rasterio.mask import mask def calc_suhi(lst_path, city_shp, buffer_km=15): """计算单个城市SUHI""" city = gpd.read_file(city_shp) with rasterio.open(lst_path) as src: # 城市核心区 city_mask, _ = mask(src, city.geometry, crop=True) city_lst = city_mask[0] city_mean = np.nanmean(city_lst) # 郊区缓冲区 city_buffer = city.geometry.buffer(buffer_km * 1000) outer = city_buffer.difference(city.geometry) outer_mask, _ = mask(src, outer, crop=True) outer_lst = outer_mask[0] outer_mean = np.nanmean(outer_lst) return city_mean - outer_mean # 示例 suhi = calc_suhi('china_lst_2020.tif', 'beijing.shp', buffer_km=15) print('北京2020年SUHI:', suhi, '℃')逻辑说明:mask函数按几何裁剪,city_mean是城市核心区LST均值,outer_mean是郊区缓冲环均值,差值就是SUHI。参数上,buffer_km=15是经验值,平原城市可以10km,山区城市可以20km。注意:缓冲区要排除城市本身,用difference。
5.2 用2020年数据验证热岛强度的三个检查点
第一,检查城市核心区LST是否高于郊区。如果反了,可能是城市边界画到了山区或水体。第二,检查SUHI是否在合理范围。中国大城市夏季白天SUHI一般2-6℃,超过8℃要怀疑QC过滤不严。第三,检查夜间SUHI。夜间热岛通常比白天弱,但更稳定。如果你只有白天数据,结论要谨慎。
我自己的习惯是:先算全国省会城市SUHI,排序看异常值。如果拉萨、西宁这种高原城市SUHI异常高,大概率是郊区缓冲区包含了荒漠或裸土,LST本身高,不是热岛。这时候要手动调整缓冲区,或者用植被指数(NDVI)辅助筛选郊区参考像元。
5.3 一个具体技巧:用QC和NDVI联合筛选参考像元
单纯用缓冲区算郊区LST,容易把裸土、沙地算进去。更稳的做法是:在缓冲区内,只保留NDVI>0.3且QC=0的像元作为郊区参考。NDVI可以从MOD13A2(1km月合成)获取,和LST同分辨率,配准后联合筛选。
def calc_suhi_ndvi(lst_path, ndvi_path, city_shp, buffer_km=15, ndvi_thresh=0.3): """用NDVI筛选郊区参考像元""" city = gpd.read_file(city_shp) with rasterio.open(lst_path) as lst_src, rasterio.open(ndvi_path) as ndvi_src: city_mask, _ = mask(lst_src, city.geometry, crop=True) city_lst = city_mask[0] city_mean = np.nanmean(city_lst) city_buffer = city.geometry.buffer(buffer_km * 1000) outer = city_buffer.difference(city.geometry) outer_lst_mask, _ = mask(lst_src, outer, crop=True) outer_ndvi_mask, _ = mask(ndvi_src, outer, crop=True) outer_lst = outer_lst_mask[0] outer_ndvi = outer_ndvi_mask[0] # 只保留NDVI>0.3的像元 valid = outer_ndvi > ndvi_thresh outer_lst_valid = outer_lst[valid] outer_mean = np.nanmean(outer_lst_valid) return city_mean - outer_mean逻辑说明:NDVI>0.3表示有植被覆盖,排除裸土和建筑。参数上,ndvi_thresh=0.3是经验值,北方干旱区可以降到0.2,南方可以升到0.4。注意:NDVI和LST要提前配准到同一网格,否则mask结果对不上。
这个技巧我用了三年,比单纯缓冲区稳得多。代价是多一步NDVI数据处理,但SUHI结果更可信。如果你只是快速验证,缓冲区法够用;如果要发论文或做业务报告,建议上NDVI联合筛选。
最后说个习惯:每次处理完2020年数据,我都会随机抽10个像元,手动对照原始HDF的QC和LST值,确认换算和过滤没错。这个后悔药,比出图后再返工便宜得多。希望帮到你。
本文还有配套的精品资源,点击获取