函数式编程概念

函数式编程简介

Earth Engine 使用并行处理系统在大量机器上执行计算。为了实现此类处理,Earth Engine 利用了函数式语言常用的标准技术,例如引用透明性和延迟评估,从而显著提高优化程度和效率。

使函数式编程与过程式编程区别开来的主要概念是没有副作用。这意味着,您编写的函数不依赖于函数外部的数据,也不会更新函数外部的数据。正如您将在下面的示例中看到的,您可以重新构建问题,以便使用无副作用的函数来解决问题,这些函数更适合并行执行。

For 循环

不建议在 Earth Engine 中使用 for 循环。您可以使用 map() 操作实现相同的结果,在该操作中,您可以指定一个可独立应用于每个元素的函数。这样,系统就可以将处理任务分配给不同的机器。

以下示例展示了如何使用 map() 获取数字列表,并创建包含每个数字的平方的另一个列表:

代码编辑器 (JavaScript)

// This generates a list of numbers from 1 to 10.
var myList = ee.List.sequence(1, 10);

// The map() operation takes a function that works on each element independently
// and returns a value. You define a function that can be applied to the input.
var computeSquares = function(number) {
  // We define the operation using the EE API.
  return ee.Number(number).pow(2);
};

// Apply your function to each item in the list by using the map() function.
var squares = myList.map(computeSquares);
print(squares);  // [1, 4, 9, 16, 25, 36, 49, 64, 81]

if/else 条件

习惯于过程式编程范式的新用户面临的另一个常见问题是如何在 Earth Engine 中正确使用 if/else 条件运算符。虽然该 API 确实提供了 ee.Algorithms.If() 算法,但我们强烈建议不要使用该算法,而应使用 map() 和过滤条件来采用更实用的方法。Earth Engine 使用 延迟执行,这意味着表达式的评估会延迟到实际需要其实现值时才进行。在某些情况下,这种执行模型会同时评估 ee.Algorithms.If() 语句的 true 和 false 分支。这可能会导致额外的计算和内存使用,具体取决于表达式以及执行这些表达式所需的资源。

假设您要解决上述示例的变体,其中任务是仅计算奇数的平方。下面展示了一种不使用 if/else 条件来解决此问题的函数式方法:

代码编辑器 (JavaScript)

// The following function determines if a number is even or odd.  The mod(2)
// function returns 0 if the number is even and 1 if it is odd (the remainder
// after dividing by 2).  The input is multiplied by this remainder so even
// numbers get set to 0 and odd numbers are left unchanged.
var getOddNumbers = function(number) {
  number = ee.Number(number);   // Cast the input to a Number so we can use mod.
  var remainder = number.mod(2);
  return number.multiply(remainder);
};

var newList = myList.map(getOddNumbers);

// Remove the 0 values.
var oddNumbers = newList.removeAll([0]);

var squares = oddNumbers.map(computeSquares);
print(squares);  // [1, 9, 25, 49, 81]

这种范式尤其适用于处理集合。如果您想根据某些条件对集合应用不同的算法,首选方法是先根据条件过滤集合,然后对每个子集应用不同的函数。map()这样,系统就可以并行执行操作。例如:

代码编辑器 (JavaScript)

// Import Landsat 8 TOA collection and filter to 2018 images.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate('2018-01-01', '2019-01-01');

// Divide the collection into 2 subsets and apply a different algorithm on them.
var subset1 = collection.filter(ee.Filter.lt('SUN_ELEVATION', 40));
var subset2 = collection.filter(ee.Filter.gte('SUN_ELEVATION', 40));

// Multiply all images in subset1 collection by 2;
// do nothing to subset2 collection.
var processed1 = subset1.map(function(image) {
  return image.multiply(2);
});
var processed2 = subset2;

// Merge the collections to get a single collection.
var final = processed1.merge(processed2);
print('Original collection size', collection.size());
print('Processed collection size', final.size());

累计迭代

您可能需要执行顺序操作,其中每次迭代的结果都会被后续迭代使用。Earth Engine 提供了 iterate() 方法来执行此类任务。请注意,iterate() 是按顺序执行的,因此对于大型操作来说会很慢。仅当您无法使用 map() 和过滤条件来实现所需输出时,才使用此函数。

iterate() 的一个很好的演示是创建 Fibonacci 数序列。在此示例中,序列中的每个数字都是前两个数字之和。iterate() 函数接受 2 个实参,即一个函数(算法)和一个起始值。该函数本身会传递 2 个值:迭代中的当前值和上一次迭代的结果。以下示例演示了如何在 Earth Engine 中实现斐波那契数列。

代码编辑器 (JavaScript)

var algorithm = function(current, previous) {
  previous = ee.List(previous);
  var n1 = ee.Number(previous.get(-1));
  var n2 = ee.Number(previous.get(-2));
  return previous.add(n1.add(n2));
};

// Compute 10 iterations.
var numIteration = ee.List.repeat(1, 10);
var start = [0, 1];
var sequence = numIteration.iterate(algorithm, start);
print(sequence);  // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

现在,您已经对 JavaScript 概念有了很好的了解,可以参阅 API 教程,了解 Earth Engine API 的地理空间功能。