程式設計最佳做法

本文件說明程式設計做法,旨在盡可能提高複雜或耗時的 Earth Engine 運算成功率。這裡所述的方法適用於交互式 (例如 Code Editor) 和批次 (Export) 運算,但一般來說,長時間運算的運算應在批次系統中執行。

避免將用戶端函式和物件與伺服器函式和物件混用

Earth Engine 伺服器物件是具有 ee 開頭的建構函式物件 (例如 ee.Imageee.Reducer),而此類物件上的任何方法都是伺服器函式。任何未以這種方式建構的物件都是用戶端物件。用戶端物件可能來自程式碼編輯器 (例如 MapChart) 或 JavaScript 語言 (例如 DateMath[]{})。

為避免發生非預期行為,請勿在指令碼中混用用戶端和伺服器函式,如這裡這裡這裡所述。如要深入瞭解 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 中的集合會透過最佳化處理,但這項最佳化作業會在將集合轉換為 ListArray 類型時中斷。除非您需要隨機存取集合元素 (也就是說,您需要取得集合的 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));

檢查已列印的收藏,確認是否已移除重複項目。