1. Introduction to Earth Engine

Acknowledgments

Introduction

Google Earth Engine is a cloud-based platform that enables large-scale processing of satellite imagery to detect changes, map trends, and quantify differences on the Earth’s surface. This course covers a range of topics in Earth Engine to give you practical skills to master the platform and implement your remote sensing projects.

Objective

The main aim of this course is to familiarize you with the many possibilities you have with Earth Engine. By its very nature, this course can only introduce the basic elements of this geospatial processing service. We hope that it sparks your enthusiasm to go the remaining mile.

The aim of today's session is to give you an introduction to the Google Earth Engine processing environment and its basic elements. By the end of this day you will know the main pillars that make up Earth Engine. We strongly recommend that you play around with the following code snippets. By default, you can't break anything. Enjoy the ride and have fun experimenting.

Prerequisites

Completion of this block course requires use of the Google Chrome browser and a Google Earth Engine account. This account is linked with Google Cloud and users must create a Google Cloud Project to use the service. A step-by-step guide for signing-up for a GEE account with a cloud-project can be found here:

Sign up for Earth Engine

Once registered you can access the Earth Engine coding environment here:

https://code.earthengine.google.com

Google Earth Engine uses the JavaScript programming language. We will cover the basics of this language during this course. If you would like more detail you can read through the introduction provided here:

JavaScript background

Learning Resources

Code repository (this course)

https://code.earthengine.google.com/?accept_repo=users/wulf/GEO717

Documentation

EE Guides
Beginner's Cookbook

Discussion Group

Google Earth Engine Developers

Online-Book

Could-Based Remote Sensing with Google Earth Engine: Fundermentals and Applications

Google earth engine tutorials (same book as above with easy web access)

User summits

2023 user summit Geo for Good

2022 user summit Geo for Good

2021 user summit Geo for Good

2020 user summit Geo for Good

2019 user summit Geo for Good

2018 user summit in Dublin

2017 user summit in Mountain View

2016 user summit in Mountain View

Google Earth Outreach

https://earthoutreachonair.withgoogle.com/#earthengine

Github

https://github.com/google/earthengine-api

https://github.com/gee-community

https://github.com/giswqs/Awesome-GEE

Python

EE Courses

SpatialThoughts

GEARS Lab

Data resources

Earth Engine data catalog

Awesome gee community catalog

The Earth Engine code editor

  1. Editor Panel

    • The Code Editor is where you type, debug, run and manage your Javascript code

  2. Right Panel

    • Console tab for printing output.

    • Inspector tab for querying map results.

    • Tasks tab for managing long­ running tasks.

  3. Left Panel

    • Scripts tab for managing your programming scripts.

    • Docs tab for accessing documentation of Earth Engine objects and methods, as well as a few specific to the Code Editor application

    • Assets tab for managing assets that you upload.

  4. Interactive Map

    • For visualizing map layer output

  5. Search Bar

    • For finding datasets and places of interest

  6. Help Menu

    • User guide­: reference documentation

    • Developers Q&A site: link to EE's gis.stackexchange.com site

    • Developers disscussion group: Google group for discussing Earth Engine

    • Shortcuts: ­Keyboard shortcuts for the Code Editor

    • Feature Tour: ­ overview of the Code Editor

Earth Engine basics

Printing

Printing out information to the console is a basic task for getting information about an object, displaying the numeric result of a computation, displaying object metadata or helping with debugging. The iconic ‘Ay caramba!’ example in the Code Editor is:

print('Ay caramba!');

Open in Code Editor or copy this line into the Code Editor and click Run. Note that the output is displayed in the Console tab, on the right of the Code Editor.

Basic data types

The two most fundamental geographic data structures in Earth Engine are Image and Feature corresponding to raster and vector data types, respectively. Images are composed of bands and a dictionary of properties. Features are composed of a Geometry and a dictionary of properties. A stack of images (e.g. an image time series) is handled by an ImageCollection. A collection of features is handled by a FeatureCollection. Other fundamental data structures in Earth Engine include Dictionary, List, Array, Date, Number and String.

Strings

Using variables to store objects and primitives helps code readability. For example, a variable that stores a string object is defined by single ' or double " quotes (but don't mix them), with single quotes preferred. Make a new string and store it in a variable called greetString:

// Use single (or double) quotes to make a string.
var greetString = 'Hi-Diddily-Ho!';

// Use parentheses to pass arguments to functions.
print(greetString);

Open in Code Editor (all data type examples)

Numbers

Note that variables are defined with the keyword var. Variables can also store numbers:

// Store a number in a variable.
var number = 42;
print('The Answer to the Ultimate Question of Life is:', number);

Lists

Define lists with square brackets []. A list of numbers, for example:

// Use square brackets [] to make a list.
var listOfMonths = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
print('List of months:', listOfMonths);

// Make a sequence the easy way!
var months = ee.List.sequence(1,12);
print('Another list of months:', months);

Lists can also store strings or other objects. For example:

// Make a list of strings.
var listOfStrings = ['s', 't', 'r', 'i', 'n', 'g', 's'];
print("no", listOfStrings, 'attached');

Objects

Objects in JavaScript are dictionaries of key: value pairs. Make an object (or dictionary) using curly brackets {}, for example:

// Use curly brackets {} to make a dictionary of key:value pairs.
var object = {
  foo: 'bar',
  lucky: 13,
  stuff: ["Whatever, ", "I'll be at ", "Moe's"]
};
print('Dictionary:', object);

// Access dictionary items using square brackets.
print('Print foo:', object['foo']);

// Access dictionary items using dot notation.
print('Print stuff:', object.stuff);

Note that you can get a value from a dictionary by supplying the key. This example shows you how to do that for JavaScript objects. Later you'll learn how to do it for dictionaries that are on the Earth Engine server.

Playtime

These are the Top 3 happiest countries (and their scores) in the world (2024):

  • Finland (7.74)

  • Denmark (7.58)

  • Iceland (7.53)

Task: (1) Create a dictionary named 'happyCountries'. The dictionary should feature the countries as keys and their scores as value. (2) Print the dictionary.

Use the Code Editor

Saving scripts

When you modify any script from the course repository, you may want to save a copy for yourself. If you try to click the Save button, you will get an error message like below

This message appears because the shared course repository is a Read-only repository. You can click Yes to save a copy in your repository. If this is the first time you are using Earth Engine, you will be prompted to choose the name of your home folder. Choose the name carefully, as it cannot be changed once created.

JavaScript Syntax

All the JavaScript you need to know (almost)

Comments

Open in Code Editor (all Syntax examples)

// Line comments start with two forward slashes. Like this line. 

/* Multi line comments start with a forward slash and a star,
and end with a star and a forward slash. */

Quotes

// String objects start and end with a single quote.
var my_variable = 'Woo Hoo!';

// String objects can also start and end with double quotes.
// But don't mix and match them.
var my_other_variable = "D'oh!";

Semi-colon

Statements can but don't need to end with a semi-colon (no complains)

var test = 'I feel (in)complete...'

Parentheses

Parentheses are used to pass parameters to functions.

print("Yes Sir, I Can Boogie.");

Square brackets

Square brackets are used for selecting items within a list.

var my_list = ['Eat', 'My', 'Shorts'];

// The zero index refers to the first item in the list.
print(my_list[0]);

Curly brackets

Curly brackets (or braces) can be used to define dictionaries (key:value pairs)

var my_dict = {'food':'donots', 'color':'yellow', 'troublemakers':3};

// Square brackets can be used to access dictionary items by key.
print(my_dict['color']);

// Or you can use the dot notation to get the same result.
print(my_dict.color);

Errors

