Prácticas recomendadas de codificación

En este documento, se describen las prácticas de programación que tienen como objetivo maximizar las posibilidades de éxito de los cálculos complejos o costosos de Earth Engine. Los métodos que se describen aquí se aplican a los cálculos interactivos (p.ej., Code Editor) y por lotes (Export), aunque, por lo general, los cálculos de larga duración deben ejecutarse en el sistema por lotes.

Evita mezclar funciones y objetos del cliente con funciones y objetos del servidor.

Los objetos del servidor de Earth Engine son objetos con constructores que comienzan con ee (p.ej., ee.Image, ee.Reducer) y cualquier método en esos objetos son funciones del servidor. Cualquier objeto que no se construya de esta manera es un objeto cliente. Los objetos cliente pueden provenir del editor de código (p.ej., Map, Chart) o del lenguaje JavaScript (p.ej., Date, Math, [], {}).

Para evitar comportamientos no deseados, no mezcles las funciones del cliente y del servidor en tu secuencia de comandos, como se explica aquí, aquí y aquí. Consulta esta página o este instructivo para obtener una explicación detallada del cliente en comparación con el servidor en Earth Engine. En el siguiente ejemplo, se ilustran los peligros de mezclar la funcionalidad del cliente y del servidor:

Error: Este código no funciona.

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

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

¿Puedes detectar el error? Ten en cuenta que table.size() es un método de servidor en un objeto de servidor y no se puede usar con la funcionalidad del cliente, como el condicional <.

Una situación en la que es posible que desees usar bucles for es con la configuración de la IU, ya que los objetos y métodos ui del editor de código son del cliente. (Obtén más información para crear interfaces de usuario en Earth Engine). Por ejemplo:

Usa funciones de cliente para la configuración de la IU.

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

Por el contrario, map() es una función del servidor y la funcionalidad del cliente no funcionará dentro de la función que se pasa a map(). Por ejemplo:

Error: Este código no funciona.

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

Para hacer algo en cada elemento de una colección, map() una función sobre la colección y set() una propiedad:

¡Usa map() y 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());

También puedes filter() la colección en función de propiedades calculadas o existentes y print() el resultado. Ten en cuenta que no puedes imprimir una colección con más de 5,000 elementos. Si recibes el error "Se canceló la consulta de la colección después de acumular más de 5,000 elementos", filter() o limit() la colección antes de imprimirla.

Evita convertir a lista innecesariamente

Las colecciones de Earth Engine se procesan con optimizaciones que se rompen cuando se convierte la colección en un tipo List o Array. A menos que necesites acceso aleatorio a los elementos de la colección (es decir, necesites obtener el elemento enésimo de una colección), usa filtros en la colección para acceder a elementos individuales de la colección. En el siguiente ejemplo, se ilustra la diferencia entre la conversión de tipo (no recomendada) y el filtrado (recomendado) para acceder a un elemento en una colección:

No conviertas a lista de forma innecesaria.

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.

Ten en cuenta que puedes activar errores fácilmente si conviertes una colección en una lista de forma innecesaria. La forma más segura es usar filter():

Usa filter().

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

Ten en cuenta que debes usar los filtros lo antes posible en tu análisis.

Evita ee.Algorithms.If()

No uses ee.Algorithms.If() para implementar la lógica de ramificación, en especial en una función asignada. Como se ilustra en el siguiente ejemplo, ee.Algorithms.If() puede ser intensivo en la memoria y no se recomienda:

No uses 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.

Ten en cuenta que el segundo argumento de map() es true. Esto significa que la función asignada puede mostrar valores nulos, que se descartarán en la colección resultante. Eso puede ser útil (sin If()), pero aquí la solución más fácil es usar un filtro:

Usa filter().

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

Como se muestra en este instructivo, un enfoque de programación funcional con filtros es la forma correcta de aplicar una lógica a algunos elementos de una colección y otra lógica a los demás elementos de la colección.

Evita reproject()

No uses la opción de volver a proyectar, a menos que sea absolutamente necesario. Una razón por la que podrías querer usar reproject() es forzar que los cálculos del editor de código se realicen en una escala específica para que puedas examinar los resultados en la escala de análisis que desees. En el siguiente ejemplo, se calculan los parches de píxeles calientes y el recuento de píxeles en cada parche. Ejecuta el ejemplo y haz clic en uno de los parches. Ten en cuenta que el recuento de píxeles difiere entre los datos proyectados y los que no se proyectaron.

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

El motivo de la discrepancia es que el nivel de zoom del editor de código establece la escala de análisis. Cuando llamas a reproject(), estableces la escala del procesamiento en lugar del editor de código. Usa reproject() con mucho cuidado por los motivos que se describen en este doc.

Filtra y select() primero

En general, filtra las colecciones de entrada por hora, ubicación o metadatos antes de hacer cualquier otra acción con ellas. Aplica filtros más selectivos antes que los menos selectivos. Los filtros espaciales o temporales suelen ser más selectivos. Por ejemplo, ten en cuenta que select() y filter() se aplican antes de 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');

