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.