Ce document décrit les pratiques de codage visant à maximiser les chances de réussite des calculs Earth Engine complexes ou coûteux. Les méthodes décrites ici s'appliquent aux calculs interactifs (par exemple, l'éditeur de code) et par lot (Export
), bien que les calculs de longue durée doivent généralement être exécutés dans le système par lot.
Éviter de mélanger les fonctions et les objets client avec les fonctions et les objets serveur
Les objets serveur Earth Engine sont des objets dont les constructeurs commencent par ee
(par exemple, ee.Image
, ee.Reducer
). Toutes les méthodes de ces objets sont des fonctions de serveur. Tout objet qui n'est pas construit de cette manière est un objet client. Les objets client peuvent provenir de l'éditeur de code (par exemple, Map
, Chart
) ou du langage JavaScript (par exemple, Date
, Math
, []
, {}
).
Pour éviter tout comportement involontaire, ne mélangez pas les fonctions client et serveur dans votre script, comme indiqué sur cette page, cette page et cette page. Consultez cette page et/ou ce tutoriel pour en savoir plus sur le client et le serveur dans Earth Engine. L'exemple suivant illustre les dangers de mélanger les fonctionnalités client et serveur:
Erreur : ce code ne fonctionne pas.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Won't work.
for(var i=0; i<table.size(); i++) {
print('No!');
}
Pouvez-vous repérer l'erreur ? Notez que table.size()
est une méthode de serveur sur un objet de serveur et ne peut pas être utilisée avec des fonctionnalités côté client telles que l'instruction conditionnelle <
.
Vous pouvez utiliser des boucles for pour la configuration de l'UI, car les objets et les méthodes ui
de l'éditeur de code sont côté client. (En savoir plus sur la création d'interfaces utilisateur dans Earth Engine) Exemple :
Utilisez des fonctions client pour la configuration de l'UI.
var panel = ui.Panel();
for(var i=1; i<8; i++) {
panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);
À l'inverse, map()
est une fonction de serveur et la fonctionnalité client ne fonctionnera pas dans la fonction transmise à map()
. Exemple :
Erreur : ce code ne fonctionne pas.
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.
});
Pour effectuer une action sur chaque élément d'une collection, map()
une fonction sur la collection et set()
une propriété:
Utilisez map()
et 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());
Vous pouvez également filter()
la collection en fonction de propriétés calculées ou existantes, puis print()
le résultat. Notez que vous ne pouvez pas imprimer une collection de plus de 5 000 éléments. Si l'erreur "Requête de collection interrompue après l'accumulation de plus de 5 000 éléments" s'affiche, filter()
ou limit()
la collection avant l'impression.
Éviter de convertir inutilement en liste
Les collections dans Earth Engine sont traitées à l'aide d'optimisations qui sont interrompues par la conversion de la collection en type List
ou Array
. Sauf si vous avez besoin d'un accès aléatoire aux éléments de la collection (c'est-à-dire que vous avez besoin d'obtenir l'élément i d'une collection), utilisez des filtres sur la collection pour accéder aux éléments individuels de la collection. L'exemple suivant illustre la différence entre la conversion de type (non recommandée) et le filtrage (recommandé) pour accéder à un élément d'une collection:
Ne convertissez pas inutilement en liste.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!!
var list = table.toList(table.size());
print(list.get(13)); // User memory limit exceeded.
Notez que vous pouvez facilement déclencher des erreurs en convertissant inutilement une collection en liste. Le moyen le plus sûr est d'utiliser filter()
:
Utilisez filter()
.
print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());
Notez que vous devez utiliser des filtres dès le début de votre analyse.
Éviter la requête ee.Algorithms.If()
N'utilisez pas ee.Algorithms.If()
pour implémenter une logique d'embranchement, en particulier dans une fonction mappée. Comme l'illustre l'exemple suivant, ee.Algorithms.If()
peut être gourmand en mémoire et n'est pas recommandé:
N'utilisez pas 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.
Notez que le deuxième argument de map()
est true
. Cela signifie que la fonction mappée peut renvoyer des valeurs nulles, qui seront supprimées dans la collection résultante.
Cela peut être utile (sans If()
), mais la solution la plus simple consiste à utiliser un filtre:
Utilisez filter()
.
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
Comme indiqué dans ce tutoriel, une approche de programmation fonctionnelle utilisant des filtres est la bonne façon d'appliquer une logique à certains éléments d'une collection et une autre logique aux autres éléments de la collection.
Éviter la requête reproject()
N'utilisez pas la reprojection, sauf si cela est absolument nécessaire. Vous pouvez utiliser reproject()
pour forcer les calculs de l'éditeur de code à s'effectuer à une échelle spécifique afin d'examiner les résultats à l'échelle d'analyse souhaitée. Dans l'exemple suivant, des zones de pixels chauds sont calculées, et le nombre de pixels dans chaque zone est calculé. Exécutez l'exemple et cliquez sur l'un des correctifs. Notez que le nombre de pixels diffère entre les données reprojetées et celles qui ne l'ont pas été.
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);
Cette différence s'explique par le fait que l'échelle d'analyse est définie par le niveau de zoom de l'éditeur de code. En appelant reproject()
, vous définissez l'échelle du calcul au lieu de l'éditeur de code. Utilisez reproject()
avec une extrême prudence pour les raisons décrites dans ce document.
Filtrer et select()
en premier
En règle générale, filtrez les collections d'entrée par heure, lieu et/ou métadonnées avant d'effectuer toute autre opération avec la collection. Appliquez des filtres plus sélectifs avant des filtres moins sélectifs. Les filtres spatiaux et/ou temporels sont souvent plus sélectifs. Par exemple, notez que select()
et filter()
sont appliqués avant
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');
Utilisez updateMask()
à la place de mask()
.
La différence entre updateMask()
et mask()
est que le premier effectue une and()
logique de l'argument (le nouveau masque) et du masque d'image existant, tandis que mask()
remplace simplement le masque d'image par l'argument. Le danger de cette dernière méthode est que vous pouvez démasquer des pixels de manière involontaire. Dans cet exemple, l'objectif est de masquer les pixels dont l'altitude est inférieure ou égale à 300 mètres. Comme vous pouvez le voir (dézoomez), l'utilisation de mask()
entraîne le masquage de nombreux pixels qui n'appartiennent pas à l'image d'intérêt:
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);
Combiner des réducteurs
Si vous avez besoin de plusieurs statistiques (par exemple, la moyenne et l'écart-type) à partir d'une seule entrée (par exemple, une région d'image), il est plus efficace de combiner des réducteurs. Par exemple, pour obtenir des statistiques sur les images, combinez les réducteurs comme suit:
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');
Dans cet exemple, notez que le réducteur de la moyenne est combiné au réducteur de l'écart type et que sharedInputs
est défini sur "true" pour permettre un seul passage dans les pixels d'entrée. Dans le dictionnaire de sortie, le nom du réducteur est ajouté au nom de la bande. Pour obtenir des images de la moyenne et de l'écart-type (par exemple, pour normaliser l'image d'entrée), vous pouvez transformer les valeurs en image et utiliser des expressions régulières pour extraire les moyennes et les écarts-types individuellement, comme illustré dans l'exemple.
Utiliser Export
Pour les calculs qui génèrent des erreurs "Limite de mémoire utilisateur dépassée" ou "Le calcul a expiré" dans l'éditeur de code, les mêmes calculs peuvent réussir en utilisant Export
. En effet, les délais avant expiration sont plus longs et l'empreinte mémoire autorisée est plus importante lors de l'exécution dans le système par lot (où les exportations s'exécutent). (Vous pouvez d'abord essayer d'autres approches, comme indiqué dans la documentation de débogage.) Poursuivons l'exemple précédent. Supposons que le dictionnaire ait renvoyé une erreur. Vous pouvez obtenir les résultats en procédant comme suit:
var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
collection: ee.FeatureCollection([ee.Feature(null, stats)]),
description: 'exported_stats_demo_' + link,
fileFormat: 'CSV'
});
Notez que le lien est intégré au nom de l'asset, à des fins de reproductibilité. Notez également que si vous souhaitez exporter toAsset
, vous devez fournir une géométrie, qui peut être n'importe quoi, par exemple le centre de gravité de l'image, qui est petit et peu coûteux à calculer. (par exemple, n'utilisez pas de géométrie complexe si vous n'en avez pas besoin).
Consultez la page de débogage pour voir des exemples d'utilisation de Export
pour résoudre les problèmes Le calcul a expiré et Trop d'agrégations simultanées. Pour en savoir plus sur l'exportation en général, consultez ce document.
Si vous n'avez pas besoin de couper, n'utilisez pas clip()
.
L'utilisation inutile de clip()
allonge le temps de calcul. Évitez clip()
, sauf si cela est nécessaire à votre analyse. En cas de doute, ne créez pas de clip. Exemple d'utilisation incorrecte d'un extrait:
N'écourtez pas les entrées inutilement.
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);
Vous pouvez complètement ignorer le recadrage des images d'entrée, car la région est spécifiée dans l'appel reduceRegion()
:
Spécifiez la région pour la sortie.
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 ce calcul expire, Export
le calcul comme dans cet exemple.
Si vous devez découper avec une collection complexe, utilisez clipToCollection()
.
Si vous devez vraiment découper quelque chose et que les géométries que vous souhaitez utiliser pour la découpe se trouvent dans une collection, utilisez 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'utilisez PAS featureCollection.geometry()
ou featureCollection.union()
sur des collections volumineuses et/ou complexes, qui peuvent être plus gourmandes en mémoire.
N'utilisez pas de collection complexe comme région pour un réducteur.
Si vous devez effectuer une réduction spatiale de sorte que le réducteur regroupe les entrées de plusieurs régions dans un FeatureCollection
, n'indiquez pas featureCollection.geometry()
comme entrée geometry
au réducteur. Utilisez plutôt clipToCollection()
et une région suffisamment grande pour englober les limites de la collection. Exemple :
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.
Utiliser un errorMargin
non nul
Pour les opérations géométriques potentiellement coûteuses, utilisez la plus grande marge d'erreur possible compte tenu de la précision requise du calcul. La marge d'erreur spécifie l'erreur maximale autorisée (en mètres) lors des opérations sur les géométries (par exemple, lors de la reprojection). Spécifier une petite marge d'erreur peut nécessiter de densifier les géométries (avec des coordonnées), ce qui peut être gourmand en mémoire. Il est recommandé de spécifier une marge d'erreur aussi large que possible pour votre calcul:
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'utilisez pas une échelle ridiculement petite avec reduceToVectors()
.
Si vous souhaitez convertir un raster en vecteur, utilisez une échelle appropriée. Spécifier une échelle très petite peut augmenter considérablement les coûts de calcul. Définissez l'échelle aussi élevée que possible pour obtenir la précision requise. Par exemple, pour obtenir des polygones représentant les masses terrestres mondiales:
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');
Dans l'exemple précédent, notez l'utilisation d'un polygone non géodésique pour les réductions globales.
Ne pas utiliser reduceToVectors()
avec reduceRegions()
N'utilisez pas un FeatureCollection
renvoyé par reduceToVectors()
comme entrée pour reduceRegions()
. Ajoutez plutôt les bandes que vous souhaitez réduire avant d'appeler 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));
Notez que d'autres méthodes permettent de réduire les pixels d'une image dans les zones d'une autre, par exemple reduceConnectedCommponents() et/ou les réducteurs de regroupement.
Utiliser fastDistanceTransform()
pour les opérations de voisinage
Pour certaines opérations de convolution, fastDistanceTransform()
peut être plus efficace que reduceNeighborhood()
ou convolve()
. Par exemple, pour effectuer une érosion et/ou une dilatation des entrées binaires:
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');
Utiliser les optimisations dans reduceNeighborhood()
Si vous devez effectuer une convolution et que vous ne pouvez pas utiliser fastDistanceTransform()
, utilisez les optimisations de 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');
Ne pas échantillonner plus de données que nécessaire
Résistez à l'envie d'augmenter inutilement la taille de votre ensemble de données d'entraînement. Bien que l'augmentation de la quantité de données d'entraînement soit une stratégie de machine learning efficace dans certains cas, elle peut également augmenter les coûts de calcul sans augmenter la précision correspondante. (Pour savoir quand augmenter la taille de l'ensemble de données d'entraînement, consultez cette référence.) L'exemple suivant montre comment demander trop de données d'entraînement peut entraîner l'erreur redoutée "Valeur calculée trop élevée" :
N'échantillonnez pas trop de données.
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
La meilleure approche consiste à commencer avec une quantité modérée de données et à ajuster les hyperparamètres du classificateur pour déterminer si vous pouvez atteindre la précision souhaitée:
Réglez les hyperparamètres.
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
}));
Dans cet exemple, le classificateur est déjà très précis. Il n'y a donc pas beaucoup de réglages à effectuer. Vous pouvez choisir l'arbre le plus petit possible (c'est-à-dire le plus grand minLeafPopulation
) qui conserve la précision requise.
Export
résultats intermédiaires
Supposons que votre objectif soit de prélever des échantillons à partir d'une image calculée relativement complexe. Il est souvent plus efficace d'Export
l'image toAsset()
, de charger l'image exportée, puis d'échantillonner. Exemple :
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...
Dans cet exemple, notez que les images sont exportées en tant que flottantes. N'exportez pas à double précision, sauf si cela est absolument nécessaire. Lors de cette exportation, notez qu'un lien vers l'éditeur de code (obtenu immédiatement avant l'exportation) est intégré au nom de fichier pour permettre la reproductibilité.
Une fois l'exportation terminée, rechargez l'élément et procédez à l'échantillonnage. Notez qu'un très petit échantillon sur une très petite zone de test est exécuté en premier, pour le débogage. Une fois que vous avez vérifié que l'opération a réussi, prenez un échantillon plus important et exportez-le.
Ces échantillons volumineux doivent généralement être exportés. Ne vous attendez pas à ce que ces exemples soient disponibles de manière interactive (par exemple via print()
) ou utilisables (par exemple en tant qu'entrée pour un classificateur) sans les exporter au préalable.
Join et map-filter
Supposons que vous souhaitiez joindre des collections en fonction de l'heure, de l'emplacement ou d'une propriété de métadonnées. En général, la méthode la plus efficace consiste à utiliser une jointure. L'exemple suivant effectue une jointure spatio-temporelle entre les collections Landsat 8 et 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);
Bien que vous deviez d'abord essayer une jointure (Export
si nécessaire), une filter()
dans une map()
peut parfois également être efficace, en particulier pour de très grandes collections.
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()
par rapport à reduceRegions()
et à la boucle for
Appeler reduceRegions()
avec un FeatureCollection
très volumineux ou complexe en entrée peut entraîner l'erreur redoutée "La valeur calculée est trop élevée". Une solution potentielle consiste à mapper reduceRegion()
sur FeatureCollection
à la place. Une autre solution consiste à utiliser une boucle for (oh ! mon dieu !). Bien que cette pratique soit fortement déconseillée dans Earth Engine, comme décrit dans cette page, cette page et cette page, reduceRegion()
peut être implémenté dans une boucle for pour effectuer de grandes réductions.
Supposons que votre objectif soit d'obtenir la moyenne des pixels (ou toute autre statistique) dans chaque élément géographique d'un FeatureCollection
pour chaque image d'un ImageCollection
.
L'exemple suivant compare les trois approches décrites précédemment:
// 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());
Notez que l'élément first()
de chaque collection est imprimé à des fins de débogage. Ne vous attendez pas à ce que le résultat complet soit disponible de manière interactive: vous devrez Export
. Notez également que les boucles for doivent être utilisées avec une extrême prudence et uniquement en dernier recours. Enfin, la boucle for nécessite d'obtenir manuellement la taille de la collection d'entrée et de la coder en dur aux emplacements appropriés. Si vous ne comprenez pas tout, n'utilisez pas de boucle for.
Utiliser la différentiation temporelle pour les voisins
Supposons que vous disposiez d'une ImageCollection
triée temporellement (c'est-à-dire d'une série temporelle) et que vous souhaitiez comparer chaque image à l'image précédente (ou suivante). Plutôt que d'utiliser iterate()
à cette fin, il peut être plus efficace d'utiliser une différenciation directe basée sur un tableau. L'exemple suivant utilise cette méthode pour dédupliquer la collection Sentinel-2, où les doublons sont définis comme des images correspondant au même jour de l'année:
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));
Inspectez les collections imprimées pour vérifier que les doublons ont été supprimés.