Praktik Terbaik Coding

Dokumen ini menjelaskan praktik coding yang dimaksudkan untuk memaksimalkan peluang keberhasilan komputasi Earth Engine yang kompleks atau mahal. Metode yang dijelaskan di sini berlaku untuk komputasi interaktif (misalnya, Code Editor) dan batch (Export), meskipun umumnya komputasi yang berjalan lama harus berjalan di sistem batch.

Hindari mencampur fungsi dan objek klien dengan fungsi dan objek server

Objek server Earth Engine adalah objek dengan konstruktor yang dimulai dengan ee (misalnya, ee.Image, ee.Reducer) dan metode apa pun pada objek tersebut adalah fungsi server. Setiap objek yang tidak dibuat dengan cara ini adalah objek klien. Objek klien dapat berasal dari Editor Kode (misalnya, Map, Chart) atau bahasa JavaScript (misalnya, Date, Math, [], {}).

Untuk menghindari perilaku yang tidak diinginkan, jangan gabungkan fungsi klien dan server dalam skrip Anda seperti yang dibahas di sini dan di sini serta di sini. Lihat halaman ini dan/atau tutorial ini untuk penjelasan mendalam tentang klien vs. server di Earth Engine. Contoh berikut menggambarkan bahaya mencampur fungsi klien dan server:

Error — kode ini tidak berfungsi.

var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');

// Won't work.
for(var i=0; i<table.size(); i++) {
  print('No!');
}

Dapatkah Anda menemukan error-nya? Perhatikan bahwa table.size() adalah metode server pada objek server dan tidak dapat digunakan dengan fungsi sisi klien seperti kondisional <.

Situasi yang mungkin Anda inginkan untuk menggunakan loop for adalah dengan penyiapan UI, karena objek dan metode ui Editor Kode bersifat sisi klien. (Pelajari lebih lanjut cara membuat antarmuka pengguna di Earth Engine). Contoh:

Gunakan fungsi klien untuk penyiapan UI.

var panel = ui.Panel();
for(var i=1; i<8; i++) {
  panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);

Sebaliknya, map() adalah fungsi server dan fungsi klien tidak akan berfungsi di dalam fungsi yang diteruskan ke map(). Contoh:

Error — kode ini tidak berfungsi.

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.
});

Untuk melakukan sesuatu pada setiap elemen dalam koleksi, map() fungsi di atas koleksi dan set() properti:

Gunakan map() dan 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());

Anda juga dapat filter() koleksi berdasarkan properti yang dihitung atau yang ada, dan print() hasilnya. Perhatikan bahwa Anda tidak dapat mencetak koleksi dengan lebih dari 5.000 elemen. Jika Anda mendapatkan error "Kueri koleksi dibatalkan setelah mengumpulkan lebih dari 5.000 elemen", filter() atau limit() koleksi sebelum dicetak.

Menghindari konversi ke daftar yang tidak perlu

Koleksi di Earth Engine diproses menggunakan pengoptimalan yang dipecah dengan mengonversi koleksi ke jenis List atau Array. Kecuali jika Anda memerlukan akses acak ke elemen koleksi (yaitu Anda memerlukan elemen ke-i dari koleksi), gunakan filter pada koleksi untuk mengakses setiap elemen koleksi. Contoh berikut mengilustrasikan perbedaan antara konversi jenis (tidak direkomendasikan) dan pemfilteran (direkomendasikan) untuk mengakses elemen dalam koleksi:

Jangan konversi ke daftar jika tidak perlu.

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.

Perhatikan bahwa Anda dapat dengan mudah memicu error dengan mengonversi koleksi menjadi daftar tanpa perlu. Cara yang lebih aman adalah menggunakan filter():

Gunakan filter().

print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());

Perhatikan bahwa Anda harus menggunakan filter sedini mungkin dalam analisis.

Menghindarinee.Algorithms.If()

Jangan gunakan ee.Algorithms.If() untuk menerapkan logika percabangan, terutama dalam fungsi yang dipetakan. Seperti yang diilustrasikan contoh berikut, ee.Algorithms.If() dapat menggunakan banyak memori dan tidak direkomendasikan:

Jangan gunakan 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.

Perhatikan bahwa argumen kedua untuk map() adalah true. Artinya, fungsi yang dipetakan dapat menampilkan null dan akan dihapus dalam koleksi yang dihasilkan. Hal ini dapat berguna (tanpa If()), tetapi di sini solusi termudah adalah menggunakan filter:

Gunakan filter().

