מושגים בתכנות פונקציונלי

מבוא לתכנות פונקציונלי

ב-Earth Engine נעשה שימוש במערכת עיבוד מקבילי כדי לבצע חישובים במספר גדול של מכונות. כדי לאפשר עיבוד כזה, Earth Engine משתמש בשיטות סטנדרטיות שבהן משתמשים בדרך כלל בשפות פונקציונליות, כמו שקיפות רפרנציאלית והערכה עצלה, כדי לשפר את האופטימיזציה והיעילות.

העיקרון המרכזי שמבדיל בין תכנות פונקציונלי לתכנות פרוצדורלי הוא היעדר תופעות לוואי. כלומר, הפונקציות שאתם כותבים לא מסתמכות על נתונים מחוץ לפונקציה ולא מעדכנות אותם. כפי שניתן לראות בדוגמאות הבאות, אפשר לשנות את מבנה הבעיה כך שניתן יהיה לפתור אותה באמצעות פונקציות ללא תופעות לוואי – שמתאימות הרבה יותר לביצוע במקביל.

לולאות For

לא מומלץ להשתמש בלולאות for ב-Earth Engine. אפשר להשיג את אותן תוצאות באמצעות פעולת map(), שבה מציינים פונקציה שאפשר להחיל בנפרד על כל רכיב. כך המערכת יכולה להפיץ את העיבוד למכונות שונות.

בדוגמה הבאה אפשר לראות איך לוקחים רשימה של מספרים ויוצרים רשימה אחרת עם הריבועים של כל מספר באמצעות map():

Code Editor (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:

Code Editor (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() פונקציה אחרת לכל אחת מקבוצות המשנה. כך המערכת יכולה לבצע את הפעולה במקביל. לדוגמה:

Code Editor (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.

Code Editor (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.