Práticas recomendadas de programação

Este documento descreve práticas de programação que visam maximizar a chance de sucesso para cálculos complexos ou caros do Earth Engine. Os métodos descritos aqui são aplicáveis a cálculos interativos (por exemplo, o Editor de código) e em lote (Export), embora cálculos de longa duração geralmente precisem ser executados no sistema em lote.

Evite misturar funções e objetos do cliente com funções e objetos do servidor

Os objetos do servidor do Earth Engine são objetos com construtores que começam com ee (por exemplo, ee.Image, ee.Reducer) e todos os métodos nesses objetos são funções do servidor. Qualquer objeto que não seja construído dessa maneira é um objeto cliente. Os objetos do cliente podem vir do editor de código (por exemplo, Map, Chart) ou da linguagem JavaScript (por exemplo, Date, Math, [], {}).

Para evitar comportamentos indesejados, não misture funções de cliente e servidor no script, conforme discutido aqui, aqui e aqui. Consulte esta página e/ou este tutorial para uma explicação detalhada sobre a diferença entre cliente e servidor no Earth Engine. O exemplo a seguir ilustra os perigos de misturar a funcionalidade do cliente e do servidor:

Erro: este código não funciona.

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

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

Você percebeu o erro? table.size() é um método do servidor em um objeto do servidor e não pode ser usado com a funcionalidade do lado do cliente, como o condicional <.

Uma situação em que você pode querer usar loops for é na configuração da interface, já que os objetos e métodos ui do editor de código são do lado do cliente. Saiba mais sobre como criar interfaces do usuário no Earth Engine. Exemplo:

Usar funções do cliente para a configuração da interface.

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

Por outro lado, map() é uma função do servidor, e a funcionalidade do cliente não vai funcionar dentro da função transmitida para map(). Exemplo:

Erro: este código não 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 fazer algo em cada elemento de uma coleção, map() uma função sobre a coleção e set() uma propriedade:

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

Também é possível filter() a coleção com base em propriedades computadas ou existentes e print() o resultado. Não é possível imprimir uma coleção com mais de 5.000 elementos. Se você receber o erro "A consulta da coleção foi abortada após acumular mais de 5.000 elementos", filter() ou limit() a coleção antes da impressão.

Evite converter em lista desnecessariamente

As coleções no Earth Engine são processadas usando otimizações que são divididas ao converter a coleção em um tipo List ou Array. A menos que você precise de acesso aleatório aos elementos da coleção (ou seja, precisa de acesso ao elemento i de uma coleção), use filtros na coleção para acessar elementos individuais da coleção. O exemplo a seguir ilustra a diferença entre a conversão de tipo (não recomendada) e a filtragem (recomendada) para acessar um elemento em uma coleção:

Não converta em lista sem necessidade.

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.

É possível acionar erros facilmente convertendo uma coleção em uma lista desnecessariamente. A forma mais segura é usar filter():

Use filter().

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

É recomendável usar filtros o mais cedo possível na análise.

Evite ee.Algorithms.If()

Não use ee.Algorithms.If() para implementar a lógica de ramificação, principalmente em uma função mapeada. Como o exemplo a seguir ilustra, ee.Algorithms.If() pode exigir muita memória e não é recomendado:

Não use 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.

O segundo argumento de map() é true. Isso significa que a função mapeada pode retornar valores nulos, que serão descartados na coleção resultante. Isso pode ser útil (sem If()), mas aqui a solução mais fácil é usar um filtro:

Use filter().

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

Como mostrado em este tutorial, uma abordagem de programação funcional usando filtros é a maneira correta de aplicar uma lógica a alguns elementos de uma coleção e outra lógica aos outros elementos da coleção.

Evite reproject()

Não use reproject, a menos que seja absolutamente necessário. Uma das razões para usar reproject() é forçar as computações do editor de código a acontecer em uma escala específica para que você possa examinar os resultados na escala de análise desejada. No exemplo a seguir, os patches de pixels quentes são calculados e a contagem de pixels em cada patch é calculada. Execute o exemplo e clique em um dos patches. A contagem de pixels é diferente entre os dados reprojetados e os que não foram reprojetados.

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

A discrepância ocorre porque a escala de análise é definida pelo nível de zoom do editor de código. Ao chamar reproject(), você define a escala da computação em vez do editor de código. Use reproject() com muito cuidado pelos motivos descritos neste documento.

Filtrar e select() primeiro

Em geral, filtre as coleções de entrada por hora, local e/ou metadados antes de fazer qualquer outra coisa com a coleção. Aplique filtros mais seletivos antes de filtros menos seletivos. Os filtros espaciais e/ou temporais geralmente são mais seletivos. Por exemplo, select() e filter() são aplicados antes 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');

