☰
2022年全球NDVI 0.05度栅格数据集:从MOD13C2到年度合成与避坑指南
2026/10/9 6:04:06 网站建设 项目流程

简介:中分辨率成像光谱仪(MODIS)二〇二二年全球零点零五度归一化植被指数(NDVI)栅格数据集,是依据美国国家航空航天局(NASA)公开发布的MOD13C2 v061月度产品加工而成的年度全球遥感数据。这份数据通过提取子数据集、投影栅格、单位换算与逐月最大合成法处理,得到WGS84坐标系下的年度全球NDVI影像,可直观反映全球陆表植被覆盖与绿度时空分布;在生态遥感监测、气候变化响应分析、农业长势评估、区域干旱监测以及相关教学科研中均有实用价值。资源包共六个文件,以GeoTIFF栅格(tif)为核心,包含tfw坐标参考、ovr金字塔概览、xml与txt元数据说明,整体约二十五点五八MB,下载后可在ArcGIS、QGIS等主流GIS软件中直接加载。目前已有五百零一人学习下载,配合ovr和xml文件可高效预览与读取,txt文档交代了数据来源和处理流程,无需重复预处理即可直接用于制图、统计或模型输入,适合需要标准化全球年度NDVI数据进行研究或项目开发的用户。

1. 全球 2022 年 NDVI 栅格:一张 0.05 度的年度植被快照

做植被遥感的人,每年初都会被同一件事卡住:想拿上一自然年的全球 NDVI 做年度对比,从 NASA 一层层点进去,下载回来却是几十个 HDF,打开全是灰度,根本不知道哪一层能用。这份 MODIS 2022 年全球 0.05 度植被指数(NDVI)栅格数据集,恰恰是替你把这段弯路走完了——它把 NASA MOD13C2 v061 月度产品做了提取子数据集、投影栅格、单位换算,再用最大合成法合并成一张年度 GeoTIFF:0.05 度分辨率、WGS84 地理坐标、覆盖全球。适合做洲际到全国尺度的植被变化、干旱监测和物候分析;不适合做乡镇、地块级别的精细评估,那是 250m 产品的活。

2. 数据溯源与文件解剖:MOD13C2 v061 到 GeoTIFF 的五份关键文件

2.1 MOD13C2 v061:0.05 度 CMG 网格本身意味着什么

MOD13C2 是 NASA 陆面过程分布式数据档案中心(LP DAAC)发布的 MODIS/Terra 植被指数月合成产品,v061 是当前维护的主力版本。C 结尾代表 CMG(Climate Modeling Grid),也就是气候模拟网格,全球按 0.05 度间隔切分成 3600 行、7200 列。一个像元在赤道附近约 5.5 公里,到中纬度约 4 公里出头,做全球一张图、洲际趋势、全国尺度统计,这个分辨率足够稳,文件体积又不像 500m 产品那样压垮内存。

摘要里那句引用已经写清楚了产品全名:MODIS/Terra Vegetation Indices Monthly L3 Global 0.05Deg CMG V061,DOI 对应 10.5067/MODIS/MOD13C2.061。需要说明的是,MOD13C2 原始文件是 HDF 格式,一个文件里同时装着 NDVI、EVI、VI 质量、可靠性等多个子数据集,直接拖进 GIS 看到的往往是灰度图或伪彩色乱七八糟。作者做的第一步就是从中抽出 NDVI 子数据集,再换算单位、写投影。这意味着你拿到的这份 tif 已经是“可食用”的成品,而不是那种需要自己折腾 HDF 的原料。

原始 MOD13C2 的 NDVI 用 int16 存储,官方标定系数是 0.0001。换句话说,文件里存的数字 5321,真实 NDVI 是 0.5321;存的是 -2000,真实值就是 -0.2。有效范围 -2000 到 10000,对应 NDVI -0.2 到 1.0。填充值用 -32768,像素在极夜、云覆盖等情况下会出现。这些数值上的细节直接决定了后续所有统计脚本怎么写,后面第 3 章会演示标准处理流程。

