מבוא לתכנות פונקציונלי
Earth Engine משתמש במערכת עיבוד מקבילי כדי לבצע חישובים במספר גדול של מכונות. כדי לאפשר עיבוד כזה, מערכת Earth Engine משתמשת בטכניקות סטנדרטיות שמשמשות בדרך כלל שפות פונקציונליות, כמו שקיפות רפרנציאלית והערכה עצלה, כדי להשיג אופטימיזציה משמעותית ושיפורים ביעילות.
המושג העיקרי שמבדיל בין תכנות פונקציונלי לבין תכנות פרוצדורלי הוא היעדר תופעות לוואי. המשמעות היא שהפונקציות שאתם כותבים לא מסתמכות על נתונים מחוץ לפונקציה ולא מעדכנות אותם. כפי שאפשר לראות בדוגמאות שלמטה, אפשר לשנות את מבנה הבעיה כך שניתן יהיה לפתור אותה באמצעות פונקציות ללא תופעות לוואי – פונקציות שמתאימות הרבה יותר להרצה במקביל.
לולאות
לא מומלץ להשתמש בלולאות for ב-Earth Engine. אפשר להשיג את אותן תוצאות באמצעות פעולת map()
שבה מציינים פונקציה שאפשר להחיל באופן עצמאי על כל רכיב. כך המערכת יכולה לחלק את העיבוד למכונות שונות.
בדוגמה הבאה מוסבר איך לקחת רשימה של מספרים וליצור רשימה אחרת עם הריבועים של כל מספר באמצעות map()
:
עורך הקוד (JavaScript)
// This generates a list of numbers from 1 to 10. var myList = ee.List.sequence(1, 10); // The map() operation takes a function that works on each element independently // and returns a value. You define a function that can be applied to the input. var computeSquares = function(number) { // We define the operation using the EE API. return ee.Number(number).pow(2); }; // Apply your function to each item in the list by using the map() function. var squares = myList.map(computeSquares); print(squares); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
תנאי If/Else
בעיה נפוצה נוספת שמשתמשים חדשים נתקלים בה, אם הם רגילים לפרדיגמת תכנות פרוצדורלית, היא השימוש הנכון באופרטורים מותנים של if/else ב-Earth Engine. למרות שממשק ה-API מספק אלגוריתם ee.Algorithms.If()
, מומלץ מאוד לא להשתמש בו, אלא בגישה פונקציונלית יותר באמצעות map()
ומסננים.
ב-Earth Engine נעשה שימוש ב
ביצוע מושהה, כלומר הערכה של ביטוי מתעכבת עד שנדרש הערך האמיתי שלו. במקרים מסוימים, מודל הביצוע הזה יבדוק את האפשרויות של True ו-False של משפט ee.Algorithms.If()
. זה יכול להוביל לחישובים נוספים ולשימוש בזיכרון, בהתאם לביטויים ולמשאבים שנדרשים להפעלתם.
נניח שרוצים לפתור וריאציה של הדוגמה שלמעלה, שבה המשימה היא לחשב את הריבועים של מספרים אי-זוגיים בלבד. הנה דוגמה לפתרון הבעיה בלי תנאי if/else:
עורך הקוד (JavaScript)
// The following function determines if a number is even or odd. The mod(2) // function returns 0 if the number is even and 1 if it is odd (the remainder // after dividing by 2). The input is multiplied by this remainder so even // numbers get set to 0 and odd numbers are left unchanged. var getOddNumbers = function(number) { number = ee.Number(number); // Cast the input to a Number so we can use mod. var remainder = number.mod(2); return number.multiply(remainder); }; var newList = myList.map(getOddNumbers); // Remove the 0 values. var oddNumbers = newList.removeAll([0]); var squares = oddNumbers.map(computeSquares); print(squares); // [1, 9, 25, 49, 81]
הפרדיגמה הזו רלוונטית במיוחד כשעובדים עם קולקציות. אם רוצים להחיל אלגוריתם אחר על האוסף על סמך תנאים מסוימים, הדרך המומלצת היא קודם לסנן את האוסף על סמך התנאי, ואז map()
פונקציה אחרת על כל אחת מקבוצות המשנה. כך המערכת יכולה להריץ את הפעולה במקביל. לדוגמה:
עורך הקוד (JavaScript)
// Import Landsat 8 TOA collection and filter to 2018 images. var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA') .filterDate('2018-01-01', '2019-01-01'); // Divide the collection into 2 subsets and apply a different algorithm on them. var subset1 = collection.filter(ee.Filter.lt('SUN_ELEVATION', 40)); var subset2 = collection.filter(ee.Filter.gte('SUN_ELEVATION', 40)); // Multiply all images in subset1 collection by 2; // do nothing to subset2 collection. var processed1 = subset1.map(function(image) { return image.multiply(2); }); var processed2 = subset2; // Merge the collections to get a single collection. var final = processed1.merge(processed2); print('Original collection size', collection.size()); print('Processed collection size', final.size());
איטרציה מצטברת
יכול להיות שתצטרכו לבצע פעולה רציפה, שבה התוצאה של כל איטרציה משמשת את האיטרציה הבאה. Earth Engine מספק שיטה iterate()
למשימות כאלה. חשוב לזכור שהפקודה iterate()
מבוצעת באופן רציף, ולכן היא תהיה איטית בפעולות גדולות. משתמשים בו רק כשאי אפשר להשתמש ב-map()
ובמסננים כדי לקבל את הפלט הרצוי.
דוגמה טובה לשימוש ב-iterate()
היא ליצירת סדרת מספרי פיבונאצ'י. בדוגמה הזו, כל מספר בסדרה הוא סכום 2 המספרים הקודמים. הפונקציה iterate()
מקבלת 2 ארגומנטים: פונקציה (אלגוריתם) וערך התחלתי. הפונקציה עצמה מקבלת 2 ערכים,
הערך הנוכחי באיטרציה והתוצאה של האיטרציה הקודמת. בדוגמה הבאה מוצג אופן ההטמעה של סדרת פיבונאצ'י ב-Earth Engine.
עורך הקוד (JavaScript)
var algorithm = function(current, previous) { previous = ee.List(previous); var n1 = ee.Number(previous.get(-1)); var n2 = ee.Number(previous.get(-2)); return previous.add(n1.add(n2)); }; // Compute 10 iterations. var numIteration = ee.List.repeat(1, 10); var start = [0, 1]; var sequence = numIteration.iterate(algorithm, start); print(sequence); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
אחרי שרוכשים הבנה טובה של מושגי JavaScript, אפשר לעיין במדריך בנושא API כדי לקבל מבוא לפונקציונליות הגיאו-מרחבית של Earth Engine API.