Best Practices für das Codieren

In diesem Dokument werden Programmierpraktiken beschrieben, mit denen die Erfolgschancen bei komplexen oder teuren Earth Engine-Berechnungen maximiert werden sollen. Die hier beschriebenen Methoden gelten sowohl für interaktive (z. B. Code-Editor) als auch für Batch-Berechnungen (Export). Langlaufende Berechnungen sollten jedoch im Batchsystem ausgeführt werden.

Vermeiden Sie es, Clientfunktionen und ‑objekte mit Serverfunktionen und ‑objekten zu vermischen.

Earth Engine-Serverobjekte sind Objekte mit Konstruktoren, die mit ee beginnen (z. B. ee.Image, ee.Reducer). Alle Methoden solcher Objekte sind Serverfunktionen. Alle Objekte, die nicht auf diese Weise erstellt wurden, sind Clientobjekte. Clientobjekte können aus dem Code-Editor (z.B. Map, Chart) oder der JavaScript-Sprache (z.B. Date, Math, [], {}) stammen.

Um unerwünschtes Verhalten zu vermeiden, sollten Sie in Ihrem Script keine Client- und Serverfunktionen mischen, wie hier, hier und hier beschrieben. Eine ausführliche Erklärung zu Client und Server in Earth Engine finden Sie auf dieser Seite und/oder in dieser Anleitung. Das folgende Beispiel veranschaulicht die Gefahren, die mit der Vermischung von Client- und Serverfunktionen verbunden sind:

Fehler: Dieser Code funktioniert nicht.

var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');

// Won't work.
for(var i=0; i<table.size(); i++) {
  print('No!');
}

Können Sie den Fehler erkennen? Hinweis: table.size() ist eine serverseitige Methode auf einem serverseitigen Objekt und kann nicht mit clientseitigen Funktionen wie der <-Bedingung verwendet werden.

For-Schleifen eignen sich beispielsweise für die UI-Einrichtung, da Code Editor ui-Objekte und ‑Methoden clientseitig sind. Weitere Informationen zum Erstellen von Benutzeroberflächen in Earth Engine Beispiel:

Verwenden Sie Clientfunktionen für die Benutzeroberfläche.

var panel = ui.Panel();
for(var i=1; i<8; i++) {
  panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);

Umgekehrt ist map() eine Serverfunktion und Clientfunktionen funktionieren nicht innerhalb der an map() übergebenen Funktion. Beispiel:

Fehler: Dieser Code funktioniert nicht.

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.
});

Wenn Sie eine Aktion auf jedes Element in einer Sammlung ausführen möchten, map() fügen Sie eine Funktion für die Sammlung und set() ein Attribut hinzu:

Verwende map() und 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());

Sie können die Sammlung auch filter() anhand berechneter oder vorhandener Properties filtern und das Ergebnis print(). Sammlungen mit mehr als 5.000 Elementen können nicht gedruckt werden. Wenn die Fehlermeldung „Die Sammlungsabfrage wurde abgebrochen, nachdem mehr als 5.000 Elemente erfasst wurden“ angezeigt wird, filter() oder limit() die Sammlung vor dem Drucken.

Unnötige Umwandlung in eine Liste vermeiden

Sammlungen in Earth Engine werden mithilfe von Optimierungen verarbeitet, die durch die Umwandlung der Sammlung in einen List- oder Array-Typ aufgelöst werden. Sofern Sie keinen Zugriff auf zufällig ausgewählte Sammlungselemente benötigen (d.h. Sie müssen das i-te Element einer Sammlung abrufen), verwenden Sie Filter für die Sammlung, um auf einzelne Sammlungselemente zuzugreifen. Im folgenden Beispiel wird der Unterschied zwischen der Typumwandlung (nicht empfohlen) und dem Filtern (empfohlen) zum Zugriff auf ein Element in einer Sammlung veranschaulicht:

Konvertieren Sie nicht unnötig in eine Liste.

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.