2.2 解压后的五份文件各顶什么用

zip 解压出来是主 tif 加四个配套文件,很多人只把 tif 拷走,后面在别的软件里加载出问题,回头才发现少带了东西。

文件类型作用处理时是否必需
global_2022_NDVI0.05.tifGeoTIFF 主文件年度 NDVI 栅格,核心数据必需
global_2022_NDVI0.05.tfw世界文件文本形式的坐标定位,6 行数字依赖 tfw 的软件必需
global_2022_NDVI0.05.tif.aux.xmlGDAL 辅助 XML投影 WKT、统计、色彩解释信息建议保留
global_2022_NDVI0.05.tif.xmlISO 元数据 XML数据来源、处理过程、引用规范存档用
global_2022_NDVI0.05.tif.ovr金字塔文件加速大图缩放显示显示优化

tfw 值得单独说。它存着六个数字,本质是一个二维仿射变换。这份全球数据的常见形态是:

0.05 0 0 -0.05 -180 90

第一行是 X 方向像元尺寸,第四行是 Y 方向像元尺寸,负号表示影像行方向自上而下;第五、六行是左上角像元坐标。第 2、3 行是旋转项,这里都是 0,说明栅格没有被旋转过,严格按经纬度对齐。这个“无旋转、左上角 -180/90”的信息非常有用:如果你自己拿 gdalwarp 重投影后生成的 tfw 里出现了非零旋转项,说明输入数据的网格没对齐,要先回查数据源。

aux.xml 里存了 min/max、mean、stddev 和直方图,GIS 首次加载时会直接读取,避免你一打开看到全黑然后怀疑数据坏了。ovr 是金字塔,没有它,缩放到全国范围会卡顿明显;有了它,渲染器只读概览层。注意:如果你后续对 tif 做了裁剪或重投影,旧的 ovr 会失效,务必用 gdaladdo 重建,否则放大会出现黑块。这个问题我放在第 5 章避坑里细说。

提示:命令行自动化处理时,tif + tfw + aux.xml 三个要成套保留。有的程序只认 tfw,有的只认 tif 内嵌坐标,少任何一个都可能出现坐标错位,而且这类错位用肉眼很难第一时间发现。

3. 从 HDF 到年度 NDVI:子数据集提取、网格对齐与 MVC 最大合成实战

3.1 提取 MOD13C2 的 NDVI 子数据集并完成单位换算

MOD13C2 原文件是 HDF4 格式,打开后不是一张图,而是多个子数据集。要用程序处理,第一步就是遍历子数据集,找到名字结尾是 NDVI 的那一层。常见做法是用 GDAL 的 GetSubDatasets 方法:

from osgeo import gdal import numpy as np hdf_path = "MOD13C2.A2022001.061.2022078.2022079.001.hdf" src = gdal.Open(hdf_path) if src is None: raise RuntimeError("无法打开 HDF,确认 GDAL 编译了 HDF4 驱动") ndvi_path = None for sub in src.GetSubDatasets(): if sub[0].split(":")[-1] == "NDVI": ndvi_path = sub[0] break if ndvi_path is None: sys.exit("未找到 NDVI 子数据集") sub = gdal.Open(ndvi_path) ndvi_int = sub.ReadAsArray() # int16 原始值 ndvi = ndvi_int.astype(np.float32) * 0.0001 fill_mask = ndvi_int == -32768 ndvi[fill_mask] = np.nan print("NDVI 有效值范围:", np.nanmin(ndvi), np.nanmax(ndvi))

这段代码的核心是单位换算和填充值掩膜两步。乘 0.0001 是因为 MOD13C2 用 int16 存整数,真实 NDVI = 存储值 × 0.0001;填充值 -32768 换算后变成 -3.2768,必须在缩放之后单独识别并转成 NaN,否则会以假值混进统计。实际落盘时我会把 NaN 转成 -9999 再写 GeoTIFF,因为大部分模型和 GIS 对负值 nodata 的处理比 NaN 稳定。

