本文件說明程式設計做法,旨在盡可能提高複雜或耗時的 Earth Engine 運算成功率。這裡所述的方法適用於交互式 (例如 Code Editor) 和批次 (Export
) 運算,但一般來說,長時間運算的運算應在批次系統中執行。
避免將用戶端函式和物件與伺服器函式和物件混用
Earth Engine 伺服器物件是具有 ee
開頭的建構函式物件 (例如 ee.Image
、ee.Reducer
),而此類物件上的任何方法都是伺服器函式。任何未以這種方式建構的物件都是用戶端物件。用戶端物件可能來自程式碼編輯器 (例如 Map
、Chart
) 或 JavaScript 語言 (例如 Date
、Math
、[]
、{}
)。
為避免發生非預期行為,請勿在指令碼中混用用戶端和伺服器函式,如這裡、這裡和這裡所述。如要深入瞭解 Earth Engine 中的用戶端和伺服器,請參閱這個頁面和/或這個教學課程。以下範例說明混用用戶端和伺服器功能的危險:
錯誤:這個程式碼無法運作!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Won't work.
for(var i=0; i<table.size(); i++) {
print('No!');
}
您能找出錯誤嗎?請注意,table.size()
是伺服器物件上的伺服器方法,無法與用戶端功能 (例如 <
條件) 搭配使用。
您可能會在 UI 設定時使用 for 迴圈,因為 Code Editor ui
物件和方法是用於用戶端。(進一步瞭解如何在 Earth Engine 中建立使用者介面)。例如:
使用用戶端函式設定 UI。
var panel = ui.Panel();
for(var i=1; i<8; i++) {
panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);
相反地,map()
是伺服器函式,因此用戶端功能無法在傳遞至 map()
的函式中運作。例如:
錯誤:這個程式碼無法運作!
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.
});
如要對集合中的每個元素執行某項操作,請 map()
在集合上執行函式,並 set()
屬性:
使用 map()
和 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());
您也可以根據計算或現有資源 filter()
收集資料,並 print()
結果。請注意,您無法列印超過 5000 個元素的集合。如果收到「Collection query aborted after accumulating over 5000 elements」(集合查詢在累積超過 5000 個元素後中止) 錯誤,請在列印前 filter()
或 limit()
集合。
避免不必要的轉換為清單
Earth Engine 中的集合會透過最佳化處理,但這項最佳化作業會在將集合轉換為 List
或 Array
類型時中斷。除非您需要隨機存取集合元素 (也就是說,您需要取得集合的 i 個元素),否則請使用集合上的篩選器存取個別集合元素。以下範例說明類型轉換 (不建議使用) 和篩選 (建議使用) 的差異,這兩種方法可用於存取集合中的元素:
請勿將資料轉換為清單,除非有必要!
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.
請注意,如果不必要地將集合轉換為清單,很容易觸發錯誤。更安全的方法是使用 filter()
:
使用 filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());
請注意,您應盡早在分析中使用篩選器。
建議不要使用 ee.Algorithms.If()
請勿使用 ee.Algorithms.If()
實作分支邏輯,尤其是在對應函式中。如以下範例所示,ee.Algorithms.If()
可能會耗用大量記憶體,因此不建議使用:
請勿使用 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.
請注意,map()
的第二個引數是 true
。也就是說,對應的函式可能會傳回空值,並且會在產生的集合中捨棄。這可能很實用 (不含 If()
),但最簡單的解決方案是使用篩選器:
使用 filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
如本教學課程所示,使用篩選器的功能式程式設計方法,是將某個邏輯套用至集合中部分元素,並將另一個邏輯套用至集合中其他元素的正確方式。
建議不要使用 reproject()
除非絕對必要,否則請勿使用重新分派作業。您可能會想使用 reproject()
的原因之一,是為了強制 Code Editor 以特定比例執行運算,以便您以所需的分析比例檢查結果。在下一個範例中,系統會計算熱點像素的圖塊,並計算每個圖塊中的像素數量。執行範例,然後按一下其中一個修補程式。請注意,重新投影資料與未重新投影資料的像素計數不同。
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);
差異的原因是,分析的縮放比例是由程式碼編輯器的縮放等級設定。您可以透過呼叫 reproject()
設定運算的規模,而非 Code Editor。請參閱這份文件,瞭解為何應謹慎使用 reproject()
。
先篩選並select()
一般來說,請先依時間、地點和/或中繼資料篩選輸入集合,再對集合執行其他操作。請先套用較具選擇性的篩選器,再套用較不具選擇性的篩選器。空間和/或時間篩選器通常會更具選擇性。舉例來說,請注意 select()
和 filter()
會在 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');
以 updateMask()
取代 mask()
updateMask()
和 mask()
的差異在於,前者會對引數 (新遮罩) 和現有圖片遮罩執行邏輯 and()
,而 mask()
只會將圖片遮罩替換為引數。後者的危險性在於您可能會不小心揭露像素。在本範例中,目標是遮蓋高度小於或等於 300 公尺的像素。如您所見 (縮小顯示),使用 mask()
會導致大量像素未遮罩,這些像素不屬於所需圖像:
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);
合併縮減器
如果您需要從單一輸入內容 (例如圖片區域) 取得多項統計資料 (例如平均值和標準差),建議您合併縮減器,以提高效率。舉例來說,如要取得圖片統計資料,請結合下列減算器:
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');
請注意,在這個範例中,平均值縮減器會與標準差縮減器結合,且 sharedInputs
為 true,可讓系統單次掃描輸入像素。在輸出字典中,會將縮減器的名稱附加到頻帶名稱。如要取得平均值和標準差圖片 (例如將輸入圖片標準化),您可以將值轉換為圖片,然後使用規則運算式分別擷取平均值和標準差,如範例所示。
使用 Export
如果運算導致 Code Editor 出現「User memory limit exceeded」或「Computation timed out」錯誤,您可以使用 Export
執行相同的運算,或許就能順利完成。這是因為在批次系統 (匯出作業執行的系統) 中執行時,逾時時間較長,且允許的記憶體用量較大。(您可能想先嘗試其他方法,詳情請參閱偵錯文件)。接著繼續前一個範例,假設字典傳回錯誤。您可以透過以下方式取得結果:
var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
collection: ee.FeatureCollection([ee.Feature(null, stats)]),
description: 'exported_stats_demo_' + link,
fileFormat: 'CSV'
});
請注意,連結會嵌入至素材資源名稱中,以利重現。另請注意,如要匯出 toAsset
,您必須提供幾何圖形,這可以是任何內容,例如圖片重心,因為這類內容小且計算成本低。(也就是說,如果不需要複雜的幾何圖形,請不要使用)。
請參閱偵錯頁面,瞭解如何使用 Export
解決「計算超時」和「同時匯總項目過多」的問題。如要進一步瞭解一般匯出作業,請參閱這份文件。
如果不需要剪輯,請勿使用 clip()
不必要使用 clip()
會增加運算時間。除非分析需要,否則請避免使用 clip()
。如果不確定,請不要剪輯。以下是使用短片的錯誤示例:
請勿不必要地剪輯輸入內容!
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);
由於 reduceRegion()
呼叫中已指定區域,因此可以完全略過裁剪輸入圖片的程序:
請指定輸出區域!
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);
如果這項運算逾時,請按照這個範例中的做法 Export
。
如果您需要使用複雜的集合來剪輯,請使用 clipToCollection()
如果您真的需要裁剪某個項目,且要用於裁剪的幾何圖形位於集合中,請使用 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);
請勿在大型和/或複雜的集合中使用 featureCollection.geometry()
或 featureCollection.union()
,因為這類集合可能會耗用大量記憶體。
請勿將複雜的集合做為縮減器的區域
如果您需要執行空間縮減作業,讓縮減器將 FeatureCollection
中多個區域的輸入內容合併,請勿將 featureCollection.geometry()
做為縮減器的 geometry
輸入內容。請改用 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'));
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.
使用非零值 errorMargin
對於可能需要大量運算的幾何圖形作業,請根據計算所需的精確度,使用盡可能大的誤差範圍。誤差範圍會指定在幾何運算 (例如重投影) 期間允許的最大誤差 (以公尺為單位)。指定較小的誤差範圍可能會導致需要以座標密集化幾何圖形,這可能會耗用大量記憶體。建議您盡可能為運算作業指定較大的誤差範圍:
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');
請勿在 reduceToVectors()
中使用過小的比例
如要將點陣圖轉換為向量圖,請使用適當的比例。指定極小的比例可能會大幅增加運算成本。請盡可能設定高精確度的比例,舉例來說,如要取得代表全球陸地的多邊形:
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');
在前述範例中,請注意使用非大地測量多邊形,以便在全球縮減作業中使用。
請勿使用 reduceToVectors()
與 reduceRegions()
請勿使用 reduceToVectors()
傳回的 FeatureCollection
做為 reduceRegions()
的輸入內容。請改為在呼叫 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));
請注意,在另一個區域內減少某張圖片的像素,還有其他方法,包括 reduceConnectedCommponents() 和/或群組化縮減器。
使用 fastDistanceTransform()
執行鄰近操作
對於某些卷積運算,fastDistanceTransform()
可能比 reduceNeighborhood()
或 convolve()
更有效率。例如,對二進位輸入內容進行侵蝕和/或擴張:
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');
使用 reduceNeighborhood()
中的最佳化功能
如果您需要執行卷積運算,但無法使用 fastDistanceTransform()
,請使用 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');
請勿取樣過多資料
請勿為了增加訓練資料集大小而增加資料集的大小。雖然在某些情況下,增加訓練資料量是有效的機器學習策略,但這麼做也會增加運算成本,且準確度並未隨之提升。(如要瞭解何時應增加訓練資料集大小,請參閱這份參考資料)。以下範例說明要求過多訓練資料可能導致令人擔心的「Computed value is too large」錯誤:
請勿取樣過多資料!
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
較好的做法是先從適量資料開始,然後調整分類器的超參數,判斷是否能達到所需的準確度:
調整超參數!
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
}));
在本例中,分類器的準確度已經很高,因此不需要進行太多調整。您可以選擇盡可能小的樹狀圖 (即最大的 minLeafPopulation
),同時仍能達到所需的準確度。
Export
中間結果
假設您的目標是從相對複雜的計算影像中擷取樣本。通常,Export
圖片 toAsset()
、載入匯出的圖片,然後取樣會更有效率。例如:
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...
請注意,在本例中,圖像會以浮點值匯出。除非絕對必要,否則請勿以雙精度匯出。進行匯出作業時,請注意,為確保可重現性,程式碼編輯器連結會嵌入檔案名稱中 (在匯出前立即取得)。
匯出完成後,請重新載入資產,並繼續從中取樣。請注意,系統會先針對非常小的測試區域執行非常少量的樣本,以便進行偵錯。確認成功後,請取樣並匯出較大的資料集。這類大型樣本通常需要匯出。請勿在未先匯出這些樣本的情況下,就期待這些樣本可供互動 (例如透過 print()
) 或使用 (例如做為分類器的輸入內容)。
彙整與對照表篩選
假設您想根據時間、地點或某些中繼資料屬性彙整資料集。一般來說,使用聯結是最有效率的做法。以下範例會在 Landsat 8 和 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);
雖然您應該先嘗試彙整 (如有需要,請使用 Export
),但有時 map()
中的 filter()
也能發揮作用,尤其是在非常大型的集合體中。
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()
與 reduceRegions()
與 for 迴圈
使用非常大或複雜的 FeatureCollection
做為輸入值,呼叫 reduceRegions()
時可能會發生令人擔心的「計算值過大」錯誤。一個潛在的解決方案是改為將 reduceRegion()
對應至 FeatureCollection
。另一個可能的解決方案是使用 (天哪) for 迴圈。雖然我們不鼓勵在 Earth Engine 中這麼做,如這裡、這裡和這裡所述,但您還是可以在 for 迴圈中實作 reduceRegion()
,以便執行大量的縮減作業。
假設您的目標是針對 ImageCollection
中的每張圖片,取得 FeatureCollection
中每個特徵的像素平均值 (或任何統計資料)。以下範例比較前述的三種方法:
// 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());
請注意,系統會為了偵錯目的而列印每個集合中的 first()
事物。您不應預期完整結果會以互動方式提供:您需要 Export
。另請注意,for 迴圈應謹慎使用,且只能在不得已的情況下使用。最後,for 迴圈需要手動取得輸入集合的大小,並在適當位置進行硬式編碼。如果您對上述任何一點感到不清楚,請不要使用 for 迴圈。
使用前向差分法處理時間相鄰的資料
假設您有按時間排序的 ImageCollection
(即時間序列),且想要比較每張圖片與前一張 (或下一張) 圖片。建議您改用以陣列為基礎的前向差異化,而非使用 iterate()
來執行這項作業,這樣可能會更有效率。以下範例使用此方法來去除 Sentinel-2 集合的重複項目,其中重複項目的定義是指同一年同日的圖像:
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));
檢查已列印的收藏,確認是否已移除重複項目。