遇到最大的困难就是GEE的客户端和服务端的数据处理问题,不过好在解决了。
我的问题是如何在GEE平台下载每天的哨兵5号(Sentinel-5P)的臭氧影像?
代码链接(挂):https://code.earthengine.google.com/86171ff00fd095da3f0da02e5872ecd6
01 代码说明
首先,自定义参数。
// 自定义参数 var start_time = '2022-07-01' var end_time = '2022-08-31' var province_name = '四川省'
接着,研究区选取。
//加载自己的研究区 var roi = ee.FeatureCollection("projects/ee-chaoqiezione/assets/china_admin_province"); roi = roi.filter(ee.Filter.eq('省', province_name)).first();
需要说明的,你需要将"projects/ee-chaoqiezione/assets/china_admin_province"替换为自己的云盘Assets中的矢量文件路径或者GEE平台上的矢量文件,并针对其修改province_name.
接着,生成start_time到end_time之间每一天日期的列表。
// 生成日期列表 var dates = ee.List.sequence( ee.Date(start_time).millis(), // 将开始日期转化为毫秒级的unix时间戳(1970年至start_time毫秒数) ee.Date(end_time).millis(), // 如此亦是 24*60*60*1000 // 一天的毫秒数 ).map(function(dateMillis){ return ee.Date(dateMillis).format('YYYY-MM-dd') // 将unix时间戳转化为年月日的字符串 })
这里是非常重要的内容,且代码不可随意更改,说明如下:
1. 对于字符串日期转化为Date对象是为了方便后续列表的生成,因为字符串不具备这些方法或者函数;
2. 将起始和结束日期转化为unix时间戳,我感觉这个和ENVI IDL的儒略日十分相似,大家可以参考;
3. 步长设置为一天的毫秒时间数;
4. 将然后将生成的起始日期到结束日期的每天的unix时间戳转化为正常的date对象,然后将date对象转化为字符串。
这里最大的疑惑就是为什么需要将处理好的date对象转化为字符串?
实际上与后面的客户端和服务端的一些东西相关,后面我们再说明,主要原因是循环不是在服务端并行运算的,而是在客户端进行的。
接着, 迭代时间==>处理并导出影像;
// 迭代时间 ==> 处理和导出影像 dates.evaluate(function(dates) { dates.map(function(date){ var start = ee.Date(date); var stop = start.advance(1, 'day'); var scol= ee.ImageCollection("COPERNICUS/S5P/NRTI/L3_O3") .filterBounds(roi) .filterDate(start,stop) .select("O3_column_number_density"); var before = scol.qualityMosaic("O3_column_number_density").clip(roi); before = before.set({name:ee.String(start.format('YYYY-MM-dd'))}); Export.image.toDrive({ image: before.select("O3_column_number_density"), region: roi.geometry(), scale:1000, description: "O3_column_number_density_1000m_" + province_name + "_" + date, folder: 'O3_column_number_density', }); }) })
我们使用了evaluate函数,evaluate() 方法允许你在客户端执行代码,这有别于大部分 Earth Engine 方法,它们是在服务器端执行。关键是理解 Earth Engine 的计算模型是延迟的,并行的,并且在服务器端执行的。
如果尝试使用map解决以上内容,即不使用evaluate函数,那么导出存在问题,因为GEE使用map函数是在服务端并行运算的,,但是GEE限制一次只能发送最多三次导出任务,这就是为什么在map中尝试导出多个任务时,实际上只有三幅影像被导出。
evaluate实际上就是异步操作,当你要使用某函数去执行dates中的某些变量时,然而这些变量还在服务端进行并行运算还没有处理好,如果我们使用了evaluate,那么一旦变量被处理好,那么该函数就会去执行这些变量,异步就在于当变量还没有处理好时,程序不会一直等待,而是继续执行其它代码,然后一边等待变量处理好。
至于其它细节这里由于时间不再一一阐述,记住一点即可。
当你想要在map函数里面使用print、toDrive等方法,那么外嵌一个evaluate是一个不错的选择,尤其是当你需要在服务器端完成复杂的计算并获取结果时。
由于单纯地使用map速度很快,这是因为它是并行执行任务,所以当你使用evaluate方法时程序运行速度会稍稍减慢。
完整代码如下:
(又稍微做了修改,自行查看区别)
// 自定义参数 var start_time = '2022-07-01' var end_time = '2022-08-31' var province_name = '四川省' //加载自己的研究区 var roi = ee.FeatureCollection("projects/ee-chaoqiezione/assets/china_admin_province"); roi = roi.filter(ee.Filter.eq('省', province_name)); print('行政区划', roi) // 生成日期列表 var dates = ee.List.sequence( ee.Date(start_time).millis(), // 将开始日期转化为毫秒级的unix时间戳(1970年至start_time毫秒数) ee.Date(end_time).millis(), // 如此亦是 24*60*60*1000 // 一天的毫秒数 ).map(function(dateMillis){ return ee.Date(dateMillis).format('YYYY-MM-dd') // 将unix时间戳转化为年月日的字符串 }) Map.addLayer(roi.style({fillColor: "00000000"}), {width: 2}, province_name) Map.centerObject(roi, 6) // 迭代时间 ==> 处理和导出影像 dates.evaluate(function(dates) { dates.map(function(date){ var start = ee.Date(date); var stop = start.advance(1, 'day'); var scol= ee.ImageCollection("COPERNICUS/S5P/NRTI/L3_O3") .filterBounds(roi) .filterDate(start,stop) .select("O3_column_number_density"); var before = scol.qualityMosaic("O3_column_number_density").clip(roi).unmask(-9999); before = before.set({name:ee.String(start.format('YYYY-MM-dd'))}); print(before) Export.image.toDrive({ image: before.select("O3_column_number_density"), region: roi.geometry(), scale:1000, description: "O3_column_number_density_1000m_" + province_name + "_" + date, folder: 'O3_column_number_density', }); }) })