W tym dokumencie opisano metody kodowania, które mają na celu zmaksymalizowanie szans na powodzenie złożonych i drogich obliczeń w Earth Engine. Opisane tu metody są stosowane zarówno w przypadku obliczeń interaktywnych (np. w Edytorze kodu), jak i obliczeń zbiorczych (Export
), choć ogólnie obliczenia długotrwałe powinny być wykonywane w systemie zbiorczym.
Unikaj mieszania funkcji i obiektów po stronie klienta z funkcjami i obiektmi po stronie serwera.
Obiekty serwera Earth Engine to obiekty z konstruktorami zaczynającymi się od ee
(np. ee.Image
, ee.Reducer
), a wszystkie metody tych obiektów to funkcje serwera. Każdy obiekt, który nie został utworzony w ten sposób, jest obiektem klienta. Obiekty klienta mogą pochodzić z Edytora kodu (np. Map
, Chart
) lub języka JavaScript (np. Date
, Math
, []
, {}
).
Aby uniknąć niezamierzonych działań, nie mieszaj w skrypcie funkcji klienta i serwera, jak opisano tutaj, tutaj i tutaj. Aby uzyskać szczegółowe informacje na temat różnic między klientem a serwerem w Earth Engine, zapoznaj się z tą stroną lub tym samouczkiem. Poniższy przykład pokazuje, jakie zagrożenia niesie ze sobą łączenie funkcji klienta i serwera:
Błąd – ten kod nie działa.
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Won't work.
for(var i=0; i<table.size(); i++) {
print('No!');
}
Czy widzisz błąd? Pamiętaj, że table.size()
to metoda serwera w obiekcie serwera i nie można jej używać z funkcjami po stronie klienta, takimi jak instrukcja <
.
Pętli for możesz używać podczas konfigurowania interfejsu użytkownika, ponieważ obiekty i metody ui
w Code Editor są po stronie klienta. (więcej informacji o tworzeniu interfejsów użytkownika w Earth Engine). Na przykład:
Używanie funkcji klienta do konfigurowania interfejsu użytkownika.
var panel = ui.Panel();
for(var i=1; i<8; i++) {
panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);
Z kolei map()
to funkcja serwera, więc funkcje klienta nie będą działać w ramach funkcji przekazanej do map()
. Na przykład:
Błąd – ten kod nie działa.
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.
});
Aby wykonać jakąś czynność na każdym elemencie w kolekcji, map()
funkcja na kolekcji i set()
właściwość:
Użyj właściwości map()
i 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());
Możesz też filter()
kolekcję na podstawie właściwości obliczanych lub istniejących oraz print()
wynik. Pamiętaj, że nie możesz wydrukować kolekcji zawierającej więcej niż 5000 elementów. Jeśli pojawi się błąd „Collection query aborted after accumulating over
5000 elements”, filter()
lub limit()
kolekcji przed wydrukowaniem.
Unikaj niepotrzebnego konwertowania na listę
Zbiory w Earth Engine są przetwarzane za pomocą optymalizacji, które są dzielone przez konwersję zbioru na typ List
lub Array
. Jeśli nie potrzebujesz losowego dostępu do elementów kolekcji (czyli nie musisz pobierać i-tego elementu kolekcji), użyj filtrów kolekcji, aby uzyskać dostęp do poszczególnych elementów kolekcji. W tym przykładzie pokazano różnicę między konwersją typu (niezalecana) a filtrowaniem (zalecane) w celu uzyskania dostępu do elementu w zbiorze:
Nie konwertuj niepotrzebnie na listę!
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.
Pamiętaj, że niepotrzebne konwertowanie kolekcji na listę może łatwo wywołać błędy. Bezpieczniejszym sposobem jest użycie filter()
:
Użyj filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());
Pamiętaj, że filtry należy stosować w analizie jak najwcześniej.
Unikaj ee.Algorithms.If()
Nie używaj funkcji ee.Algorithms.If()
do implementowania logiki rozgałęzienia, zwłaszcza w przypadku funkcji mapowanej. Jak pokazuje poniższy przykład, funkcja ee.Algorithms.If()
może zajmować dużo pamięci i nie jest zalecana:
Nie używaj 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.
Drugi argument funkcji map()
to true
. Oznacza to, że zmapowana funkcja może zwracać wartości null, które zostaną pominięte w kolekcji wynikowej.
Może to być przydatne (bez If()
), ale najprostszym rozwiązaniem jest użycie filtra:
Użyj filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
Jak pokazano w tym samouczku, podejście programowania funkcyjnego z użyciem filtrów to właściwy sposób na zastosowanie jednej logiki do niektórych elementów kolekcji i innej logiki do pozostałych elementów kolekcji.
Unikaj reproject()
Nie używaj funkcji ponownego projektowania, chyba że jest to absolutnie konieczne. Jednym z powodów, dla których warto używać reproject()
, jest wymuszenie obliczeń w Edytorze kodu na określonym poziomie skali, aby można było analizować wyniki w pożądanej skali. W następnym przykładzie obliczamy obszary gorących pikseli i liczbę pikseli w każdym z nich. Uruchom przykład i kliknij jedną z łat. Pamiętaj, że liczba pikseli różni się między danymi z ponowną projekcją a danymi bez ponownej projekcji.
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);
Ta rozbieżność wynika z tego, że skale analizy nadaje poziom powiększenia w Edytorze kodu. Wywołując funkcję reproject()
, ustawiasz skalę obliczeń zamiast edytora kodu. Z usługi reproject()
należy korzystać z bardzo dużą ostrożnością z powodu powodów opisanych w tym dokumencie.
Najpierw filtrowanie i select()
Zasadniczo przed wykonaniem jakichkolwiek innych działań na kolekcji należy odfiltrować kolekcje danych według czasu, lokalizacji lub metadanych. Stosuj bardziej selektywne filtry przed mniej selektywne. Filtry przestrzenne lub czasowe są często bardziej selektywne. Zwróć uwagę, że select()
i filter()
są stosowane przed
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');
Użyj updateMask()
zamiast mask()
Różnica między funkcjami updateMask()
i mask()
polega na tym, że pierwsza z nich wykonuje logiczną operację and()
argumentu (nowej maski) i dotychczasowej maski obrazu, podczas gdy funkcja mask()
po prostu zastępuje maskę obrazu argumentem. Niebezpieczeństwo polega na tym, że możesz niezamierzenie odsłonić piksele. W tym przykładzie celem jest zamaskowanie pikseli o wysokości mniejszej lub równej 300 metrom. Jak widać (powiększenie), użycie mask()
powoduje odsłonięcie wielu pikseli, które nie należą do interesującego obrazu:
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);
Łączenie ograniczników
Jeśli potrzebujesz kilku statystyk (np. średniej i odchylenia standardowego) z jednego wejścia (np. regionu obrazu), bardziej wydajne jest połączenie reduktora. Aby na przykład uzyskać statystyki dotyczące obrazów, połącz reduktor w ten sposób:
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');
W tym przykładzie reduktor średniej jest połączony z reduktorem odchylenia standardowego, a wartość sharedInputs
ma wartość Prawda, co umożliwia wykonanie pojedynczego przejścia przez piksele wejściowe. W słowniku wyjściowym nazwa reduktora jest dołączana do nazwy pasma. Aby uzyskać obrazy średniej i SD (np. w celu normalizacji obrazu wejściowego), możesz przekształcić wartości w obraz i użyć wyrażeń regularnych do wyodrębnienia średnich i SD osobno, jak pokazano w tym przykładzie.
Użyj konta Export
W przypadku obliczeń, które w Edytorze kodu powodują błędy „Przekroczony limit pamięci użytkownika” lub „Przekroczono limit czasu obliczeń”, te same obliczenia mogą się udać, jeśli użyjesz funkcji Export
. Dzieje się tak, ponieważ limity czasu są dłuższe, a dopuszczalny rozmiar pamięci jest większy w systemie zbiorczym (gdzie wykonywane są eksporty). (W doku dotyczącym debugowania znajdziesz inne podejścia, które warto wypróbować najpierw). Wracając do poprzedniego przykładu, załóżmy, że słownik zwrócił błąd. Możesz uzyskać te wyniki, wykonując te czynności:
var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
collection: ee.FeatureCollection([ee.Feature(null, stats)]),
description: 'exported_stats_demo_' + link,
fileFormat: 'CSV'
});
Pamiętaj, że link jest umieszczony w nazwie zasobu, aby można było go odtworzyć. Pamiętaj też, że jeśli chcesz wyeksportować toAsset
, musisz podać geometrię, która może być dowolna, na przykład centroid obrazu, który jest mały i tani w obliczaniu. (czyli nie używaj złożonej geometrii, jeśli jej nie potrzebujesz).
Na stronie debugowania znajdziesz przykłady użycia parametru Export
do rozwiązywania problemów z przekroczonym czasem oczekiwania na obliczenia i zbyt dużą liczbą równoczesnych agregacji. Więcej informacji o eksportowaniu danych znajdziesz w tym dokumencie.
Jeśli nie musisz przycinać, nie używaj clip()
Niepotrzebne używanie funkcji clip()
wydłuża czas obliczeń. Unikaj clip()
, chyba że jest to konieczne do analizy. Jeśli nie masz pewności, nie przycinaj. Przykład niewłaściwego użycia klipu:
Nie skracaj niepotrzebnie danych wejściowych.
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);
Przycinanie obrazów wejściowych można całkowicie pominąć, ponieważ region jest określony w wywołaniu funkcji reduceRegion()
:
Określ region danych wyjściowych.
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);
Jeśli obliczenia przekroczą limit czasu, Export
je tak, jak w tym
przykładzie.
Jeśli chcesz utworzyć klip z skomplikowaną kolekcją, użyj clipToCollection()
Jeśli naprawdę musisz coś przyciąć, a geometrie, których chcesz użyć do przycięcia, znajdują się w kolekcji, użyj 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);
NIE używaj opcji featureCollection.geometry()
ani featureCollection.union()
w przypadku dużych lub złożonych kolekcji, które mogą wymagać więcej pamięci.
Nie używaj złożonej kolekcji jako regionu w reductorze
Jeśli chcesz zastosować redukcję przestrzenną, aby zreduktor mógł łączyć dane wejściowe z wielu regionów w FeatureCollection
, nie podawaj wartości featureCollection.geometry()
jako danych wejściowych geometry
dla reduktora. Zamiast tego użyj clipToCollection()
i obszaru wystarczająco dużego, aby objąć granice kolekcji. Na przykład:
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.
Użyj wartości errorMargin
różnej od 0.
W przypadku operacji geometrycznych, które mogą być kosztowne, użyj największego możliwego marginesu błędu przy wymaganej dokładności obliczeń. Margines błędu określa maksymalny dopuszczalny błąd (w metrach) dozwolony podczas operacji na geometriach (np. podczas ponownej projekcji). Określenie małego marginesu błędu może spowodować konieczność zagęszczenia geometrii (za pomocą współrzędnych), co może wymagać dużej ilości pamięci. Dobrą praktyką jest określanie jak największego marginesu błędu w obliczeniach:
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');
Nie używaj śmiesznie małej skali w przypadku reduceToVectors()
Jeśli chcesz przekonwertować obraz rastrowy na wektorowy, użyj odpowiedniej skali. Podanie bardzo małej skali może znacznie zwiększyć koszt obliczeń. Ustaw skalę na jak najwyższą, aby uzyskać wymaganą dokładność. Aby na przykład uzyskać wielokąty reprezentujące ląd na całym świecie:
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');
W poprzednim przykładzie zwróć uwagę na użycie wielokąta niegeodezyjnego do globalnych redukcji.
Nie używaj reduceToVectors()
w przypadku reduceRegions()
Nie używaj wartości FeatureCollection
zwracanej przez funkcję reduceToVectors()
jako danych wejściowych funkcji reduceRegions()
. Zamiast tego dodaj pasma, które chcesz ograniczyć, zanim zaczniesz dzwonić:
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));
Inne sposoby zmniejszania liczby pikseli jednego obrazu w strefach innego obrazu to reduceConnectedCommponents() lub grouping reducers.
Używanie aplikacji fastDistanceTransform()
do operacji dotyczących sąsiedztwa
W przypadku niektórych operacji konwekcji fastDistanceTransform()
może być wydajniejsze niż reduceNeighborhood()
lub convolve()
. Na przykład aby wykonać erozje lub rozszerzenie binarnych danych wejściowych:
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');
Korzystanie z optymalizacji w sekcji reduceNeighborhood()
Jeśli musisz wykonać splot i nie możesz użyć funkcji fastDistanceTransform()
, użyj optymalizacji w 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');
Nie pobieraj więcej danych niż potrzebujesz
Nie zwiększaj niepotrzebnie rozmiaru zbioru danych treningowych. Wzrost ilości danych treningowych to skuteczna strategia uczenia maszynowego, ale w niektórych okolicznościach może też zwiększyć koszty obliczeniowe bez odpowiedniego wzrostu dokładności. (Informacje o tym, kiedy należy zwiększyć rozmiar zbioru danych do trenowania, znajdziesz w tym dokumencie referencyjnym). Poniższy przykład pokazuje, jak wysłanie żądania zbyt dużej ilości danych do trenowania może spowodować wystąpienie niechcianego błędu „Obliczona wartość jest zbyt duża”:
Nie wybieraj zbyt dużej ilości danych.
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
Lepszym podejściem jest rozpoczęcie od umiarkowanej ilości danych i dostosowanie hiperparametrów klasyfikatora, aby określić, czy możesz osiągnąć pożądaną dokładność:
Dostosuj hiperparametry
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
}));
W tym przykładzie klasyfikator jest już bardzo dokładny, więc nie trzeba go zbytnio dostosowywać. Możesz wybrać jak najmniejsze drzewo (czyli największe minLeafPopulation
), które nadal ma wymaganą dokładność.
Export
wyników pośrednich
Załóżmy, że Twoim celem jest pobranie próbek z dość złożonego wygenerowanego obrazu. Często łatwiej jest Export
obraz toAsset()
, załadować wyeksportowany obraz, a potem pobrać próbkę. Na przykład:
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...
W tym przykładzie obrazy są eksportowane jako liczby zmiennoprzecinkowe. Nie eksportuj danych z podwójną dokładnością, chyba że jest to absolutnie konieczne. Podczas eksportowania pamiętajmy, że w pliku z plikiem docelowym jest zakodowany link do Edytora kodu (pobierany bezpośrednio przed eksportem), aby umożliwić odtworzenie.
Po zakończeniu eksportowania ponownie załaduj zasób i przeprowadź próbkowanie. Pamiętaj, że najpierw do debugowania używana jest bardzo mała próbka z bardzo małej testowej części obszaru. Gdy okaże się to skuteczne, weź większą próbkę i wyeksportuj ją.
Tak duże próbki zwykle trzeba wyeksportować. Nie należy oczekiwać, że takie próbki będą dostępne w interaktywny sposób (np. za pomocą funkcji print()
) lub nadawać się do użycia (np. jako dane wejściowe do klasyfikatora) bez ich wcześniejszego wyeksportowania.
Dołączanie a map-filter
Załóżmy, że chcesz złączyć kolekcje na podstawie czasu, lokalizacji lub niektórych metadanych. Zazwyczaj można to zrobić najskuteczniej za pomocą funkcji złączenia. W tym przykładzie wykonujemy sprzężenie czasoprzestrzenne zbiorów Landsat 8 i 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);
Najpierw spróbuj użyć funkcji złączania (Export
w razie potrzeby), ale czasami filter()
w ramach map()
może też być skuteczne, zwłaszcza w przypadku bardzo dużych kolekcji.
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()
a reduceRegions()
a pętla for
Wywołanie funkcji reduceRegions()
z bardzo dużą lub złożoną wartością FeatureCollection
może spowodować błąd „Obliczona wartość jest zbyt duża”. Jednym z potencjalnych rozwiązań jest zmapowanie reduceRegion()
na FeatureCollection
. Innym potencjalnym rozwiązaniem jest użycie (o zgrozo!) pętli for. W Earth Engine nie zalecamy tego, jak opisano tutaj, tutaj i tutaj, ale reduceRegion()
można zastosować w pętli for, aby przeprowadzić duże redukcje.
Załóżmy, że Twoim celem jest uzyskanie średniej wartości pikseli (lub dowolnej statystyki) w każdej funkcji w FeatureCollection
dla każdego obrazu w ImageCollection
.
W tym przykładzie porównano 3 opisane wcześniej podejścia:
// 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());
Pamiętaj, że w celu debugowania w ramach każdej kolekcji jest drukowany obiekt first()
. Nie oczekuj, że pełny wynik będzie dostępny w sposób interaktywny: musisz Export
. Pamiętaj też, że pętli for należy używać z bardzo dużą ostrożnością i tylko w sytuacji, gdy inne rozwiązania zawiodą. Na koniec pętla for wymaga ręcznego uzyskania rozmiaru kolekcji danych wejściowych i zakodowania go na stałe w odpowiednich miejscach. Jeśli coś jest dla Ciebie niejasne, nie używaj pętli for.
Używanie różnicowania do przodu w przypadku sąsiadów w czasie
Załóżmy, że masz dane ImageCollection
posortowane czasowo (czyli serię czasową) i chcesz porównać każdy obraz z poprzednim (lub następnym). Zamiast używać funkcji iterate()
, lepiej jest zastosować zróżnicowanie do przodu oparte na tablicy. W tym przykładzie ta metoda jest używana do usuwania duplikatów z kolecji Sentinel-2. Duplikaty są tu zdefiniowane jako obrazy z tym samym dniem w roku:
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));
Sprawdź wydrukowane kolekcje, aby upewnić się, że zduplikowane elementy zostały usunięte.