Concetti di programmazione funzionale

Introduzione alla programmazione funzionale

Earth Engine utilizza un sistema di elaborazione parallela per eseguire i calcoli su un numero elevato di macchine. Per consentire questo tipo di elaborazione, Earth Engine sfrutta tecniche standard comunemente utilizzate dai linguaggi funzionali, come la trasparenza referenziale e la valutazione pigra, per ottenere significativi miglioramenti in termini di ottimizzazione ed efficienza.

Il concetto principale che distingue la programmazione funzionale dalla programmazione procedurale è l'assenza di effetti collaterali. Ciò significa che le funzioni che scrivi non si basano su dati esterni alla funzione né li aggiornano. Come vedrai negli esempi riportati di seguito, è possibile ristrutturare il problema in modo che possa essere risolto utilizzando funzioni senza effetti collaterali, che sono molto più adatte per essere eseguite in parallelo.

Loop for

L'utilizzo di cicli for è sconsigliato in Earth Engine. Gli stessi risultati possono essere ottenuti utilizzando un'operazione map() in cui specifichi una funzione che può essere applicata indipendentemente a ogni elemento. In questo modo il sistema può distribuire l'elaborazione a macchine diverse.

L'esempio riportato di seguito mostra come prendere un elenco di numeri e creare un altro elenco con i quadrati di ogni numero utilizzando map():

Editor di codice (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]

Condizioni If/Else

Un altro problema comune riscontrato dai nuovi utenti abituati al paradigma di programmazione procedurale è l'uso corretto degli operatori condizionali if/else in Earth Engine. Sebbene l'API fornisca un algoritmo ee.Algorithms.If(), il suo utilizzo è fortemente sconsigliato a favore di un approccio più funzionale che utilizza map() e filtri. Earth Engine utilizza l' esecuzione differita, il che significa che la valutazione di un'espressione viene ritardata finché non è effettivamente necessario il suo valore realizzato. In alcuni casi, questo tipo di modello di esecuzione valuta sia le alternative true che false di un'istruzione ee.Algorithms.If(). Ciò può comportare un utilizzo aggiuntivo di calcoli e memoria, a seconda delle espressioni e delle risorse necessarie per eseguirle.

Supponiamo di voler risolvere una variante dell'esempio precedente, in cui l'attività consiste nel calcolare i quadrati di solo numeri dispari. Di seguito viene illustrato un approccio funzionale per risolvere il problema senza condizioni if/else:

Editor di codice (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]

Questo paradigma è particolarmente applicabile quando si lavora con le raccolte. Se vuoi applicare un algoritmo diverso alla raccolta in base ad alcune condizioni, il modo migliore è prima filtrare la raccolta in base alla condizione e poi map() una funzione diversa a ciascuno dei sottoinsiemi. Ciò consente al sistema di parallelizzare l'operazione. Ad esempio:

Editor di codice (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());

Iterazione cumulativa

Potresti dover eseguire un'operazione sequenziale, in cui il risultato di ogni iterazione viene utilizzato dall'iterazione successiva. Earth Engine fornisce un metodo iterate() per queste attività. Ricorda che iterate() viene eseguito in modo sequenziale e pertanto sarà lento per le operazioni di grandi dimensioni. Utilizzalo solo quando non riesci a utilizzare map() e i filtri per ottenere l'output desiderato.

Un buon esempio di iterate() è la creazione della sequenza di numeri di Fibonacci. In questo caso, ogni numero della serie è la somma dei due numeri precedenti. La funzione iterate() richiede due argomenti: una funzione (algoritmo) e un valore iniziale. La funzione stessa riceve due valori: il valore corrente nell'iterazione e il risultato dell'iterazione precedente. Il seguente esempio mostra come implementare una sequenza di Fibonacci in Earth Engine.

Editor di codice (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]

Ora che hai una buona conoscenza dei concetti di JavaScript, puoi consultare il tutorial sull'API per un'introduzione alla funzionalità geospaziale dell'API Earth Engine.