Beachten Sie, dass Sie leicht Fehler auslösen können, wenn Sie eine Sammlung unnötig in eine Liste umwandeln. Besser ist es, filter() zu verwenden:

Verwenden Sie filter()!

print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());

Filter sollten Sie so früh wie möglich in Ihrer Analyse verwenden.

Auf ee.Algorithms.If() verzichten

Verwenden Sie ee.Algorithms.If() nicht, um Verzweigungslogik zu implementieren, insbesondere nicht in einer zugeordneten Funktion. Wie das folgende Beispiel zeigt, kann ee.Algorithms.If() speicherintensiv sein und wird daher nicht empfohlen:

Verwenden Sie If() nicht!

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.

Das zweite Argument für map() ist true. Das bedeutet, dass die zugeordnete Funktion möglicherweise Nullwerte zurückgibt, die in der resultierenden Sammlung gelöscht werden. Das kann nützlich sein (ohne If()), aber hier ist die einfachste Lösung die Verwendung eines Filters:

Verwenden Sie filter()!

print(table.filter(ee.Filter.eq('country_na', 'Chad')));

Wie in dieser Anleitung gezeigt, ist ein funktionaler Programmieransatz mithilfe von Filtern die richtige Methode, um eine Logik auf einige Elemente einer Sammlung und eine andere Logik auf die anderen Elemente der Sammlung anzuwenden.

Auf reproject() verzichten

Verwenden Sie die Funktion „Neu projizieren“ nur, wenn dies unbedingt erforderlich ist. Eine Möglichkeit, reproject() zu verwenden, besteht darin, Berechnungen im Code-Editor auf eine bestimmte Skala zu erzwingen, damit Sie die Ergebnisse auf der gewünschten Analyseskala untersuchen können. Im nächsten Beispiel werden Hotpixel-Cluster berechnet und die Anzahl der Pixel in jedem Cluster. Führen Sie das Beispiel aus und klicken Sie auf einen der Patches. Die Anzahl der Pixel unterscheidet sich zwischen den neu projizierten Daten und den nicht neu projizierten Daten.

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);

Der Grund für die Abweichung ist, dass der Maßstab der Analyse durch die Zoomstufe des Code-Editors festgelegt wird. Wenn Sie reproject() aufrufen, legen Sie den Maßstab der Berechnung fest, nicht der Code-Editor. Verwenden Sie reproject() mit äußerster Vorsicht. Die Gründe dafür sind in diesem Dokument beschrieben.

Zuerst filtern und select()

Filtern Sie Eingabesammlungen im Allgemeinen nach Zeit, Ort und/oder Metadaten, bevor Sie etwas anderes mit der Sammlung tun. Wenden Sie möglichst selektive Filter vor weniger selektiven Filtern an. Räumliche und/oder zeitliche Filter sind oft selektiver. Beachten Sie beispielsweise, dass select() und filter() vor map() angewendet werden:

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');

updateMask() anstelle von mask() verwenden

Der Unterschied zwischen updateMask() und mask() besteht darin, dass bei ersterem eine logische and() des Arguments (der neuen Maske) und der vorhandenen Bildmaske ausgeführt wird, während mask() die Bildmaske einfach durch das Argument ersetzt. Das Problem dabei ist, dass Sie versehentlich Pixel enttarnen können. In diesem Beispiel soll die Höhe von Pixeln, die maximal 300 Meter betragen, ausgeblendet werden. Wie Sie sehen können (herauszoomen), werden durch die Verwendung von mask() viele Pixel entmaskiert, die nicht zum gewünschten Bild gehören:

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);

Reduzierer kombinieren

