इस दस्तावेज़ में, कोडिंग के ऐसे तरीकों के बारे में बताया गया है जिनका मकसद, जटिल या महंगे 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-लूप का इस्तेमाल किया जा सकता है. ऐसा इसलिए, क्योंकि Code Editor 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()
और नतीजे को print()
करने का विकल्प भी है. ध्यान दें कि 5,000 से ज़्यादा एलिमेंट वाले कलेक्शन को प्रिंट नहीं किया जा सकता. अगर आपको "5,000 से ज़्यादा एलिमेंट इकट्ठा करने के बाद, कलेक्शन क्वेरी को बंद कर दिया गया" गड़बड़ी का मैसेज मिलता है, तो प्रिंट करने से पहले कलेक्शन को filter()
या limit()
करें.
ज़रूरत न होने पर, सूची में बदलने से बचें
Earth Engine में कलेक्शन को ऑप्टिमाइज़ेशन का इस्तेमाल करके प्रोसेस किया जाता है. हालांकि, कलेक्शन को List
या Array
टाइप में बदलने पर, ऑप्टिमाइज़ेशन की प्रोसेस रुक जाती है. अगर आपको कलेक्शन के एलिमेंट को रैंडम तरीके से ऐक्सेस करने की ज़रूरत नहीं है, तो कलेक्शन के अलग-अलग एलिमेंट को ऐक्सेस करने के लिए, कलेक्शन पर फ़िल्टर का इस्तेमाल करें. यहां दिए गए उदाहरण में, किसी कलेक्शन में मौजूद एलिमेंट को ऐक्सेस करने के लिए, टाइप बदलने (इसका सुझाव नहीं दिया जाता) और फ़िल्टर करने (इसका सुझाव दिया जाता है) के बीच का अंतर दिखाया गया है:
बिना ज़रूरत के, कैटगरी को सूची में न बदलें!
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');
mask()
के बजाय updateMask()
का इस्तेमाल करें
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
में कई इलाकों के इनपुट इकट्ठा कर सके, तो रिड्यूसर में geometry
इनपुट के तौर पर featureCollection.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');
पिछले उदाहरण में, ग्लोबल रिडक्शन में इस्तेमाल करने के लिए, नॉन-जियोडेसिक पॉलीगॉन के इस्तेमाल पर ध्यान दें.
reduceRegions()
के साथ reduceToVectors()
का इस्तेमाल न करें
reduceToVectors()
से मिले FeatureCollection
का इस्तेमाल, reduceRegions()
के इनपुट के तौर पर न करें. इसके बजाय, reduceToVectors()
को कॉल करने से पहले, वे बैंड जोड़ें जिन्हें कम करना है:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
var mod11a1 = ee.ImageCollection('MODIS/006/MOD11A1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
var lst = mod11a1.first().select(0);
var means = bounds.selfMask().addBands(lst).reduceToVectors({
reducer: ee.Reducer.mean(),
geometry: almostGlobal,
scale: 1000,
maxPixels: 1e10
});
print(means.limit(10));
ध्यान दें कि किसी दूसरी इमेज के ज़ोन में मौजूद एक इमेज के पिक्सल को कम करने के अन्य तरीकों में, reduceConnectedCommponents() और/या 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...
इस उदाहरण में, ध्यान दें कि इमेज को फ़्लोट के तौर पर एक्सपोर्ट किया गया है. जब तक ज़रूरी न हो, तब तक डबल प्रिसीज़न में एक्सपोर्ट न करें. इस एक्सपोर्ट को करते समय, ध्यान दें कि फिर से चलाने के लिए, फ़ाइल के नाम में कोड एडिटर का लिंक (एक्सपोर्ट करने से ठीक पहले मिला) जोड़ा गया है.
एक्सपोर्ट पूरा होने के बाद, ऐसेट को फिर से लोड करें और उससे सैंपलिंग की प्रोसेस शुरू करें. ध्यान दें कि डीबग करने के लिए, पहले बहुत छोटे टेस्ट एरिया पर बहुत छोटा सैंपल चलाया जाता है. जब यह काम हो जाए, तो बड़ा सैंपल लें और उसे एक्सपोर्ट करें.
आम तौर पर, ऐसे बड़े सैंपल एक्सपोर्ट करने होते हैं. ऐसे सैंपल को पहले एक्सपोर्ट किए बिना, इंटरैक्टिव तरीके से (उदाहरण के लिए, print()
के ज़रिए) उपलब्ध होने या इस्तेमाल किए जाने (उदाहरण के लिए, क्लासिफ़ायर के इनपुट के तौर पर) की उम्मीद न करें.
जॉइन बनाम मैप-फ़िल्टर
मान लें कि आपको समय, जगह या किसी मेटाडेटा प्रॉपर्टी के आधार पर कलेक्शन में शामिल होना है. आम तौर पर, जॉइन का इस्तेमाल करके यह काम सबसे बेहतर तरीके से किया जा सकता है. यहां दिए गए उदाहरण में, Landsat 8 और Sentinel-2 कलेक्शन के बीच, जगह और समय के हिसाब से जॉइन किया गया है:
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var joined = ee.Join.saveAll('landsat').apply({
primary: s2,
secondary: l8,
condition: ee.Filter.and(
ee.Filter.maxDifference({
difference: 1000 * 60 * 60 * 24, // One day in milliseconds
leftField: 'system:time_start',
rightField: 'system:time_start',
}),
ee.Filter.intersects({
leftField: '.geo',
rightField: '.geo',
})
)
});
print(joined);
हालांकि, आपको सबसे पहले जॉइन (ज़रूरत पड़ने पर Export
) की कोशिश करनी चाहिए. हालांकि, कभी-कभी map()
में filter()
भी असरदार हो सकता है. खास तौर पर, बहुत बड़े कलेक्शन के लिए.
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var mappedFilter = s2.map(function(image) {
var date = image.date();
var landsat = l8
.filterBounds(image.geometry())
.filterDate(date.advance(-1, 'day'), date.advance(1, 'day'));
// Return the input image with matching scenes in a property.
return image.set({
landsat: landsat,
size: landsat.size()
});
}).filter(ee.Filter.gt('size', 0));
print(mappedFilter);
reduceRegion()
बनाम reduceRegions()
बनाम for-loop
इनपुट के तौर पर बहुत बड़े या जटिल FeatureCollection
के साथ reduceRegions()
को कॉल करने पर, "गिनती की गई वैल्यू बहुत बड़ी है" गड़बड़ी का मैसेज दिख सकता है. एक संभावित समाधान यह है कि FeatureCollection
के बजाय reduceRegion()
को मैप किया जाए. एक और संभावित समाधान, (gasp) for-loop का इस्तेमाल करना है. हालांकि, Earth Engine में ऐसा करने का सुझाव नहीं दिया जाता, जैसा कि यहां, यहां, और यहां बताया गया है. हालांकि, reduceRegion()
को बड़े डेटा को कम करने के लिए, for-loop में लागू किया जा सकता है.
मान लें कि आपका मकसद, ImageCollection
में मौजूद हर इमेज के लिए, FeatureCollection
की हर सुविधा में पिक्सल (या कोई आंकड़ा) का औसत निकालना है.
यहां दिए गए उदाहरण में, पहले बताए गए तीन तरीकों की तुलना की गई है:
// Table of countries.
var countriesTable = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017");
// Time series of images.
var mod13a1 = ee.ImageCollection("MODIS/006/MOD13A1");
// MODIS vegetation indices (always use the most recent version).
var band = 'NDVI';
var imagery = mod13a1.select(band);
// Option 1: reduceRegions()
var testTable = countriesTable.limit(1); // Do this outside map()s and loops.
var data = imagery.map(function(image) {
return image.reduceRegions({
collection: testTable,
reducer: ee.Reducer.mean(),
scale: 500
}).map(function(f) {
return f.set({
time: image.date().millis(),
date: image.date().format()
});
});
}).flatten();
print(data.first());
// Option 2: mapped reduceRegion()
var data = countriesTable.map(function(feature) {
return imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
});
}).flatten();
print(data.first());
// Option 3: for-loop (WATCH OUT!)
var size = countriesTable.size();
// print(size); // 312
var countriesList = countriesTable.toList(1); // Adjust size.
var data = ee.FeatureCollection([]); // Empty table.
for (var j=0; j<1; j++) { // Adjust size.
var feature = ee.Feature(countriesList.get(j));
// Convert ImageCollection > FeatureCollection
var fc = ee.FeatureCollection(imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
}));
data = data.merge(fc);
}
print(data.first());
ध्यान दें कि डीबग करने के लिए, हर कलेक्शन से first()
चीज़ को प्रिंट किया जाता है. आपको यह उम्मीद नहीं करनी चाहिए कि पूरा नतीजा इंटरैक्टिव तरीके से उपलब्ध होगा: आपको Export
करना होगा. यह भी ध्यान रखें कि for-लूप का इस्तेमाल, ज़्यादा से ज़्यादा सावधानी के साथ और सिर्फ़ आखिरी विकल्प के तौर पर किया जाना चाहिए. आखिर में, for-loop के लिए, इनपुट कलेक्शन का साइज़ मैन्युअल तरीके से हासिल करना और उसे सही जगहों पर हार्डकोड करना ज़रूरी है. अगर आपको इनमें से कोई भी बात समझ नहीं आ रही है, तो 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));
प्रिंट किए गए कलेक्शन की जांच करके पुष्टि करें कि डुप्लीकेट आइटम हटा दिए गए हैं.