Usa updateMask() en lugar de mask()

La diferencia entre updateMask() y mask() es que el primero realiza una and() lógica del argumento (la máscara nueva) y la máscara de imagen existente, mientras que mask() simplemente reemplaza la máscara de imagen por el argumento. El peligro de esta última opción es que puedes desenmascarar píxeles de forma no intencional. En este ejemplo, el objetivo es enmascarar los píxeles con una elevación inferior o igual a 300 metros. Como puedes ver (al alejar la imagen), usar mask() hace que muchos píxeles se desmasquen, píxeles que no pertenecen a la imagen de interés:

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

Combina reductoras

Si necesitas varias estadísticas (p.ej., la media y la desviación estándar) a partir de una sola entrada (p.ej., una región de imagen), es más eficiente combinar los reductores. Por ejemplo, para obtener estadísticas de imágenes, combina los reductores de la siguiente manera:

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

En este ejemplo, ten en cuenta que el reductor de la media se combina con el reductor de la desviación estándar y sharedInputs es verdadero para permitir un solo pase por los píxeles de entrada. En el diccionario de salida, el nombre del reductor se agrega al nombre de la banda. Para obtener imágenes de la media y la desviación estándar (por ejemplo, para normalizar la imagen de entrada), puedes convertir los valores en una imagen y usar regex para extraer las medias y las desviaciones estándar de forma individual, como se muestra en el ejemplo.

Usa Export

En el caso de los cálculos que generan errores de "Se superó el límite de memoria del usuario" o "Se agotó el tiempo de espera del cálculo" en el editor de código, es posible que los mismos cálculos se puedan realizar correctamente con Export. Esto se debe a que los tiempos de espera son más largos y la huella de memoria permitida es mayor cuando se ejecuta en el sistema por lotes (donde se ejecutan las exportaciones). (Hay otros enfoques que te recomendamos probar primero, como se detalla en el documento de depuración). Siguiendo con el ejemplo anterior, supongamos que el diccionario mostró un error. Para obtener los resultados, puedes hacer lo siguiente:

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

Ten en cuenta que el vínculo está incorporado en el nombre del recurso para que se pueda reproducir. Además, ten en cuenta que, si deseas exportar toAsset, deberás proporcionar una geometría, que puede ser cualquier cosa, por ejemplo, el centroide de la imagen, que es pequeño y económico de calcular. (es decir, no uses una geometría compleja si no la necesitas).

Consulta la página de depuración para ver ejemplos del uso de Export para resolver Se agotó el tiempo de espera de la operación y Hay demasiadas agregaciones simultáneas. Consulta este documento para obtener detalles sobre la exportación en general.

Si no necesitas recortar, no uses clip().

El uso innecesario de clip() aumentará el tiempo de procesamiento. Evita clip() a menos que sea necesario para tu análisis. Si no estás seguro, no lo hagas. Ejemplo de un uso incorrecto del clip:

No recortes las entradas innecesariamente.

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

Se puede omitir por completo el recorte de las imágenes de entrada, ya que la región se especifica en la llamada a reduceRegion():

Especifica la región para el resultado.

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

Si se agota el tiempo de espera de este cálculo, Export como en este ejemplo.

Si necesitas recortar con una colección compleja, usa clipToCollection().

Si realmente necesitas recortar algo y las geometrías que deseas usar para el recorte están en una colección, usa 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);

NO uses featureCollection.geometry() ni featureCollection.union() en colecciones grandes o complejas, que pueden consumir más memoria.

No uses una colección compleja como región para un reductor.

Si necesitas realizar una reducción espacial para que el reductor agrupe entradas de varias regiones en un FeatureCollection, no proporciones featureCollection.geometry() como la entrada geometry al reductor. En su lugar, usa clipToCollection() y una región lo suficientemente grande como para abarcar los límites de la colección. Por ejemplo:

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.

Usa un errorMargin distinto de cero

Para operaciones de geometría posiblemente costosas, usa el margen de error más grande posible según la precisión requerida del cálculo. El margen de error especifica el error máximo permitido (en metros) durante las operaciones en geometrías (p.ej., durante la reproyección). Especificar un margen de error pequeño puede generar la necesidad de densificar las geometrías (con coordenadas), lo que puede requerir mucho espacio en la memoria. Se recomienda especificar un margen de error lo más grande posible para tu cálculo:

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

No uses una escala ridículamente pequeña con reduceToVectors().

Si quieres convertir un ráster en un vector, usa una escala adecuada. Especificar una escala muy pequeña puede aumentar considerablemente el costo de procesamiento. Establece la escala lo más alta posible para obtener la precisión requerida. Por ejemplo, para obtener polígonos que representen masas de tierra globales, haz lo siguiente:

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

En el ejemplo anterior, observa el uso de un polígono no geodésico para usarlo en las reducciones globales.

No uses reduceToVectors() con reduceRegions()

No uses un FeatureCollection que devuelva reduceToVectors() como entrada para reduceRegions(). En su lugar, agrega las bandas que deseas reducir antes de llamar a reduceToVectors():

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