Wenn Sie mehrere Statistiken (z.B. Mittelwert und Standardabweichung) aus einer einzelnen Eingabe (z.B. einer Bildregion) benötigen, ist es effizienter, mehrere Reducer zu kombinieren. Wenn Sie beispielsweise Bildstatistiken abrufen möchten, kombinieren Sie die Reducer so:

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 diesem Beispiel wird der Mittelwert-Reducer mit dem Standardabweichungs-Reducer kombiniert und sharedInputs ist wahr, um einen einzelnen Durchlauf durch die Eingabepixel zu ermöglichen. Im Ausgabewörterbuch wird der Name des Reduzierers an den Bandnamen angehängt. Wenn Sie Bilder für Mittelwert und Standardabweichung erhalten möchten (z. B. um das Eingabebild zu normalisieren), können Sie die Werte in ein Bild umwandeln und Mittelwerte und Standardabweichungen wie im Beispiel gezeigt einzeln mit regulären Ausdrücken extrahieren.

Export“ verwenden

Bei Berechnungen, die im Code-Editor zu Fehlern wie „Nutzerspeicherlimit überschritten“ oder „Berechnung hat Zeitüberschreitung verursacht“ führen, können dieselben Berechnungen mit Export möglicherweise erfolgreich durchgeführt werden. Das liegt daran, dass die Zeitüberschreitungen länger sind und der zulässige Arbeitsspeicherbedarf beim Ausführen im Batchsystem (wo Exporte ausgeführt werden) höher ist. Es gibt noch andere Ansätze, die Sie zuerst ausprobieren können, wie im Debugging-Dokument beschrieben. Angenommen, das Dictionary hat im vorherigen Beispiel einen Fehler zurückgegeben. Sie könnten die Ergebnisse so abrufen:

var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
  collection: ee.FeatureCollection([ee.Feature(null, stats)]),
  description: 'exported_stats_demo_' + link,
  fileFormat: 'CSV'
});

Der Link ist zur Reproduzierbarkeit in den Asset-Namen eingebettet. Wenn Sie toAsset exportieren möchten, müssen Sie außerdem eine Geometrie angeben. Das kann alles sein, z. B. der Bildmittelpunkt, der klein und kostengünstig zu berechnen ist. Verwenden Sie also keine komplexe Geometrie, wenn Sie sie nicht benötigen.

Auf der Seite zur Fehlerbehebung finden Sie Beispiele dafür, wie Sie mit Export die Fehler Berechnung hat Zeitüberschreitung und Zu viele gleichzeitige Aggregationen beheben. In diesem Dokument finden Sie weitere Informationen zum Exportieren.

Wenn du keinen Clip erstellen möchtest, verwende clip() nicht.

Wenn Sie clip() unnötig verwenden, erhöht sich die Rechenzeit. Verwenden Sie clip() nur, wenn es für Ihre Analyse erforderlich ist. Wenn du dir nicht sicher bist, solltest du keine Clips erstellen. Beispiel für eine unzulässige Verwendung von Clips:

Kürzen Sie die Eingaben nicht unnötig!

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);

Das Zuschneiden der Eingabebilder kann vollständig übersprungen werden, da die Region im reduceRegion()-Aufruf angegeben wird:

Geben Sie die Region für die Ausgabe an.

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);

Wenn bei dieser Berechnung ein Zeitlimit erreicht wird, Export es wie in diesem Beispiel.

Wenn du einen Clip mit einer komplexen Sammlung erstellen möchtest, verwende clipToCollection().

Wenn Sie etwas zuschneiden müssen und die Geometrien, die Sie zum Zuschneiden verwenden möchten, sich in einer Sammlung befinden, verwenden Sie 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);

Verwenden Sie featureCollection.geometry() oder featureCollection.union() NICHT für große und/oder komplexe Sammlungen, da diese speicherintensiver sein können.

Verwenden Sie keine komplexe Sammlung als Region für einen Reducer.

Wenn Sie eine räumliche Reduzierung vornehmen müssen, damit der Reducer Eingaben aus mehreren Regionen in einer FeatureCollection zusammenfasst, geben Sie featureCollection.geometry() nicht als geometry-Eingabe für den Reducer an. Verwenden Sie stattdessen clipToCollection() und eine Region, die groß genug ist, um die Grenzen der Sammlung zu umfassen. Beispiel:

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.