var bad = ['something', 'is', 'missing';

Playtime

Task: Get to know your enemy. Generate errors by using (a) different quotes in a list and (b) missing brackets and debug them.

Use the code editor

ee.Objects

To ease processing at Google' cloud servers, Earth Engine wants you to put JavaScript objects and primitives into Earth Engine containers.

Declaring variables

var variableName = ee.ContainerType(value);

A container object (usually in the form ee.SomeVariableType) is used to wrap a native JavaScript object so that Google's servers can perform operations on it.

Strings

Think of ee.Thing as a container for a thing that exists on the server. In this example, the string is defined first, then put into the container.

Open in Code Editor (all ee.Objects examples)

// Define a string, then put it into an EE container.
var aString = 'To the cloud!';
var eeString = ee.String(aString);
print('Where to?', eeString);

Although the first argument to print() is just a string on the client, the second argument is actually sent to the server to be evaluated, then sent back.

Numbers

var num = ee.Number(42);

Arrays

var arr = ee.Array([[5, 2, 3], [-2, 7, 10], [6, 6, 9]]);

Lists

var list = ee.List([5, 'five', 6, 'six']); 

Dictionaries

var dict = ee.Dictionary({five: 5, six: 6}); 

Casting

Sometimes, Earth Engine doesn't know the type of an object that gets returned from a method. You, as the programmer, know that the object and need to cast it into the correct container.

// Make a sequence
var sequence = ee.List.sequence(1, 5);

// Use a method on an ee.List to extract a value.
var value = sequence.get(2);

// value.add is not a function
/* 
Performing an operation will result in an error
because EE does not know which object 'value' is.
The resulting error message reads:
"value.add is not a function"
*/

print('Error', value.add(3));

// Cast the return value of get() to a number.
print('No error:', ee.Number(value).add(3));

Task: What is the correct result of this operation?

Dates

Date objects are the way Earth Engine represents time. As in the previous examples, it is important to distinguish between a JavaScript Date object and an Earth Engine ee.Date object. Construct an ee.Date from a string, from a JavaScript Date, or using static methods provided by the ee.Date class. (See the Date section in the Docs tab for details). This example illustrates the construction of dates from strings or a JavaScript date representing milliseconds since midnight on January 1, 1970:

// Define a date in Earth Engine.
var date = ee.Date('2015-12-31');
print('Date:', date);

// Get the current time using the JavaScript Date.now() method.
var now = Date.now();
print('Milliseconds since January 1, 1970', now);

// Initialize an ee.Date object.
var eeNow = ee.Date(now);
print('Now:', eeNow);

Dates are useful for filtering collections, specifically as arguments to the filterDate() method.

Playtime

Task 1: Numbers

  • Generate a list of numbers from 1 to 10

  • Extract the number 4 from that list and define it a new variable

  • Add the number 7 to it by using the function ".add()"

Task 2: Dictionary

  • Read in the following data: var data = {'city': 'Marseille', 'population': 860000, 'elevation': 36}

  • Print the name of the city using the ".get()" function.

Task 3: Dates

  • Define your birthday as an "ee.Date".

  • Calculate your current age in days using the ".difference()" function.

  • Add ten years to your birthday using the function ".advance()".

Use the Code Editor

Functions

A function is a set of instructions to perform a specific task. Define a function with the function keyword. Function names start with a letter and have a pair of parentheses at the end. Functions often take parameters which tell the function what to do. These parameters go inside the parentheses (). The set of statements making up the function go inside curly brackets. The return keyword indicates what the function output is. There are several ways to declare a function, but here we'll use something like this:

var myFunction = function(parameter1, parameter2, parameter3) {
  statement;
  statement;
  statement;
  return statement;
};

Let's consider the lines one by one. The first line creates a new function and assigns it to the variable myFunction. This variable could have been named anything. It defines how to call the function later. The terms in the parentheses after the function name (i.e. parameter1, parameter2, parameter3) are the parameter names and could have been named anything as well, though it's good practice to give them unique names that are different from the code outside the function. Whatever you name them, these are the names that function will use to refer to the values that are passed into the function when it is called. The value of a parameter once it's been passed into a function is called an argument. Although functions can use variables declared outside the function (global variables), function arguments are not visible outside the function. Functions can take as many parameters as you need, even zero. Here's a simple example of a function that just returns its argument with some text:

Open in Code Editor

// Functions can be defined as a way to reuse code 
// and make it easier to read
var my_naming_function = function(name, firstName) {
  return 'I have a funny name: ' + firstName + ' ' + name;
};
print(my_naming_function('Tuckie', 'Ken'));

Here is another simple example using numbers.

// Define a function that takes a number and adds 1 to it
var my_counting_function = function(number) {
  return number + 1;
}
print(my_counting_function(1));

Calling a function

var result = functionName(input);

Playtime

Task: Define one function that states your name and calculates your favourite number.

Use the Code Editor

Mapping a function

Mapping a function over a collection applies the function to every element in the collection.

var outputCollection = inputCollection.map(functionName);

Here is a simple example:

var myList = ee.List.sequence(1, 10);

// Redefine the previous 'add 1' function using ee.Objects
var myFunction = function(number) { 
  return ee.Number(number).add(1);
}

// Map the function on the list
var newList = myList.map(myFunction);
print(newList);

Playtime

Task: Define a function that computes the squares (".pow(2)") and map it on a list of number from 1 to 10.

Use the Code Editor

Image

Okily Dokily! Let's get started with remote sensing images. Any raster data are represented as Image objects in Earth Engine. Images are composed of one or more bands and each band has its own name, data type, scale, mask and projection. Each image has metadata stored as a set of properties. An easy way to look into these properties is to print the image.

Printing

The following example prints the metadata of a Landsat 8 image:

Open in Code Editor

// Load an image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Print the image.
print('Landsat 8 image', image);

Inspect the output in the console to see metadata available for Landsat images.

Playtime

Task: Let's find out at which date and time the image was acquired.

  • to retrieve image properties use: var property_name = image.get('property')

  • to convert the time from milliseconds to a common date format, use the ee.Date() container and print it.

  • Be aware that the default time zone is UTC

Use the Code Editor

Image Visualization

The following illustrates the use of parameters to style a Landsat 8 image as a false-color composite:

Open in Code Editor

// Define the basemap
Map.setOptions('TERRAIN');  // "ROADMAP", "SATELLITE", "HYBRID" or "TERRAIN" 

// Load an image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Define the visualization parameters.
var vizParams = {
  bands: ['B5', 'B4', 'B3'],
  min: 0.05,
  max: 0.3,
};

// Center the map and display the image.
Map.centerObject(image); // Carmague, France
Map.addLayer(image, vizParams, 'false color infrared', true);

Playtime

Tasks:

  • Display the image as a 'natural false color image' using the bands B7 (SWIR2), B5 (NIR) and B4 (red).

  • Adjust the maximim reflectance of each band to B7 = 0.25, B5 = 0.35, B4 = 0.25.

  • Use the command "Map.setCenter()" to define the display location (lon: 4.99, lat: 43.43) and zoom level (11).

Use the Code Editor

Image Math

Image math can be performed using operators like add() and subtract(), but for complex computations with more than a couple of terms, the expression() function provides a good alternative.

Operators

Math operators perform basic arithmetic operations on image bands. They take two inputs: either two images or one image and a constant term, which is interpreted as a single-band constant image with no masked pixels. Operations are performed per pixel for each band.

As a simple example, consider the task of calculating the Normalized Difference Vegetation Index (NDVI) using Landsat imagery, where add(), subtract(), and divide() operators are used:

NDVI=(NIRRed)/(NIR+Red)NDVI = (NIR - Red) / (NIR + Red)

Open in Code Editor

// Load an image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Select the relevant spectral bands: red and NIR
var red = image.select('B4');
var nir = image.select('B5');

// Perform the mathematical operations
var ndvi = nir.subtract(red).divide(nir.add(red));

// Stick the resulting NDVI image on the map
Map.addLayer(ndvi, {min:0, max:1}, 'NDVI');

Note: the normalized difference operation is available as a shortcut method: normalizedDifference().

Open in Code Editor

// NDVI the easy way
// Load an image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Perform the normalized difference operation
var ndvi = image.normalizedDifference(['B5', 'B4']);

// Stick the resulting NDVI image on the map
Map.addLayer(ndvi, {min:0, max:1}, 'NDVI');

Playtime

Task: Perform the normalizedDifference() operation to calculate the NDWI (normalized difference water index, formula below) for the same scene.

NDWI=(B3B5)/(B3+B5)NDWI = (B3 - B5) / (B3 + B5)

Use the Code Editor

Expressions

Excellent! To implement more complex mathematical expressions, consider using image.expression(), which parses a text representation of a math operation. The following example uses expression() to compute the Enhanced Vegetation Index (EVI):

Open in Code Editor

// Load an image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Compute the EVI using an expression.
var evi = image.expression(
    '2.5 * ((NIR - Red) / (NIR + 6 * Red - 7.5 * Blue + 1))', {
      'NIR': image.select('B5'),
      'Red': image.select('B4'),
      'Blue': image.select('B2')
});

// Stick the resulting EVI image on the map
Map.centerObject(image, 9);
Map.addLayer(evi, {min: -0.4, max: 0.6, 
  palette: ['blue','cyan','yellow','green']}, 'EVI');

Observe that the first argument to expression() is the textual representation of the math operation, the second argument is a dictionary where the keys are variable names used in the expression and the values are the image bands to which the variables should be mapped.

Playtime

Task: Perform an image.expression() operation to calculate the Bare Soil Index (BSI, see formula below):

BSI=((Red+SWIR1)(NIR+Blue))/((Red+SWIR1)+(NIR+Blue))BSI = ((Red+SWIR1) – (NIR+Blue)) / ((Red+SWIR1) + (NIR+Blue))

Use the Code Editor

Image Operations

ee.Image objects have a set of relational, conditional, and boolean methods for constructing decision-making expressions. The results of these methods are useful for limiting analysis to certain pixels or regions through masking, developing classified maps, and value reassignment.

Relational and Boolean Operations

Relational methods include:eq()(meaning 'equal' or '=') ,gt()(meaning 'greater than' or '>') , gte(), (meaning 'greater than or equal' or '>=') ,lt(), (meaning 'less than' or '<') and lte()(meaning 'less than or equal' or '<=') .

Boolean methods include: and(),or(), and not()

To perform per-pixel comparisons between images, use relational operators. To extract urbanized areas in an image, this example uses relational operators to threshold spectral indices, combining the thresholds with the and operator:

Open in Code Editor

// Load a Landsat 8 image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Create NDVI and NDWI spectral indices.
var ndvi = image.normalizedDifference(['B5', 'B4']);
var ndwi = image.normalizedDifference(['B3', 'B5']);

// Create a binary layer using logical operations.
var bare = ndvi.lt(0.3).and(ndwi.lt(0));


// Stick the resulting images on the map
Map.addLayer(ndvi, {min:0, max:1}, 'NDVI', false);
Map.addLayer(ndwi, {min:0, max:1}, 'NDWI', false);

// Mask and display the binary layer.
Map.setCenter(4.7875, 43.5408, 10);
Map.setOptions('satellite');
Map.addLayer(bare, {}, 'bare');
Map.addLayer(bare.selfMask(), {}, 'bare selfMask');
Map.addLayer(bare.updateMask(bare), {}, 'bare updateMask');

Playtime

Task:

  • ".select()" the thermal band 'B10' from the image, threshold it at 300 K, and mask areas that are 'no water'.

  • Display the water in blue on top of the natural false colour image (B7, B5, B2).

Use the Code Editor

Conditional Operations

Another way to implement conditional operations on images is with the where() operator. Consider the need to replace masked pixels with some other data. In the following example, cloudy pixels are replaced by pixels from a cloud-free image using where():

Open in Code Editor

// Load a cloudy Landsat 8 image.
var image = ee.Image('LANDSAT/LC08/C02/T1_TOA/LC08_196030_20180712')
print('cloud cover (%)', image.get('CLOUD_COVER'))

// Define the visualization parameters.
var vizParams = {
  bands: ['B7', 'B5', 'B3'],
  min: 0.05,
  max: 0.3,
};

// Center the map and display the image.
Map.centerObject(image, 9); // Carmague, France
Map.addLayer(image, vizParams, 'cloudy L8 image', true);

// Load another image to replace the cloudy pixels.
var replacement = ee.Image('LANDSAT/LC08/C02/T1_TOA/LC08_196030_20180914');
Map.addLayer(replacement, vizParams, 'replacement image', true);

// Compute a cloud score band.
var cloud = ee.Algorithms.Landsat.simpleCloudScore(image).select('cloud');
Map.addLayer(cloud, {}, 'L8 - cloud score', true);

// Set cloudy pixels to the other image.
var replaced = image.where(cloud.gt(10), replacement);

// Display the result.
Map.addLayer(replaced, vizParams, 'clouds replaced', true);

There are a couple of things in this code that are worth mentioning in detail. First, the select() function is useful for extracting the bands of interest from an image. Here, we select only the band we care about: cloud. The next thing is the logical operator gt() which stands for "greater thean." We use gt(10) to create a binary image in which all the pixels that exceed the value of 10 (those that are clouds) get replaced by pixel values of the less cloudy image.

Playtime

Task: Replace the snow covered pixels from one image with the snow free pixel from the other image

  • Threshold the NDSI at 0.35 to identify snow covered pixel

  • Seperate water from snow using the NIR reflectance at 0.2

  • Replace the snow pixels using the ".where()" function

Use the Code Editor

Masking

Masking pixels in an image makes those pixels transparent and excludes them from analysis. Each pixel in each band of an image has a mask. Those with a mask value of 0 or below will be transparent. Those with a mask of any value above 0 will be rendered. The mask of an image is set using a call like image1.mask(image2). This call takes the values of image2 and makes them the mask of image1. Any pixels in image2 that have the value 0 will be made transparent in image1.

Open in Code Editor

var image = ee.Image('LANDSAT/LC08/C02/T1_TOA/LC08_196030_20180712')
print('L8 image', image)

// Define the visualization parameters.
var vizParams = {
  bands: ['B7', 'B5', 'B3'],
  min: 0.05,
  max: 0.3,
};

// Center the map and display the image.
Map.centerObject(image); // Carmague, France
Map.addLayer(image, vizParams, 'L8 - false color image', true);


// Compute a cloud score band.
var cloud = ee.Algorithms.Landsat.simpleCloudScore(image).select('cloud');
Map.addLayer(cloud, {}, 'L8 - cloud score', true);

// mask the image with a boolean cloud image using .updateMask()
image = image.updateMask(cloud.gt(10).not())
Map.addLayer(image, vizParams, 'L8 - masked for clouds', true);

The command .updateMask()updates an image's mask at all positions where the existing mask is not zero. In other words, if mask == 1 the data is retained, if mask == 0 the data is masked (set to transparent, NaN). The output image retains the metadata and footprint of the input image. The same operations apply to the command .mask()

The command .selfMask() updates an image's mask at all positions where the existing mask is not zero using the value of the image as the new mask value. In other words, all pixel with the image value zero get masked (set to transparent).

The command .unmask() replaces mask and value of the input image with the mask and value of another image at all positions where the input mask is zero. If only the input image gets unmasked, all masked pixel are set to zero.

Playtime

Task: Mask a Landsat 7 image based on its VZA-band (viewing zenith angle) to exclude all pixel affected by its SLC (scan line corrector) failure.

Use the Code Editor

Morphology

Earth Engine implements morphological operations as focal operations, specifically focal_max(), focal_min(), focal_median(), and focal_mode() instance methods in the Image class. The morphological operators are useful for performing operations such as erosion, dilation, opening and closing. For example, to perform an opening operation, use focal_min() followed by focal_max():

Open in Code Editor

// Load a Landsat 8 image, select the NIR band, threshold, display.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")
            .select('B5').gt(0.10);
Map.setCenter(4.71777, 43.38775, 13);
Map.addLayer(image, {}, 'NIR threshold');

// Define a kernel.
var kernel = ee.Kernel.circle({radius: 1});

// Perform an erosion followed by a dilation, display.
var opened = image
             .focal_min({kernel: kernel, iterations: 2})
             .focal_max({kernel: kernel, iterations: 2});
Map.addLayer(opened, {}, 'opened');

Note that in the previous example, a kernel argument is provided to the morphological operator. The pixels covered by non-zero elements of the kernel are used in the computation. The iterations argument indicates how many times to apply the operator.

Playtime

Task: Let's go to a happy place (Finland) and buffer clouds. Calculate the simpleCloudScore, threshold the score at 50 and perform an opening operation to eliminate false positives and buffer the cloud margins. Mask the image based on your buffered cloud mask.

Use the Code Editor

ImageCollection

An ImageCollection is a stack or sequence of images. An ImageCollection can be loaded by pasting an Earth Engine asset ID into the ImageCollection constructor. You can find ImageCollection IDs in the data catalog. For example, to load the Sentinel-2 surface reflectance collection:

var s2col = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');

For each dataset you will find an "Explore with Earth Engine" section, which provides a code snippet to load and visualise the collection. This snippet is a great starting point for your work with this dataset.

ImageCollection overview

Filtering

This collection contains every Sentinel-2 image in the public catalog. There are a lot. Usually you want to filter the collection to include only relevant data that supports your purpose. Consider dates, spatial extent, quality, and other properties specific to a given dataset.

For instance, filter a Sentinel-2 surface reflectance collection by a single date range, a region of interest, or an image property.

var s2col = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterDate('2018-01-01', '2019-01-01')
  .filterBounds(ee.Geometry.Point(23.6, 37.9))
  .filter('CLOUDY_PIXEL_PERCENTAGE < 50');

Sorting

Sort a collection by time to ensure proper chronological sequence, or order by a property of your choice. By default, the visualization frame series is sorted in natural order of the collection. The arrangement of the series can be altered using the sort collection method, whereby an Image property is selected for sorting in either ascending or descending order. For example, to sort by time of observation, use the ubiquitous system:time_start property.

var s2col = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(ee.Geometry.Point(23.6, 37.9))
  .sort('system:time_start');

Or perhaps the order should be defined by increasing cloudiness:

var s2col = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterBounds(ee.Geometry.Point(23.6, 37.9))
  .sort('CLOUDY_PIXEL_PERCENTAGE');

Task: Filter the Sentinel-2 image collection to display the least-cloudy image of your hometown. Use the function ".first()" to select the first image of a collection.

Information and Metadata

As with Images, there are a variety of ways to get information about an ImageCollection. The collection can be printed directly to the console, but the console printout is limited to 5000 elements. Collections larger than 5000 images will need to be filtered before printing. Printing a large collection will be correspondingly slower. The following example shows various ways of getting information about image collections programmatically:

Open in Code Editor

var s2col = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED')
  .filterDate('2018-01-01', '2020-01-01')
  .filterBounds(ee.Geometry.Point(23.6, 37.9))
  .filter('CLOUDY_PIXEL_PERCENTAGE < 50');
  
print('Sentinel-2 collection',s2col)
  
// Get the number of images.
var count = s2col.size();
print('Nr. of images: ', count);

// Get the date range of images in the collection.
var range = s2col.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
print('Date range: ', ee.Date(range.get('min')), ee.Date(range.get('max')))

// Get statistics for a property of the images in the collection.
var sunStats = s2col.aggregate_stats('MEAN_SOLAR_ZENITH_ANGLE');
print('Sun elevation statistics: ', sunStats);

// Sort by a cloud cover property, get the least cloudy image.
var image = ee.Image(s2col.sort('CLOUDY_PIXEL_PERCENTAGE').first());
print('Least cloudy image: ', image);

// Get the 2nd least cloudy image
var image_2nd = ee.Image(s2col.sort('CLOUDY_PIXEL_PERCENTAGE').toList(5).get(1)); 
// .toList(5) generates a list of the first five images from the collection
// .get(1) retrieves the 2nd image of that list, as it starts counting from 0
print('2nd least cloudy image: ', image_2nd);

// Limit the collection to the 10 most recent images.
var recent = s2col.sort('system:time_start', false).limit(10);
print('Recent images: ', recent);  

Task: Provide the date for least-cloudy Sentinel-2 image with the lowest solar zenith angle (least amount of shadows) obtained in Istanbul.

Use the Code Editor

Reducing an ImageCollection

To composite images in an ImageCollection, use imageCollection.reduce(). This will composite all the images in the collection to a single image representing, for example, the min, max, mean or standard deviation of the images. (See the Reducers section for more information about reducers). For example, to create a median value image from a collection:

Open in Code Editor

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filter(ee.Filter.eq('WRS_PATH', 199))
  .filter(ee.Filter.eq('WRS_ROW', 34))
  .filterDate('2014-01-01', '2015-01-01');

// Compute a median image and display.
var median = collection.median();
Map.setCenter(-1.7461, 37.7906, 9);
Map.addLayer(median, {bands: ['B7', 'B5', 'B3'], max: 0.4}, 'median');

At each location in the output image, in each band, the pixel value is the median of all unmasked pixels in the input imagery (the images in the collection). In the previous example, median() is a convenience method for the following call:

Open in Code Editor

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filter(ee.Filter.eq('WRS_PATH', 199))
  .filter(ee.Filter.eq('WRS_ROW', 34))
  .filterDate('2014-01-01', '2015-01-01');

// Reduce the collection with a median reducer.
var median = collection.reduce(ee.Reducer.median());

// Display the median image.
Map.setCenter(-1.7461, 37.7906, 9);
Map.addLayer(median,
             {bands: ['B7_median', 'B5_median', 'B3_median'], max: 0.4},
             'also median');

Note that the band names differ as a result of using reduce() instead of the convenience method. Specifically, the names of the reducer have been appended to the band names.

Playtime

Task: Use the entire Landsat 8 Collection 2 (TOA) from 2014 to 2021 at path 172 and row 72 as your input dataset.

  • Print the number of images from this collection.

  • Compute and display the median and the mean of the collection.

  • Compute and display the ratio image between the sum of all optical mean bands and the sum of all optical median bands.

Use the Code Editor

Mapping over an ImageCollection

To apply a function to every Image in an ImageCollection use imageCollection.map(). The only argument to map() is a function which takes one parameter: an ee.Image. For example, the following code adds a timestamp band to every image in the collection:

Open in Code Editor

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filter(ee.Filter.eq('WRS_PATH', 195))
  .filter(ee.Filter.eq('WRS_ROW', 28));

// This function adds a band representing the image timestamp.
var addTime = function(image) {
  return image.addBands(image.metadata('system:time_start').rename('time'));
};

// Map the function over the collection and display the result.
print(collection.map(addTime));

Note that in the predefined function, the metadata() method is used to create a new Image from the value of a property. Having that time band is useful for the linear modelling of change and for making composites.

Even more useful is to apply a cloud filter to an entire ImageCollection. Let's see the example below:

Open in Code Editor

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate('2018-01-01', '2020-01-01')
  .filterBounds(ee.Geometry.Point(-4.441, 57.336)) // Scotland

// Compute a median image and display.
var median = collection.median();
Map.setCenter(-4.441, 57.336, 8);
Map.addLayer(median, {bands: ['B7', 'B5', 'B3'], max: 0.4}, 'median');

// This function masks clouds in Landsat 8 imagery.
var maskClouds = function(image) {
  var scored = ee.Algorithms.Landsat.simpleCloudScore(image);
  return image.updateMask(scored.select(['cloud']).lt(20));
};

// Map the cloud masking function over the collection.
var collection_cloudfree = collection.map(maskClouds);

// Compute a cloud-filtered median image and display.
var median_cloudfree = collection_cloudfree.median();
Map.addLayer(median_cloudfree, {bands: ['B7', 'B5', 'B3'], max: 0.4}, 'median cloudfree');

Note the marked difference in image quality between the 'normal' median and the 'cloud-free' median image. Mapping a function over an ImageCollection is a powerful tool, we will use a lot.

Playtime

Task: Write and map a function that adds a NDVI-band to the ImageCollection (use the command ".addBands()") and filters clouds. Display the cloud-filtered median of the NDVI.

Use the Code Editor

Compositing and Mosaicking

In general, compositing refers to the process of combining spatially overlapping images into a single image based on an aggregation function. Mosaicking refers to the process of spatially assembling image datasets to produce a spatially continuous image. In Earth Engine, these terms are used interchangeably, though both compositing and mosaicking are supported.

Consider the need to mosaic two, or more, neighboring Landsat scenes as these cover your study area. The following example demonstrates that using imageCollection.mosaic():

Open in Code Editor

// Study area
var aoi = ee.Geometry.Rectangle(9.0, 46.0, 12.0, 48.0)

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate('2019-09-01', '2019-09-17')
  .filterBounds(aoi)

print('collection', collection)

// Spatially mosaic the images in the collection and display.
var mosaic = collection.mosaic()
print('mosaic', mosaic)

Map.setCenter(9.4, 46.6, 7);
Map.addLayer(mosaic, {}, 'spatial mosaic');

// Clip the mosaic to the study area and display.
var mosaic_clip = mosaic.clip(aoi);
Map.addLayer(mosaic_clip, {bands:['B7','B5', 'B3'], max:0.4}, 'clipped mosaic');

Note that there is significant overlap in the Landsat tiles in the previous example. The mosaic() method composites overlapping images according to their order in the collection (last on top). To control the source of pixels in a mosaic (or a composite), you can make use of imageCollection.qualityMosaic()to maximizes an arbitrary band in the collection. The qualityMosaic() method sets each pixel in the composite based on which image in the collection has a maximum value for the specified band. For example, the following code demonstrates making a greenest pixel composite and a recent value composite:

Open in Code Editor

// This function masks clouds in Landsat 8 imagery.
var maskClouds = function(image) {
  var scored = ee.Algorithms.Landsat.simpleCloudScore(image);
  return image.updateMask(scored.select(['cloud']).lt(20));
};

// This function masks clouds and adds quality bands to Landsat 8 images.
var addQualityBands = function(image) {
  return maskClouds(image)
    // NDVI
    .addBands(image.normalizedDifference(['B5', 'B4']).rename('ndvi'))
    // time in milliseconds since 1970
    .addBands(image.metadata('system:time_start'));
};

// Load a 2014 Landsat 8 ImageCollection.
// Map the cloud masking and quality band function over the collection.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate(ee.Date(Date.now()).advance(-1,'year'), ee.Date(Date.now()))
  .map(addQualityBands);

// Create a cloud-free, most recent value composite.
var recentValueComposite = collection.qualityMosaic('system:time_start');

// Create a greenest pixel composite.
var greenestPixelComposite = collection.qualityMosaic('ndvi');

// Display the results.
Map.setCenter(10.0, 46.0, 6); // San Francisco Bay
var vizParams = {bands: ['B7', 'B5', 'B3'], min: 0, max: 0.4};
Map.addLayer(recentValueComposite, vizParams, 'recent value composite');
Map.addLayer(greenestPixelComposite, vizParams, 'greenest pixel composite');

Playtime

Task: Generate a "whitest pixel composite" by maximizing snow cover using the NDSI ( .normalizedDifference(['Green', 'SWIR1']) ).

Use the Code Editor

Geometry

Earth Engine handles vector data with the Geometry type. The GeoJSON spec describes in detail the type of geometries supported by Earth Engine, including Point (a list of coordinates in some projection), LineString (a list of points), LinearRing (a closed LineString), and Polygon (a list of LinearRings where the first is a shell and subsequent rings are holes). Earth Engine also supports MultiPoint, MultiLineString, and MultiPolygon. The GeoJSON GeometryCollection is also supported, although it has the name MultiGeometry within Earth Engine.

Creating Geometries

Using any of the drawing tools will automatically create a new geometry layer and add an import for that layer to the Imports section. To add geometries to a new layer, hover on the Geometry Imports in the map display and click the +new layer link. You can also toggle visibility of the geometries from the Geometry Imports section.

To configure the way geometries are imported to your script, click the settings icon next to the layer in the Geometry Imports section on the map or in the Imports section of the code editor. The geometry layer settings tool will be displayed in a dialog box which should look something like the Figure below. Note that you can import the drawn shapes as geometries, features or feature collections. The geometry import settings also allow you to change the color with which the layer is displayed, add properties to the layer (if it is imported as a Feature or FeatureCollection) or rename the layer.

Finally, to prevent geometries in a layer from being edited, you can lock the layer by pressing the "lock_open" icon next to the layer. This will prevent adding, deleting, or editing any geometries on the layer. To unlock the layer again, press the "lock" icon.

Playtime

Test the geometry tools, by playing with them.

  • Edit layer properties (e.g. rename them, adjust the colors, and lock the editing mode).

  • Adjust the geometries (e.g. move them, change the extent, delete them)

To create a Geometry programmatically, provide the constructor with the proper list(s) of coordinates. For example:

Open in Code Editor

// Display various geometries
Map.setCenter(0.0, 0.0, 3)  // (lon, lat, zoom)
Map.setOptions('TERRAIN');  // "ROADMAP", "SATELLITE", "HYBRID" or "TERRAIN" 

var point = ee.Geometry.Point([0.0, 0.0]);
Map.addLayer(point, {color:'red'}, 'Point')  

var lineString = ee.Geometry.LineString(
  [[-30, -5], [0, -10], [30, -5]]);
Map.addLayer(lineString, {color:'black'}, 'LineString')  

var linearRing = ee.Geometry.LinearRing(
  [[20, 10], [10, 10], [10, 20], [20, 20], [20, 10]]);
Map.addLayer(linearRing, {color:'blue'}, 'LinearRing')  

var rectangle = ee.Geometry.Rectangle([-20, 10, -10, 20]);
Map.addLayer(rectangle, {color:'blue'}, 'Rectangle')  

var polygon = ee.Geometry.Polygon([
  [[-45, -15], [45, -15], [45, 25], [-45, 25], [-45, 25]]
]);
Map.addLayer(polygon, {color:'yellow'}, 'Polygon')  

To display a geometry just by its outline without any fill color, you need to convert the geometry into an image and paint its margins.

Open in Code Editor

// Display a polygon by its outline
var polygon2 = ee.Geometry.Polygon([
  [[-50, -25], [50, -25], [50, 25], [-50, 25], [-50, 25]]
]);

var polygon_outline = ee.Image().byte().paint({
  featureCollection: polygon2,
  color: 1,
  width: 3
});
Map.centerObject(polygon2)
Map.addLayer(polygon_outline, {palette: ['red']}, 'Polygon Outline')

Playtime

Task: Create a circular geometry around Rio de Janeiro by buffering a point with a 100 km radius. Display the outline of the geometry.

Use the Code Editor

Geometry Information

To view information about a geometry, print it. To access the information programmatically, Earth Engine provides several methods. For example, to get information about a polygon, use:

Open in Code Editor

// Extract various geometry information and metadata
var country_name = 'Switzerland'; // 'Liechtenstein'; //
var countries = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017");  
// https://developers.google.com/earth-engine/datasets/catalog/USDOS_LSIB_SIMPLE_2017

var country = countries.filter(ee.Filter.eq('country_na', country_name));
var polygon = country.geometry(); 

print('Polygon printout: ', polygon);
Map.centerObject(polygon) 
Map.addLayer(polygon, {color:'red'}, 'Polygon')  

// Print polygon area in square kilometers.
print('Polygon area [km^2]: ', polygon.area().divide(1000 * 1000).round());

// Print polygon perimeter length in kilometers.
print('Polygon perimeter [km]: ', polygon.perimeter().divide(1000).round());

// Print the GeoJSON 'type'.
print('Geometry type: ', polygon.type());

// Print the coordinates as lists.
print('Polygon coordinates: ', polygon.coordinates());

Observe that the perimeter (or length) of a geometry is returned in meters and the area is returned in square meters unless a projection is specified. By default, the computation is performed on the WGS84 spheroid and the result is computed in meters or square meters.

Playtime

Task: Which country has the most/least compact shape?

A features compactness can be measured by using the Polsby-Popper test. First you need to map functions that add the features area and perimeter as a property.

Use the Code Editor

Geometric Operations

Earth Engine supports a wide variety of operations on Geometry objects. These include operations on individual geometries such as computing a buffer, centroid, bounding box, perimeter, convex hull, etc. For example:

// Create a polygon.
var polygon = ee.Geometry.Polygon([
  [[-5, 40], [65, 40], [65, 60], [-5, 60], [-5, 60]]
]);

// Compute a 1000-km buffer of the polygon .
var buffer = polygon.buffer(1000000);

// Compute the centroid of the polygon.
var centroid = polygon.centroid();

// Display the results
Map.centerObject(buffer) 
Map.addLayer(buffer, {color:'blue'}, 'buffer');
Map.addLayer(polygon, {color:'red'}, 'polygon');
Map.addLayer(centroid, {color:'yellow'}, 'centroid');

Observe from the previous example that the buffer distance is specified in meters.

Supported geometric operations also include relational computations between geometries such as intersection, union, difference, distance, contains, etc.

// Create two circular geometries.
var poly1 = ee.Geometry.Point([-50, 30]).buffer(1e6);
var poly2 = ee.Geometry.Point([-40, 30]).buffer(1e6);

// Display polygon 1 in red and polygon 2 in blue.
Map.setCenter(-45, 30);
Map.addLayer(poly1, {color: 'FF0000'}, 'poly1');
Map.addLayer(poly2, {color: '0000FF'}, 'poly2');

// Compute the intersection, display it in green.
var intersection = poly1.intersection(poly2, ee.ErrorMargin(1));
Map.addLayer(intersection, {color: '00FF00'}, 'intersection');

// Compute the union, display it in magenta.
var union = poly1.union(poly2, ee.ErrorMargin(1));
Map.addLayer(union, {color: 'FF00FF'}, 'union');

// Compute the difference, display in yellow.
var diff1 = poly1.difference(poly2, ee.ErrorMargin(1));
Map.addLayer(diff1, {color: 'FFFF00'}, 'diff1');

// Compute symmetric difference, display in black.
var symDiff = poly1.symmetricDifference(poly2, ee.ErrorMargin(1));
Map.addLayer(symDiff, {color: '000000'}, 'symmetric difference');

In these examples, note that that maxError parameter is set to one meter for the geometry operations. The maxError is the maximum allowable error, in meters, from transformations (such as projection or reprojection) that may alter the geometry. If one of the geometries is in a different projection from the other, Earth Engine will do the computation in a spherical coordinate system, with a projection precision given by maxError. You can also specify a specific projection in which to do the computation, if necessary.

FeatureCollection

Features

A Feature in Earth Engine is defined as a GeoJSON Feature. Specifically, a Feature is an object with a geometry property storing a Geometry object (or null) and a properties property storing a dictionary of other properties.

To create a Feature, provide the constructor with a Geometry and (optionally) a dictionary of other properties. For example:

Open in Code Editor

// Make a feature and set some properties.
var feature = ee.Feature(ee.Geometry.Point([-122.22599, 37.17605]))
  .set('genus', 'Sequoia').set('species', 'sempervirens');

// Get a property from the feature.
var species = feature.get('species');
print(species);

// Set a new property.
feature = feature.set('presence', 1);

// Overwrite the old properties with a new dictionary.
var newDict = {genus: 'Brachyramphus', species: 'marmoratus'};
var feature = feature.set(newDict);

// Check the result.
print(feature);

In the previous example, note that properties can be set with either a key: value pair, or with a dictionary as a JavaScript literal. Also note that feature.set() overwrites existing properties.

Playtime

Task: Define a feature for your hometown (place of birth) and set the properties 'name', 'population' and 'country'. Calculate its distance to Campus Irchel.

Use the Code Editor

Collections

Groups of related features can be combined into a FeatureCollection, to enable additional operations on the entire set such as filtering, sorting and rendering. Besides just simple features (geometry + properties), feature collections can also contain other collections.

One way to create a FeatureCollection is to provide the constructor with a list of features. The features do not need to have the same geometry type or the same properties. For example

// Make a list of Features.
var features = [
  ee.Feature(ee.Geometry.Rectangle(30.01, 59.80, 30.59, 60.15), {name: 'Voronoi'}),
  ee.Feature(ee.Geometry.Point(-73.96, 40.781), {name: 'Thiessen'}),
  ee.Feature(ee.Geometry.Point(6.4806, 50.8012), {name: 'Dirichlet'})
];

// Create a FeatureCollection from the list and print it.
var fromList = ee.FeatureCollection(features);
print(fromList);

Earth Engine hosts a variety of table datasets. To load a table dataset, provide the table ID to the FeatureCollection constructor. For example, to load TIGER roads data:

var fc = ee.FeatureCollection('TIGER/2016/Roads');
Map.setCenter(-73.9596, 40.7688, 12);
Map.addLayer(fc, {}, 'US Census roads')
print(fc.limit(20))

To get a collection of random points in a specified region, you can use:

// Define a region in which to compute random points.
var country_name = 'Latvia'; 
var countries = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017");  
var country = countries.filter(ee.Filter.eq('country_na', country_name));
var region = country.geometry(); 

// Create 500 random points in the region.
var randomPoints = ee.FeatureCollection.randomPoints(region, 500);

// Display the points.
Map.centerObject(region);
Map.addLayer(randomPoints, {color:'red'}, 'random points');

Visualisation

As with images, geometries and features, feature collections can be added to the map directly with Map.addLayer(). The default visualisation will display the vectors with solid black lines and semi-opaque black fill. To render the vectors in color, specify the color parameter. For more control over how a FeatureCollection is displayed, use image.paint() with the FeatureCollection as an argument. Note that the empty image into which you paint the features needs to be cast prior to painting. Both the color and width with which the boundaries are drawn can be set with properties.

// Load a FeatureCollection from a table dataset: 'RESOLVE' ecoregions.
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');

// Display as default and with a custom color.
Map.setCenter(20,50,5)
Map.addLayer(ecoregions, {color: 'FF0000'}, 'default colored display');

// Create an empty image into which to paint the features, cast to byte.
var empty = ee.Image().byte();

// Paint all the polygon edges with the same number and width, display.
var outline = empty.paint({
  featureCollection: ecoregions,
  color: 1,
  width: 3
});
Map.addLayer(outline, {palette: 'black'}, 'edges');

// Paint the edges with different colors and widths.
var outlines = empty.paint({
  featureCollection: ecoregions,
  color: 'BIOME_NUM',
  width: 'NNH'
});
var palette = ['FF0000', '00FF00', '0000FF'];
Map.addLayer(outlines, {palette: palette, max: 14}, 'different color, width edges');

// Paint the interior of the polygons with different colors.
var fills = empty.paint({
  featureCollection: ecoregions,
  color: 'BIOME_NUM',
});
Map.addLayer(fills, {palette: palette, max: 14}, 'colored fills');

// Paint both the fill and the edges.
var filledOutlines = empty.paint(ecoregions, 'BIOME_NUM').paint(ecoregions, 0, 2);
Map.addLayer(filledOutlines, {palette: ['000000'].concat(palette), max: 14}, 'edges and fills');

Filtering

Methods for getting information from feature collection metadata are the same as those for image collections. Also, filtering a FeatureCollection is analogous to filtering an ImageCollection. (See the Filtering an ImageCollection section). There are the featureCollection.filterDate(), and featureCollection.filterBounds() convenience methods and the featureCollection.filter() method for use with any applicable ee.Filter.

Open in Code Editor

// Lake analysis

// Load lakes from a data table.
var lakes = ee.FeatureCollection("projects/sat-io/open-datasets/HydroLakes/lake_poly_v10");
print('lake example', lakes.first())             

// extract the list of continents distinguished in the lakes dataset 
var continents = lakes.distinct('Continent').aggregate_array('Continent')
print('list of continents', continents)

// This function calculates the total size of all lakes per continent
var lakeSum = function(continent_filter) {
  // filter the collection
  var lakes_unit = lakes.filter(ee.Filter.eq('Continent', continent_filter))
  // calculate the desired stats per continent
  var lakes_sum = lakes_unit.reduceColumns(ee.Reducer.sum(), ['Lake_area'])
  // extract and round the number from the resulting dictionary
  var lakes_sum_number = ee.Number(lakes_sum.get('sum')).round()
  return lakes_sum_number
}

// Map the function
var lakes_sum = continents.map(lakeSum)
print('lakes area sum per continent', lakes_sum)


// Display the lakes in Europe.
var lakes_europe = lakes.filter(ee.Filter.eq('Continent', 'Europe'))
Map.addLayer(lakes_europe, {color:'blue'}, 'lakes europe');

// Print the number of lakes.
print('Lakes count:', lakes.size());

// Print stats for the area property.
var area_stats = lakes.aggregate_stats('Lake_area')
print('Area stats:', area_stats);

// Filter lakes on a desired property
var largest_lake = lakes.filter(ee.Filter.eq('Lake_area', ee.Number(area_stats.get('max'))));
print('biggest puddle', largest_lake.first().get('Lake_name'))

// Sort lakes on a desired property
var deep_lakes = lakes.sort('Depth_avg', false).limit(5)
print('deep lakes', deep_lakes)

Playtime

Task: Filter the global river dataset for rivers in India with and upstream watershed area ('UPLAND_SKM') larger than 10 km^2.

Use the Code Editor

Mapping

To apply the same operation to every Feature in a FeatureCollection, use featureCollection.map(). For example, to add another area attribute to every feature in a watersheds FeatureCollection and to generate an entirely new FeatureCollection , use:

// Load lakes from a data table.
var lakes = ee.FeatureCollection("projects/sat-io/open-datasets/HydroLakes/lake_poly_v10");
print('lake example', lakes.first())             

// Display the lakes in Europe.
var lakes_europe = lakes.filter(ee.Filter.eq('Continent', 'Europe'))
Map.addLayer(lakes_europe, {color:'blue'}, 'lakes europe');

// This function computes the feature's perimeter and adds it as a property.
var addPerimeter = function(feature) {
  return feature.set({perimeter: feature.perimeter(100)});
};

// Map the area getting function over the FeatureCollection.
var lakes_europe_perimeter = lakes_europe.map(addPerimeter);

// Print the first feature from the collection with the added property.
print('First feature with perimeter:', lakes_europe_perimeter.first());

var keepProperties = lakes_europe_perimeter.first().propertyNames()
print('all properties', keepProperties)


// This function creates a new feature from the centroid of the geometry.
var getCentroid = function(feature) {
  // Keep this list of properties.
  var keepProperties = ['name', 'huc6', 'tnmid', 'areasqkm'];
  // Get the centroid of the feature's geometry.
  var centroid = feature.geometry().centroid();
  // Return a new Feature, copying properties from the old Feature.
  return ee.Feature(centroid).copyProperties(feature, keepProperties);
};

// Map the centroid getting function over the features.
var lakes_europe_centroids = lakes_europe_perimeter.map(getCentroid);
print('lakes_europe_centroids first', lakes_europe_centroids)
// Display the results.
Map.addLayer(lakes_europe_centroids, {color: 'red'}, 'centroids');

In the previous example, note that a new property is set based on a computation with the feature’s geometry. Properties can also be set using a computation involving existing properties. Note further that all selected properties are propagated to the features in the new collection.

Reducing

Which is the wettest or driest country in the world?

To aggregate data in the properties of a FeatureCollection, use featureCollection.reduceColumns().

Open in Code Editor

// Precipitation dataset
var dataset = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE')
                  .filter(ee.Filter.date('2019-01-01', '2020-01-01'));
var precip = dataset.select('pr').mean();

// This time, reprojection is required after reducing
precip = precip.select('pr').reproject('EPSG:4326', null, 10000)


var precipVis = {
  min: 0.0,
  max: 400.0,
  palette: [
    '1a3678', '2955bc', '5699ff', '8dbae9', 'acd1ff', 'caebff', 'e5f9ff',
    'fdffb4', 'ffe6a2', 'ffc969', 'ffa12d', 'ff7c1f', 'ca531a', 'ff0000',
    'ab0000'
  ],
};
Map.addLayer(precip, precipVis, 'Mean Precipitation');


// Administative units
var admin = ee.FeatureCollection("USDOS/LSIB/2017")
              .filter(ee.Filter.gt('Shape_Area', 1));  

var styleParams = {
  fillColor: 'b5ffb4',
  color: '00909F',
  width: 1.0,
};

var admin_style = admin.style(styleParams);
Map.addLayer(admin_style, {}, 'Administrative Units');

// Add the mean of each country as new properties of each feature.
var withPrecip = precip.reduceRegions(admin, ee.Reducer.mean())

// Extract the wettest 5 countries
var wettest5 = withPrecip.sort('mean', false)
                       .limit(5)
                       .reduceColumns(ee.Reducer.toList(), ['COUNTRY_NA']).get('list');
print('wettest5', wettest5)


// Extract the dryest 5 countries
var dryest5 = withPrecip.sort('mean', true)
                       .limit(5)
                       .reduceColumns(ee.Reducer.toList(), ['COUNTRY_NA']).get('list');
print('dryest5', dryest5)

Playtime

Task: Which is the windiest / calmest country on Earth?

Use the Code Editor

Reducer

Reducers are the way to aggregate data over time, space, bands, arrays and other data structures in Earth Engine. The ee.Reducer class specifies how data is aggregated. The reducers in this class can specify a simple statistic to use for the aggregation (e.g. minimum, maximum, mean, median, standard deviation, etc.), or a more complex summary of the input data (e.g. histogram, linear regression, list). Reductions may occur over time (imageCollection.reduce()), space (image.reduceRegion(), image.reduceNeighborhood()), bands (image.reduce()), or the attribute space of a FeatureCollection (featureCollection.reduceColumns() or FeatureCollection methods that start with aggregate_).

Reducers take an input dataset and produce a single output. When a single input reducer is applied to a multi-band image, Earth Engine automatically replicates the reducer and applies it separately to each band. As a result, the output image has the same number of bands as the input image; each band in the output is the reduction of pixels from the corresponding band in the input data. Some reducers take tuples of input datasets. These reducers will not be automatically replicated for each band. For example, ee.Reducer.LinearRegression() takes multiple predictor datasets (representing independent variables in the regression) in a particular order.

ImageCollection Reductions

Consider the example of needing to take the median over a time series of images represented by an ImageCollection. To reduce an ImageCollection, use imageCollection.reduce(). This reduces the collection of images to an individual image as illustrated in Figure below. Specifically, the output is computed pixel-wise, such that each pixel in the output is composed of the median value of all the images in the collection at that location. To get other statistics, such as mean, sum, variance, an arbitrary percentile, etc., the appropriate reducer should be selected and applied. For basic statistics like min, max, mean, etc., ImageCollection has shortcut methods like min(), max(), mean(), etc. They function in exactly the same way as calling reduce(), except the resultant band names will not have the name of the reducer appended.

For an example of reducing an ImageCollection, consider a collection of Landsat 8 images, filtered for cloud cover.

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA')
  .filterDate('2018-01-01', '2020-01-01')
  .filterBounds(ee.Geometry.Point(-4.441, 57.336)) // Scotland

// This function masks clouds in Landsat 8 imagery.
var maskClouds = function(image) {
  var scored = ee.Algorithms.Landsat.simpleCloudScore(image);
  return image.updateMask(scored.select(['cloud']).lt(20));
};

// Map the cloud masking function over the collection.
var collection_cloudfree = collection.map(maskClouds);

// Compute a cloud-filtered median image and display.
var median_cloudfree = collection_cloudfree.median();
Map.addLayer(median_cloudfree, {bands: ['B7', 'B5', 'B3'], max: 0.4}, 'median cloudfree');

Image Reductions

To reduce an Image, use image.reduce(). Reducing an image functions in an analogous way to imageCollection.reduce(), except the bands of the image are input to the reducer rather than the images in the collection. The output is also an image with number of bands equal to number of reducer outputs. For example:

Open in Code Editor

// Load an image.
var image = ee.Image("UMD/hansen/global_forest_change_2023_v1_11")
              .select(['last_b70', 'last_b50', 'last_b40']);

// Reduce the image to get a one-band maximum value image.
var maxValue = image.reduce(ee.Reducer.max());

// Display the result.
Map.setCenter(172.78, -43.74, 11);
Map.addLayer(maxValue, {min: 0, max: 120}, 'Maximum value image');

Playtime

Task: Provide the mean annual cloud probability for Zurich.

Use the Code Editor

Statistics of an Image Region

To get statistics of pixel values in a region of an ee.Image, use image.reduceRegion(). This reduces all the pixels in the region(s) to a statistic or other compact representation of the pixel data in the region (e.g. histogram). The region is represented as a Geometry, which might be a polygon, containing many pixels, or it might be a single point, in which case there will only be one pixel in the region. In either case, as illustrated in the Figure below, the output is a statistic derived from the pixels in the region.

For an example of getting pixel statistics in a region of an image using reduceRegion(), consider finding the mean spectral values of a 5-year Landsat composite within the boundaries of the Sierra Nevada Coniferous Forest

Open in Code Editor

// Landsat 8 image
var image = ee.Image('LANDSAT/LC08/C02/T1_TOA/LC08_194028_20190701')
Map.addLayer(image, {bands: ['B7', 'B5', 'B4'], max: [0.3, 0.5, 0.6], min: [0.05]}, 
'Landsat 8 - before cloud mask')

// Randolf glacier inventory
var RGI = ee.FeatureCollection("projects/sat-io/open-datasets/RGI/RGI_VECTOR_MERGED_V7");

// filter the RGI for the Aletsch glacier
var Aletsch = RGI.filter(ee.Filter.eq('glims_id', 'G008032E46504N'));   

// Display the region.
var Aletsch_outline = ee.Image().byte().paint({
  featureCollection: Aletsch,
  color: 1,
  width: 3
});
Map.addLayer(Aletsch_outline, {palette: '#ffffff'}, 'Aletsch glacier')  

// extract the glacier geometry
var aoi = Aletsch.first().geometry();                                 

// Reduce the region. The region parameter is the Feature geometry.
var meanDictionary = image.reduceRegion({
  reducer: ee.Reducer.mean(),
  geometry: aoi,
  scale: 30,
  maxPixels: 1e9
});

// The result is a Dictionary.  Print it.
print(meanDictionary);

Note that in this example the reduction is specified by providing the reducer (ee.Reducer.mean()), the geometry (Aletsch.first().geometry()), the scale (30 meters) and maxPixels for the maximum number of pixels to input to the reducer. A scale should always be specified in reduceRegion() calls. See this page for more information about how Earth Engine handles scale.

Area calculation

To calculate the area covered by raster images, you can also use image.reduceRegion(). You can calculate areas for any geometry using the .area() function. You can calculate the area within a geometry covered by a raster image using the pixelArea() function. See the example below for the Aletsch Glacier.

Open in Code Editor

// Area calculation using vector and raster data

// Landsat 8 image
var image = ee.Image('LANDSAT/LC08/C02/T1_TOA/LC08_194028_20190701')
Map.addLayer(image, {bands: ['B7', 'B5', 'B4'], max: [0.3, 0.5, 0.6], min: [0.05]}, 
'Landsat 8 - before cloud mask')

/* Masking parts of the image changes the image area calculation
var qa = image.select('QA_PIXEL');
var qa_mask = qa.bitwiseAnd(parseInt('11111', 2)).eq(0);
// option to cloud-mask the image
image = image.updateMask(qa_mask) 
Map.addLayer(image, {bands: ['B7', 'B5', 'B4'], max: 0.6, min: 0.1}, 
'Landsat 8 - after cloud mask')
*/

// Randolf glacier inventory
var RGI = ee.FeatureCollection("projects/sat-io/open-datasets/RGI/RGI_VECTOR_MERGED_V7");

// filter the RGI for the Aletsch glacier
var Aletsch = RGI.filter(ee.Filter.eq('glims_id', 'G008032E46504N'));   

// extract the glacier geometry
var aoi = Aletsch.first().geometry();                                 

// Area in the feature property 
var area_property = ee.Number(Aletsch.first().get('area_km2'))
                                             .multiply(100).round().divide(100)
print('Total glacier area (km2) - property', area_property)

// Calculate area of a geometry
var aoi_area_km2_vector = aoi.area().round().divide(10000).round().divide(100)
print('Total glacier area (km2) - vector', aoi_area_km2_vector)

// Calculate the area covered by a raster image
// ee.Image.pixelArea() generates an image in which the value of each pixel 
// is the area of that pixel in square meters.
var area_stats = image.select('B2').gte(0).multiply(ee.Image.pixelArea()).reduceRegion({
  reducer: ee.Reducer.sum(),
  geometry: aoi,
  scale: 100,
  maxPixels: 1e10,
  tileScale: 16
})

// extract the area value and convert to km2
var aoi_area_km2_raster = area_stats.getNumber('B2').divide(10000).round().divide(100)

// print(area_stats)
print('Total glacier area (km2) - raster', aoi_area_km2_raster)

var area_coverage_percent = aoi_area_km2_raster.divide(area_property)
                                               .multiply(10000).round().divide(100)
// set the new properties to the feature
Aletsch = Aletsch.set('area_km2_vector', aoi_area_km2_vector)
                 .set('area_km2_raster', aoi_area_km2_raster)
                 .set('area_km2_percent', area_coverage_percent)

print('Total glacier area (%) - calculated', Aletsch.get('area_km2_percent'))

Statistics of Image Regions

To get image statistics in multiple regions stored in a FeatureCollection, you can use image.reduceRegions() to reduce multiple regions at once. The input to reduceRegions() is an Image and a FeatureCollection. The output is another FeatureCollection with the reduceRegions() output set as properties on each Feature. In this example, means of the Landsat 7 annual composite NDVI in each feature geometry will be added as properties to the input features:

// Is Greenland the greenest country in Europe?

// Load input imagery: MODIS
var dataset = ee.ImageCollection('MODIS/061/MOD13Q1')
                .filter(ee.Filter.date('2022-01-01', '2023-01-01'));
var evi = dataset.select('EVI').mean();

var eviVis = { min: 0, max: 6000, palette: ['ffffff', 'ce7e45', 'df923d', 
    'f1b555', 'fcd163', '99b718', '74a901', '66a000', '529400', '3e8601', 
    '207401', '056201', '004c00', '023b01', '012e01', '011d01', '011301'] };
Map.setCenter(6.746, 46.529, 4);
Map.addLayer(evi, eviVis, 'evi');

var aoi = ee.Geometry.Rectangle([-30, 38, 27, 70], null, false); // Europe
Map.addLayer(aoi, {}, 'aoi', false)

// Load a FeatureCollection of countries
var countries = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017").filterBounds(aoi);

// Paint all the polygon edges with the same number and width, display.
var outline = ee.Image().byte().paint({
  featureCollection: countries,
  color: 1,
  width: 1
});
Map.addLayer(outline, {palette: 'white'}, 'borders');

// Add reducer output to the Features in the collection.
var EVImeans = evi.reduceRegions({
  collection: countries,
  reducer: ee.Reducer.mean(),
  scale: 1000,
  tileScale: 16
});

// Extract the top 5 evi
var greenest5 = EVImeans.sort('mean', false)
                       .limit(5)
                       .reduceColumns(ee.Reducer.toList(), ['country_na']).get('list');
print('greenest5', greenest5)


// Extract the bottom 5 evi
var brownest5 = EVImeans.sort('mean', true)
                       .limit(5)
                       .reduceColumns(ee.Reducer.toList(), ['country_na']).get('list');
print('brownest5', brownest5)

Linear Regressions

Earth Engine has several methods for performing linear regression using reducers: The simplest linear regression reducer is linearFit() which computes the least squares estimate of a linear function of one variable with a constant term. For a more flexible approach to linear modelling, use one of the linear regression reducers which allow for a variable number of independent and dependent variables. linearRegression() implements ordinary least squares regression(OLS). robustLinearRegression() uses a cost function based on regression residuals to iteratively de-weight outliers in the data (O’Leary, 1990). ridgeRegression() does linear regression with L2 regularization.

Regression analysis with these methods is suitable for reducing ee.ImageCollection, ee.Image, ee.FeatureCollection, and ee.List objects. The following examples demonstrate an application for each. Note that linearRegression(), robustLinearRegression(), and ridgeRegression() all have the same input and output structures, but linearFit() expects a two-band input (X followed by Y) and ridgeRegression() has an additional parameter (lambda, optional) and output (pValue).

linearFit()

The data should be set up as a two-band input image, where the first band is the independent variable and the second band is the dependent variable. The following example shows the estimation of the linear trend of climate reanalysis data (ERA5-Land) for air temperatures over the last decades. The dependent variable is air-temperature and the independent variable is time (years), added prior to calling linearFit():

Open in Code Editor

// ERA-5 variable to analyse
var variable = 'temperature_2m';

// Load input imagery: MODIS
var ERA5 = ee.ImageCollection("ECMWF/ERA5_LAND/DAILY_AGGR")
             .select(variable);

// colorramps
var palettes = require('users/gena/packages:palettes');
var lajolla = palettes.crameri.lajolla[25].reverse()
var vik = palettes.crameri.vik[25]

// Filter the image collection by years and calculate the mean
var years = ee.List.sequence(1980, 2023)

// This function aggregates the daily collection to an annual collection
var ERA5_yearly = ee.ImageCollection.fromImages(
  years.map(function(y) {
  return ERA5
      .filter(ee.Filter.calendarRange(y, y, 'year'))
      .mean()
      .subtract(273.15)
      .set('year', y)
      .set('system:time_start',ee.Date.fromYMD(y,1,1).millis());
}));
print('Years total', ERA5_yearly)

// This function adds a time band to the image.
var createTimeBand = function(image) {
  return image.addBands(image.metadata('year'));
};
// map the function
ERA5_yearly = ERA5_yearly.map(createTimeBand)

// apply the fit
var linearFit = ERA5_yearly.select(['year', variable])
                           .reduce(ee.Reducer.linearFit());

Map.addLayer(linearFit.select('offset'), {min:-100, max: 100, palette: vik}, 'offset', false)
Map.addLayer(linearFit.select('scale'), {min:0, max: 0.08, palette: lajolla}, 'scale')
Map.addLayer(linearFit.select('scale'), {min:-0.08, max: 0.08, palette: vik}, 'scale')

Observe that the output contains two bands, the ‘offset’ (intercept) and the ‘scale’ ('scale' in this context refers to the slope of the line and is not to be confused with the scale parameter input to many reducers, which is the spatial scale). The scale is given in two different colour ramps. Look at the range of values to interpret the colours accordingly. A value of 0.08 refers to 0.08°C warming per year. Over four decades, this equates to 3.6°C of warming.

Playtime

Task 1: Use the same 'ECMWF/ERA5_LAND/DAILY_AGGR' dataset to analyse the global precipitations trends since 1980..

Use the Code Editor

Task 2: Use the 'MODIS/006/MOD13A1' (16-day vegetation indices) dataset to analyse the global vegetation trends by the EVI since 2000.

Use the Code Editor

linearRegression()

For example, suppose there are two dependent variables: temperature and precipitation, and two independent variables: a constant and time. The collection is identical to the previous example, but the constant band must be manually added prior to the reduction. The first two bands of the input are the ‘X’ (independent, explanatory) variables and the next two bands are the ‘Y’ (dependent, response) variables. In this example, first get the regression coefficients, then flatten the array image to extract the bands of interest:

Open in Code Editor

// GEO717 - Introduction to Earth Engine - Reducer - LinearRegressions - Example 2

// ERA-5 variables to analyse
var variables = ['temperature_2m', 'total_precipitation_sum'];

// Load input imagery: MODIS
var ERA5 = ee.ImageCollection("ECMWF/ERA5_LAND/DAILY_AGGR")
             .select(variables);

// colorramps
var palettes = require('users/gena/packages:palettes');
var lajolla = palettes.crameri.lajolla[25].reverse()
var vik = palettes.crameri.vik[25]

// Filter the image collection by years and calculate the mean
var years = ee.List.sequence(1980, 2023)

var ERA5_yearly = ee.ImageCollection.fromImages(
  years.map(function(y) {
  return ERA5
      .filter(ee.Filter.calendarRange(y, y, 'year'))
      .mean()
      .subtract(273.15)
      .set('year', y)
      .set('system:time_start',ee.Date.fromYMD(y,1,1).millis());
}));
print('Years total', ERA5_yearly)

// This function adds a time band to the image.
var createTimeBand = function(image) {
  return image.addBands(image.metadata('year'));
};

// This function adds a constant band to the image.
var createConstantBand = function(image) {
  return ee.Image(1).addBands(image);
};

// map the function
ERA5_yearly = ERA5_yearly.map(createTimeBand)
                         .map(createConstantBand)
                         // Select the predictors and the responses.
                         .select(ee.List(['constant', 'year']).add(variables).flatten());

// Compute ordinary least squares regression coefficients.
var linearRegression = ERA5_yearly.reduce(
  ee.Reducer.linearRegression({
    numX: 2,
    numY: 2 // adjust according to the numbers of variables
}));

// Compute robust linear regression coefficients.
var robustLinearRegression = ERA5_yearly.reduce(
  ee.Reducer.robustLinearRegression({
    numX: 2,
    numY: 2 // adjust according to the numbers of variables
}));

// The results are array images that must be flattened for display.
// These lists label the information along each axis of the arrays.
var bandNames = [['intercept', 'slope'], // 0-axis variation.
                 variables]; // 1-axis variation.

// Flatten the array images to get multi-band images according to the labels.
var lrImage = linearRegression.select(['coefficients']).arrayFlatten(bandNames);
print('linear regression', lrImage)
var rlrImage = robustLinearRegression.select(['coefficients']).arrayFlatten(bandNames);
print('robust linear regression', rlrImage)

Map.addLayer(lrImage.select('slope_temperature_2m'), 
    {min:0, max: 0.08, palette: lajolla}, 'scale - lr', true)
Map.addLayer(lrImage.select('intercept_temperature_2m'), 
    {min:-100, max: 100, palette: vik}, 'offset - lr', true)

Map.addLayer(rlrImage.select('slope_temperature_2m'), 
    {min:0, max: 0.08, palette: lajolla}, 'scale - rlr', false)
Map.addLayer(rlrImage.select('intercept_temperature_2m'), 
    {min:-100, max: 100, palette: vik}, 'offset - rlr', false)

Inspect the results to discover that linearRegression() output is equivalent to the coefficients estimated by the linearFit() reducer, though the linearRegression() output also has coefficients for the other dependent variable, total_precipitation_sum. Robust linear regression coefficients are different from the OLS estimates. The example compares the coefficients from the different regression methods at a specific point.

This simple 1-dimensional example highlights the difference between the linear regression and the robust linear regression.

Charts

The Earth Engine JavaScript Code Editor seamlessly integrates with Google Charts for convenient tabular data visualization via ui.Chart functions. Charts can be displayed interactively in the Code Editor console, ui.Panel widgets, and in stand-alone browser tabs.

Chart overview

A variety of chart types can be produced; for example: scatter, line, bar, pie, and histogram. Specifically, any chart type that is available in the Google Charts corechart package can be generated. Use the ui.Chart.setChartType() method to set chart type. Each page linked to in the Earth Engine object charts and DataTable charts sections include examples for generating several chart types.

var y_data = ee.List([2, 5, 6, 2, 7])
var x_data = ee.List([1, 2, 3, 3.5, 5])

var chart = ui.Chart.array.values(y_data, 0, x_data) // (array, axis, xLabels)
  .setChartType('ScatterChart');
print(chart);

Interactivity

Charts are interactive by default. Hover over points, lines, bars, etc. to see respective x, y, and series values. Axis zooming and panning are optionally permitted by activating a chart's "explorer" functionality.

Styling

Google Charts are highly customizable via styling properties. Use the ui.Chart.setOptions() method to set chart style properties. See the Chart Styling guide for full details.

Limitations

ui.Chart functions will only render 5,000 features. If your FeatureCollection, ImageCollection, Array or List has more elements, consider ways you might limit the data. If you have a long time series with a high cadence rate, try using a shorter time period, temporal sampling, or generate temporal composites.

DataTable Charts

The ui.Chart function is essentially a 2-D table with rows that represent observations and columns that represent observation attributes. It is a good option when a high degree of chart customization is required.

Suppose you have a small amount of static data you want to display to a chart. Use either the JavaScript array or object specifications to construct an input to pass to the ui.Chart function. Here, the top five asian populations from a 2022 census are encoded as a JavaScript array with column header objects that define column properties.

// Define a DataTable using a JavaScript array with a column property header.
var dataTable = [
  [
    {label: 'Country', role: 'domain', type: 'string'},
    {label: 'Population', role: 'data', type: 'number'},
    {label: 'World share', role: 'annotation', type: 'string'}
  ],
  ['CN', 1439323776, '18.4%'],
  ['IN', 1380004385, '17.7%'],
  ['ID',  273523615, '3.5%'],
  ['PK',  220892340, '2.8%'],
  ['BD',  164689383, '2.1%']
];

// Define the chart and print it to the console.
var chart = ui.Chart(dataTable).setChartType('ColumnChart').setOptions({
  title: 'Asian Countries by population (2022)',
  legend: {position: 'none'},
  hAxis: {title: 'Countries', titleTextStyle: {italic: false, bold: true}},
  vAxis: {title: 'Population', titleTextStyle: {italic: false, bold: true}},
  colors: ['1d6b99']
});
print(chart);

Image Charts

The ui.Chart.image module contains a set of functions for reducing Image objects by region(s) and rendering charts from the results. The choice of function dictates the arrangement of data in the chart, i.e., what defines x- and y-axis values and what defines the series. See the respective website on Image Charts to get a more comprehensive overview of chart functions and respective examples. We will focus on two chart functions: ui.Chart.image.regions and ui.Chart.image.histogram

ui.Chart.image.regions

The ui.Chart.image.regions function accepts a list that allows you to plot data from multiple regions of your image. The following example displays a spectral profiler.

Open in Code Editor

// Load an image.
var image = ee.Image("LANDSAT/LC08/C01/T1_SR/LC08_196030_20190629")

// Make a FeatureCollection of Features.
var samples = ee.FeatureCollection([
  ee.Feature(ee.Geometry.Point(5.4789, 43.168).buffer(200), {name: 'Water'}),
  ee.Feature(ee.Geometry.Point(5.47025, 43.24038).buffer(200), {name: 'Forest'}),
  ee.Feature(ee.Geometry.Point(5.3788, 43.288).buffer(200), {name: 'Urban'})
]);

// Define the visualization parameters.
var vizParams = {bands: ['B7', 'B5', 'B3'], min: 200, max: 3000,};
Map.setOptions('SATELLITE');  // "ROADMAP", "SATELLITE", "HYBRID" or "TERRAIN" 
Map.centerObject(samples); // Carmague, France

Map.addLayer(image, vizParams, 'false color infrared', false);
Map.addLayer(samples, {color:'red'}, 'sample locations');

// See http://landsat.usgs.gov/band_designations_landsat_satellites.php
var wavelengths = [0.44, 0.48, 0.56, 0.65, 0.86, 1.61, 2.2];

var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'];

// Select only the reflectance bands of interest and scale to %.
var opticalImage = image.select(bands).divide(100);


// Define the chart and print it to the console.
var chart = ui.Chart.image.regions({
                  image: opticalImage,
                  regions: samples,
                  reducer: ee.Reducer.mean(),
                  scale: 30,
                  seriesProperty: 'name',
                  xLabels: wavelengths
                })
                .setChartType('LineChart')
                .setOptions({
                  title: 'Average Reflectance by Land Cover',
                  hAxis: {
                    title: 'Wavelength [µm]',
                    titleTextStyle: {italic: false, bold: false},
                    // ticks: xPropLabels
                  },
                  vAxis: {
                    title: 'Reflectance [%]',
                    titleTextStyle: {italic: false, bold: false}
                  },
                  colors: ['blue', 'green', 'red'],
                  lineWidth: 1,
                  pointSize: 4,
                });
print(chart);

Playtime

Use the Code Editor

ui.Chart.image.histogram

Imagine you want to identify a suitable threshold for the normalized difference water index (NDWI) to distinguish water from land. Displaying the histogram of the respetive index image is a suitable way to identify the target region.

Open in Code Editor

// NDWI histogram
// Load an image.
var image = ee.Image("LANDSAT/LC08/C02/T1_TOA/LC08_196030_20190629")

// Perform the normalized difference operation
var ndwi = image.normalizedDifference(['B3', 'B5']);

// Stick the resulting NDWI image on the map
Map.addLayer(ndwi, {min:0, max:1}, 'NDWI');

// Define a region to calculate histogram for.
var aoi = ndwi.geometry();

// Define the chart and print it to the console.
var chart =
    ui.Chart.image.histogram({image: ndwi, region: aoi, scale: 100})
        .setSeriesNames(['NDWI'])
        .setOptions({
          title: 'NDWI Histogram',
          hAxis: {
            title: 'NDWI values',
            titleTextStyle: {italic: false, bold: false},
          },
          vAxis:
              {title: 'Count', titleTextStyle: {italic: false, bold: false}},
          colors: ['blue']
        });
print(chart);
// Based on the chart you can now identify a suitable threshold value
// to distinguish land from water

var water = ndwi.gt(0)

// mask land values (zero values)
water = water.selfMask()

// Define the visualization parameters.
var vizParams = {bands: ['B7', 'B5', 'B3'], min: 0.05, max: 0.3};

// Center the map and display the image.
Map.centerObject(image); // Carmague, France
Map.addLayer(image, vizParams, 'natural false color image', true);
Map.addLayer(water, {palette: 'blue'}, 'water', true);

Playtime

Task: Load the DEM dataset "USGS/SRTMGL1_003", clip it to the image geometry (see given example) and display its histogram. Adjust the number of buckets/bins to 300.

Use the Code Editor

ImageCollection Charts

The ui.Chart.image module contains a set of functions for rendering charts from the results of spatiotemporal reduction of images within an ImageCollection. The choice of function dictates the arrangement of data in the chart, i.e., what defines x- and y-axis values and what defines the series. Use the function descriptions and examples to determine the best function for your purpose.

In this following example we will compare two charts on the same dataset.

Use ui.Chart.image.doySeriesByRegion to display a single image band day-of-year time series for multiple regions, where each distinct region is presented as a unique series. It is useful for comparing annual single-band time series among regions. For instance, in this example, annual MODIS-derived EVI profiles for three forest regions are plotted, providing a convenient comparison of EVI response by region. Note that intra-annual observations occurring on the same day-of-year are reduced by their mean.

Use ui.Chart.image.series to display an image time series for a given region; each image band is presented as a unique series. It is useful for comparing the time series of individual image bands. Here, a MODIS image collection with bands representing NDVI and EVI vegetation indices are plotted. The date of every image observation is included along the x-axis, while the mean reduction of pixels intersecting the forest ecoregion defines the y-axis.

// Two examples of reducing a vegetation time series 

// Make a FeatureCollection of Features.
var samples = ee.FeatureCollection([
  ee.Feature(ee.Geometry.Point(8.54749, 47.26468).buffer(500), {name: 'Sihlwald'}),
  ee.Feature(ee.Geometry.Point(6.99366, 47.37159).buffer(500), {name: 'Jura'}),
  ee.Feature(ee.Geometry.Point(8.63704, 46.3003).buffer(500), {name: 'Tichino'})
]);

// Load MODIS vegetation indices data.
var vegIndices = ee.ImageCollection('MODIS/006/MOD13A1')
                     .filterDate('2017-01-01', '2020-01-01')
                     .select(['NDVI', 'EVI']);
                     
// Visualize the data.
var vizParams = {bands: 'NDVI', min: 0, max: 10000,};
Map.setOptions('SATELLITE');  // "ROADMAP", "SATELLITE", "HYBRID" or "TERRAIN" 
Map.centerObject(samples); 

Map.addLayer(vegIndices.first(), vizParams, 'NDVI - first image', false);
Map.addLayer(samples, {color:'red'}, 'sample locations');

 
// Define the chart to distinguish the regions.
var chart = ui.Chart.image
                .doySeriesByRegion({
                  imageCollection: vegIndices,
                  bandName: 'EVI',
                  regions: samples,
                  regionReducer: ee.Reducer.mean(),
                  scale: 500,
                  yearReducer: ee.Reducer.mean(),
                  seriesProperty: 'name',
                  startDay: 1,
                  endDay: 365

                })
                .setOptions({
                  title: 'Average EVI Value by Day of Year by Forest Region',
                  hAxis: {
                    title: 'Day of year',
                    titleTextStyle: {italic: false, bold: true}
                  },
                  vAxis: {
                    title: 'EVI (x1e4)',
                    titleTextStyle: {italic: false, bold: true}
                  },
                  lineWidth: 2,
                  colors: ['f0af07', '0f8755', '76b349'],
                });
print(chart);
                     
// Define the chart to distinguish the years.                     
var chart2 =
    ui.Chart.image
        .series({
          imageCollection: vegIndices,
          region: samples,
          reducer: ee.Reducer.mean(),
          scale: 500,
          xProperty: 'system:time_start'
        })
        .setSeriesNames(['EVI', 'NDVI'])
        .setOptions({
          title: 'Average Vegetation Index Value by Date for all Forest Regions',
          hAxis: {title: 'Date', titleTextStyle: {italic: false, bold: true}},
          vAxis: {
            title: 'Vegetation index (x1e4)',
            titleTextStyle: {italic: false, bold: true}
          },
          lineWidth: 3,
          colors: ['e37d05', '1d6b99'],
          curveType: 'function'
        });
print(chart2);                     
                     

Importing & exporting data

Importing and Managing Assets

To upload and manage geospatial datasets, use the Asset Manager in the Code Editor. The Asset Manager is on the Assets tab at the left side of the Code Editor (Figure below. See Importing Raster Data for instructions on uploading raster (image) data and Importing Table Data for instructions on uploading table data. Your assets are initially private, but may be shared with others. See the Sharing Assets section for details.

Exporting Data

You can export images, map tiles, tables and video from Earth Engine. Most commonly, vector data are exported as a CSV or a Shapefile, while Rasters are exported as GeoTIFF files. The exports can be sent to your Google Drive account, to Google Cloud Storage (a fee-based service) or to a new Earth Engine asset.

Exporting raster data to Google Drive

To export an image to your Drive account, use Export.image.toDrive(). For example, to export our previously defined Landsat median image of Scotland, define the export parameters, then call Export.image.toDrive():

Open in Code Editor

// Load a Landsat 8 collection for a single path-row.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate('2018-01-01', '2020-01-01')
  .filterBounds(ee.Geometry.Point(-4.441, 57.336)) // Scotland

var aoi = ee.Geometry.Rectangle([-6.82, 56.53, -1.76, 57.75], null, false);

// This function masks clouds in Landsat 8 imagery.
var maskClouds = function(image) {
  var scored = ee.Algorithms.Landsat.simpleCloudScore(image);
  return image.updateMask(scored.select(['cloud']).lt(20));
};

// Map the cloud masking function over the collection.
var collection_cloudfree = collection.map(maskClouds);

// Compute a cloud-filtered median image and display.
var median_cloudfree = collection_cloudfree.median().clip(aoi);
Map.addLayer(median_cloudfree, {bands: ['B7', 'B5', 'B3'], max: 0.4}, 'median cloudfree');


///////////////////////////////////////////////////////////////
// Export

// Decide on a suitable file format and name
Export.image.toDrive({
  image: median_cloudfree.multiply(10000).uint16(), 
  description: 'L8_C2_toa_Scotland_2018-01-01_2020-01-01_simpleCloudScore_median',
  fileFormat: 'GeoTIFF',
  region: aoi,
  scale: 30, 
  maxPixels: 1e13,
  folder: 'EE_exports',
  crs: 'EPSG:32630',
});

Exporting vector data to Google Drive

You can export a FeatureCollection as CSV, SHP (shapefile), GeoJSON, KML, KMZ or TFRecord using Export.table. The FeatureCollection may represent vectors or simply a table of data. In the latter case, the features in the collection will have null geometry.

To export a FeatureCollection to your Drive account, use Export.table.toDrive(). For example:

// Make a collection of points.
var features = ee.FeatureCollection([
  ee.Feature(ee.Geometry.Point(30.41, 59.933), {name: 'Voronoi'}),
  ee.Feature(ee.Geometry.Point(-73.96, 40.781), {name: 'Thiessen'}),
  ee.Feature(ee.Geometry.Point(6.4806, 50.8012), {name: 'Dirichlet'})
]);

// Export the FeatureCollection to a KML file.
Export.table.toDrive({
  collection: features,
  description:'vectorsToDriveExample',
  fileFormat: 'SHP'
});

Note that the output format is specified as SHP to handle geographic data. To export just a table of data, without any geographic information, export features with null geometry in CSV format. The following demonstrates using Export.table.toDrive() to get the results of a potentially long running reduction:

// Load a Landsat TOA image.
var image = ee.Image('LANDSAT/LC08/T1_TOA/LC08_044034_20140318');

// Create an arbitrary rectangle.
var region = ee.Geometry.Rectangle(-122.2806, 37.1209, -122.0554, 37.2413);

// Get a dictionary of means in the region.
var means = image.reduceRegion({
  reducer: ee.Reducer.mean(),
  geometry: region,
  crs: projection.crs,
  crsTransform: projection.transform,
});

// Make a feature without geometry and set the properties to the dictionary of means.
var feature = ee.Feature(null, means);

// Wrap the Feature in a FeatureCollection for export.
var featureCollection = ee.FeatureCollection([feature]);

// Export the FeatureCollection.
Export.table.toDrive({
  collection: featureCollection,
  description: 'exportTableExample',
  fileFormat: 'CSV'
});

By now you have learned quite a bit about the possibilities of the Earth Engine. In the next chapter we will focus more on time series analysis.

Last updated