Becoming a better EE Programmer
Ujaval Gandhi, ujaval@google.com
Earth Engine India Advanced Summit 2019
#EEIA19
Slides: https://goo.gl/fShfHt
Earth Engine is different
#EEIA19
Geospatial�Datasets
Algorithmic�Primitives
add
focal_min
filter
reduce
join
distance
mosaic
convolve
Results
Storage and Compute
Requests
Client vs. Server
#EEIA19
var serverString = ee.String('I am on server')
var clientString = 'I am on client'
var strings = clientString + '| ' + serverString
print(strings)
Client vs. Server
#EEIA19
var myList = ee.List.sequence(1, 10);
var myFunction = function(number) {
return number + 1; // client-side code
}
print(myFunction(1));
print(myList.map(myFunction)) // FAIL. Client side objects in map() call
// Re-define the function to use server-side objects
var myFunction = function(number) {
return ee.Number(number).add(1); // servers-side code using EE API
}
var newList = myList.map(myFunction);
print(newList);
Map/reduce needs server side objects
#EEIA19
When to use client-side code
#EEIA19
There are only a few scenarios where client side code is appropriate
Getting current date
Looping through a list to add/export multiple layers
Building an object that is later wrapped in a server-side object
Building UI Apps
We will see now examples that illustrate these scenarios
// Get client-side time and convert to server-side object
var today = ee.Date(Date.now());
var lastMonth = today.advance(-1, 'month');
var collection = ee.ImageCollection("COPERNICUS/S2");
var lastMonthImages = collection.
filterDate(lastMonth, today).
filterBounds(geometry);
Map.addLayer(lastMonthImages, {min:0, max:3000, bands: ['B4', 'B3', 'B2']}}
Example 1: Get the current date
#EEIA19
Example 2: Exporting all images in a collection
#EEIA19
Some options to consider
toBands()
Converts the collection to a single gigantic image containing all the bands
Export.video()
Converts the collection to a video with each image as a frame
If you really need individual images
Use Python API, it allows creating and starting tasks without manual intervention
Or ….
var filtered = l8_sr.filterDate('2018-01-01', '2018-12-31')
.filterBounds(geometry);
function visualizeNDVI(image) {
var ndvi = image.normalizedDifference(['B5', 'B4']);
var vis = ndvi.visualize({bands: 'nd', min:0, max:1, palette:ndviPalette});
return vis.set('system:index', image.get('system:index'))
}
var visualized = filtered.map(visualizeNDVI);
print(visualized.size()) // 23
Example 2: Setup
#EEIA19
var size = visualized.size().getInfo();
for (var i = 0; i < size ; i++) {
var image = ee.Image(visualized.toList(1, i).get(0));
Export.image.toDrive({
image: image,
region: geometry,
scale: 30,
fileNamePrefix: image.get('system:index').getInfo(),
folder: 'earthengine',
description: 'Export_' + i
})
}
Example 2: The ‘bad’ way
#EEIA19
Introducing evaluate()
#EEIA19
If you use an Earth Engine result (such as ids of images) in a widget, you will need to get the value from the server.
To keep from hanging the entire UI while that value is computed, you can use the evaluate() function to get the value asynchronously.
The evaluate() function begins a request for a value, and when the value is ready calls a callback function to do something with the result.
var doExport = function() {
var ids = visualized.aggregate_array('system:index');
ids.evaluate(function(imageIds) {
for(var i = 0; i < imageIds.length; i++) {
var image = ee.Image(visualized.toList(1, i).get(0));
Export.image.toDrive({
image: image,
...
fileNamePrefix: imageIds[i],
...
})}
})
print('Click button below to start export')
var button = ui.Button({label: 'Export', onClick: doExport})
print(button)
Example 2: A slightly better way
#EEIA19
Example 3: Visualizing a collection
#EEIA19
You want to visualize all images in a collection
Map.addLayer() is a client-side function.
Making addLayer() calls in a for-loop will block the UI and may hang your browser
// Client-side for loop to add each layer
var size = visualized.size().getInfo();
for (var i = 0; i < size ; i++) {
var image = ee.Image(visualized.toList(1, i).get(0));
var id = image.get('system:index').getInfo();
Map.addLayer(image, {}, id)
}
Example 3: The ‘bad’ way
#EEIA19
// Display the image with the given ID.
var display = function(id) {
var image = visualized.filter(ee.Filter.eq("system:index", id))
Map.layers().reset()
Map.addLayer(image, {}, id)
}
// Get the list of IDs and put them into a select
visualized.aggregate_array("system:index").evaluate(function(ids) {
Map.add(ui.Select({
items: ids,
onChange: display
}))
});
Example 3: A better way
#EEIA19
Example 4: Using UI Elements
#EEIA19
User Interface (UI) elements, like buttons, panels, slider etc. are client-side objects
They are added to the Map object in the code editor
You need to use client-side objects when working with UI elements
Example 4: A simple app to view random locations
#EEIA19
If you need random numbers in EE, consider the following server-side methods first
But here we need a random number of the client side every-time the user clicks a button, so we need to use a client-side library.
Math.random() - client-side javascript method
Client-side if/else to set label values
evaluate() to make asynchronous call
var goToLocation = function() {
var lon = (Math.random() * 360) -180;
var lat = (Math.random() * 140) -75;
Map.setCenter(lon, lat, 12)
label1.setValue(lat + ',' + lon)
}
var label1 = ui.Label('coordinates');
var button = ui.Button({
label: 'Go to a random place',
onClick: goToLocation,
})
Map.setOptions('SATELLITE')
Map.add(button)
Map.add(label1)
Example 4: Click and fly to a random location
#EEIA19
label2.setValue('Looking for images')
var geometry = ee.Geometry.Point([lon, lat])
var filtered = s2.filterBounds(geometry)
var size = filtered.size();
size.evaluate(function(size) {
if (size !== 0) {
var image = ee.Image(filtered.limit(1, 'system:time_start',false).first())
var date = ee.Date(image.get('system:time_start')).format('YYYY-MM-dd');
label2.setValue(date.getInfo())
Map.layers().reset()
Map.addLayer(image)
}
else {
label2.setValue('no image')
}
});
Example 4: Add bits to fetch and display an image
#EEIA19
Functional Programming Concepts
#EEIA19
To enable parallel processing, Earth Engine takes advantage of standard techniques commonly used by functional languages
The main concept that sets functional programming apart from procedural programming is the absence of side effects. What it means is that the functions that you write doesn’t rely on or update data that is outside of the function.
var collection = ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA');
// 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));
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);
Use map() and filter() instad of if/else conditions
#EEIA19
// 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]
Use map() instead of for-loops
#EEIA19
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]
Use iterate() when you need cumulative iteration
#EEIA19
Functional Programming: Example
#EEIA19
You want to create a collection of landsat images
You want to select images based on certain conditions
Use Landsat 4 images from year 1982 to 1984
Use Landsat 5 images from year 1984, to 1999
….
You want to harmonize the bands so ‘R’, ‘G’, and ‘B’ correspond to the respective band numbers based on the collection
The ‘bad’ way
#EEIA19
Write if/else statements to create these selections
Use a for-loop to go through all images and add them to a master collection
The ‘good’ way
#EEIA19
Use filters to select appropriate images
Use iterate() to cumulatively apply the filters and create a master collection
// We define a feature collection, where each feature holds the appropriate year range,
// collection and R/G/B bands so we can pick the one relevant for the year
var landsats = ee.FeatureCollection([
ee.Feature(null, { collection: ee.ImageCollection('LANDSAT/LT04/C01/T1_TOA'), red: 'B3', green: 'B2', blue: 'B1', from: 1982 , to: 1984 }),
ee.Feature(null, { collection: ee.ImageCollection('LANDSAT/LT05/C01/T1_TOA'), red: 'B3', green: 'B2', blue: 'B1', from: 1984 , to: 1999 }),
ee.Feature(null, { collection: ee.ImageCollection('LANDSAT/LE07/C01/T1_TOA'), red: 'B3', green: 'B2', blue: 'B1', from: 1999 , to: 2014 }),
ee.Feature(null, { collection: ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA'), red: 'B4', green: 'B3', blue: 'B2', from: 2014 , to: 2020 })]);
Pack all relevant data in a collection
#EEIA19
// Get the appropriate Landsat collection for the year
function getLandsatByYear(year) {
return landsats
.filter(ee.Filter.and(
ee.Filter.lte('from', year),
ee.Filter.gt ('to', year)
)).first();
}
We can filter a collection easily
#EEIA19
var accumulateImages = function(year, imageCollection){
var startDate = ee.Date(ee.String(ee.Number(year).toInt()).cat(startDay));
var endDate = ee.Date(ee.String(ee.Number(year).toInt()).cat(endDay));
var landsat = getLandsatByYear(year);
var rgbCollection = ee.ImageCollection(landsat.get('collection'))
.filterDate(startDate, endDate)
// R/G/B bands differ for each collection, select the appropriate bands
// and rename them so they match across images
.select([landsat.get('red'), landsat.get('green'), landsat.get('blue')],
['red', 'green', 'blue'])
return ee.ImageCollection(imageCollection).merge(rgbCollection);
}
Create a function for iterate()
#EEIA19
// Create a master collection
// which has the appropriate RGB images from different collections.
var yearList = ee.List.sequence(startYear, endYear);
var resultCollection = ee.ImageCollection(
yearList.iterate(accumulateImages, ee.ImageCollection([])));
}
Cumulatively create a master collection
#EEIA19
#EEIA19
Questions?
Client vs. Server: https://developers.google.com/earth-engine/client_server
Functional Programming: https://developers.google.com/earth-engine/tutorial_js_03