函数式编程简介
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 的地理空间功能。