print(table.filter(ee.Filter.eq('country_na', 'Chad')));

Seperti yang ditunjukkan dalam tutorial ini, pendekatan pemrograman fungsional menggunakan filter adalah cara yang benar untuk menerapkan satu logika ke beberapa elemen koleksi dan logika lain ke elemen koleksi lainnya.

Menghindarinreproject()

Jangan gunakan proyeksi ulang kecuali jika benar-benar diperlukan. Salah satu alasan Anda mungkin ingin menggunakan reproject() adalah untuk memaksa komputasi Code Editor terjadi pada skala tertentu sehingga Anda dapat memeriksa hasilnya pada skala analisis yang diinginkan. Dalam contoh berikutnya, patch piksel panas dihitung dan jumlah piksel di setiap patch dihitung. Jalankan contoh dan klik salah satu patch. Perhatikan bahwa jumlah piksel berbeda antara data yang diproyeksikan ulang dan data yang belum diproyeksikan ulang.

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);

Penyebab perbedaan ini karena skala analisis ditetapkan oleh tingkat zoom Code Editor. Dengan memanggil reproject(), Anda menetapkan skala komputasi, bukan Editor Kode. Gunakan reproject() dengan sangat hati-hati karena alasan yang dijelaskan dalam dokumen ini.

Filter dan select() terlebih dahulu

Secara umum, filter koleksi input berdasarkan waktu, lokasi, dan/atau metadata sebelum melakukan hal lain dengan koleksi. Terapkan filter yang lebih selektif sebelum filter yang kurang selektif. Filter spasial dan/atau temporal sering kali lebih selektif. Misalnya, perhatikan bahwa select() dan filter() diterapkan sebelum 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');

Gunakan updateMask(), bukan mask()

Perbedaan antara updateMask() dan mask() adalah yang pertama melakukan and() logis dari argumen (mask baru) dan mask gambar yang ada sedangkan mask() hanya mengganti mask gambar dengan argumen. Bahaya dari hal kedua adalah Anda dapat secara tidak sengaja mengungkap piksel. Dalam contoh ini, tujuannya adalah untuk menyamarkan piksel dengan ketinggian kurang dari atau sama dengan 300 meter. Seperti yang dapat Anda lihat (perkecil), penggunaan mask() menyebabkan banyak piksel menjadi tidak disamarkan, piksel yang tidak termasuk dalam gambar yang diinginkan:

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);

Menggabungkan pengurangan

Jika Anda memerlukan beberapa statistik (misalnya, rata-rata dan deviasi standar) dari satu input (misalnya, wilayah gambar), akan lebih efisien untuk menggabungkan pengurangan. Misalnya, untuk mendapatkan statistik gambar, gabungkan pengurangan sebagai berikut:

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');

Dalam contoh ini, perhatikan bahwa pengurangan rata-rata digabungkan dengan pengurangan deviasi standar dan sharedInputs bernilai benar untuk mengaktifkan satu penerusan melalui piksel input. Dalam kamus output, nama reducer ditambahkan ke nama band. Untuk mendapatkan gambar mean dan SD (misalnya untuk menormalisasi gambar input), Anda dapat mengubah nilai menjadi gambar dan menggunakan ekspresi reguler untuk mengekstrak mean dan SD satu per satu seperti yang ditunjukkan dalam contoh.

Gunakan Export

Untuk komputasi yang menghasilkan error "Batas memori pengguna terlampaui" atau "Waktu tunggu komputasi habis" di Code Editor, komputasi yang sama mungkin dapat berhasil dengan menggunakan Export. Hal ini karena waktu tunggu lebih lama dan jejak memori yang diizinkan lebih besar saat berjalan di sistem batch (tempat ekspor berjalan). (Ada pendekatan lain yang dapat Anda coba terlebih dahulu seperti yang dijelaskan dalam dokumen proses debug). Melanjutkan contoh sebelumnya, misalkan kamus menampilkan error. Anda bisa mendapatkan hasil dengan melakukan hal seperti:

var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
  collection: ee.FeatureCollection([ee.Feature(null, stats)]),
  description: 'exported_stats_demo_' + link,
  fileFormat: 'CSV'
});

Perhatikan bahwa link disematkan ke dalam nama aset, untuk reproduksi. Perhatikan juga bahwa jika ingin mengekspor toAsset, Anda harus menyediakan geometri, yang dapat berupa apa saja, misalnya centroid gambar, yang kecil dan murah untuk dihitung. (yaitu, jangan gunakan geometri yang rumit jika Anda tidak memerlukannya).

