Google Earth Engine 中带有 map() 函数的双循环

Double loop with map() function in Google Earth Engine

Google Earth Engine Developer's Guide中,有一个避免for()循环的建议。他们建议使用 map() 函数作为这个例子:

// to avoid
var clientList = [];
for(var i = 0; i < 8; i++) {
  clientList.push(i + 1);
}
print(clientList);

// to use
var serverList = ee.List.sequence(0, 7);
serverList = serverList.map(function(n) {
  return ee.Number(n).add(1);
});
print(serverList);

我正在尝试 select 每个 month/year 的 MODIS 场景,然后再计算 VCI。因此,我采用的方法是使用双循环:

modis = ee.ImageCollection("MODIS/MYD13A1");

var modis_list = [];
for(var i = 1; i <13; i++) {
  for(var j = 2000; j <2018; j++){
    modis_list.push(modis.filter(ee.Filter.calendarRange(i, i, 'month'))
                          .filter(ee.Filter.calendarRange(j, j, 'year')));
  }
}
print(modis_list);

有没有办法用 map() 函数复制这样的双循环以达到服务器端方法?

首先声明我对 Google Earth Engine 一无所知,我的信息来自函数式编程知识。


map 的独特之处在于它不会生成循环遍历的内容。您从一个列表开始,然后 map 遍历该列表中的每个项目并对其进行转换。如果您没有该列表,那么 map 就不太合适。

您似乎正在创建一个列表,其中包含每个 month/year 组合。我会把它分成几个步骤。构建月份和年份列表,构建表示 2 个列表的笛卡尔积的列表,然后转换为 ee 对象。

var range = (from, to) => new Array(end-start+1).fill(0).map((_,i)=>i+from)
var cartesianProduct = (a, b) => // not gonna do this here but it returns pairs [ [ a[1], b[1] ], ... ]
var getEE = ([month, year]) => modis
    .filter(ee.Filter.calendarRange(month, month, 'month'))
    .filter(ee.Filter.calendarRange(year, year, 'year'));

var months = range(1,12);
var years = range(2000, 2017);
var data = cartesianProduct(months, years)
    .map(getEE)

可能有更好的方法(比如每次我们想要一个月份对象(使用字典)时不遍历 modis 的整个列表)但要点是相同的。

假设你只是想了解 GEE 的 map() 功能,以及如何相当于一个普通的 js for loop,代码将是:

var map_m = function(i) {
  i = ee.Number(i)
  var years = ee.List.sequence(2000, 2017)
  var filtered_col = years.map(function(j) {
    var filtered = modis.filter(ee.Filter.calendarRange(i, i, 'month'))
                        .filter(ee.Filter.calendarRange(j, j, 'year'))
    return filtered
  })
  return filtered_col
}

var months = ee.List.sequence(1, 12)
var modis_list2 = months.map(map_m).flatten()

此代码复制了一个正常的 for loop。首先,它逐项进入年列表,然后逐项进入月列表,然后,一旦你有了年份和月,过滤集合并将其添加到列表中(map 自动执行)。当您使用 2 个 map 函数(一个超过几年,另一个超过几个月)时,您会得到一个列表列表,因此要获得 ImageCollection 的列表,请使用 flatten() 函数。不知何故打印的对象有点不同,但我相信结果是一样的。

最简单的方法是在您关心的 "months" 上使用一张地图。

// Collect images for each month, starting from 2000-01-01.
var months = ee.List.sequence(0, 18*12).map(function(n) {
  var start = ee.Date('2000-01-01').advance(n, 'month')
  var end = start.advance(1, 'month')
  return ee.ImageCollection("MODIS/MYD13A1").filterDate(start, end)
})
print(months.get(95))

这将 return ImageCollections 列表。大多数月份只有 1 张图片,因为 MYD13A1 包含 16 天的图片,但有些月份会有两张。第 95 个月是 2008 年 1 月,有两个。

或者,您可以加入包含日期集合的集合,但这更简单。

您应该尽可能选择 filterDate 而不是 calendarRange,因为它已经过优化。