errorMargin muss eine positive Zahl sein

Verwenden Sie für möglicherweise teure geometrische Vorgänge die größte Fehlertoleranz, die bei der erforderlichen Genauigkeit der Berechnung möglich ist. Mit der Fehlertoleranz wird der maximal zulässige Fehler (in Metern) bei Vorgängen an Geometrien angegeben, z.B. bei der Reprojektion. Wenn Sie eine kleine Fehlertoleranz angeben, kann es erforderlich sein, Geometrien (mit Koordinaten) zu verdichten. Das kann sehr speicherintensiv sein. Es empfiehlt sich, für die Berechnung eine möglichst große Fehlertoleranz anzugeben:

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');

Verwenden Sie keine unverhältnismäßig kleine Skala mit reduceToVectors()

Wenn Sie ein Raster in einen Vektor konvertieren möchten, verwenden Sie einen geeigneten Maßstab. Die Angabe eines sehr kleinen Maßstabs kann die Rechenkosten erheblich erhöhen. Legen Sie die Skalierung so hoch wie möglich fest, um die erforderliche Genauigkeit zu erreichen. So rufen Sie beispielsweise Polygone ab, die die Landmassen der Erde darstellen:

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');

Beachten Sie im vorherigen Beispiel die Verwendung eines nicht geodätischen Polygons für globale Reduktionen.

reduceToVectors() nicht mit reduceRegions() verwenden

Verwenden Sie keine von reduceToVectors() zurückgegebene FeatureCollection als Eingabe für reduceRegions(). Fügen Sie stattdessen die Bänder hinzu, die Sie reduzieren möchten, bevor Sie reduceToVectors() aufrufen:

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));

Weitere Möglichkeiten, die Pixel eines Bildes in Zonen eines anderen zu reduzieren, sind reduceConnectedCommponents() und/oder Gruppierungsminderer.

fastDistanceTransform() für Nachbarschaftsoperationen verwenden

Bei einigen Convolutions-Vorgängen ist fastDistanceTransform() möglicherweise effizienter als reduceNeighborhood() oder convolve(). So führen Sie beispielsweise eine Erosion und/oder eine Dilatation von Binärinputs aus:

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');

Optimierungen in reduceNeighborhood() verwenden

Wenn Sie eine Convolution ausführen müssen und fastDistanceTransform() nicht verwenden können, verwenden Sie die Optimierungen 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');

Nicht mehr Daten erfassen, als Sie benötigen

Widerstehen Sie dem Drang, den Trainingsdatensatz unnötig zu vergrößern. Die Erhöhung der Anzahl der Trainingsdaten ist zwar in einigen Fällen eine effektive Strategie für maschinelles Lernen, kann aber auch die Rechenkosten erhöhen, ohne dass sich die Genauigkeit entsprechend verbessert. Informationen dazu, wann Sie die Größe des Trainingsdatensatzes erhöhen sollten, finden Sie hier. Im folgenden Beispiel wird gezeigt, wie das Anfordern zu vieler Trainingsdaten zu dem gefürchteten Fehler „Berechneter Wert ist zu groß“ führen kann:

Nicht zu viele Daten erfassen

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

Es ist besser, mit einer moderaten Datenmenge zu beginnen und die Hyperparameter des Klassifikators so anzupassen, dass die gewünschte Genauigkeit erreicht wird:

Hyperparameter abstimmen

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 diesem Beispiel ist der Klassifikator bereits sehr genau, sodass nicht viel optimiert werden muss. Sie können den kleinstmöglichen Baum (d.h. die größte minLeafPopulation) auswählen, der immer noch die erforderliche Genauigkeit hat.

Export Zwischenergebnisse

