1 of 32

Becoming a better EE Programmer

Ujaval Gandhi, ujaval@google.com

Earth Engine India Advanced Summit 2019

#EEIA19

2 of 32

Earth Engine is different

#EEIA19

  • This session will focus on 2 common areas where new developers struggle
    • Client vs. Server
    • Conditionals and Loops

3 of 32

Geospatial�Datasets

Algorithmic�Primitives

add

focal_min

filter

reduce

join

distance

mosaic

convolve

Results

Storage and Compute

Requests

4 of 32

Client vs. Server

#EEIA19

  • Server objects and methods start with ee.
  • Classes that don’t start with ee are client-side, i.e. print(), Map., Export.
  • Javascript literals are client side, i.e. var string = ‘This is a string’
  • Client-side processing is done in your browser
  • Server-side processing is done in a Google data center - distributed among many machines
  • Server-side objects don't necessarily work with client side functions and vice versa.

5 of 32

var serverString = ee.String('I am on server')

var clientString = 'I am on client'

var strings = clientString + '| ' + serverString

print(strings)

Client vs. Server

#EEIA19

6 of 32

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

7 of 32

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

8 of 32

// 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

9 of 32

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 ….

10 of 32

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

11 of 32

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

12 of 32

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.

13 of 32

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

14 of 32

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

15 of 32

// 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

16 of 32

// 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

17 of 32

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

18 of 32

Example 4: A simple app to view random locations

#EEIA19

If you need random numbers in EE, consider the following server-side methods first

  • randomColumn(), Adds a deterministic pseudorandom numbers property to a image/feature collection
    • Useful to generate training/validation partitions
  • sample() / sampleRegions() / stratifiedSample(), Extracts a random sample of points from an image
    • useful for accuracy assessment

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

19 of 32

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

20 of 32

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

21 of 32

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.

22 of 32

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

23 of 32

// 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

24 of 32

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

25 of 32

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

26 of 32

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

27 of 32

The ‘good’ way

#EEIA19

Use filters to select appropriate images

Use iterate() to cumulatively apply the filters and create a master collection

28 of 32

// 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

29 of 32

// 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

30 of 32

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

31 of 32

// 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

32 of 32

#EEIA19