Lihat halaman proses debug untuk mengetahui contoh penggunaan Export guna menyelesaikan Waktu tunggu komputasi habis dan Terlalu banyak agregasi serentak. Lihat dokumen ini untuk mengetahui detail tentang ekspor secara umum.

Jika Anda tidak perlu memotong, jangan gunakan clip()

Menggunakan clip() secara tidak perlu akan meningkatkan waktu komputasi. Hindari clip() kecuali jika diperlukan untuk analisis Anda. Jika Anda tidak yakin, jangan memotong. Contoh penggunaan klip yang buruk:

Jangan memotong input secara tidak perlu.

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);

Pemotongan gambar input dapat dilewati sepenuhnya, karena region ditentukan dalam panggilan reduceRegion():

Tentukan region untuk output.

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);

Jika waktu tunggu komputasi ini habis, Export-kan seperti dalam contoh ini.

Jika Anda perlu memotong dengan koleksi yang kompleks, gunakan clipToCollection()

Jika Anda benar-benar perlu memangkas sesuatu, dan geometri yang ingin digunakan untuk pemangkasan berada dalam koleksi, gunakan 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);

JANGAN gunakan featureCollection.geometry() atau featureCollection.union() pada koleksi besar dan/atau kompleks, yang dapat lebih banyak menggunakan memori.

Jangan gunakan koleksi yang kompleks sebagai region untuk reducer

Jika Anda perlu melakukan pengurangan spasial sehingga pengurangan menggabungkan input dari beberapa region dalam FeatureCollection, jangan berikan featureCollection.geometry() sebagai input geometry ke pengurangan. Sebagai gantinya, gunakan clipToCollection() dan region yang cukup besar untuk mencakup batas koleksi. Contoh:

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.

Menggunakan errorMargin yang bukan nol

Untuk operasi geometri yang mungkin mahal, gunakan margin error terbesar yang memungkinkan dengan presisi komputasi yang diperlukan. Margin error menentukan error maksimum yang diizinkan (dalam meter) selama operasi pada geometri (misalnya, selama re-proyeksi). Menentukan margin error yang kecil dapat menyebabkan kebutuhan untuk memadatkan geometri (dengan koordinat), yang dapat memerlukan banyak memori. Sebaiknya tentukan margin error sebesar mungkin untuk komputasi Anda:

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');

Jangan gunakan skala yang sangat kecil dengan reduceToVectors()

Jika Anda ingin mengonversi raster menjadi vektor, gunakan skala yang sesuai. Menentukan skala yang sangat kecil dapat meningkatkan biaya komputasi secara signifikan. Tetapkan skala setinggi mungkin untuk memberikan presisi yang diperlukan. Misalnya, untuk mendapatkan poligon yang mewakili daratan global:

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');

Pada contoh sebelumnya, perhatikan penggunaan poligon non-geodesik untuk digunakan dalam pengurangan global.

Jangan gunakan reduceToVectors() dengan reduceRegions()

Jangan gunakan FeatureCollection yang ditampilkan oleh reduceToVectors() sebagai input ke reduceRegions(). Sebagai gantinya, tambahkan band yang ingin Anda kurangi sebelum memanggil 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));

Perhatikan bahwa cara lain untuk mengurangi piksel satu gambar dalam zona gambar lain meliputi reduceConnectedCommponents() dan/atau pengurangnya.

Menggunakan fastDistanceTransform() untuk operasi lingkungan

Untuk beberapa operasi konvolusi, fastDistanceTransform() mungkin lebih efisien daripada reduceNeighborhood() atau convolve(). Misalnya, untuk melakukan erosi dan/atau dilation input biner:

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');

Menggunakan pengoptimalan di reduceNeighborhood()

Jika Anda perlu melakukan konvolusi dan tidak dapat menggunakan fastDistanceTransform(), gunakan pengoptimalan di 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');

Jangan mengambil sampel data lebih dari yang Anda butuhkan

Jangan tergoda untuk meningkatkan ukuran set data pelatihan Anda secara tidak perlu. Meskipun meningkatkan jumlah data pelatihan adalah strategi machine learning yang efektif dalam beberapa situasi, hal ini juga dapat meningkatkan biaya komputasi tanpa peningkatan akurasi yang sesuai. (Untuk memahami kapan harus meningkatkan ukuran set data pelatihan, lihat referensi ini). Contoh berikut menunjukkan bagaimana meminta terlalu banyak data pelatihan dapat menyebabkan error "Nilai yang dihitung terlalu besar" yang mengerikan:

