Conceitos de programação funcional

Introdução à programação funcional

O Earth Engine usa um sistema de processamento paralelo para realizar cálculos em um grande número de máquinas. Para permitir esse processamento, o Earth Engine aproveita técnicas padrão comumente usadas por linguagens funcionais, como transparência referencial e avaliação lenta, para otimização e ganhos de eficiência significativos.

O principal conceito que diferencia a programação funcional da programação procedural é a ausência de efeitos colaterais. Isso significa que as funções que você escreve não dependem nem atualizam dados que estão fora da função. Como você verá nos exemplos abaixo, é possível reestruturar o problema para que ele possa ser resolvido usando funções sem efeitos colaterais, que são muito mais adequadas para execução em paralelo.

Repetições for

O uso de loops for não é recomendado no Earth Engine. Os mesmos resultados podem ser alcançados usando uma operação map() em que você especifica uma função que pode ser aplicada de forma independente a cada elemento. Isso permite que o sistema distribua o processamento para máquinas diferentes.

O exemplo abaixo ilustra como pegar uma lista de números e criar outra com os quadrados de cada número usando map():

Editor de código (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]

Condições If/Else

Outro problema comum enfrentado por novos usuários acostumados com o paradigma de programação procedural é o uso adequado de operadores condicionais if/else no Earth Engine. Embora a API forneça um algoritmo ee.Algorithms.If(), o uso dele é altamente desaconselhável em favor de uma abordagem mais funcional usando map() e filtros. O Earth Engine usa a execução adiada, o que significa que a avaliação de uma expressão é adiada até que o valor realizado seja realmente necessário. Em alguns casos, esse tipo de modelo de execução vai avaliar as alternativas verdadeira e falsa de uma instrução ee.Algorithms.If(). Isso pode levar a um uso extra de computação e uso da memória, dependendo das expressões e dos recursos necessários para executá-las.

Digamos que você queira resolver uma variante do exemplo acima, em que a tarefa é calcular quadrados de apenas números ímpares. Uma abordagem funcional para resolver isso sem condições if/else é demonstrada abaixo:

Editor de código (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]

Esse paradigma é especialmente aplicável ao trabalhar com coleções. Se você quiser aplicar um algoritmo diferente à coleção com base em algumas condições, a maneira preferencial é primeiro filtrar a coleção com base na condição e, em seguida, map() uma função diferente para cada um dos subconjuntos. Isso permite que o sistema paralelize a operação. Exemplo:

Editor de código (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());

Iteração cumulativa

Talvez seja necessário realizar uma operação sequencial, em que o resultado de cada iteração é usado pela iteração seguinte. O Earth Engine oferece um método iterate() para essas tarefas. Lembre-se de que iterate() é executado de maneira sequencial e, portanto, será lento para operações grandes. Use-o somente quando não for possível usar map() e filtros para alcançar o resultado desejado.

Uma boa demonstração de iterate() é a criação da sequência de números de Fibonacci. Aqui, cada número na série é a soma dos dois números anteriores. A função iterate() usa dois argumentos: uma função (algoritmo) e um valor inicial. A função em si recebe 2 valores: o valor atual na iteração e o resultado da iteração anterior. O exemplo a seguir demonstra como implementar uma sequência de Fibonacci no Earth Engine.

Editor de código (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]

Agora que você já entende os conceitos de JavaScript, confira o tutorial da API para uma introdução à funcionalidade geoespacial da API Earth Engine.