Ten en cuenta que otras formas de reducir los píxeles de una imagen dentro de las zonas de otra incluyen reduceConnectedCommponents() o reductores de agrupación.

Usa fastDistanceTransform() para operaciones de vecindario

Para algunas operaciones de convolución, fastDistanceTransform() puede ser más eficiente que reduceNeighborhood() o convolve(). Por ejemplo, para realizar la erosión o la dilatación de entradas binarias, haz lo siguiente:

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

Usa las optimizaciones en reduceNeighborhood()

Si necesitas realizar una convolución y no puedes usar fastDistanceTransform(), usa las optimizaciones en 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');

No tomes muestras de más datos de los que necesitas.

Resiste la tentación de aumentar el tamaño del conjunto de datos de entrenamiento innecesariamente. Si bien aumentar la cantidad de datos de entrenamiento es una estrategia de aprendizaje automático eficaz en algunas circunstancias, también puede aumentar el costo computacional sin un aumento correspondiente en la precisión. (Para comprender cuándo aumentar el tamaño del conjunto de datos de entrenamiento, consulta esta referencia). En el siguiente ejemplo, se muestra cómo solicitar demasiados datos de entrenamiento puede generar el temido error "El valor calculado es demasiado grande":

No tomes muestras de demasiados datos.

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

El mejor enfoque es comenzar con una cantidad moderada de datos y ajustar los hiperparámetros del clasificador para determinar si puedes lograr la precisión deseada:

Ajusta los hiperparámetros

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

En este ejemplo, el clasificador ya es muy preciso, por lo que no hay mucho que ajustar. Te recomendamos que elijas el árbol más pequeño posible (es decir, el minLeafPopulation más grande) que aún tenga la precisión requerida.

Resultados intermedios de Export

Supongamos que tu objetivo es tomar muestras de una imagen calculada relativamente compleja. A menudo, es más eficiente Export la imagen toAsset(), cargar la imagen exportada y, luego, tomar muestras. Por ejemplo:

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...

En este ejemplo, ten en cuenta que las imágenes se exportan como números de punto flotante. No realices exportaciones con precisión doble, a menos que sea absolutamente necesario. Cuando realices esta exportación, ten en cuenta que un vínculo al editor de código (obtenido inmediatamente antes de la exportación) está incorporado en el nombre del archivo para que se pueda reproducir.

Una vez que se complete la exportación, vuelve a cargar el recurso y continúa con el muestreo. Ten en cuenta que, para la depuración, primero se ejecuta una muestra muy pequeña en un área de prueba muy pequeña. Cuando se muestre que se realizó correctamente, toma una muestra más grande y expórtala. Por lo general, se deben exportar muestras tan grandes. No esperes que esos ejemplos estén disponibles de forma interactiva (por ejemplo, a través de print()) o que se puedan usar (por ejemplo, como entrada para un clasificador) sin exportarlos primero.

Unión frente a filtro de mapa

Supongamos que deseas unir colecciones según la hora, la ubicación o alguna propiedad de metadatos. Por lo general, esto se logra de manera más eficiente con una unión. En el siguiente ejemplo, se realiza una unión espacio-temporal entre las colecciones de Landsat 8 y 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);

Aunque primero debes probar una unión (Export si es necesario), a veces un filter() dentro de un map() también puede ser eficaz, en especial para colecciones muy grandes.

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

Comparación de reduceRegion(), reduceRegions() y el bucle for

Llamar a reduceRegions() con un FeatureCollection muy grande o complejo como entrada puede generar el temido error “El valor calculado es demasiado grande”. Una posible solución es asignar reduceRegion() sobre FeatureCollection. Otra posible solución es usar un (¡uf!) bucle for. Aunque no se recomienda en Earth Engine, como se describe aquí, aquí y aquí, reduceRegion() se puede implementar en un bucle for para realizar grandes reducciones.

Supongamos que tu objetivo es obtener el promedio de píxeles (o cualquier estadística) en cada componente de un FeatureCollection para cada imagen de un ImageCollection. En el siguiente ejemplo, se comparan los tres enfoques descritos anteriormente:

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

Ten en cuenta que se imprime el elemento first() de cada colección para depurar. No esperes que el resultado completo esté disponible de forma interactiva: deberás Export. Ten en cuenta también que los bucles for se deben usar con extrema precaución y solo como último recurso. Por último, el bucle for requiere obtener manualmente el tamaño de la colección de entrada y codificarlo en las ubicaciones adecuadas. Si algo de esto no te resulta claro, no uses un bucle for.

Usa la diferenciación directa para los vecinos en el tiempo

Supongamos que tienes un ImageCollection ordenado de forma temporal (es decir, una serie temporal) y quieres comparar cada imagen con la anterior (o la siguiente). En lugar de usar iterate() para este fin, puede ser más eficiente usar una diferenciación directa basada en un array. En el siguiente ejemplo, se usa este método para quitar los duplicados de la colección de Sentinel-2, en la que los duplicados se definen como imágenes con el mismo día del año:

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

Inspecciona las colecciones impresas para verificar que se hayan quitado los duplicados.