Usar updateMask() em vez de mask()

A diferença entre updateMask() e mask() é que o primeiro faz uma and() lógica do argumento (a nova máscara) e a máscara de imagem existente, enquanto mask() simplesmente substitui a máscara de imagem pelo argumento. O perigo do último é que você pode desmascarar pixels acidentalmente. Neste exemplo, o objetivo é mascarar pixels com elevação menor ou igual a 300 metros. Como você pode ver (com zoom para fora), o uso de mask() faz com que muitos pixels sejam desmascarados, pixels que não pertencem à imagem de 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);

Combinar redutores

Se você precisar de várias estatísticas (por exemplo, média e desvio padrão) de uma única entrada (por exemplo, uma região de imagem), será mais eficiente combinar redutores. Por exemplo, para receber estatísticas de imagem, combine os redutores da seguinte maneira:

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

Neste exemplo, o redutor médio é combinado com o redutor de desvio padrão, e sharedInputs é verdadeiro para permitir uma única passagem pelos pixels de entrada. No dicionário de saída, o nome do redutor é anexado ao nome da banda. Para receber imagens de média e desvio padrão (por exemplo, para normalizar a imagem de entrada), você pode transformar os valores em uma imagem e usar expressões regulares para extrair médias e desvios padrão individualmente, conforme demonstrado no exemplo.

Usar Export

Para cálculos que resultam em erros "Limite de memória do usuário excedido" ou "Tempo limite de cálculo excedido" no editor de código, as mesmas computações podem ser sucedidas usando Export. Isso acontece porque os tempos limite são mais longos e o volume de memória permitido é maior quando executado no sistema de lote (onde as exportações são executadas). Há outras abordagens que você pode tentar primeiro, conforme detalhado no documento de depuração. Continuando o exemplo anterior, suponha que o dicionário tenha retornado um erro. Você pode receber os resultados fazendo algo como:

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

O link é incorporado ao nome do recurso para que ele possa ser reproduzido. Além disso, se você quiser exportar toAsset, será necessário fornecer uma geometria, que pode ser qualquer coisa, por exemplo, o centroide da imagem, que é pequeno e barato para computar. ou seja, não use uma geometria complexa se não precisar dela.

Consulte a página de depuração para conferir exemplos de como usar Export para resolver Tempo limite de computação e Muitas agregações simultâneas. Consulte este documento para saber mais sobre exportação em geral.

Se você não precisar de clipes, não use clip()

O uso desnecessário de clip() aumenta o tempo de computação. Evite usar clip(), a menos que seja necessário para sua análise. Se você não tiver certeza, não recorte. Um exemplo de uso indevido do clipe:

Não corte entradas desnecessariamente.

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

O recorte das imagens de entrada pode ser totalmente ignorado, porque a região é especificada na chamada reduceRegion():

Especifique a região para a saída.

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 esse cálculo expirar, Export, como neste exemplo.

Se você precisar cortar com uma coleção complexa, use clipToCollection()

Se você realmente precisar recortar algo e as geometrias que você quer usar para o recorte estiverem em uma coleção, use 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);

NÃO use featureCollection.geometry() ou featureCollection.union() em coleções grandes e/ou complexas, que podem exigir mais memória.

Não use uma coleção complexa como a região de um redutor

Se você precisar fazer uma redução espacial para que o redutor agrupe entradas de várias regiões em um FeatureCollection, não forneça featureCollection.geometry() como a entrada geometry para o redutor. Em vez disso, use clipToCollection() e uma região grande o suficiente para abranger os limites da coleção. Exemplo:

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.

Use um errorMargin diferente de zero

Para operações de geometria possivelmente caras, use a maior margem de erro possível, considerando a precisão necessária do cálculo. A margem de erro especifica o erro máximo permitido (em metros) durante as operações em geometrias (por exemplo, durante a reprojeção). Especificar uma pequena margem de erro pode resultar na necessidade de aumentar a densidade de geometrias (com coordenadas), o que pode exigir muita memória. É recomendável especificar a maior margem de erro possível para a computação:

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

Não use uma escala ridiculamente pequena com reduceToVectors()

Se você quiser converter um raster em um vetor, use uma escala adequada. Especificar uma escala muito pequena pode aumentar substancialmente o custo de computação. Defina a escala mais alta possível para fornecer a precisão necessária. Por exemplo, para receber polígonos que representam massas terrestres globais:

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

No exemplo anterior, observe o uso de um polígono não geodésico para uso em reduções globais.

Não use reduceToVectors() com reduceRegions()

Não use um FeatureCollection retornado por reduceToVectors() como uma entrada para reduceRegions(). Em vez disso, adicione as faixas que você quer reduzir antes de chamar 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));

