“去年做全球粮食风险评估的时候,我刚把需求听完就知道这事绕不开 Google Earth Engine。要在全球尺度上画出农田范围分布数据集 1000m 那套东西,还要算面积、出图、对比多时相,换成五年前,光下载和预处理 MODIS 产品就够折腾一个月。现在好了,GEE 把全球农田范围分布数据直接放在云端,剩下的问题就是:你知道该用哪个数据集、怎么提取、怎么算面积、怎么判断结果是不是靠谱。”
这篇内容适用于刚接触 GEE 的遥感学生、做农业监测或粮食安全分析的从业者,也适合那些想用现成数据集快速验证自己想法、但不想从头跑一遍分类算法的研究者。我会把“全球农田范围分布数据集 1000m”这个标题背后的数据选型、提取逻辑、实操代码和坑,一次说清楚。
1. 1000m 农田数据为什么仍是全球农业分析的“黄金尺度”
先说一个反直觉的结论:处理全球尺度的农业问题,1000m(1km)分辨率的农田分布数据,很多时候比 10m 高分辨率数据更实用。原因不复杂:农田分布分析的核心目标是“圈范围、看趋势、算面积”,而不是“数地块”。就像一个城市的菜市场分布图,你只需要知道哪个片区集中卖菜,不需要画出每个摊位的位置。
GEE 里常被用来做“全球农田范围分布数据集 1000m”的底层产品,主流有这么几类,我整理了一个表格方便对比:
| 数据集 | 分辨率 | 时间跨度 | 农田定义方式 | GEE 资产示例 | 优缺点 |
|---|---|---|---|---|---|
| MODIS MCD12Q1 | 500m(常作为 1km 级使用) | 2001 年至今逐年 | IGBP 分类中的 Croplands / Cropland-Natural Vegetation Mosaic | MODIS/061/MCD12Q1 |
时间序列长,年更新稳定,分类体系成熟 |
| GFSAD1000 | 1000m | 2000 年前后一版 | 多源遥感融合的农田范围/强度 | projects/sat-io/open-datasets/GFSAD/GFSAD1000_CROPLAND |
专为农田设计,范围更聚焦,但版本较老 |
| ESA WorldCover | 10m | 2020/2021 等单期 | 土地覆盖分类中的 Cropland | ESA/WorldCover/v100 |
精度高,适合局部验证,全球统计计算量很大 |
| ESRI Global Land Cover | 10m | 2010-2021 逐年 | 土地覆盖分类中的 Cropland | projects/sat-io/open-datasets/ESRI/Global-LULC |
年际更新,适合做趋势验证,同样存在全球计算开销问题 |
从这张表你应该能看出来,MCD12Q1 是全球农田范围分析里最“能打”的通用数据集。它虽然标称分辨率是 500m,但在实际使用中,大家普遍把它归到“千m级”数据集里讨论。而且它自 2001 年起每年更新,适合做长时间序列的农田变化分析。GFSAD1000 则是真正意义上的“1000m 农田专项数据集”,分类目标更聚焦,适合用来和 MCD12Q1 做交叉验证。
那为什么不是越高分辨率越好?因为全球尺度的农田统计,对空间精度的要求没有你想象中那么高。举个例子,你要估算“全球农田面积大概是多少平方公里”,10m 和 1km 数据算出来的总量级不会差太多,但 10m 数据的处理成本会高出一个数量级。GEE 虽然号称“云计算”,但你让它对全球范围做一次 10m 分辨率的统计分析,照样可能把任务压到内存溢出,或者排队排到怀疑人生。而 1000m 尺度下,一次全球统计通常几秒到几十秒就可以跑完,迭代验证的效率完全不是一个级别。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MCD12Q1 和 GFSAD 两套数据到底差在哪
说到具体数据集,很多人会直接把 MCD12Q1 里的第 12 类(Croplands)当作农田范围提取出来用,这没有问题,但你需要理解它的分类逻辑,否则统计结果会出现很大的解释偏差。
2.1 MCD12Q1 的 IGBP 分类逻辑
MCD12Q1 是 MODIS 卫星 Terra 和 Aqua 的联合土地覆盖产品,提供多种分类方案,其中使用频率最高的是 LC_Type1,也就是 IGBP 全球植被分类体系。这套分类把全球地表分成 17 类,和农田直接相关的是:
- Class 12:Croplands,即农田,指像元内以农作物种植为主的区域;
- Class 14:Cropland/Natural Vegetation Mosaics,即农田与自然植被镶嵌混合的区域。
这里的关键在于,MCD12Q1 采用的是“像元主导类型”的判定方式。意思是,如果一个 500m 像元里,耕地面积占了大约 60%,那这个像元就会被判为农田类;但如果某个区域地块破碎,一个像元里既有农田又有林地、草地,且没有哪个类别占绝对主导,它就可能被分到 mosaic 类(第 14 类)。
这就直接导致了一个使用习惯分化:有人只提取第 12 类,得到的是“纯农田像元”;有人把第 12 和 14 类都算作农田,得到的是一个更宽泛的农业活动区域。两种口径得到的全球农田面积差异巨大,可以相差数百万平方公里。
2.2 GFSAD 的农田专项视角
GFSAD(Global Food Security-support Analysis Data)项目的出发点就是解决全球农田分布数据不一致的问题。它的 1000m 产品不是单纯的土地覆盖分类,而是专门针对农田范围进行了多源遥感数据融合,比如结合了 Landsat 和 MODIS 的时间序列特征,输出结果的核心是“这个像元有多少比例是农田”。
所以当你用 GFSAD 数据时,其实更像是在看“农田强度图”,而不是一张简单的农田/非农田二值图。这种设计在处理非洲、东南亚这种小农经济为主、田块破碎的地区时,比 MCD12Q1 有优势,因为那些地方的农田经常连 500m 像元的一半都占不到,在 MCD12Q1 里很容易被漏掉。
我在实际项目里的做法是“双数据源交叉验证”:先用 MCD12Q1 提取 12、14 类,得到农田范围下限和上限;再用 GFSAD 的农田强度图做参考,看关键区域的判断是否一致。如果两套数据在某个区域的结论差异特别大,那基本可以断定这个地方的土地利用非常复杂,需要引入更高分辨率的数据进一步确认。
3. 在 GEE 里提取全球农田范围的完整代码流程
下面进入实操环节。这部分代码我按“加载 MCD12Q1 -> 提取农田类别 -> 可视化 -> 统计面积”的顺序来写,可以直接复制到 GEE 编辑器中运行。
3.1 加载并提取农田类别
javascript复制// 1. 设定目标年份,这里以 2020 年为例
var year = 2020;
// 2. 加载 MODIS MCD12Q1 v6.1 土地覆盖数据集
var mcd12q1 = ee.ImageCollection('MODIS/061/MCD12Q1')
.filterDate(year + '-01-01', (year + 1) + '-01-01')
.first()
.select('LC_Type1');
// 建议先打印类别名称,确认产品版本中的分类编号
print('查看 LC_Type1 类别说明:', mcd12q1.get('LC_Type1_class_names'));
这里有个细节:filterDate 的用法比直接 filter(ee.Filter.calendarRange(year, year, 'year')) 直观不少。MCD12Q1 按年合成,每年一个影像,过滤出该年 1 月 1 日到次年 1 月 1 日之间的影像并取第一个,就能稳定拿到当年的土地覆盖数据。
javascript复制// 3. 提取农田类: Class 12 = Croplands
var cropland = mcd12q1.eq(12);
// 4. 如果需要把镶嵌类也算进来,就加上 Class 14
var croplandMosaic = mcd12q1.eq(14);
// 通常建议先只使用 Class 12,后续再视区域情况叠加 14
var agriculture = cropland;
Map.centerObject(ee.Geometry.Point([105, 35]), 4);
Map.addLayer(
agriculture.selfMask(),
{palette: ['#f0b429']},
'Cropland (Class 12) 2020'
);
在这里,eq(12) 返回的是一个二值影像,像元值为 1 表示农田,0 表示非农田。selfMask() 的作用是把 0 值转成空值,这样在地图上就只显示农田像元,视觉上更干净。
3.2 叠加 Mosaic 类的处理
如果你研究的是非洲或东南亚这类农田破碎度高的地区,建议再用下面代码叠加第 14 类看看差异:
javascript复制var agricultureWithMosaic = cropland.add(croplandMosaic).gt(0);
Map.addLayer(
agricultureWithMosaic.selfMask(),
{palette: ['#d95f02']},
'Cropland + Mosaic (12/14) 2020'
);
把两个图层叠加显示之后,你会很直观地看到:加入了第 14 类之后,山区、农牧交错带的农田范围明显扩大。做这个操作之前先想清楚你要回答什么问题——如果是“粮食主产区在哪里”,用 Class 12 就够了;如果是“人类农业活动影响到了哪些区域”,那 Class 14 必须加上。
3.3 用 GFSAD1000 做对比提取
javascript复制// 这里的资产名可能随数据版本更新而调整,建议先在 GEE 搜索框中搜索 GFSAD 确认
var gfsadCropland = ee.Image('projects/sat-io/open-datasets/GFSAD/GFSAD1000_CROPLAND');
// 不同版本的 GFSAD 类别定义不完全一样,一般 1 及以上表示农田
var gfsadAgri = gfsadCropland.gte(1);
Map.addLayer(
gfsadAgri.selfMask(),
{palette: ['#1a9850']},
'GFSAD1000 Cropland'
);
gte(1) 的意义是“农田强度大于等于 1 的都算作农田”,具体阈值你可以根据自己的研究区域调整。如果只想保留农田占比足够高的区域,可以改成 gte(2) 等更高阈值。
4. 面积统计避坑:pixelArea、等面积投影与内存控制
提取出农田范围之后,下一个高频需求是算面积。这里有一个非常经典的坑:直接在 GEE 里用 count 乘以像元大小,算出来的面积不准确。
4.1 为什么不能直接数像元个数
普通经纬度坐标系(EPSG:4326)下,一个像元的“名义尺寸”是固定度数,比如 0.0089285714 度,大约对应 1km。但地球是个椭球,经度方向上同样的度数,在赤道附近代表的实际距离,和在高纬度地区完全不同。所以在高纬度地区,直接数像元数再乘 1000x1000(平方米),会造成严重的高估。
正确做法是用 ee.Image.pixelArea()。这个函数会为影像中的每个像元生成一个“实际地表面积”的波段,单位是平方米,它考虑了投影和地球形状带来的面积差异。
4.2 全球农田面积统计的标准写法
javascript复制// 1. 农田像元乘以像元实际面积,得到每个像元的农田面积
var areaImage = agriculture.multiply(ee.Image.pixelArea());
// 2. 如果叠加了 Mosaic,需要写 agricultureWithMosaic 那一个影像
var areaStats = areaImage.reduceRegion({
reducer: ee.Reducer.sum(),
geometry: ee.Geometry.Polygon([-180, -60, 180, -60, 180, 85, -180, 85]),
scale: 1000,
maxPixels: 1e13,
bestEffort: true,
tileScale: 4
});
print('农田面积(平方米):', areaStats.get('LC_Type1'));
print('农田面积(万平方公里):', ee.Number(areaStats.get('LC_Type1')).divide(1e10));
这里把 maxPixels 设置成了 1e13,是为了避免 GEE 在大范围统计时报 “Too many pixels” 错误。bestEffort: true 的含义是,如果计算量太大,GEE 会自动调整重采样尺度,尽可能完成任务。tileScale 是内部任务分块的参数,调大这个值可以降低单块计算压力,从而减少内存溢出风险。
4.3 按行政区统计面积
有时候你要的不是全球总量,而是各个国家的农田面积。这时候可以用 reduceRegions:
javascript复制// 加载 FAO 全球国家边界
var countries = ee.FeatureCollection('FAO/GAUL/2015/level0');
// 给每个国家统计农田面积
var countriesWithArea = areaImage.reduceRegions({
collection: countries,
reducer: ee.Reducer.sum(),
scale: 1000,
maxPixels: 1e13,
tileScale: 4
});
// 提取国家名和面积字段
var result = countriesWithArea.select(['ADM0_NAME', 'sum']);
Map.addLayer(result, {}, 'Countries area stats');
这段代码跑出来的结果是以“平方米”为单位的字段,导出到 CSV 后在本地除以 1e10 就是“万平方公里”。实际使用中,我一般会把结果转成 GeoJSON 或 CSV 再导入表格软件里做排序、图表,比较高效。
4.4 和 FAO 官方统计对不上的原因
你在做完面积统计之后,多半会发现结果和 FAO 年鉴里的“耕地面积”存在出入。这不一定是代码写错了。FAO 的统计口径通常是“耕地面积”,包含休耕地、临时草地等;而 MCD12Q1 的分类基于某一年度的遥感影像特征,只能反映“这个像元当年长得像农田”,两者定义就不同。再加上 500m 像元混合问题,结果存在 10%-30% 的偏差都算正常。这里我给的建议是:做趋势分析时看相对变化,不要死磕绝对数值。
5. 实测中容易翻车的四个细节
在 GEE 里用这套全球农田范围数据集的过程中,我踩过不少坑,挑四个最值得说的。
5.1 产品版本不同,类别编号可能变化
MCD12Q1 有 v6 和 v6.1 两个常见版本,多数情况下 LC_Type1 的类 12 都是农田,但在某些类别编号上,两个版本存在调整。最稳妥的办法不是背编号,而是用我前面代码里的那行:
javascript复制print(mcd12q1.get('LC_Type1_class_names'));
把类别说明打出来看一眼,确认你要提取的类别编号语义。这个习惯能避免你在换了版本之后,提取出完全错误的结果却不自知。
5.2 农田像元不等于纯农田
经常有人在答辩或者报告里被问到:“你这块是农田,为什么边界这么不齐?” 原因就是像元混合。MODIS 的像元尺度太大,一个像元里可能既有村镇、又有农田、又有道路,分类结果只能反映“主导地类”。在华北平原这种大面积连片农田的区域,分类精度很高;但到了西南山区,一个像元里常常一半是林子一半是田,分类结果就会很碎,误差也随之上升。
5.3 全球一次性统计的内存控制
我见过不少新手在统计全球面积时直接写 reduceRegion({scale: 1000}) 然后挂在那边等半天,最后报错。解决思路有三个:
maxPixels调大但没有上限,建议1e10到1e13之间;scale不要小于数据本身分辨率,MCD12Q1 到 500m 就够了,完全没必要硬压到 100m;tileScale调大,比如 4 到 8,能明显减少内存溢出的情况。
还有一个经验是:如果单次全球统计连续失败,就分区域统计再汇总,比如按大洲拆成几个 geometry,或者直接用导出任务(Export.table.toDrive)跑到后台去慢慢算。
5.4 用遥感数据做“农田”判断,要结合区域物候
MCD12Q1 的年度分类是全年合成的,但热带地区常年有云、种植季不规律,实际分类精度会比温带差一些。有些地方种植的是多年生作物(比如橡胶、油棕),它们在 IGBP 分类里常常被归到森林或灌木类,而不是农田类。这些区域如果要准确识别,就需要配合高分辨率影像做目视解译或训练自己的分类器,单靠现成的 1000m 产品会有盲区。
6. 农田提取之后还能怎么玩:多源验证与时间序列分析
农田范围提取只是第一步,实际项目里往往还要验证精度、看变化趋势,这里给几个我常用的扩展做法。
6.1 用 ESA WorldCover 10m 做局部精度抽检
从 1000m 数据集上看出“某地有农田”之后,为了确认结果没有明显误判,我会用 ESA WorldCover 10m 数据在同一区域切一个局部对比窗口:
javascript复制var esaWorldCover = ee.Image('ESA/WorldCover/v100');
Map.addLayer(
esaWorldCover.select('Map').eq(40).selfMask(),
{palette: ['#ffff4d']},
'ESA Cropland (10m)'
);
10m 分辨率在当前全屏分辨率下,通常能看到田块的细节形态。两者在空间上大致重合,说明 1km 数据的判读基本可信;如果出现大面积一 1km 像元有而 10m 没有的情况,就要注意是不是混合像元误判了。
6.2 逐年提取农田,做变化趋势
利用 MCD12Q1 的年更新特性,可以做农田范围的长时间序列变化分析。这个对粮食安全评估、土地利用变化研究特别有用。思路是构建一个 ImageCollection,逐年计算农田范围的像元比例:
javascript复制function getCroplandByYear(year) {
var image = ee.ImageCollection('MODIS/061/MCD12Q1')
.filterDate(year + '-01-01', (year + 1) + '-01-01')
.first()
.select('LC_Type1');
return image.eq(12).set('system:time_start', ee.Date.fromYMD(year, 1, 1));
}
var yearlyCropland = ee.ImageCollection(
ee.List.sequence(2001, 2020)
.map(function(y) {
return getCroplandByYear(ee.Number(y));
})
);
// 每个像元在 2001-2020 年间农田出现的年次数占比
var croplandFrequency = yearlyCropland.sum().divide(20);
Map.addLayer(
croplandFrequency.selfMask(),
{min: 0.1, max: 1, palette: ['#fee08b', '#d73027']},
'Cropland Occurrence Frequency'
);
这段代码统计的是“20 年来这个像元有多少年被认为是农田”。稳定农田区域会出现深红色,而轮作区、不稳定耕作区会显示浅黄色。这个指标对判断一个区域的农业稳定性非常有用,比单看一年的分类结果要稳健得多。
6.3 结合非遥感数据做综合分析
农田范围提取出来之后,后续还可以叠加人口密度、降雨量、土壤属性、夜间灯光等数据做综合分析。比如“农田范围集中但与人口密度明显不匹配”的区域,往往意味着农业机械化程度高、劳动力输出型农业等;再比如农田范围与降雨量严重不匹配的区域,大概率存在灌溉农业。GEE 里这些数据都有现成资产,比如 CHIRPS 降雨、WorldPop 人口密度、SoilGrids 土壤数据,都可以直接用图层拼接或者统计到刚才算出来的农田区域里。
我在实际做农业风险评估时,最常用的组合是“农田范围 + 降雨距平 + 夜间灯光变化”,三张图叠在一起,基本能快速判断一个区域是否出现农业异常。这套逻辑在应急响应、粮食监测类项目里非常实用。
最后再分享一点个人体会:数据分辨率不是越高越好,关键是和你研究的问题尺度匹配。全球农田范围分布数据集 1000m 这个级别的数据,胜在覆盖时间长、更新稳定、计算成本低,特别适合做宏观格局判断和趋势分析。如果你只是需要知道“全球哪些地方在种地、种地范围怎么变化”,直接用这套体系就足够了。只有当研究对象缩小到一个流域、一个县的时候,才需要考虑换成 ESA 10m 或者 GLAD 30m 这类更高分辨率的数据。
