مفاهیم برنامه‌نویسی تابعی

مقدمه‌ای بر برنامه‌نویسی تابعی

Earth Engine از سیستم پردازش موازی برای انجام محاسبات در تعداد زیادی از دستگاه‌ها استفاده می‌کند. برای فعال کردن چنین پردازشی، Earth Engine از تکنیک‌های استانداردی که معمولاً توسط زبان‌های تابعی استفاده می‌شود، مانند شفافیت ارجاعی و ارزیابی تنبل، برای بهینه‌سازی قابل‌توجه و افزایش کارایی بهره می‌برد.

مفهوم اصلی که برنامه‌نویسی تابعی را از برنامه‌نویسی رویه‌ای متمایز می‌کند عدم وجود عوارض جانبی است. این یعنی توابعی که می‌نویسید به داده‌های خارج از تابع متکی نیستند یا آن‌ها را به‌روز نمی‌کنند. همان‌طور که در مثال‌های زیر خواهید دید، می‌توان ساختار مشکل را به‌گونه‌ای تغییر داد که بااستفاده از توابع بدون عوارض جانبی حل شود - که برای اجرای موازی بسیار مناسب‌تر هستند.

حلقه‌های For

استفاده از حلقه‌های for در Earth Engine توصیه نمی‌شود. بااستفاده از عملیات map() که در آن تابعی را مشخص می‌کنید که می‌تواند به‌طور مستقل برای هر عنصر اعمال شود، می‌توان به نتایج یکسانی دست یافت. این کار به سیستم اجازه می‌دهد پردازش را بین دستگاه‌های مختلف توزیع کند.

مثال زیر نشان می‌دهد که چگونه می‌توانید فهرستی از اعداد را بگیرید و فهرست دیگری با مربع هر عدد بااستفاده از map() ایجاد کنید:

ویرایشگر کد (جاوا اسکریپت)

// 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 است. درحالی‌که، میانای برنامه‌سازی کاربردی الگوریتم ee.Algorithms.If() را ارائه می‌دهد، استفاده از آن به‌شدت منع می‌شود و رویکرد کاربردی‌تر بااستفاده از map() و فیلترها توصیه می‌شود. Earth Engine از اجرای معوق استفاده می‌کند، یعنی ارزیابی عبارت تا زمانی که مقدار واقعی آن واقعاً موردنیاز باشد، به تأخیر می‌افتد. در برخی موارد، این نوع مدل اجرا هر دو گزینه درست و نادرست عبارت ee.Algorithms.If() را ارزیابی می‌کند. این امر می‌تواند منجر به محاسبات اضافی و استفاده از حافظه شود، بسته به عبارت‌ها و منابع موردنیاز برای اجرای آن‌ها.

فرض کنید می‌خواهید نوعی از مثال بالا را حل کنید که در آن تکلیف محاسبه مربع فقط اعداد فرد است. رویکردی کاربردی برای حل این مشکل بدون شرایط if/else در زیر نشان داده شده است:

ویرایشگر کد (جاوا اسکریپت)

// 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() کنید. این کار به سیستم اجازه می‌دهد عملیات را موازی‌سازی کند. برای مثال:

ویرایشگر کد (جاوا اسکریپت)

// 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() برای ایجاد دنباله عدد فیبوناچی است. در اینجا، هر عدد در سری مجموع ۲ عدد قبلی است. تابع iterate() دو متغیر مستقل، یک تابع (الگوریتم)، و یک مقدار شروع می‌گیرد. خود تابع ۲ مقدار را منتقل می‌کند، مقدار فعلی در تکرار، و نتیجه تکرار قبلی. مثال زیر نحوه پیاده‌سازی دنباله فیبوناچی را در Earth Engine نشان می‌دهد.

ویرایشگر کد (جاوا اسکریپت)

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]

اکنون که درک خوبی از مفاهیم جاوا اسکریپت دارید، می‌توانید آموزش API را برای آشنایی با عملکرد جغرافیایی Earth Engine API ببینید.