Outras maneiras de reduzir os pixels de uma imagem dentro das zonas de outra incluem reduceConnectedCommponents() e/ou grupos de redutores.

Use fastDistanceTransform() para operações de vizinhança

Para algumas operações de convolução, fastDistanceTransform() pode ser mais eficiente do que reduceNeighborhood() ou convolve(). Por exemplo, para fazer erosão e/ou dilatação de entradas binárias:

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

Usar as otimizações em reduceNeighborhood()

Se você precisar realizar uma convolução e não puder usar fastDistanceTransform(), use as otimizações em 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');

Não coletar mais dados do que o necessário

Não aumente o tamanho do conjunto de dados de treinamento sem necessidade. Embora aumentar a quantidade de dados de treinamento seja uma estratégia eficaz de aprendizado de máquina em algumas circunstâncias, isso também pode aumentar o custo computacional sem um aumento correspondente na precisão. Para entender quando aumentar o tamanho do conjunto de dados de treinamento, consulte esta referência. O exemplo a seguir demonstra como solicitar muitos dados de treinamento pode resultar no temido erro "O valor computado é muito grande":

Não use amostras com muitos dados.

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

A melhor abordagem é começar com uma quantidade moderada de dados e ajustar os hiperparâmetros do classificador para determinar se você pode alcançar a precisão desejada:

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

Nesse exemplo, o classificador já é muito preciso, então não é necessário fazer muitos ajustes. Talvez seja melhor escolher a árvore menor possível (ou seja, a maior minLeafPopulation) que ainda tenha a precisão necessária.

Export resultados intermediários

Suponha que seu objetivo seja tirar amostras de uma imagem computacional relativamente complexa. Muitas vezes, é mais eficiente Export a imagem toAsset(), carregar a imagem exportada e, em seguida, fazer a amostragem. Exemplo:

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

Neste exemplo, as imagens são exportadas como flutuantes. Não exporte com precisão dupla, a menos que seja absolutamente necessário. Ao fazer essa exportação, observe que um link do Code Editor (obtido imediatamente antes da exportação) é incorporado ao nome do arquivo para que ele possa ser reproduzido.

Quando a exportação for concluída, recarregue o recurso e prossiga com a amostragem. Uma amostra muito pequena em uma área de teste muito pequena é executada primeiro para depurar. Quando isso for mostrado como sucesso, colete uma amostra maior e exporte. Essas amostras grandes geralmente precisam ser exportadas. Não espere que essas amostras fiquem disponíveis de forma interativa (por exemplo, pelo print()) ou utilizáveis (por exemplo, como entrada para um classificador) sem exportá-las primeiro.

Junção x filtro de mapa

Digamos que você queira mesclar coleções com base no tempo, no local ou em alguma propriedade de metadados. Geralmente, isso é feito de maneira mais eficiente com uma mesclagem. O exemplo a seguir faz uma junção espaço-temporal entre as coleções 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);

Embora seja recomendável tentar uma mesclagem primeiro (Export, se necessário), às vezes, um filter() em um map() também pode ser eficaz, principalmente para coleções muito 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);

reduceRegion() x reduceRegions() x loop for

Chamar reduceRegions() com um FeatureCollection muito grande ou complexo como entrada pode resultar no temido erro "O valor computado é muito grande". Uma solução possível é mapear reduceRegion() sobre o FeatureCollection. Outra solução possível é usar um laço for. Embora isso seja desencorajado no Earth Engine, conforme descrito aqui, aqui e aqui, reduceRegion() pode ser implementado em um loop for para realizar grandes reduções.

Suponha que seu objetivo seja obter a média dos pixels (ou qualquer estatística) em cada elemento em um FeatureCollection para cada imagem em um ImageCollection. O exemplo a seguir compara as três abordagens descritas 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());

A entidade first() de cada coleção é impressa para fins de depuração. Não espere que o resultado completo esteja disponível de forma interativa: você vai precisar Export. Além disso, os loops for precisam ser usados com extrema cautela e apenas como último recurso. Por fim, o laço for requer obter manualmente o tamanho da coleção de entrada e fixá-lo nos locais apropriados. Se alguma dessas informações não estiver clara para você, não use um loop for.

Usar a diferença de avanço para vizinhos no tempo

Suponha que você tenha uma ImageCollection classificada temporalmente (ou seja, uma série temporal) e queira comparar cada imagem com a anterior (ou a próxima). Em vez de usar iterate() para essa finalidade, pode ser mais eficiente usar uma diferenciação direta baseada em matriz. O exemplo a seguir usa esse método para eliminar duplicações da coleção Sentinel-2, em que as duplicações são definidas como imagens com o mesmo dia do ano:

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

Inspecione as coleções impressas para verificar se as duplicatas foram removidas.