Questo documento descrive le pratiche di codifica che hanno lo scopo di massimizzare le probabilità di esito positivo per i calcoli di Earth Engine complessi o costosi. I metodi
descritti qui sono applicabili sia ai calcoli interattivi (ad es. Editor di codice) sia ai calcoli
in batch (Export
), anche se in genere i calcoli di lunga esecuzione devono essere
eseguiti nel sistema batch.
Evita di mescolare funzioni e oggetti client con funzioni e oggetti server
Gli oggetti del server Earth Engine sono oggetti con costruttori che iniziano con ee
(ad es. ee.Image
, ee.Reducer
) e tutti i metodi su questi oggetti sono funzioni del server. Qualsiasi oggetto non costruito in questo modo è un oggetto client. Gli oggetti client possono provenire dall'editor di codice (ad es. Map
, Chart
) o dal linguaggio JavaScript (ad es. Date
, Math
, []
, {}
).
Per evitare comportamenti indesiderati, non mescolare le funzioni client e server nello script, come spiegato qui, qui e qui. Consulta questa pagina e/o questo tutorial per una spiegazione approfondita della differenza tra client e server in Earth Engine. L'esempio seguente illustra i pericoli della combinazione di funzionalità client e server:
Errore: questo codice non funziona.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Won't work.
for(var i=0; i<table.size(); i++) {
print('No!');
}
Riesci a individuare l'errore? Tieni presente che table.size()
è un metodo del server in un oggetto del server e non può essere utilizzato con funzionalità lato client come il condizionale <
.
Una situazione in cui potresti voler utilizzare i cicli for è la configurazione dell'interfaccia utente, poiché gli oggetti e i metodi ui
di Code Editor sono lato client. Scopri di più sulla creazione di interfacce utente in Earth Engine. Ad esempio:
Utilizza le funzioni client per la configurazione dell'interfaccia utente.
var panel = ui.Panel();
for(var i=1; i<8; i++) {
panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);
Al contrario, map()
è una funzione di server e la funzionalità client non funzionerà all'interno della funzione passata a map()
. Ad esempio:
Errore: questo codice non funziona.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Error:
var foobar = table.map(function(f) {
print(f); // Can't use a client function here.
// Can't Export, either.
});
Per fare qualcosa a ogni elemento di una raccolta, map()
una funzione sulla raccolta e set()
una proprietà:
Utilizza map()
e set()
.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
print(table.first());
// Do something to every element of a collection.
var withMoreProperties = table.map(function(f) {
// Set a property.
return f.set('area_sq_meters', f.area())
});
print(withMoreProperties.first());
Puoi anche filter()
la raccolta in base a proprietà calcolate o esistenti
e print()
il risultato. Tieni presente che non puoi stampare una raccolta con più di 5000 elementi. Se ricevi l'errore "Query sulla raccolta interrotta dopo aver accumulato più di 5000 elementi", filter()
o limit()
la raccolta prima di stampare.
Evita di convertire in elenco inutilmente
Le raccolte in Earth Engine vengono elaborate utilizzando ottimizzazioni che vengono interrotte con la conversione della raccolta in un tipo List
o Array
. A meno che tu non abbia bisogno di accedere in modo random ai singoli elementi della raccolta (ad es. devi recuperare l'elemento i di una raccolta), utilizza i filtri sulla raccolta per accedere ai singoli elementi. L'esempio seguente illustra la differenza tra la conversione di tipo (non consigliata) e l'applicazione di filtri (consigliata) per accedere a un elemento in una raccolta:
Non convertire in elenco inutilmente.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!!
var list = table.toList(table.size());
print(list.get(13)); // User memory limit exceeded.
Tieni presente che puoi attivare facilmente errori convertendo inutilmente una raccolta in un elenco. Il modo più sicuro è utilizzare filter()
:
Usa filter()
.
print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());
Tieni presente che devi utilizzare i filtri il prima possibile nell'analisi.
Evita ee.Algorithms.If()
Non utilizzare ee.Algorithms.If()
per implementare la logica di diramazione, in particolare in una funzione mappata. Come illustrato nell'esempio seguente, ee.Algorithms.If()
può richiedere molto utilizzo di memoria e non è consigliato:
Non utilizzare If()
.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!
var veryBad = table.map(function(f) {
return ee.Algorithms.If({
condition: ee.String(f.get('country_na')).compareTo('Chad').gt(0),
trueCase: f, // Do something.
falseCase: null // Do something else.
});
}, true);
print(veryBad); // User memory limit exceeded.
// If() may evaluate both the true and false cases.
Tieni presente che il secondo argomento di map()
è true
. Ciò significa che la funzione mappata potrebbe restituire valori null che verranno eliminati nella raccolta risultante.
Può essere utile (senza If()
), ma qui la soluzione più semplice è utilizzare un
filtro:
Usa filter()
.
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
Come mostrato in questo tutorial, un approccio alla programmazione funzionale che utilizza i filtri è il modo corretto per applicare una logica ad alcuni elementi di una raccolta e un'altra logica agli altri elementi della raccolta.
Evita reproject()
Non utilizzare la reimpostazione se non è assolutamente necessario. Uno dei motivi per cui potresti volere usare reproject()
è forzare i calcoli di Editor di codice su una scala specifica in modo da poter esaminare i risultati con la scala di analisi che preferisci. Nel
prossimo esempio, vengono calcolate le macchie di pixel caldi e il conteggio dei pixel in ogni
macchia. Esegui l'esempio e fai clic su una delle patch. Tieni presente che il conteggio dei pixel è diverso tra i dati sottoposti a riproiezione e quelli che non lo sono stati.
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var sf = ee.Geometry.Point([-122.405, 37.786]);
Map.centerObject(sf, 13);
// A reason to reproject - counting pixels and exploring interactively.
var image = l8sr.filterBounds(sf)
.filterDate('2019-06-01', '2019-12-31')
.first();
image = image.multiply(0.00341802).add(149); // Apply scale factors.
Map.addLayer(image, {bands: ['ST_B10'], min: 280, max: 317}, 'image');
var hotspots = image.select('ST_B10').gt(317)
.selfMask()
.rename('hotspots');
var objectSize = hotspots.connectedPixelCount(256);
Map.addLayer(objectSize, {min: 1, max: 256}, 'Size No Reproject', false);
// Beware of reproject! Don't zoom out on reprojected data.
var reprojected = objectSize.reproject(hotspots.projection());
Map.addLayer(reprojected, {min: 1, max: 256}, 'Size Reproject', false);
La discrepanza è dovuta al fatto che la scala di analisi è impostata dal livello di zoom di Editor di codice. Chiamando reproject()
imposti la scala del calcolo anziché l'editor di codice. Utilizza reproject()
con estrema cautela per i motivi descritti in questo
documento.
Filtra e select()
prima
In genere, filtra le raccolte di input in base a ora, posizione e/o metadati prima di fare qualcos'altro con la raccolta. Applica i filtri più selettivi prima di quelli meno selettivi. I filtri spaziali e/o temporali sono spesso più selettivi. Ad esempio, tieni presente che select()
e filter()
vengono applicati prima
map()
:
var images = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var sf = ee.Geometry.Point([-122.463, 37.768]);
// Expensive function to reduce the neighborhood of an image.
var reduceFunction = function(image) {
return image.reduceNeighborhood({
reducer: ee.Reducer.mean(),
kernel: ee.Kernel.square(4)
});
};
var bands = ['B4', 'B3', 'B2'];
// Select and filter first!
var reasonableComputation = images
.select(bands)
.filterBounds(sf)
.filterDate('2018-01-01', '2019-02-01')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 1))
.aside(print) // Useful for debugging.
.map(reduceFunction)
.reduce('mean')
.rename(bands);
var viz = {bands: bands, min: 0, max: 10000};
Map.addLayer(reasonableComputation, viz, 'reasonableComputation');
Utilizza updateMask()
anziché mask()
La differenza tra updateMask()
e mask()
è che la prima esegue un
and()
logico dell'argomento (la nuova maschera) e della maschera dell'immagine esistente
mentre mask()
sostituisce semplicemente la maschera dell'immagine con l'argomento. Il pericolo di quest'ultimo è che puoi smascherare i pixel involontariamente. In questo esempio, lo scopo è mascherare i pixel con un'altitudine inferiore o uguale a 300 metri. Come puoi vedere (riduci lo zoom), l'utilizzo di mask()
comporta lo sblocco di molti pixel, che non appartengono all'immagine di interesse:
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var sf = ee.Geometry.Point([-122.40554461769182, 37.786807309873716]);
var aw3d30 = ee.Image('JAXA/ALOS/AW3D30_V1_1');
Map.centerObject(sf, 7);
var image = l8sr.filterBounds(sf)
.filterDate('2019-06-01', '2019-12-31')
.first();
image = image.multiply(0.0000275).subtract(0.2); // Apply scale factors.
var vis = {bands: ['SR_B4', 'SR_B3', 'SR_B2'], min: 0, max: 0.3};
Map.addLayer(image, vis, 'image', false);
var mask = aw3d30.select('AVE').gt(300);
Map.addLayer(mask, {}, 'mask', false);
// NO! Don't do this!
var badMask = image.mask(mask);
Map.addLayer(badMask, vis, 'badMask');
var goodMask = image.updateMask(mask);
Map.addLayer(goodMask, vis, 'goodMask', false);
Combinare i riduttori
Se hai bisogno di più statistiche (ad es. media e deviazione standard) da un singolo input (ad es. una regione di immagine), è più efficiente combinare i riduttori. Ad esempio, per ottenere le statistiche sulle immagini, combina i riduttori come segue:
var image = ee.Image(
'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU');
// Get mean and SD in every band by combining reducers.
var stats = image.reduceRegion({
reducer: ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(),
sharedInputs: true
}),
geometry: ee.Geometry.Rectangle([-2.15, 48.55, -1.83, 48.72]),
scale: 10,
bestEffort: true // Use maxPixels if you care about scale.
});
print(stats);
// Extract means and SDs to images.
var meansImage = stats.toImage().select('.*_mean');
var sdsImage = stats.toImage().select('.*_stdDev');
In questo esempio, tieni presente che il riduttore della media è combinato con il riduttore della deviazione standard e sharedInputs
è true per attivare un singolo passaggio tra i pixel di input. Nel dizionario di output, il nome del riduttore viene aggiunto al nome del gruppo. Per ottenere le immagini della media e della deviazione standard (ad esempio per normalizzare l'immagine di input), puoi trasformare i valori in un'immagine e utilizzare le espressioni regolari per estrarre singolarmente la media e la deviazione standard, come mostrato nell'esempio.
Utilizza Export
Per i calcoli che generano errori "Limite di memoria utente superato" o "Il calcolo ha superato il tempo di attesa" nell'editor di codice, le stesse operazioni potrebbero essere eseguite correttamente utilizzando Export
. Questo perché i timeout sono più lunghi e l'impronta in memoria consentita è maggiore quando viene eseguito nel sistema batch (dove vengono eseguite le esportazioni). (Esistono altri approcci che potresti provare prima, come descritto nel documento di debug). Continuando con l'esempio precedente, supponiamo che il dizionario abbia restituito un errore. Puoi ottenere i risultati facendo qualcosa di simile:
var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
collection: ee.FeatureCollection([ee.Feature(null, stats)]),
description: 'exported_stats_demo_' + link,
fileFormat: 'CSV'
});
Tieni presente che il link è incorporato nel nome della risorsa per la riproducibilità. Inoltre, tieni presente che se vuoi esportare toAsset
, dovrai fornire una geometria, che può essere qualsiasi, ad esempio il baricentro dell'immagine, che è di piccole dimensioni e facile da calcolare. (ad es. non utilizzare una geometria complessa se non è necessaria).
Consulta la pagina di debug per esempi di utilizzo di Export
per risolvere i problemi Completamento calcolo e Troppe aggregazioni contemporaneamente. Per informazioni dettagliate sull'esportazione in generale, consulta questo documento.
Se non hai bisogno di ritagliare, non utilizzare clip()
L'utilizzo non necessario di clip()
aumenterà i tempi di calcolo. Evita clip()
se non è necessario per la tua analisi. Se hai dubbi, non creare il clip. Un
esempio di cattivo utilizzo di un clip:
Non tagliare gli input inutilmente.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var belgium = table.filter(ee.Filter.eq('country_na', 'Belgium')).first();
// Do NOT clip unless you need to.
var unnecessaryClip = l8sr
.select('SR_B4') // Good.
.filterBounds(belgium.geometry()) // Good.
.filterDate('2019-01-01', '2019-04-01') // Good.
.map(function(image) {
return image.clip(belgium.geometry()); // NO! Bad! Not necessary.
})
.median()
.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: belgium.geometry(),
scale: 30,
maxPixels: 1e10,
});
print(unnecessaryClip);
Il ritaglio delle immagini di input può essere saltato del tutto, perché la regione è specificata nella chiamata reduceRegion()
:
Specifica la regione per l'output.
var noClipNeeded = l8sr
.select('SR_B4') // Good.
.filterBounds(belgium.geometry()) // Good.
.filterDate('2019-01-01', '2019-12-31') // Good.
.median()
.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: belgium.geometry(), // Geometry is specified here.
scale: 30,
maxPixels: 1e10,
});
print(noClipNeeded);
Se il calcolo supera il tempo di attesa, Export
come in questo
esempio.
Se devi creare un clip con una raccolta complessa, utilizza clipToCollection()
Se hai davvero bisogno di ritagliare qualcosa e le geometrie che vuoi utilizzare per il ritaglio si trovano in una raccolta, utilizza clipToCollection()
:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var image = ee.Image('JAXA/ALOS/AW3D30_V1_1');
var complexCollection = ecoregions
.filter(ee.Filter.eq('BIOME_NAME',
'Tropical & Subtropical Moist Broadleaf Forests'));
Map.addLayer(complexCollection, {}, 'complexCollection');
var clippedTheRightWay = image.select('AVE')
.clipToCollection(complexCollection);
Map.addLayer(clippedTheRightWay, {}, 'clippedTheRightWay', false);
NON utilizzare featureCollection.geometry()
o featureCollection.union()
su raccolte grandi e/o complesse, che possono richiedere una maggiore quantità di memoria.
Non utilizzare una raccolta complessa come regione per un riduttore
Se devi eseguire una riduzione spaziale in modo che il riduttore raggruppi gli input di più regioni in un FeatureCollection
, non fornire featureCollection.geometry()
come input geometry
al riduttore. Utilizza invece clipToCollection()
e una regione abbastanza grande da includere i limiti della raccolta. Ad esempio:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var image = ee.Image('JAXA/ALOS/AW3D30_V1_1');
var complexCollection = ecoregions
.filter(ee.Filter.eq('BIOME_NAME', 'Tropical & Subtropical Moist Broadleaf Forests'));
var clippedTheRightWay = image.select('AVE')
.clipToCollection(complexCollection);
Map.addLayer(clippedTheRightWay, {}, 'clippedTheRightWay');
var reduction = clippedTheRightWay.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: ee.Geometry.Rectangle({
coords: [-179.9, -50, 179.9, 50], // Almost global.
geodesic: false
}),
scale: 30,
maxPixels: 1e12
});
print(reduction); // If this times out, export it.
Utilizza un valore errorMargin
diverso da zero
Per operazioni di geometria potenzialmente costose, utilizza il margine di errore più grande possibile in base alla precisione richiesta del calcolo. Il margine di errore specifica l'errore massimo consentito (in metri) durante le operazioni sulle geometrie (ad es. durante la riproiezione). La specifica di un piccolo margine di errore può comportare la necessità di aumentare la densità delle geometrie (con coordinate), il che può richiedere molto spazio in memoria. È buona norma specificare un margine di errore il più ampio possibile per il calcolo:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var complexCollection = ecoregions.limit(10);
Map.centerObject(complexCollection);
Map.addLayer(complexCollection);
var expensiveOps = complexCollection.map(function(f) {
return f.buffer(10000, 200).bounds(200);
});
Map.addLayer(expensiveOps, {}, 'expensiveOps');
Non utilizzare una scala ridicolmente piccola con reduceToVectors()
Se vuoi convertire un raster in un vettore, utilizza una scala appropriata. La specifica di una scala molto piccola può aumentare notevolmente il costo di calcolo. Imposta la scala più alta possibile per ottenere la precisione richiesta. Ad esempio, per ottenere i poligoni che rappresentano le masse continentali globali:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
Map.addLayer(almostGlobal, {}, 'almostGlobal');
var vectors = bounds.selfMask().reduceToVectors({
reducer: ee.Reducer.countEvery(),
geometry: almostGlobal,
// Set the scale to the maximum possible given
// the required precision of the computation.
scale: 50000,
});
Map.addLayer(vectors, {}, 'vectors');
Nell'esempio precedente, tieni presente l'utilizzo di un poligono non geodetico per le riduzioni globali.
Non utilizzare reduceToVectors()
con reduceRegions()
Non utilizzare un FeatureCollection
restituito da reduceToVectors()
come input per
reduceRegions()
. Aggiungi invece le bande che vuoi ridurre prima di chiamarereduceToVectors()
:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
var mod11a1 = ee.ImageCollection('MODIS/006/MOD11A1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
var lst = mod11a1.first().select(0);
var means = bounds.selfMask().addBands(lst).reduceToVectors({
reducer: ee.Reducer.mean(),
geometry: almostGlobal,
scale: 1000,
maxPixels: 1e10
});
print(means.limit(10));
Tieni presente che esistono altri modi per ridurre i pixel di un'immagine all'interno delle zone di un'altra, tra cui reduceConnectedCommponents() e/o i riduttore di raggruppamento.
Utilizza fastDistanceTransform()
per le operazioni sui quartieri
Per alcune operazioni di convoluzione, fastDistanceTransform()
potrebbe essere più efficiente
di reduceNeighborhood()
o convolve()
. Ad esempio, per eseguire l'erosione e/o la dilatazione degli input binari:
var aw3d30 = ee.Image('JAXA/ALOS/AW3D30_V1_1');
// Make a simple binary layer from a threshold on elevation.
var mask = aw3d30.select('AVE').gt(300);
Map.setCenter(-122.0703, 37.3872, 11);
Map.addLayer(mask, {}, 'mask');
// Distance in pixel units.
var distance = mask.fastDistanceTransform().sqrt();
// Threshold on distance (three pixels) for a dilation.
var dilation = distance.lt(3);
Map.addLayer(dilation, {}, 'dilation');
// Do the reverse for an erosion.
var notDistance = mask.not().fastDistanceTransform().sqrt();
var erosion = notDistance.gt(3);
Map.addLayer(erosion, {}, 'erosion');
Utilizza le ottimizzazioni in reduceNeighborhood()
Se devi eseguire una convezione e non puoi utilizzare fastDistanceTransform()
,
utilizza le ottimizzazioni in reduceNeighborhood()
.
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var bands = ['B4', 'B3', 'B2'];
var optimizedConvolution = composite.select(bands).reduceNeighborhood({
reducer: ee.Reducer.mean(),
kernel: ee.Kernel.square(3),
optimization: 'boxcar' // Suitable optimization for mean.
}).rename(bands);
var viz = {bands: bands, min: 0, max: 72};
Map.setCenter(-122.0703, 37.3872, 11);
Map.addLayer(composite, viz, 'composite');
Map.addLayer(optimizedConvolution, viz, 'optimizedConvolution');
Non campionare più dati di quelli necessari
Resisti alla tentazione di aumentare inutilmente le dimensioni del set di dati di addestramento. Sebbene l'aumento della quantità di dati di addestramento sia una strategia di machine learning efficace in alcune circostanze, può anche aumentare il costo computazionale senza un corrispondente aumento dell'accuratezza. Per sapere quando aumentare le dimensioni del set di dati di addestramento, consulta questa fonte di riferimento. L'esempio seguente mostra come la richiesta di troppi dati di addestramento possa comportare l'errore temuto "Il valore calcolato è troppo grande":
Non campionare troppi dati.
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var labels = ee.FeatureCollection('projects/google/demo_landcover_labels');
// No! Not necessary. Don't do this:
labels = labels.map(function(f) { return f.buffer(100000, 1000); });
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var training = composite.select(bands).sampleRegions({
collection: labels,
properties: ['landcover'],
scale: 30
});
var classifier = ee.Classifier.smileCart().train({
features: training,
classProperty: 'landcover',
inputProperties: bands
});
print(classifier.explain()); // Computed value is too large
L'approccio migliore è iniziare con una quantità moderata di dati e ottimizzare gli iperparametri del classificatore per determinare se puoi ottenere l'accuratezza che ti interessa:
Ottimizza gli iperparametri.
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var labels = ee.FeatureCollection('projects/google/demo_landcover_labels');
// Increase the data a little bit, possibly introducing noise.
labels = labels.map(function(f) { return f.buffer(100, 10); });
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var data = composite.select(bands).sampleRegions({
collection: labels,
properties: ['landcover'],
scale: 30
});
// Add a column of uniform random numbers called 'random'.
data = data.randomColumn();
// Partition into training and testing.
var training = data.filter(ee.Filter.lt('random', 0.5));
var testing = data.filter(ee.Filter.gte('random', 0.5));
// Tune the minLeafPopulation parameter.
var minLeafPops = ee.List.sequence(1, 10);
var accuracies = minLeafPops.map(function(p) {
var classifier = ee.Classifier.smileCart({minLeafPopulation: p})
.train({
features: training,
classProperty: 'landcover',
inputProperties: bands
});
return testing
.classify(classifier)
.errorMatrix('landcover', 'classification')
.accuracy();
});
print(ui.Chart.array.values({
array: ee.Array(accuracies),
axis: 0,
xLabels: minLeafPops
}));
In questo esempio, il classificatore è già molto preciso, quindi non c'è molto da fare per l'ottimizzazione. Ti consigliamo di scegliere l'albero più piccolo possibile (ovvero il più grandeminLeafPopulation
) che abbia comunque la precisione richiesta.
Export
risultati intermedi
Supponiamo che il tuo obiettivo sia acquisire campioni da un'immagine calcolata relativamente complessa. Spesso è più efficiente Export
l'immagine toAsset()
, caricare l'immagine estratta e poi eseguire il campionamento. Ad esempio:
var image = ee.Image('UMD/hansen/global_forest_change_2018_v1_6');
var geometry = ee.Geometry.Polygon(
[[[-76.64069800085349, 5.511777325802095],
[-76.64069800085349, -20.483938229362376],
[-35.15632300085349, -20.483938229362376],
[-35.15632300085349, 5.511777325802095]]], null, false);
var testRegion = ee.Geometry.Polygon(
[[[-48.86726050085349, -3.0475996402515717],
[-48.86726050085349, -3.9248707849303295],
[-47.46101050085349, -3.9248707849303295],
[-47.46101050085349, -3.0475996402515717]]], null, false);
// Forest loss in 2016, to stratify a sample.
var loss = image.select('lossyear');
var loss16 = loss.eq(16).rename('loss16');
// Scales and masks Landsat 8 surface reflectance images.
function prepSrL8(image) {
var qaMask = image.select('QA_PIXEL').bitwiseAnd(parseInt('11111', 2)).eq(0);
var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
return image.addBands(opticalBands, null, true)
.addBands(thermalBands, null, true)
.updateMask(qaMask);
}
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.map(prepSrL8);
// Create two annual cloud-free composites.
var composite1 = collection.filterDate('2015-01-01', '2015-12-31').median();
var composite2 = collection.filterDate('2017-01-01', '2017-12-31').median();
// We want a strtatified sample of this stack.
var stack = composite1.addBands(composite2)
.float(); // Export the smallest size possible.
// Export the image. This block is commented because the export is complete.
/*
var link = '0b8023b0af6c1b0ac7b5be649b54db06'
var desc = 'Logistic_regression_stack_' + link;
Export.image.toAsset({
image: stack,
description: desc,
assetId: desc,
region: geometry,
scale: 30,
maxPixels: 1e10
})
*/
// Load the exported image.
var exportedStack = ee.Image(
'projects/google/Logistic_regression_stack_0b8023b0af6c1b0ac7b5be649b54db06');
// Take a very small sample first, to debug.
var testSample = exportedStack.addBands(loss16).stratifiedSample({
numPoints: 1,
classBand: 'loss16',
region: testRegion,
scale: 30,
geometries: true
});
print(testSample); // Check this in the console.
// Take a large sample.
var sample = exportedStack.addBands(loss16).stratifiedSample({
numPoints: 10000,
classBand: 'loss16',
region: geometry,
scale: 30,
});
// Export the large sample...
In questo esempio, tieni presente che le immagini vengono esportate come valori float. Non eseguire l'esportazione con precisione doppia, a meno che non sia assolutamente necessario. Quando esegui questa esportazione, tieni presente che un link a Code Editor (ottenuto immediatamente prima dell'esportazione) è incorporato nel nome file per la riproducibilità.
Al termine dell'esportazione, ricarica l'asset e procedi con il campionamento. Tieni presente che per il debugging viene eseguito prima un campione molto piccolo in un'area di test molto piccola. Se l'operazione va a buon fine, prendi un campione più grande ed esportalo.
In genere, questi campioni di grandi dimensioni devono essere esportati. Non aspettarti che questi esempi siano disponibili in modo interattivo (ad esempio tramite print()
) o utilizzabili (ad esempio come input per un classificatore) senza prima esportarli.
JOIN e map-filter
Supponiamo che tu voglia unire le collezioni in base a ora, località o a una proprietà di metadati. In genere, questo viene eseguito in modo più efficiente con un join. L'esempio seguente esegue un join spazio-temporale tra le raccolte Landsat 8 e Sentinel-2:
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var joined = ee.Join.saveAll('landsat').apply({
primary: s2,
secondary: l8,
condition: ee.Filter.and(
ee.Filter.maxDifference({
difference: 1000 * 60 * 60 * 24, // One day in milliseconds
leftField: 'system:time_start',
rightField: 'system:time_start',
}),
ee.Filter.intersects({
leftField: '.geo',
rightField: '.geo',
})
)
});
print(joined);
Sebbene sia consigliabile provare prima un'unione (Export
se necessario), a volte anche un
filter()
all'interno di un map()
può essere efficace, in particolare per raccolte molto grandi.
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var mappedFilter = s2.map(function(image) {
var date = image.date();
var landsat = l8
.filterBounds(image.geometry())
.filterDate(date.advance(-1, 'day'), date.advance(1, 'day'));
// Return the input image with matching scenes in a property.
return image.set({
landsat: landsat,
size: landsat.size()
});
}).filter(ee.Filter.gt('size', 0));
print(mappedFilter);
reduceRegion()
rispetto a reduceRegions()
rispetto a un ciclo for
Se chiami reduceRegions()
con un FeatureCollection
molto grande o complesso come input, potresti riscontrare l'errore temuto "Il valore calcolato è troppo grande". Una potenziale soluzione è mappare reduceRegion()
sopra FeatureCollection
. Un'altra potenziale soluzione è utilizzare un ciclo for (orrore). Sebbene questa pratica sia vivamente sconsigliata in Earth Engine, come descritto qui, qui e qui, reduceRegion()
può essere implementata in un ciclo for per eseguire grandi riduzioni.
Supponiamo che il tuo obiettivo sia ottenere la media dei pixel (o di qualsiasi statistica) in ogni elemento di un FeatureCollection
per ogni immagine di un ImageCollection
.
L'esempio seguente mette a confronto i tre approcci descritti in precedenza:
// Table of countries.
var countriesTable = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017");
// Time series of images.
var mod13a1 = ee.ImageCollection("MODIS/006/MOD13A1");
// MODIS vegetation indices (always use the most recent version).
var band = 'NDVI';
var imagery = mod13a1.select(band);
// Option 1: reduceRegions()
var testTable = countriesTable.limit(1); // Do this outside map()s and loops.
var data = imagery.map(function(image) {
return image.reduceRegions({
collection: testTable,
reducer: ee.Reducer.mean(),
scale: 500
}).map(function(f) {
return f.set({
time: image.date().millis(),
date: image.date().format()
});
});
}).flatten();
print(data.first());
// Option 2: mapped reduceRegion()
var data = countriesTable.map(function(feature) {
return imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
});
}).flatten();
print(data.first());
// Option 3: for-loop (WATCH OUT!)
var size = countriesTable.size();
// print(size); // 312
var countriesList = countriesTable.toList(1); // Adjust size.
var data = ee.FeatureCollection([]); // Empty table.
for (var j=0; j<1; j++) { // Adjust size.
var feature = ee.Feature(countriesList.get(j));
// Convert ImageCollection > FeatureCollection
var fc = ee.FeatureCollection(imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
}));
data = data.merge(fc);
}
print(data.first());
Tieni presente che l'elemento first()
di ogni raccolta viene stampato per scopi di debugging. Non dovresti aspettarti che il risultato completo sia disponibile in modo interattivo: dovrai Export
. Tieni inoltre presente che i cicli for devono essere utilizzati con estrema cautela e solo come ultima risorsa. Infine, il ciclo for richiede di ottenere manualmente le dimensioni della raccolta di input e di codificarle nelle posizioni appropriate. Se non ti è chiaro, non utilizzare un loop for.
Utilizzare la differenza incrementale per i vicini nel tempo
Supponiamo di avere un ImageCollection
ordinato in base al tempo (ovvero una serie temporale) e di voler confrontare ogni immagine con quella precedente (o successiva). Anziché utilizzare
iterate()
per questo scopo, potrebbe essere più efficiente utilizzare una differenza forward basata su array. L'esempio seguente utilizza questo metodo per deduplicare la raccolta Sentinel-2, dove i duplicati sono definiti come immagini con lo stesso giorno dell'anno:
var sentinel2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var sf = ee.Geometry.Point([-122.47555371521855, 37.76884708376152]);
var s2 = sentinel2
.filterBounds(sf)
.filterDate('2018-01-01', '2019-12-31');
var withDoys = s2.map(function(image) {
var ndvi = image.normalizedDifference(['B4', 'B8']).rename('ndvi');
var date = image.date();
var doy = date.getRelative('day', 'year');
var time = image.metadata('system:time_start');
var doyImage = ee.Image(doy)
.rename('doy')
.int();
return ndvi.addBands(doyImage).addBands(time)
.clip(image.geometry()); // Appropriate use of clip.
});
var array = withDoys.toArray();
var timeAxis = 0;
var bandAxis = 1;
var dedupe = function(array) {
var time = array.arraySlice(bandAxis, -1);
var sorted = array.arraySort(time);
var doy = sorted.arraySlice(bandAxis, -2, -1);
var left = doy.arraySlice(timeAxis, 1);
var right = doy.arraySlice(timeAxis, 0, -1);
var mask = ee.Image(ee.Array([[1]]))
.arrayCat(left.neq(right), timeAxis);
return array.arrayMask(mask);
};
var deduped = dedupe(array);
// Inspect these outputs to confirm that duplicates have been removed.
print(array.reduceRegion('first', sf, 10));
print(deduped.reduceRegion('first', sf, 10));
Controlla le raccolte stampate per verificare che i duplicati siano stati rimossi.