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 ativar esse processamento, o Earth Engine usa técnicas padrão comumente usadas por linguagens funcionais, como transparência referencial e avaliação lenta, para otimizar e aumentar a eficiência de maneira significativa.

O principal conceito que diferencia a programação funcional da 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 serem executadas 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 ao 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 desencorajado 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 avalia as alternativas verdadeira e falsa de uma instrução ee.Algorithms.If(). Isso pode levar a um uso extra de computação e memória, dependendo das expressões e dos recursos necessários para executá-las.

Suponha que você queira resolver uma variante do exemplo acima, em que a tarefa é calcular os 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 recomendada é 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 paralelise a operação. Por 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 fazer uma operação sequencial, em que o resultado de cada iteração é usado pela iteração subsequente. 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 apenas 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 de uma 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 recebe dois 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ê tem um bom entendimento dos conceitos de JavaScript, confira o tutorial da API para uma introdução à funcionalidade geoespacial da API Earth Engine.