Angenommen, Sie möchten Stichproben aus einem relativ komplexen berechneten Bild nehmen. Oft ist es effizienter, das ExportBild zu toAsset(), das exportierte Bild zu laden und dann zu samplen. Beispiel:

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 diesem Beispiel werden die Bilder als Float exportiert. Exportieren Sie nur dann mit doppelter Genauigkeit, wenn es unbedingt erforderlich ist. Hinweis: Beim Exportieren wird ein Link zum Code-Editor (der unmittelbar vor dem Export abgerufen wird) in den Dateinamen eingebettet, damit das Modell reproduziert werden kann.

Wenn der Export abgeschlossen ist, laden Sie das Asset noch einmal hoch und fahren Sie mit der Stichprobenerhebung fort. Hinweis: Für das Debuggen wird zuerst eine sehr kleine Stichprobe in einem sehr kleinen Testbereich ausgeführt. Wenn das funktioniert, nehmen Sie eine größere Stichprobe und exportieren Sie sie. Solche großen Stichproben müssen in der Regel exportiert werden. Solche Samples sind nicht interaktiv verfügbar (z. B. über print()) und können auch nicht verwendet werden (z. B. als Eingabe für einen Klassifikator), ohne dass sie zuerst exportiert werden.

Zusammenführen und Zuordnungsfilter

Angenommen, Sie möchten Sammlungen basierend auf Zeit, Ort oder einer Metadateneigenschaft zusammenführen. Im Allgemeinen ist dies am effizientesten mit einem Join möglich. Im folgenden Beispiel wird eine raumzeitliche Zusammenführung der Landsat 8- und Sentinel-2-Sammlungen durchgeführt:

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);

Sie sollten zuerst einen Join versuchen (Export bei Bedarf). Gelegentlich kann auch eine filter() in einer map() effektiv sein, insbesondere bei sehr großen Sammlungen.

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() im Vergleich zu reduceRegions() im Vergleich zur For-Schleife

Wenn Sie reduceRegions() mit einer sehr großen oder komplexen FeatureCollection als Eingabe aufrufen, kann der Fehler „Berechneter Wert ist zu groß“ auftreten. Eine mögliche Lösung besteht darin, reduceRegion() stattdessen über FeatureCollection abzubilden. Eine weitere mögliche Lösung ist die Verwendung einer (oh Schreck!) For-Schleife. In Earth Engine wird dies jedoch dringend abgeraten, wie hier, hier und hier beschrieben. reduceRegion() kann jedoch in einer For-Schleife implementiert werden, um große Reduktionen vorzunehmen.

Angenommen, Sie möchten für jedes Bild in einem ImageCollection den Mittelwert der Pixel (oder eine andere Statistik) in jedem Element in einem FeatureCollection ermitteln. Im folgenden Beispiel werden die drei zuvor beschriebenen Ansätze verglichen:

// 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());

Hinweis: Für die Fehlerbehebung wird das first()-Element aus jeder Sammlung ausgegeben. Das vollständige Ergebnis ist nicht interaktiv verfügbar. Sie müssen Export. Außerdem sollten For-Schleifen nur mit äußerster Vorsicht und nur als letztes Mittel verwendet werden. Schließlich muss die Größe der Eingabesammlung manuell ermittelt und an den entsprechenden Stellen hartcodiert werden. Wenn Ihnen das nicht klar ist, sollten Sie keine For-Schleife verwenden.

Vorwärtsdifferenzierung für Zeitnachbarn verwenden

Angenommen, Sie haben eine zeitlich sortierte ImageCollection (d.h. eine Zeitreihe) und möchten jedes Bild mit dem vorherigen (oder nächsten) Bild vergleichen. Anstatt iterate() für diesen Zweck zu verwenden, ist es möglicherweise effizienter, eine arraybasierte Vorwärtsdifferenzierung zu verwenden. Im folgenden Beispiel wird diese Methode verwendet, um die Sentinel-2-Sammlung zu entduplizieren. Dabei werden Duplikate als Bilder mit demselben Tag des Jahres definiert:

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));

Prüfen Sie die ausgedruckten Sammlungen, um sicherzustellen, dass Duplikate entfernt wurden.