判断子数据集名称时,sub[0].split(":")[-1] 拿到的是子数据集短名,不同文件可能叫 NDVI 也可能带前缀,更稳妥的做法是把整个子数据集列表打印出来人工确认一次,再写死索引。批量处理几十个月文件时,这个确认环节能省掉后面大量返工。

3.2 对齐 0.05 度全球网格:GeoTransform、投影信息与 tfw 重写

摘要里提到的“投影栅格”这一步,对 MOD13C2 来说不是把经纬度投影成 UTM,而是把子数据集自带的投影和地理变换完整写进输出 GeoTIFF,并让输出严格落在 0.05 度网格上。这一步做不好,后续逐月数据叠在一起会错位半个像元,最大合成后出现条带伪影。

def align_to_grid(gt, pixel=0.05): x0 = np.floor(gt[0] / pixel) * pixel y0 = np.ceil(gt[3] / pixel) * pixel return (x0, pixel, 0.0, y0, 0.0, -pixel) gt_old = sub.GetGeoTransform() gt_new = align_to_grid(gt_old, 0.05) out_ds.SetGeoTransform(gt_new) out_ds.SetProjection(sub.GetProjection())

align_to_grid 把左上角坐标吸附到最近的 0.05 度整数倍上,返回的六元组里第二项和第六项是 X/Y 方向像元大小,正负号方向固定。这样做的好处是:1 月到 12 月每一期的像元边界完全重合,后面做 MVC 时逐像元取最大值不会因网格错位引入噪声。

命令行等价写法是 gdal_translate,重点在几个创建选项:

gdal_translate -ot Float32 -a_nodata -9999 -co TILED=YES -co COMPRESS=DEFLATE \ "HDF4_EOS:EOS_GRID:...:NDVI" ndvi_202201.tif

-ot Float32 把 int16 转成浮点,-a_nodata -9999 统一填充值,-co TILED=YES 让 tif 采用分块存储,网络磁盘和云对象存储上读取更快,-co COMPRESS=DEFLATE 无损压缩,全球栅格能省不少空间。如果还需要 tfw,加一条 -co TFW=YES,GDAL 会顺手写出和 tif 内嵌信息一致的坐标文件,避免手工写错。

3.3 MVC 最大合成法:为什么取最大值而不是均值

云和大气气溶胶会让 NDVI 显著偏低,这是植被遥感的基础认知。对同一个像元来说,一年里拿到 12 个月观测,其中真正接近无云状态的观测,NDVI 大概率是最高值之一。最大合成法(Maximum Value Composite,MVC)就是逐像元取多时相最大值,用年内最高 NDVI 近似植被生长最旺盛那一刻。这是全球 NDVI 产品几十年的标准做法,比简单平均抗云污染得多。

import glob import numpy as np from osgeo import gdal filelist = sorted(glob.glob("ndvi_2022_*.tif")) if len(filelist) != 12: print("警告:只找到%d个月文件,年份可能不完整" % len(filelist)) stack = [] for f in filelist: ds = gdal.Open(f) band = ds.GetRasterBand(1) data = band.ReadAsArray().astype(np.float32) nodata = band.GetNoDataValue() if band.GetNoDataValue() is not None else -9999 data[data == nodata] = np.nan stack.append(data) gt = ds.GetGeoTransform() proj = ds.GetProjection() cube = np.stack(stack, axis=0) # 形状 [12, rows, cols] annual = np.nanmax(cube, axis=0) # 逐像元最大合成 valid_count = np.sum(~np.isnan(cube), axis=0) # 逐像元有效观测数 annual[valid_count < 3] = np.nan # 有效观测少于3个月不输出 annual[np.isnan(annual)] = -9999 out = gdal.GetDriverByName("GTiff").Create("global_2022_NDVI0.05.tif", cube.shape[2], cube.shape[1], 1, gdal.GDT_Float32) out.SetGeoTransform(gt) out.SetProjection(proj) out.GetRasterBand(1).WriteArray(annual) out.GetRasterBand(1).SetNoDataValue(-9999) out = None

