เอกสารนี้อธิบายแนวทางการเขียนโค้ดที่มีจุดประสงค์เพื่อเพิ่มโอกาสให้ประสบความสำเร็จสูงสุดในการประมวลผลของ Earth Engine ที่ซับซ้อนหรือมีค่าใช้จ่ายสูง วิธีการที่อธิบายที่นี่ใช้ได้กับทั้งการคํานวณแบบอินเทอร์แอกทีฟ (เช่น ตัวแก้ไขโค้ด) และแบบเป็นกลุ่ม (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()
เป็นเมธอดเซิร์ฟเวอร์บนออบเจ็กต์เซิร์ฟเวอร์ และใช้ร่วมกับฟังก์ชันฝั่งไคลเอ็นต์ไม่ได้ เช่น เงื่อนไข <
สถานการณ์ที่คุณอาจต้องใช้วงวน for คือการตั้งค่า UI เนื่องจากออบเจ็กต์และเมธอด ui
ของ Code Editor อยู่ฝั่งไคลเอ็นต์ (ดูข้อมูลเพิ่มเติมเกี่ยวกับการสร้างอินเทอร์เฟซผู้ใช้ใน 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()
ผลลัพธ์ได้ด้วย โปรดทราบว่าคุณจะพิมพ์คอลเล็กชันที่มีองค์ประกอบมากกว่า 5,000 รายการไม่ได้ หากได้รับข้อผิดพลาด "ยกเลิกการค้นหาคอลเล็กชันหลังจากรวบรวมองค์ประกอบมากกว่า 5, 000 รายการ" ให้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.
โปรดทราบว่าอาร์กิวเมนต์ที่ 2 ของ map()
คือ true
ซึ่งหมายความว่าฟังก์ชันที่แมปอาจแสดงผลลัพธ์เป็นค่า Null และระบบจะทิ้งค่า Null เหล่านั้นในคอลเล็กชันผลลัพธ์
ซึ่งอาจมีประโยชน์ (โดยไม่มี If()
) แต่วิธีแก้ปัญหาที่ง่ายที่สุดคือการใช้ตัวกรอง ดังนี้
ใช้ filter()
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
ดังที่แสดงในบทแนะนํานี้ แนวทางการเขียนโปรแกรมเชิงฟังก์ชันโดยใช้ตัวกรองเป็นวิธีที่ถูกต้องในการใช้ตรรกะหนึ่งกับองค์ประกอบบางอย่างของคอลเล็กชัน และตรรกะอื่นกับองค์ประกอบอื่นๆ ของคอลเล็กชัน
หลีกเลี่ยง reproject()
อย่าใช้การโปรเจ็กต์ใหม่เว้นแต่จำเป็นจริงๆ เหตุผลหนึ่งที่คุณอาจต้องใช้ reproject()
คือเพื่อบังคับให้เครื่องมือแก้ไขโค้ดทำการคำนวณในระดับที่เฉพาะเจาะจงเพื่อให้คุณตรวจสอบผลลัพธ์ในระดับการวิเคราะห์ที่ต้องการได้ ในตัวอย่างถัดไป ระบบจะคำนวณแพตช์ของพิกเซลที่ร้อนและคำนวณจำนวนพิกเซลในแต่ละแพตช์ เรียกใช้ตัวอย่างและคลิกแพตช์รายการใดรายการหนึ่ง โปรดทราบว่าจำนวนพิกเซลจะแตกต่างกันระหว่างข้อมูลที่โปรเจ็กต์ใหม่กับข้อมูลที่ไม่ได้โปรเจ็กต์ใหม่
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()
จะกำหนดขนาดของการคำนวณแทนเครื่องมือแก้ไขโค้ด ใช้ 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()
คือ updateMask()
จะทำ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
เป็นจริงเพื่อเปิดใช้การเรียกใช้พิกเซลอินพุตเพียงครั้งเดียว ในพจนานุกรมเอาต์พุต ระบบจะเพิ่มชื่อตัวลดต่อท้ายชื่อกลุ่ม หากต้องการดูรูปภาพค่ามัธยฐานและ SD (เช่น เพื่อทำให้รูปภาพอินพุตเป็นมาตรฐาน) ให้เปลี่ยนค่าเป็นรูปภาพและใช้นิพจน์ทั่วไปเพื่อดึงค่ามัธยฐานและ SD แยกกันตามที่แสดงในตัวอย่าง
ใช้ Export
สําหรับการคํานวณที่ทําให้เกิดข้อผิดพลาด "ใช้หน่วยความจําของผู้ใช้เกินขีดจํากัด" หรือ "การคํานวณหมดเวลา" ในเครื่องมือแก้ไขโค้ด การคํานวณเดียวกันอาจทําสําเร็จได้โดยใช้ 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
ที่ไม่ใช่ 0
สําหรับการดำเนินการเกี่ยวกับเรขาคณิตที่อาจใช้ทรัพยากรมาก ให้ใช้ส่วนต่างข้อผิดพลาดที่ใหญ่ที่สุดที่เป็นไปได้โดยพิจารณาจากความแม่นยำของการคำนวณที่ต้องการ ส่วนต่างของข้อผิดพลาดจะระบุข้อผิดพลาดสูงสุดที่อนุญาต (เป็นเมตร) ในระหว่างการดำเนินการกับเรขาคณิต (เช่น ในระหว่างการโปรเจ็กต์ใหม่) การระบุส่วนต่างของข้อผิดพลาดเล็กน้อยอาจส่งผลให้ต้องเพิ่มความหนาแน่นของรูปทรงเรขาคณิต (ที่มีพิกัด) ซึ่งอาจใช้หน่วยความจำมาก แนวทางปฏิบัติแนะนำคือระบุส่วนต่างของข้อผิดพลาดให้มากที่สุดเท่าที่จะเป็นไปได้สำหรับการคํานวณ
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()
อย่าใช้ FeatureCollection
ที่ reduceToVectors()
แสดงผลเป็นอินพุตสำหรับ 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() และ/หรือ grouping reducers
ใช้ 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');
อย่าสุ่มตัวอย่างข้อมูลเกินกว่าที่จําเป็น
พยายามอย่าเพิ่มขนาดชุดข้อมูลการฝึกโดยไม่จำเป็น แม้ว่าการเพิ่มขึ้นของปริมาณข้อมูลการฝึกอบรมจะเป็นกลยุทธ์แมชชีนเลิร์นนิงที่มีประสิทธิภาพในบางกรณี แต่ก็อาจเพิ่มต้นทุนการประมวลผลโดยไม่เพิ่มความแม่นยำได้ (ดูข้อมูลว่าควรเพิ่มขนาดชุดข้อมูลการฝึกเมื่อใดได้ที่ข้อมูลอ้างอิงนี้) ตัวอย่างต่อไปนี้แสดงให้เห็นว่าการขอข้อมูลการฝึกมากเกินไปอาจทําให้เกิดข้อผิดพลาด "ค่าที่คำนวณได้ใหญ่เกินไป"
อย่าสุ่มตัวอย่างข้อมูลมากเกินไป
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...
ในตัวอย่างนี้ โปรดทราบว่าระบบส่งออกภาพเป็นประเภทลอย อย่าส่งออกที่ความแม่นยำแบบเลขทศนิยม 2 ตำแหน่ง เว้นแต่จำเป็นจริงๆ เมื่อส่งออก โปรดทราบว่าระบบจะฝังลิงก์เครื่องมือแก้ไขโค้ด (ได้รับทันทีก่อนส่งออก) ไว้ในชื่อไฟล์เพื่อให้ทำซ้ำได้
เมื่อส่งออกเสร็จแล้ว ให้โหลดชิ้นงานอีกครั้งแล้วทำการสุ่มตัวอย่างจากชิ้นงาน โปรดทราบว่าระบบจะเรียกใช้ตัวอย่างจำนวนน้อยมากในพื้นที่ทดสอบขนาดเล็กมากก่อนเพื่อแก้ไขข้อบกพร่อง เมื่อการทดสอบแสดงผลสำเร็จ ให้นำตัวอย่างที่ใหญ่ขึ้นและส่งออก
โดยทั่วไปแล้ว จะต้องส่งออกตัวอย่างขนาดใหญ่ดังกล่าว โปรดทราบว่าตัวอย่างดังกล่าวจะใช้งานแบบอินเทอร์แอกทีฟ (เช่น ผ่าน 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
หากจําเป็น) แต่บางครั้งfilter()
ภายในmap()
ก็อาจมีประสิทธิภาพเช่นกัน โดยเฉพาะสําหรับคอลเล็กชันขนาดใหญ่มาก
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-loop
การเรียกใช้ reduceRegions()
ด้วย FeatureCollection
ขนาดใหญ่หรือซับซ้อนมากเป็นอินพุตอาจทำให้เกิดข้อผิดพลาด "ค่าที่คำนวณได้ใหญ่เกินไป" วิธีแก้ปัญหาที่เป็นไปได้วิธีหนึ่งคือการแมป reduceRegion()
ทับ FeatureCollection
แทน อีกวิธีหนึ่งที่อาจช่วยแก้ปัญหาได้คือการใช้ (อุ๊ย) ลูป for แม้ว่าเราจะไม่แนะนำให้ใช้วิธีนี้ใน Earth Engine ตามที่อธิบายไว้ที่นี่ ที่นี่ และที่นี่ แต่คุณก็สามารถใช้ reduceRegion()
ในวงวน for เพื่อลดจำนวนข้อมูลจำนวนมากได้
สมมติว่าวัตถุประสงค์ของคุณคือหาค่าเฉลี่ยของพิกเซล (หรือสถิติใดๆ) ในฟีเจอร์แต่ละรายการใน FeatureCollection
สำหรับรูปภาพแต่ละรูปใน ImageCollection
ตัวอย่างต่อไปนี้เปรียบเทียบแนวทางทั้ง 3 วิธีที่อธิบายไว้ก่อนหน้านี้
// 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-loop
ใช้การหาค่าต่างของอนุกรมเวลาถัดไปสำหรับองค์ประกอบใกล้เคียงกัน
สมมติว่าคุณมี 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));
ตรวจสอบคอลเล็กชันที่พิมพ์เพื่อยืนยันว่านำรายการที่ซ้ำกันออกแล้ว