Jangan mengambil sampel terlalu banyak data.

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

Pendekatan yang lebih baik adalah memulai dengan jumlah data yang sedang dan menyesuaikan hyperparameter pengklasifikasi untuk menentukan apakah Anda dapat mencapai akurasi yang diinginkan:

Sesuaikan hyperparameter.

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
}));

Dalam contoh ini, pengklasifikasi sudah sangat akurat, sehingga tidak perlu banyak penyesuaian. Sebaiknya pilih hierarki terkecil (yaitu minLeafPopulation terbesar) yang masih memiliki akurasi yang diperlukan.

Export hasil perantara

Misalnya, tujuan Anda adalah mengambil sampel dari gambar komputasi yang relatif kompleks. Sering kali lebih efisien untuk Export gambar toAsset(), memuat gambar yang diekspor, lalu mengambil sampel. Contoh:

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...

Dalam contoh ini, perhatikan bahwa gambar diekspor sebagai float. Jangan mengekspor dengan presisi ganda kecuali jika benar-benar diperlukan. Saat melakukan ekspor ini, perhatikan bahwa link Code Editor (diperoleh tepat sebelum ekspor) disematkan dalam nama file untuk reproduksi.

Setelah ekspor selesai, muat ulang aset dan lanjutkan pengambilan sampel dari aset tersebut. Perhatikan bahwa sampel yang sangat kecil di area pengujian yang sangat kecil dijalankan terlebih dahulu, untuk proses debug. Jika berhasil, ambil sampel yang lebih besar dan ekspor. Sampel besar seperti itu biasanya perlu diekspor. Jangan berharap sampel tersebut tersedia secara interaktif (misalnya melalui print()) atau dapat digunakan (misalnya sebagai input ke pengklasifikasi) tanpa mengekspornya terlebih dahulu.

Join vs. filter peta

Misalnya, Anda ingin menggabungkan koleksi berdasarkan waktu, lokasi, atau beberapa properti metadata. Umumnya, hal ini dilakukan dengan join secara paling efisien. Contoh berikut melakukan penggabungan spasial-temporal antara koleksi Landsat 8 dan 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);

Meskipun Anda harus mencoba join terlebih dahulu (Export jika diperlukan), terkadang filter() dalam map() juga dapat efektif, terutama untuk koleksi yang sangat besar.

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() vs. reduceRegions() vs. loop for

Memanggil reduceRegions() dengan FeatureCollection yang sangat besar atau rumit sebagai input dapat menyebabkan error "Nilai yang dihitung terlalu besar" yang mengerikan. Salah satu potensial solusinya adalah memetakan reduceRegion() di atas FeatureCollection. Solusi potensial lainnya adalah menggunakan loop for. Meskipun hal ini sangat tidak dianjurkan di Earth Engine seperti yang dijelaskan di sini, di sini, dan di sini, reduceRegion() dapat diterapkan dalam loop for untuk melakukan pengurangan besar.

Misalkan tujuan Anda adalah mendapatkan rata-rata piksel (atau statistik apa pun) di setiap fitur dalam FeatureCollection untuk setiap gambar dalam ImageCollection. Contoh berikut membandingkan tiga pendekatan yang dijelaskan sebelumnya:

// 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());

Perhatikan bahwa hal first() dari setiap koleksi dicetak, untuk tujuan proses debug. Anda tidak boleh berharap bahwa hasil lengkap akan tersedia secara interaktif: Anda harus Export. Perhatikan juga bahwa loop for harus digunakan dengan sangat hati-hati dan hanya sebagai upaya terakhir. Terakhir, loop for memerlukan ukuran pengumpulan input yang diperoleh secara manual dan melakukan hardcoding di lokasi yang sesuai. Jika salah satu dari hal tersebut terdengar tidak jelas bagi Anda, jangan gunakan loop for.

Menggunakan perbedaan maju untuk tetangga dalam waktu

Misalkan Anda memiliki ImageCollection yang diurutkan secara temporal (yaitu deret waktu) dan ingin membandingkan setiap gambar dengan gambar sebelumnya (atau berikutnya). Daripada menggunakan iterate() untuk tujuan ini, mungkin lebih efisien untuk menggunakan perbedaan maju berbasis array. Contoh berikut menggunakan metode ini untuk menghapus duplikat koleksi Sentinel-2, dengan duplikat didefinisikan sebagai gambar dengan hari yang sama dalam setahun:

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));

Periksa koleksi yang dicetak untuk memverifikasi bahwa duplikat telah dihapus.