يوضّح هذا المستند ممارسات الترميز التي تهدف إلى زيادة فرصة
النجاح في العمليات الحسابية المعقدة أو المكلّفة في 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
"محرر الرموز" تكون من جهة العميل. (مزيد من المعلومات حول إنشاء
واجهات مستخدِم في Earth Engine) على سبيل المثال:
استخدام دوالّ العميل لإعداد واجهة المستخدم
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 عنصر. إذا ظهرت لك رسالة الخطأ "تم إلغاء طلب البحث عن المجموعة بعد تجميع أكثر من
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()
هو فرض إجراء عمليات الحساب في "محرِّر الرموز البرمجية" على مستوى
معيّن حتى تتمكّن من فحص النتائج على مستوى التحليل المطلوب. في المثال التالي، يتم احتساب مساحات وحدات البكسل ذات درجة الحرارة المرتفعة وعدد وحدات البكسل في كل مساحة. شغِّل المثال وانقر على أحد الرقع. يُرجى العِلم أنّ
عدد البكسلات يختلف بين البيانات التي تمت إعادة إسقاطها والبيانات التي لم تتم
إعادة إسقاطها.
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()
هو أنّ الأول ينفّذ
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
صحيحًا لتفعيل جولة واحدة من
وحدات البكسل المُدخلة. في معجم الإخراج، يتم إلحاق اسم المُخفِّض ب
اسم النطاق. للحصول على صور متوسّط وتشويش (مثلاً لتسويت محتوى
الصورة)، يمكنك تحويل القيم إلى صورة واستخدام التعبيرات العادية لاستخراج القيم المتوسّطة
والتشويش بشكلٍ فردي كما هو موضّح في المثال.
استخدام 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
غير صفرية.
بالنسبة إلى عمليات الأشكال الهندسية التي قد تكون باهظة التكلفة، استخدِم أكبر هامش خطأ ممكن، مع مراعاة الدقة المطلوبة في الحساب. يحدِّد هامش الخطأ الحد الأقصى المسموح به للخطأ (بالمتر) أثناء العمليات على الأشكال الهندسية (مثلاً أثناء إعادة الإسقاط). يمكن أن يؤدي تحديد هامش خطأ صغير إلى الحاجة إلى تكثيف الأشكال الهندسية (مع الإحداثيات)، ما قد يؤدي إلى استخدام ذاكرة مكثفة. من الممارسات الجيدة تحديد هامش خطأ كبير قدر الإمكان للعملية الحساب:
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() و/أو العوامل المُخفِّضة للمجموعات.
استخدام 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...
في هذا المثال، يُرجى ملاحظة أنّه يتم تصدير الصور على أنّها عنصر عائم. لا تُصدِّر بدقة مضاعفة إلا إذا كان ذلك ضروريًا للغاية. عند إجراء عملية التصدير هذه، يُرجى العِلم أنّه يتم تضمين رابط "محرِّر الرموز البرمجية" (الذي تم الحصول عليه مباشرةً قبل التصدير) في اسمملف لإعادة الإنتاج.
بعد اكتمال عملية التصدير، أعِد تحميل مادة العرض واصِل عملية أخذ العيّنات منها. يُرجى العِلم أنّه يتم أولاً تشغيل عيّنة صغيرة جدًا على مساحة اختبار صغيرة جدًا بغرض
تصحيح الأخطاء. وعندما يتبيّن أنّ ذلك ينجح، يمكنك أخذ عيّنة أكبر وتصديرها.
وعادةً ما يكون من الضروري تصدير هذه العيّنات الكبيرة. لا تتوقّع أن تكون هذه العيّنات متوفرة بشكل تفاعلي (على سبيل المثال من خلال print()
) أو قابلة للاستخدام (على سبيل المثال كإدخال إلى أحد المصنّفات) بدون تصديرها أولاً.
عملية الدمج مقابل فلترة الخرائط
لنفترض أنّك تريد دمج المجموعات استنادًا إلى الوقت أو الموقع الجغرافي أو بعض سمات metadata. وبشكل عام، يمكن إجراء ذلك بفعالية أكبر باستخدام عملية ربط. يُجري المثال التالي عملية دمج مكاني زمني بين مجموعتَي 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
قد يؤدي استدعاء reduceRegions()
باستخدام FeatureCollection
كبير جدًا أو معقّد كأحد
المدخلات إلى ظهور الخطأ المخيف "القيمة المحسوبة كبيرة جدًا". أحد
الحلول المحتملة هو ربط reduceRegion()
بالحساب FeatureCollection
بدلاً من ذلك. هناك حلّ آخر محتمل وهو استخدام حلقة for. على الرغم من أنّه
يُنصح بشدة بعدم استخدام هذا الإجراء في Earth Engine كما هو موضّح
هنا،
هنا وهنا،
يمكن تنفيذreduceRegion()
في حلقة for لإجراء عمليات تقليل كبيرة.
لنفترض أنّ هدفك هو الحصول على متوسط عدد البكسل (أو أيّ إحصاءات) في
كل ميزة في FeatureCollection
لكل صورة في ImageCollection
.
يقارن المثال التالي بين النهج الثلاثة الموضّحة سابقًا:
// 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()
عنصر من كل مجموعة لأغراض debugging. لا تتوقّع أن تكون النتيجة الكاملة متاحة
بشكل تفاعلي: عليك Export
. يُرجى العلم أيضًا أنّه يجب استخدام حلقات for
بحرص شديد وحصرًا كحل أخير. أخيرًا، تتطلّب حلقة for-loop الحصول على حجم مجموعة الإدخال يدويًا وبرمجة هذا الحجم بشكل ثابت في المواقع المناسبة. إذا لم يكن أيّ من ذلك واضحًا لك، لا تستخدِم حلقة 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));
راجِع المجموعات المطبوعة للتأكّد من إزالة النُسخ المكرّرة.