01 说明
- 任务分成两个:第一是指定时间范围,提取该范围内的所有GPP影像求取均值;第二是指定时间范围,按年尺度提取每一年中的
GPP
影像求取均值(一年一景) - 要求包括:全球尺度、输出地理坐标系WGS84且分辨率为0.05°、无效值处理;
02 代码说明
2.1 输出指定时间段的影像均值
完整代码:
javascript
// 选择需要输出GPP产品的时间范围
var start_date = ee.Date.fromYMD(2023, 1, 1); // 开始日期
var end_date = ee.Date.fromYMD(2023, 12, 31); // 结束日期
var gpp_name = 'Gpp'; // 需要的波段名称, MOD17A3HGF包括Gpp、Npp以及Npp_QC三个波段
var apply_land_mask = true; // 是否掩膜只要陆地地区, 若为true则掩膜;若为false则不进行掩膜
var min_value = 0; // 有效值的范围
var max_value = 65500; // 同理
var out_res = 0.05 // 输出分辨率(单位为°)
// 加载MODIS-GPP产品(MOD17A3HGF)
var gpp_collection = ee.ImageCollection("MODIS/061/MOD17A3HGF"); // 从云计算平台加载指定数据集
// 筛选指定年份的影像
var gpp_img = gpp_collection.filterDate(start_date, end_date).select(gpp_name).mean();
// 获取并应用有效值范围的掩膜
var valid_range_mask = gpp_img.gte(min_value).and(gpp_img.lte(max_value));
gpp_img = gpp_img.updateMask(valid_range_mask);
// 应用缩放因子(缩放后单位为:kg*C/m^2, 由于MOD17A3是年尺度,因此这也可以视为kg*C/m^2/year)
gpp_img = gpp_img.multiply(0.0001);
/*
为什么要应用缩放因子?
因为GEE上存储的数据量非常大, 整数存储相比于小数存储在大数据量面前将会有非常大的性价比减少
更多的存储空间, 因此存储时会先将小数放大n倍转化为小数,在需要时乘以缩放因子即可恢复原来的数值.
*/
// 掩膜(只需要陆地地区)
if (apply_land_mask === true){
var water_mask = ee.Image('MODIS/MOD44W/MOD44W_005_2000_02_24').select('water_mask');
var land_mask = water_mask.eq(0);
gpp_img = gpp_img.updateMask(land_mask);
}
// 定义地理范围
var global_region = ee.Geometry.Rectangle([-180, -90, 180, 90], 'EPSG:4326', false);
// 可视化参数
var gpp_vis_param = {
min: 0.0,
max: 3.0,
palette: ['#bbe029', '#0a9501', '#074b03']
};
// 可视化
Map.setCenter(0, 0, 2); // 显示的中心位置和缩放大小, 参数依次为(经度: 0, 纬度: 0, 缩放级别:2)
// 导出影像到Google Drive
var cur_year = start_date.get('year');
cur_year.evaluate(function(cur_year){
Map.addLayer(gpp_img, gpp_vis_param, 'Annual_GPP_' + cur_year);
Export.image.toDrive({
image: gpp_img.resample('bilinear'), // 输出使用双线性插值, 若不需要删除.resample('bilinear')
description: 'Global_GPP_' + cur_year,
folder: 'Global_GPP',
fileNamePrefix: 'Global_GPP_' + cur_year,
region: global_region,
crs: 'EPSG:4326', // 坐标系为WGS84
crsTransform: [out_res, 0, -180, 0, -out_res, 90], //
maxPixels: 1e13, // 防止像元个数过多拒绝导出, 增加导出上限
formatOptions: {'noData': -9999} // NoData值为-9999
});
});
可能需要说明的是evaluate
方法(这段代码使用evaluate方法有点多余,但是后续批量输出下载等是很难避免使用这个方法,不如在这里将这个方法稍微理一理):
原文为:Asynchronously retrieves the value of this object from the server and passes it to the provided callback function. 即异步地将该变量(谁调用的
evaluate
方法谁就是此处所指代的the value,例如year_list.evaluate()
,则the value指代year_list
)传递给服务器,让服务器计算它的值,服务器计算好该值之后再将其提供给回调函数。 所以含义是什么? 不管异步这一操作,就是在客户端(你的浏览器)上存在一个变量,这个变量具体的值还不清楚没有计算但是你现在又需要这里面的值来进行后续处理,那么你就可以通过evaluate
方法将其传递给服务器(GEE
的远程云计算平台),让服务器计算好之后,再传送回给客户端浏览器,再继续在客户端本地进行后续处理,这里的后续处理就是前面提及的回调函数,服务器计算好的结果应该怎么传送回给本地浏览器呢?你用一个变量去接?接住了还需要进行处理? ⇒ 那么方法就是定义一个回调函数以及一个函数的参数,服务器计算好之后将这个计算好的变量值传递给这个回调函数参数,然后运行这个回调函数。 那么异步是什么意思呢?就是year_list.evaluate
执行之后,变量到了服务器就好,接着往后运行后续代码,而至于.evaluate()
内部的回调函数的定义、运行回调函数它慢慢运行就好了,我的主程序是不会等待的。举一个不恰当的例子就是我在去银行取号排队,中间排队这个过程就是evaluate
函数内部做的事情(变量上传给服务器、服务器计算结果、结果返回给回调函数,执行回调函数),但是排队过程我无需排队一直等待(即不用等前面括号内的这一系列事情做完),我可以去做我想做的事情(执行后续代码)例如买零食买玩具。
上述的evaluate方法因为时间关系并没有解释特别到位,但是这里列举一点示例稍微说明一下这个函数
javascript
// 情况1
var year_list = [2010, 2011, 2012, 2013, 2014];
year_list.map(function(cur_year){
print(cur_year);
});
// 情况2
var year_list = ee.List.sequence(2010, 2014)
year_list.map(function(cur_year){
print(cur_year);
});
// 情况3
var year_list = ee.List.sequence(2010, 2014)
year_list.evaluate(function(year_list){
year_list.map(function(cur_year){
print(cur_year);
});
})
对于上面三种情况,只有情况2无法运行会报错,为什么呢?这里就需要理解为什么要使用.evaluate()
方法了。由于year_list
中的各个元素值是需要print输出的。而通过ee.List.sequence
定义的year_list
使用的map
方法是需要在服务端上运行的(这里就要提及为什么情况1的map
就不需要,因为通过[]定义的是具体array数组,其map
方法不需要在服务器中运行,此外这两个map
方法也有一定的差别,如果对于情况2的报错内容仔细查看发现首先是报错map
方法没有返回值),而map
方法中的print
是在本地进行运行的(因为print
输出肯定是在浏览器中展示结果自然是需要在本地浏览器中运行),因此map
在服务器中运行时,遇到了print
,服务器不知道打印到哪里去,因此报错了。但是对于情况3,year_list.evaluate
将变量传递给服务器计算之后传回本地浏览器,执行回调函数,由于变量已经传回本地了因此print
输出自然没有问题。
至于异步这一问题,查看下方代码和输出自行理解即可,这里不再赘述:
javascript
// 情况4
print('1. 代码开始运行')
var year_list = ee.List.sequence(2010, 2014)
year_list.evaluate(function(year_list){
print('3. 获取得到服务器传递的变量值, 批量输出中···')
year_list.map(function(cur_year){
print(cur_year);
});
print('4. 回调函数执行完毕')
})
print('2. 主程序运行完毕')
输出结果:

2.2 年尺度上批量输出指定时间段的一年影像均值(一年一景)
完整代码:
javascript
// 选择需要输出GPP产品的时间范围(一年一景)
var start_date = ee.Date.fromYMD(2001, 1, 1); // 开始日期, MOD17A3的生产范围是2001-1-1至今
var end_date = ee.Date.fromYMD(2020, 12, 31); // 结束日期
var gpp_name = 'Gpp'; // 需要的波段名称, MOD17A3HGF包括Gpp、Npp以及Npp_QC三个波段
var apply_land_mask = true; // 是否掩膜只要陆地地区, 若为true则掩膜;若为false则不进行掩膜
var is_vis = true // 是否在下方地图中显示每一年的GPP产品(耗时)
var min_value = 0; // 有效值的范围
var max_value = 65500; // 同理
var out_res = 0.05; // 输出分辨率(单位为°)
// 获取年列表-用于批量输出
var start_year = start_date.get('year');
var end_year = end_date.get('year');
var year_list = ee.List.sequence(start_year, end_year)
// 加载MODIS-GPP产品(MOD17A3HGF)
var gpp_collection = ee.ImageCollection("MODIS/061/MOD17A3HGF"); // 从云计算平台加载指定数据集
// 获取陆地掩膜
var water_mask = ee.Image('MODIS/MOD44W/MOD44W_005_2000_02_24').select('water_mask');
var land_mask = water_mask.eq(0);
// 定义地理范围(默认全球)
var global_region = ee.Geometry.Rectangle([-180, -90, 180, 90], 'EPSG:4326', false)
// 可视化参数
var gpp_vis_param = {
min: 0.0,
max: 3.0,
palette: ['#bbe029', '#0a9501', '#074b03']
};
// 可视化
Map.setCenter(0, 0, 2); // 显示的中心位置和缩放大小, 参数依次为(经度: 0, 纬度: 0, 缩放级别:2)
// 导出影像到Google Drive(ps: 一年一景是理论, 实际上由于全球区域太大且分辨率过高,因此一年的tiff会分块输出成多个tiff文件)
year_list.evaluate(function(year_list){
year_list.map(function(cur_year){
var cur_start_date = ee.Date.fromYMD(cur_year, 1, 1);
var cur_end_date = ee.Date.fromYMD(cur_year, 12, 31);
// 筛选指定年份的影像
var gpp_img = gpp_collection.filterDate(cur_start_date, cur_end_date).select(gpp_name).first();
// 获取并应用有效值范围的掩膜
var valid_range_mask = gpp_img.gte(min_value).and(gpp_img.lte(max_value));
gpp_img = gpp_img.updateMask(valid_range_mask);
// 应用缩放因子(缩放后单位为:kg*C/m^2, 由于MOD17A3是年尺度,因此这也可以视为kg*C/m^2/year)
gpp_img = gpp_img.multiply(0.0001);
/*
为什么要应用缩放因子?
因为GEE上存储的数据量非常大, 整数存储相比于小数存储在大数据量面前将会有非常大的性价比减少
更多的存储空间, 因此存储时会先将小数放大n倍转化为小数,在需要时乘以缩放因子即可恢复原来的数值.
*/
// 掩膜(只需要陆地地区)
if (apply_land_mask === true){
gpp_img = gpp_img.updateMask(land_mask);
}
// 是否可视化
if (is_vis === true){
Map.addLayer(gpp_img, gpp_vis_param, 'Annual_GPP_' + cur_year);
}
Export.image.toDrive({
image: gpp_img.resample('bilinear'), // 若需要最近邻插值, 删除.resample('bilinear')即可
description: 'Global_GPP_' + cur_year,
folder: 'Global_GPP',
fileNamePrefix: 'Global_GPP_' + cur_year,
crs: 'EPSG:4326', // 坐标系为WGS84
crsTransform: [out_res, 0, -180, 0, -out_res, 90], // 仿射系数
region: global_region, // 输出的地理范围
maxPixels: 1e13, // 防止像元个数过多拒绝导出, 增加导出上限
formatOptions: {'noData': -9999} // NoData值为-9999
});
})
});
这里关于代码说明不在过多赘述,原理类似,只是多加了一层循环。
本文由博客一文多发平台 OpenWrite 发布!