lp 关键参数是有效观测数阈值。我默认写成 3,这是给中高纬度留的余量——极夜期连续几个月没有有效观测,阈值设 6 以上北极圈会整片空白;设太低,像元只靠一两个月拼出来,代表不了“年度”。热带地区可以提高到 6,寒带用 3 更稳。np.nanmax 和 np.max 的区别在于前者忽略 NaN,如果图省事用 np.max,一个像元只要有一个月缺测,全年结果就跟着消失。最后落盘前把 NaN 转 -9999,后续模型读取时统一按 nodata 处理,比处理 NaN 简单得多。

4. 数据能干什么:NDVI 转 FVC 植被覆盖度与产品选型对比

4.1 2022 年全球 NDVI 的典型应用场景

这张年度 NDVI 最直接的用途是区域对比:非洲之角干旱、中亚草原退化、亚马逊雨林异常,这些 2022 年的植被信号都会在年度 NDVI 上留下痕迹。0.05 度分辨率做洲际制图完全够用,整张全球图加载也不至于卡死。叠加行政区边界后,可以按省、按流域统计平均 NDVI,和多年均值做距平,判断这一年植被是偏绿还是偏干。

要注意的是年度合成把季节变化压缩掉了,做物候分析必须回去用月度数据。比如华北冬小麦的返青和收割在年度 NDVI 里只有“一年最高”这一个数字,看不出生长节律;想监测生长季长度、峰值时间这类指标,这份年度数据帮不上忙。我一般把它定位成“快速摸底”数据:先看全球哪里出了问题,再针对性下更高分辨率的 MOD13A1 或 MOD13Q1 细看。

另一个常用场景是掩膜和分区统计的底图。做土地利用分类、荒漠化评估时,先把 NDVI 低于某个阈值的像元筛掉,能省很多事。0.05 度全球一张图,内存占用可控,用 Python 读一次全图也就几百 MB,适合批量处理多个年份。

4.2 NDVI 转 FVC:植被覆盖度计算的标准操作

最近总有人问“NDVI 怎么算植被覆盖度”,这个 ndvi 计算 fvc 的操作是植被遥感里的标准流程。思路是假设一个像元由裸土和植被两种端元线性混合,FVC 就是 NDVI 在土壤背景和浓密植被之间的相对位置。公式本身很简单,坑在端元怎么取。

import numpy as np from osgeo import gdal src = gdal.Open("global_2022_NDVI0.05.tif") band = src.GetRasterBand(1) ndvi = band.ReadAsArray().astype(np.float32) nodata = band.GetNoDataValue() ndvi[ndvi == nodata] = np.nan valid = ndvi[~np.isnan(ndvi)] ndvi_soil, ndvi_veg = np.percentile(valid, 5), np.percentile(valid, 95) fvc = (ndvi - ndvi_soil) / (ndvi_veg - ndvi_soil) fvc = np.clip(fvc, 0, 1) fvc[np.isnan(fvc)] = -9999 print(f"端元取值 soil={ndvi_soil:.3f}, veg={ndvi_veg:.3f}") print(f"FVC范围: {np.nanmin(fvc):.3f} ~ {np.nanmax(fvc):.3f}")

取 5% 和 95% 分位数是统计驱动做法,好处是不依赖先验知识,坏处是全球数据连在一起统计时,沙漠和热带雨林互相污染,可能把土壤端元拉得过低、植被端元拉得过高。做省域或小流域时,我更习惯直接用典型端元值:裸土 NDVI 约 0.1 到 0.2,高覆盖植被约 0.8 到 0.9,再对结果裁剪到 0 到 1。不想写代码的话,ArcGIS 栅格计算器里Con(IsNull(ndvi), -9999, (ndvi - 0.1) / (0.8 - 0.1))也是一样的效果,关键是水体像元要先掩掉,否则负值会被 clip 成 0,看着不脏实际已经污染统计。

4.3 与 MOD13A1、MYD13C2 的选型差异

手边同时备着几类 MODIS 植被指数产品是常态,但选错产品会直接带偏结论。我这里按自己实际使用频率列一张对比表:

产品卫星空间分辨率时间分辨率适合尺度典型局限
MOD13C2 v061Terra0.05°(约5km)月 / 年合成全球、洲际、全国局地细节不足
MOD13A1 v061Terra500m16 天省域、流域全球处理数据量大
MYD13C2 v061Aqua0.05°(约5km)月全球,与Terra互补与MOD13C2信号高度相关
MOD13Q1 v061Terra250m16 天县域、地块噪声大、需要认真滤波

我的判断标准是:只做 2022 年全球一张图,首选这份年度 0.05 度数据;做全国尺度且想看季节转折,用月度 0.05 度,而不是把年度图硬插值回季节;做田间尺度的长势监测,老老实实去下 500m 的 MOD13A1。业务里见过不少人拿 5km 数据做乡镇分析,结论被混合像元带偏,最后返工换数据——这是选型问题,不是数据质量问题。

5. 避坑指南:我在 0.05 度年度 NDVI 上踩过的五个坑

5.1 一打开全黑或数值飘到五位数:scale factor 没除干净

现象:在 ArcGIS 或 QGIS 里加载 tif,栅格拉伸后画面全黑或花白,点查询显示像元值 5312。

原因:这份 GeoTIFF 是已经换算过单位的成品,NDVI 正常值域在 -0.2 到 1.0 之间。显示 5312 这类大数,一般是拿着 MOD13C2 原始 int16 存储值直接展示,没有乘 0.0001 标定系数。

解决:先看随包 aux.xml 里的统计信息,再用 gdalinfo 确认一次:

gdalinfo -stats global_2022_NDVI0.05.tif

如果最大值大于 1.0,说明数据本身是整数存储,回到第 3 章的换算流程重新处理;如果统计正常,只是显示问题,在图层属性里手动把拉伸范围设为 -0.2 到 1.0 即可。注意不要对已经换算好的 float 栅格再跑一次-scale -2000 10000 -0.2 1.0,那会把负值区域整体拉伸错位。

5.2 tfw 与内嵌坐标对不上:加载后要素错位半像元

现象:tfw 和 tif 放在同一目录,GIS 里叠到行政边界上,边界与 NDVI 栅格错开约 0.025 到 0.05 度,放大后像元边界呈斜格。

原因:tfw 世界文件只描述仿射变换,不同软件对“tfw 记录的是像元中心还是角点”这类细节有历史差异。更多时候是有人用另一份分辨率相同但左上角坐标不同的 tfw 覆盖了原文件。

解决:以 GeoTIFF 内嵌的 GeoTransform 为准,不要随手生成同名 tfw 覆盖原文件。命令行验证:

gdalinfo global_2022_NDVI0.05.tif | grep -E "Origin|Pixel Size"

确认内嵌坐标正确后,删除或忽略同名 tfw 即可;反过来,如果旧软件只认 tfw,就把内嵌信息原样抄写成 tfw,保证两处一致。从那以后我每次处理栅格都会在开头打印一次这一行,省掉很多“看起来没错但叠不上”的玄学问题。

5.3 高纬度年度结果出现大片空洞或全填 -9999

现象:北极圈内、格陵兰边缘等区域,2022 年度 NDVI 整片是 nodata,面积统计时缺掉一大块。

原因:极夜期连续几个月无有效观测。如果 MVC 前没有统计有效观测数,直接用 np.max 或 np.nanmax 处理,缺测月份的 NaN 要么吞掉全年结果,要么该像元全为 NaN 后落盘变成 nodata。

解决:MVC 时先算有效观测数,低于阈值直接标记为无效:

valid_count = np.sum(~np.isnan(cube), axis=0) annual = np.nanmax(cube, axis=0) annual[valid_count < 3] = np.nan

阈值按纬度调整:中低纬度 6 个月,高纬度 3 个月。如果研究区就是高纬度,建议改用生长季(4 月到 9 月)合成,信号更干净,也不容易被极夜期的空洞干扰。

5.4 全局分位数算 FVC,把水体当植被覆盖了

