1. 项目缘起:为什么我们需要一个整理好的GEE土地覆盖数据集?
如果你正在做国土空间规划、生态环境评估、或者任何与地表变化相关的研究,手头有一个现成的、长时间序列的、可以直接在云端调用的土地覆盖数据集,那感觉就像在沙漠里找到了绿洲。我最近在做一个关于中国城市扩张对周边农田影响的课题,核心需求就是分析过去三十年全国尺度的土地覆盖变化。理论上,中国有多套权威的土地覆盖产品,比如GlobeLand30、FROM-GLC等,它们都提供了1990年以来的30米分辨率数据,精度和权威性都没得说。
但实际操作起来,问题就来了。这些数据通常以分幅的GeoTIFF文件形式发布,动辄几十个GB甚至上百GB。下载、拼接、投影转换、格式统一……这套流程走下来,不仅对本地计算资源是巨大考验,更耗时耗力。往往数据还没处理完,研究的热情就先被磨掉了一半。更重要的是,当你想快速验证一个想法,或者进行大范围的时空分析时,这种基于本地文件的模式显得笨重且不灵活。
就在这时,Google Earth Engine(GEE)进入了视野。GEE是一个强大的云端地理空间数据处理平台,它托管了海量的遥感数据集,并提供了在云端进行并行计算的能力。理想情况下,如果这些权威的土地覆盖数据集能直接在GEE中调用,我们就能跳过繁琐的数据准备阶段,直接进入分析环节,效率会有质的飞跃。
然而,现实是骨感的。虽然GEE数据目录里有一些全球土地覆盖产品,但针对中国区域、长时间序列、且基于国内权威数据源的数据集,并没有一个现成的、整理好的“产品”。你需要自己去寻找原始数据源,理解其数据结构,编写代码将其导入到GEE的资产(Assets)中,并处理好时间、波段、属性等元数据。这个过程对于GEE新手,甚至是有一定经验但非地信专业的研究者来说,门槛相当高。
正是基于这个普遍的痛点,我决定动手整理。这个项目的目标非常明确:将中国1990-2022年逐年30米土地覆盖数据集(以广泛使用的GlobeLand30为例)整理并发布为GEE可调用的资产,并提供清晰、完整的调用代码示例。让后来者,无论是学生、研究员还是工程师,都能通过复制粘贴几行代码,立刻获取并分析这套宝贵的数据,把时间和精力真正花在科学问题上,而不是数据工程的泥潭里。
2. 数据基石:理解GlobeLand30及其在GEE中的呈现逻辑
在开始写代码之前,我们必须先吃透我们要处理的数据本身。我选择以GlobeLand30(全球30米地表覆盖数据)作为核心数据源进行整理,这是目前国内最主流、应用最广泛的30米土地覆盖产品之一。它由我国牵头制作,涵盖了1990、2000、2010、2020等基准年份(部分版本有逐年数据),包含耕地、森林、草地、灌木地、湿地、水体、苔原、人造地表、裸地、冰川和永久积雪等10个一级类型。
2.1 GlobeLand30的数据特点与挑战
GlobeLand30的官方数据以分幅的GeoTIFF文件提供,通常按经纬度网格(如10°×10°)分块。对于中国全境,大概需要下载几十个文件。在GEE中直接使用这些原始文件是不现实的,我们需要将它们“搬”到云端。这里有几个关键点需要处理:
- 数据拼接:GEE的
ImageCollection可以看作是一个云端影像栈。我们需要将每一年的所有分幅数据,在GEE内拼接成一幅覆盖中国区域的完整影像。GEE的mosaic()方法可以自动处理重叠区域,通常我们选择后传入的像素值覆盖先传入的(或者指定合并规则)。 - 波段与属性:原始的GlobeLand30 TIFF文件通常只有一个波段,像素值即土地覆盖分类代码(如10代表耕地,20代表森林)。在导入GEE时,我们需要将这个波段命名(例如
‘landcover’),并为整个影像设置关键属性,最核心的就是‘system:time_start’。GEE的强大之处在于其时间序列分析能力,为影像设置正确的获取时间,是后续按时间筛选的基础。例如,1990年的数据,其system:time_start应设置为ee.Date(‘1990-01-01’)。 - 投影与尺度:GlobeLand30采用地理坐标系(WGS84)。在GEE中处理时,需要注意输出时的投影和尺度(scale)设置,特别是在进行统计(如
reduceRegion)时,明确的尺度参数(如scale: 30)能保证统计结果的准确性。 - 数据量与管理:将多年份、全国范围的数据导入个人GEE资产,会占用你的资产配额。虽然GEE提供免费的存储额度,但对于超大数据集仍需合理规划。一种高效的方式是,每年数据存储为一个
Image资产,而不是一个包含所有年份的ImageCollection资产,这样在代码中按需组合成ImageCollection更加灵活。
2.2 在GEE中构建数据集的策略
我的整理策略如下:
- 按年份组织:为1990、2000、2010、2020等每个年份创建一个独立的GEE
Image资产,命名为GlobeLand30_1990,GlobeLand30_2000等。 - 统一属性:为每个
Image设置一致的波段名(‘landcover’)和土地覆盖分类代码的‘class_values’、‘class_palette’(用于可视化)属性。 - 代码化调用:编写一个辅助函数,用户只需指定年份,函数内部就返回对应的
Image,或者将所有年份的Image组合成一个ImageCollection。这样,用户无需关心数据存储在GEE的哪个具体路径,只需调用一个干净的接口。
注意:由于数据版权和共享政策,我无法直接将我处理好的GEE资产公开分享给所有人。本项目的核心产出是完整的、可复现的数据处理与导入GEE的代码流程,以及调用这些数据进行分析的示例代码。你可以按照我的流程,使用你已合法下载的GlobeLand30数据,在你自己GEE账户中创建私有资产,或者寻找已获得授权在GEE中共享的同类数据集。
3. 从本地到云端:数据预处理与GEE资产上传全流程
假设你已经从GlobeLand30官网或其他合法渠道,下载了1990-2020(或2022)各年份的中国区域分幅数据。接下来,我们将一步步把它们变成GEE里的Image。
3.1 本地预处理(可选但推荐)
为了上传过程更顺畅,可以在本地对数据做轻度预处理:
- 检查与合并:确保同一年的所有分幅数据具有相同的坐标系(WGS84)、相同的分类体系。可以使用GDAL(
gdal_merge.py或gdalwarp)在本地先将它们拼接成一张中国区域的大图。这样做的好处是上传到GEE的文件数量大大减少,管理更方便。命令示例如下:
这条命令将多个输入文件合并并统一到WGS84坐标系。gdalwarp -t_srs EPSG:4326 input1.tif input2.tif ... output_merged_1990.tif - 压缩:使用LZW或DEFLATE等无损压缩方式压缩TIFF文件,可以显著减小文件体积,加快上传速度。
- 重命名:将处理好的文件按年份清晰命名,如
China_GlobeLand30_1990.tif。
3.2 GEE资产上传的两种路径
GEE提供了多种数据上传方式,对于这种批量、大文件的操作,我强烈推荐使用命令行工具earthengine。
方法一:使用GEE Python API上传(适合自动化脚本)首先,确保你已安装GEE Python API (
earthengine-api) 并完成了身份验证 (earthengine authenticate)。import ee import subprocess import os # 初始化GEE API ee.Initialize() # 设置本地数据文件夹和GEE资产路径 local_dir = ‘/path/to/your/gl30_data/’ gee_asset_path = ‘projects/your-project-name/assets/GlobeLand30/’ # 遍历本地文件 for filename in os.listdir(local_dir): if filename.endswith(‘.tif’) and ‘China’ in filename: # 从文件名提取年份,例如 ‘China_GlobeLand30_1990.tif‘ -> 1990 year = filename.split(‘_’)[-1].split(‘.’)[0] local_file = os.path.join(local_dir, filename) asset_id = gee_asset_path + ‘China_GL30_’ + year # 构建earthengine上传命令 cmd = [ ‘earthengine’, ‘upload’, ‘image’, ‘--asset_id’, asset_id, ‘--pyramiding_policy’, ‘MODE’, # 对分类数据,金字塔采用众数采样 ‘--time_start’, f‘{year}-01-01’, # 设置影像时间属性 local_file ] # 执行上传命令 print(f‘Uploading {filename} to {asset_id}...’) subprocess.run(cmd) print(f‘Upload task started for {filename}. Check status with ‘earthengine task list‘.’)这段代码会自动遍历文件夹下的TIFF文件,提取年份,并为每个文件发起一个上传任务。
--pyramiding_policy MODE参数至关重要,它指定在生成金字塔(多尺度预览)时,对分类数据采用“众数”算法,避免出现无效的混合像元值。方法二:直接使用earthengine命令行如果你更喜欢手动控制,可以对每个文件执行类似命令:
earthengine upload image \ --asset_id=projects/your-project/assets/GlobeLand30/China_GL30_1990 \ --pyramiding_policy=MODE \ --time_start=1990-01-01 \ /path/to/China_GlobeLand30_1990.tif
3.3 上传后处理与属性设置
文件上传完成后,在GEE代码编辑器中,我们还需要为这些Image资产设置更丰富的属性,以便于调用。这步必须在GEE的JavaScript或Python API代码中完成。
// 假设我们已经将1990年数据上传到了 ‘projects/your-project/assets/GlobeLand30/China_GL30_1990‘ var image1990 = ee.Image(‘projects/your-project/assets/GlobeLand30/China_GL30_1990‘); // 定义GlobeLand30的分类代码和对应的颜色 var classValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; var classNames = [‘Cropland‘, ‘Forest‘, ‘Grassland‘, ‘Shrubland‘, ‘Wetland‘, ‘Water‘, ‘Tundra‘, ‘Artificial Surface‘, ‘Bareland‘, ‘Permanent Snow/Ice‘]; var classPalette = [‘#FFFF00‘, ‘#008000‘, ‘#00FF00‘, ‘#00A000‘, ‘#00FFFF‘, ‘#0000FF‘, ‘#AAAAFF‘, ‘#FF0000‘, ‘#C0C0C0‘, ‘#FFFFFF‘]; // 为影像设置属性 image1990 = image1990 .rename(‘landcover‘) // 将波段重命名为‘landcover‘ .set({ ‘landcover_class_values‘: classValues, ‘landcover_class_names‘: classNames, ‘landcover_class_palette‘: classPalette, ‘system:time_start‘: ee.Date(‘1990-01-01‘), // 确保时间已设置 ‘dataset‘: ‘GlobeLand30‘, ‘version‘: ‘v2020‘, ‘resolution‘: 30 }); // 最后,将处理好的影像重新导出到资产,覆盖原资产或保存为新资产。 Export.image.toAsset({ image: image1990, description: ‘GlobeLand30_1990_Final‘, assetId: ‘projects/your-project/assets/GlobeLand30_Final/GL30_1990‘, region: image1990.geometry(), // 指定区域 scale: 30, maxPixels: 1e13 });这个过程需要对每个年份的影像执行一次。虽然繁琐,但一劳永逸。完成后,你就拥有了一个属性完善、可直接用于分析的GEE土地覆盖资产库。
4. GEE调用实战:封装与典型应用场景分析
当所有年份的数据都规整地成为GEE资产后,真正的乐趣就开始了。我们可以像搭积木一样,快速构建各种分析。下面分享几个最常用的调用模式和场景。
4.1 数据调用封装
首先,我们创建一个方便的调用函数,避免每次重复写资产路径。
// 定义一个函数,根据年份返回对应的GlobeLand30影像 function getGlobeLand30(year) { // 基础资产路径,根据你的实际位置修改 var baseAssetPath = ‘projects/your-project/assets/GlobeLand30_Final/GL30_‘; var assetId = baseAssetPath + year; var image = ee.Image(assetId); // 确保影像有我们设置的属性,如果没有可以在这里补设 image = image .rename(‘landcover‘) .set(‘system:time_start‘, ee.Date(year + ‘-01-01‘)); return image; } // 示例:获取1990、2000、2010、2020年的影像,并组成一个ImageCollection var years = [‘1990‘, ‘2000‘, ‘2010‘, ‘2020‘]; var landcoverCollection = ee.ImageCollection(years.map(getGlobeLand30)); print(‘Landcover Collection:‘, landcoverCollection); // 可视化单一年份 var visParams = { bands: [‘landcover‘], min: 10, max: 100, palette: getGlobeLand30(‘1990‘).get(‘landcover_class_palette‘).getInfo() // 获取颜色盘 }; Map.centerObject(ee.Geometry.Point([116.4, 39.9]), 6); // 定位到北京 Map.addLayer(getGlobeLand30(‘2020‘), visParams, ‘GlobeLand30 2020‘);4.2 应用场景一:土地利用变化检测(Transition Matrix)
计算两个年份之间土地类型转移矩阵,是变化检测的核心。
// 计算1990-2020土地覆盖转移矩阵 var lc1990 = getGlobeLand30(‘1990‘).select(‘landcover‘); var lc2020 = getGlobeLand30(‘2020‘).select(‘landcover‘); // 将两个年份的代码组合成一个双位数代码 // 公式:oldClass * 1000 + newClass (假设类别代码<1000) var changeCode = lc1990.multiply(1000).add(lc2020); // 定义一个区域,例如京津冀边界 var roi = ee.FeatureCollection(‘TIGER/2018/States‘) .filter(ee.Filter.inList(‘NAME‘, [‘Beijing‘, ‘Tianjin‘, ‘Hebei‘])); // 统计转移矩阵 var changeStats = changeCode.reduceRegion({ reducer: ee.Reducer.frequencyHistogram(), geometry: roi.geometry(), scale: 1000, // 使用1km尺度进行统计以加快速度,可根据精度要求调整 maxPixels: 1e13 }); print(‘土地利用转移统计(部分):‘, changeStats); // 输出结果是一个字典,键是‘old_new‘组合码,值是该变化发生的像元数。 // 需要后续将代码解析回类别名称,并整理成矩阵表格。4.3 应用场景二:特定地类面积时空变化分析
分析过去30年,全国或特定区域耕地、森林、建设用地的面积变化。
// 分析ROI内人造地表(代码80)的面积变化 var roi = ee.Geometry.Rectangle([110, 20, 125, 45]); // 示例:中国东部区域 // 定义一个函数,用于计算单景影像中某类别的面积 function calculateArea(image, classValue, scale) { var areaImage = image.eq(classValue) // 等于目标类别的像素变为1,否则为0 .multiply(ee.Image.pixelArea()) // 乘以每个像素的面积(平方米) .divide(10000); // 转换为公顷 var areaStats = areaImage.reduceRegion({ reducer: ee.Reducer.sum(), geometry: roi, scale: scale, maxPixels: 1e13, bestEffort: true // 如果像素过多,尝试近似计算 }); return ee.Number(areaStats.get(‘landcover‘)).getInfo(); // 返回面积值 } // 遍历年份进行计算 var artificialSurfaceArea = []; years.forEach(function(year) { var img = getGlobeLand30(year); var area = calculateArea(img, 80, 300); // 使用300米尺度统计平衡精度与速度 artificialSurfaceArea.push({year: year, area_ha: area}); print(‘Year ‘ + year + ‘, Artificial Surface Area (ha): ‘ + area); }); // 结果可以导出到Google Drive或用图表显示4.4 应用场景三:与其它遥感数据联动分析
GEE的强大之处在于数据融合。我们可以轻松地将土地覆盖数据与夜间灯光数据(NPP/VIIRS)、植被指数(NDVI)、气象数据等结合。
// 示例:分析2020年不同土地覆盖类型上的平均NDVI(使用MODIS数据) var lc2020 = getGlobeLand30(‘2020‘); var ndvi2020 = ee.ImageCollection(‘MODIS/006/MOD13A1‘) .filterDate(‘2020-06-01‘, ‘2020-08-01‘) .select(‘NDVI‘) .mean(); // 计算夏季平均NDVI // 使用土地覆盖数据作为分区,分区统计NDVI var ndviByClass = ndvi2020.addBands(lc2020.select(‘landcover‘)) .reduceRegion({ reducer: ee.Reducer.mean().group({ groupField: 1, // 按第二个波段(landcover)分组 groupName: ‘landcover_class‘ }), geometry: roi, scale: 500, // MODIS NDVI尺度 maxPixels: 1e13 }); print(‘不同土地覆盖类型的平均NDVI:‘, ndviByClass);5. 避坑指南与性能优化心得
在整理和调用这套数据的过程中,我踩过不少坑,也总结出一些优化技巧,希望能帮你节省时间。
5.1 数据上传与管理的坑
- 坑1:上传任务失败或卡住。GEE上传大文件有时会因网络不稳定或服务器端问题失败。对策:使用
earthengine task list和earthengine task cancel [TASK_ID]管理任务。对于失败的任务,可以尝试重新上传。更稳妥的做法是将大文件分割成稍小的块(如按省份)分别上传,再在GEE内拼接。 - 坑2:资产权限混乱。如果你在团队中协作,资产权限设置不当会导致他人无法访问。对策:在GEE代码编辑器的“Assets”选项卡中,清晰地为文件夹和资产设置共享权限。对于公开项目,可以考虑将最终处理好的资产发布为公开数据集(需遵守数据许可协议)。
- 坑3:属性丢失。通过命令行上传的影像,在GEE中可能缺少我们在代码中设置的
system:time_start等属性。对策:如3.3节所示,上传原始数据后,务必运行一个“后处理”脚本,将属性设置好并重新导出为最终资产。原始上传资产可以作为“原材料”保留。
5.2 计算性能与精度平衡
- 挑战1:全国尺度统计耗时过长。对30米分辨率数据做全国范围的
reduceRegion,像素量巨大,极易超时或超出内存限制。对策:- 降低统计尺度:如示例中,将
scale参数设为1000或更大,虽然损失了细节,但能极大提升速度,适用于宏观趋势分析。 - 使用
bestEffort: true:让GEE在超限时尝试返回一个近似结果。 - 分区域统计:将全国按省或流域分区,循环计算后再汇总。可以使用
ee.FeatureCollection的iterate方法或map函数。 - 导出结果:对于极其复杂的计算,更好的方式是
Export.table.toDrive或Export.image.toDrive,将中间或最终结果导出到Google Drive,然后在本地分析。
- 降低统计尺度:如示例中,将
- 挑战2:可视化渲染慢。在地图上加载全国30米土地覆盖,前端渲染压力大。对策:GEE会自动使用金字塔(Pyramid)进行多尺度显示。确保上传时设置了正确的
pyramiding_policy(分类数据用MODE)。在代码中,也可以使用.reproject()或.reduceResolution()在显示前主动降低分辨率。
5.3 代码健壮性与可复用性
- 心得1:封装与模块化。如4.1节所示,将数据获取逻辑封装成函数
getGlobeLand30(year),极大提高了代码的清晰度和复用性。你可以进一步封装变化检测、面积统计等常用功能为函数。 - 心得2:善用
print和Chart调试。在开发复杂分析时,多用print()输出中间变量的属性、大小。使用ui.Chart系列函数快速绘制时间序列或统计图表,直观验证分析逻辑是否正确。 - 心得3:理解GEE的“延迟执行”。GEE的代码是生成一个处理任务链,直到遇到
print、Map.addLayer或Export等需要输出结果的操作时,才会真正发送到服务器执行。这意味着你不能用传统的JavaScript思维进行for循环和条件判断。所有针对ee.Object(Image,FeatureCollection等)的操作,都必须使用GEE提供的客户端函数(如ee.List.sequence,iterate,map)。
最后,我想说的是,整理这套数据集的初衷是为了“偷懒”——让后续的分析工作更便捷。虽然前期投入了时间在数据上传和属性整理上,但看到后来能用寥寥数行代码完成过去需要数天数据准备才能开始的分析,这种效率的提升是实实在在的。GEE的学习曲线确实存在,但一旦掌握了它的核心思想(客户端与服务器端分离、延迟执行、面向对象的数据模型),它就会成为你进行大规模地理空间分析不可或缺的利器。希望这份整理和分享,能成为你打开GEE和长时间序列土地覆盖分析大门的一把钥匙。