现象:用 NDVI 转 FVC 后,海洋附近、大湖区域的植被覆盖度达到 30% 甚至更高,明显不符合常识。

原因:水体 NDVI 常为负值,全局 5% 分位数端元把大量水体噪声算进了土壤端元,整个 FVC 基线被抬高。公式只做了 clip,没有先做水体掩膜。

解决:FVC 计算前先把 NDVI 小于 0.1 的像元直接置为 0,再做 clip:

fvc = np.where(ndvi < 0.1, 0.0, fvc)

先置 0 再 clip 的次序很重要。先 clip 会让 0 到 0.1 之间的小正数保留下来,看着不脏,实际仍是水体噪声。更严格的做法是用 MODIS 自带的水体掩膜波段,但 0.1 阈值在多数区域已经够用。

5.5 金字塔过期:切图黑块或加载极慢

现象:缩放到全国图层显示正常,放大到华南局部区域出现黑色斑块;做地图切片时耗时飙升。

原因:随包的 .ovr 是原始打包时生成的。对 tif 做过裁剪、重投影或填 nodata 之后,旧金字塔失效,切片软件会反复读取底层原始数据。

解决:处理完数据后重建金字塔:

gdaladdo -r average global_2022_NDVI0.05.tif 2 4 8 16

参数里 2 4 8 16 表示在底层之上生成 4 层概览,-r average 用平均重采样,对 NDVI 这类连续值比最近邻更平滑。处理完的 tif 再进服务发布流程,黑块问题基本绝迹。

6. 手动收尾技巧:裁剪到研究区并在三分钟内完成量纲体检

6.1 用 gdalwarp 把全球栅格切到目标范围

全球 7200 × 3600 个像元,任何分析批量跑起来都偏重,多数时候要先裁剪到研究区。以中国范围为例:

gdalwarp -te 73 3 135 53 -te_srs EPSG:4326 -tr 0.05 0.05 \ -r near -dstnodata -9999 global_2022_NDVI0.05.tif china_2022_ndvi.tif

-te 后面四个数字是 minx miny maxx maxy,顺序固定;-te_srs 声明这四个数字的坐标系;-tr 0.05 0.05 强制输出像元大小与源数据一致,防止 gdalwarp 自动改分辨率;-r near 在平移裁剪场景下最保真,不需要做插值。如果只切矩形范围,gdal_translate 更轻:

gdal_translate -projwin 73 53 135 3 -projwin_srs EPSG:4326 \ global_2022_NDVI0.05.tif china_clip.tif

注意 -projwin 的顺序是 ulx uly lrx lry,即左上角经纬度和右下角经纬度,和 gdalwarp 的 -te 顺序正好相反,第一次用基本都会写反,写反的后遗症就是裁剪结果空白。

6.2 量纲体检:三分钟确认 NDVI 数值可信

数据进模型前,我习惯跑一遍统计体检,脚本很短但信息量很大:

from osgeo import gdal import numpy as np ds = gdal.Open("china_2022_ndvi.tif") b = ds.GetRasterBand(1) arr = b.ReadAsArray().astype(np.float32) nodata = b.GetNoDataValue() valid = arr[arr != nodata] print("行数列数:", arr.shape) print("min/max:", float(valid.min()), float(valid.max())) print("负值占比:", float((valid < 0).mean())) print("有效像元占比:", float((arr != nodata).sum() / arr.size))

判断标准很简单:min 小于 -0.3 或者 max 大于 1.01,基本可以判定换算或填充值处理出错;负值占比在 10% 到 40% 之间正常,沙漠、水体、云残留都会拉低 NDVI,如果负值占比超过 60%,要怀疑是不是把 EVI 当成 NDVI 用;有效像元占比明显低于 90%,检查裁剪范围是不是含了大量海洋,或者高纬度缺测太严重。

从那以后,我每拿到一份栅格数据,都强制先跑一遍 gdalinfo 和量纲统计,确认 min/max、nodata、投影网格都对上了再进后续模型。数据源头错了,后面无论怎么调参数都是白做,这是我在几份“看起来没问题的 NDVI”上缴过的学费。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询