End-To-End Google Earth Engine (Full Course) - PNQ
End-To-End Google Earth Engine (Full Course) - PNQ
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 2/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 3/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
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 the full range of topics in Earth Engine to give the participants practical skills
to master the platform and implement their remote sensing projects.
(https://www.youtube.com/watch?
v=jGuCnu1I2qw&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=1)
Introduction to Remote Sensing (https://www.youtube.com/watch?v=xAyNu9HbK8s): This video introduces the remote sensing
concepts, terminology and techniques.
Introduction to Google Earth Engine (https://www.youtube.com/watch?v=kpfncBHZBto): This video gives a broad overview of Google
Earth Engine with selected case studies and application. The video also covers the Earth Engine architecture and how it is different
than traditional remote sensing software.
YouTube
We have created a YouTube Playlist with separate videos for each module to enable effective online-learning. Access the YouTube Playlist ↗
(https://www.youtube.com/playlist?list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha)
Vimeo
We are also making the module videos available on Vimeo. These videos can be downloaded for offline learning. Access the Vimeo Playlist ↗
(https://vimeo.com/showcase/11467828?share=copy)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 4/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
If you do not see the repository in the Reader section, click Refresh repository cache button in your Scripts tab and it will show up.
There are several slide decks containing useful information and references. You can access all the presentations used in the course from the
links below.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 5/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
(https://www.youtube.com/watch?
v=daYhxVVIpJU&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=2)
The Code Editor is an Integrated Development Environment (IDE) for Earth Engine Javascript API.. It offers an easy way to type, debug, run
and manage code. Type the code below and click Run to execute it and see the output in the Console tab.
Tip: You can use the keyboard shortcut Ctrl+Enter to run the code in the Code Editor
Hello World
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 6/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
print('Hello World');
// Variables
var city = 'Bengaluru';
var country = 'India';
print(city, country);
// List
var majorCities = ['Mumbai', 'Delhi', 'Chennai', 'Kolkata'];
print(majorCities);
// Dictionary
var cityData = {
'city': city,
'population': 8400000,
'elevation': 930
};
print(cityData);
// Function
var greet = function(name) {
return 'Hello ' + name;
};
print(greet('World'));
// This is a comment
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F01c_Hello_World_(exercise))
This is because the shared class 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 a Earth Engine username. Choose the name carefully, as it cannot be changed once
created.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 7/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
After entering your username, your home folder will be created. After that, you will be prompted to enter a new repository. A repository can
help you organize and share code. Your account can have multiple repositories and each repository can have multiple scripts inside it. To get
started, you can create a repository named default. Finally, you will be able to save the script.
In the code snippet, You will see a function Map.setCenter() which sets the viewport to a specific location and zoom level. The function
takes the X coordinate (longitude), Y coordinate (latitude) and Zoom Level parameters. Replace the X and Y coordinates with the coordinates
of your city and click Run to see the images of your city.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 8/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F02c_Image_Collections_(exercise))
Filter by metadata: You can apply a filter on the image metadata using filters such as ee.Filter.eq() , ee.Filter.lt() etc. You can
filter by PATH/ROW values, Orbit number, Cloud cover etc.
Filter by date: You can select images in a particular date range using filters such as ee.Filter.date() .
Filter by location: You can select the subset of images with a bounding box, location or geometry using the ee.Filter.bounds() . You
can also use the drawing tools to draw a geometry for filtering.
After applying the filters, you can use the size() function to check how many images match the filters.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 9/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
// Filter by metadata
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30));
// Filter by date
var filtered = s2.filter(ee.Filter.date('2019-01-01', '2020-01-01'));
// Filter by location
var filtered = s2.filter(ee.Filter.bounds(geometry));
// Instead of applying filters one after the other, we can 'chain' them
// Use the . notation to apply all the filters together
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
print(filtered.size());
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F03c_Filtering_Image_Collection_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 10/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
print(filtered.size());
// Exercise
// Delete the 'geometry' variable
// Add a point at your chosen location
// Change the filter to find images from January 2023
We can also create a composite image by applying selection criteria to each pixel from all pixels in the stack. Here we use the median()
function to create a composite where each pixel value is the median of all pixels from the stack.
Tip: If you need to create a mosaic where the images are in a specific order, you can use the .sort()
function to sort your collection by a property first.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 11/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Map.centerObject(geometry, 10);
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F04c_Mosaics_and_Composites_(exercise))
// Create a median composite for the year 2020 and load it to the map
Search for GAUL Second Level Administrative Boundaries and load the collection. This is a global collection that contains all Admin2
boundaries. We can apply a filter using the ADM1_NAME property to get all Admin2 boundaries (i.e. Districts) from a state.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 12/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F05c_Feature_Collections_(exercise))
// Exercise
// Apply filters to select your chosen Admin2 region
// Display the results in 'red' color
Importing a Shapefile
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 13/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F06c_Import_(exercise))
// Exercise
// Apply a filter to select only large urban centers
// in your country and display it on the map.
While in a Desktop software, clipping is desirable to remove unnecessary portion of a large image and save
computation time, in Earth Engine clipping can actually increase the computation time. As described in the
Earth Engine Coding Best Practices (https://developers.google.com/earth-engine/guides/best_practices?
hl=en#if-you-dont-need-to-clip,-dont-use-clip) guide, avoid clipping the images or do it at the end of your
script.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 14/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Open in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-
Engine-Basics%2F07b_Clipping_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/ghs_urban_centers');
// Find the name of the urban centre
// by adding the layer to the map and using Inspector.
var filtered = urban
.filter(ee.Filter.eq('UC_NM_MN', 'Bengaluru'))
.filter(ee.Filter.eq('CTR_MN_NM', 'India'));
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
Map.centerObject(geometry);
Map.addLayer(clipped, rgbVis, 'Clipped');
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F07c_Clipping_(exercise))
Tip: Code Editor supports autocompletion of API functions using the combination Ctrl+Space. Type a few
characters of a function and press Ctrl+Space to see autocomplete suggestions. You can also use the same
key combination to fill all parameters of the function automatically.
Once you run this script, the Tasks tab will be highlighted. Switch to the tab and you will see the tasks waiting. Click Run next to each task to
start the process.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 15/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
On clicking the Run button, you will be prompted for a confirmation dialog. Verify the settings and click Run to start the export.
Once the Export finishes, a GeoTiff file for each export task will be added to your Google Drive in the specified folder. You can download
them and use it in a GIS software.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 16/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 17/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/ghs_urban_centers');
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
Map.centerObject(geometry);
Map.addLayer(clipped, rgbVis, 'Clipped');
Export.image.toDrive({
image: visualized,
description: 'Bangalore_Composite_Visualized',
folder: 'earthengine',
fileNamePrefix: 'bangalore_composite_visualized',
region: geometry,
scale: 10,
maxPixels: 1e9
});
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A01-Earth-Engine-
Basics%2F08c_Export_(exercise))
// Write the export function to export the results for your chosen urban area
Assignment 1
Load the Night Lights Data for May 2015 and May 2020. Compare the imagery for your region and find the changes in the city due to
COVID-19 effect.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 18/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Assignment
// Export the Night Lights images for May,2015 and May,2020
// Workflow:
// Load the VIIRS Nighttime Day/Night Band Composites collection
// Filter the collection to the date range
// Extract the 'avg_rad' band which represents the nighttime lights
// Clip the image to the geometry of your city
// Export the resulting image as a GeoTIFF file.
// Hint1:
// Hint2:
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 19/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
(https://www.youtube.com/watch?
v=UW3jinsnLko&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=3)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 20/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Let's see how to take a list of numbers and add 1 to each element
var myList = ee.List.sequence(1, 10);
// Casting
// You get an error because Earth Engine doesn't know what is the type of 'value'
// We need to cast it to appropriate type first
var value = ee.Number(value);
var newValue = value.add(1);
print(newValue);
// Dictionary
// Convert javascript objects to EE Objects
var data = {'city': 'Bengaluru', 'population': 8400000, 'elevation': 930};
var eeData = ee.Dictionary(data);
// Once converted, you can use the methods from the
// ee.Dictionary module
print(eeData.get('city'));
// Dates
// For any date computation, you should use ee.Date module
As a general rule, you should always use Earth Engine API methods in your code, there is one exception
where you will need to use client-side Javascript method. If you want to get the current time, the server
doesn’t know your time. You need to use javascript method and cast it to an Earth Engine object.
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-Engine-
Intermediate%2F01c_Earth_Engine_Objects_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 21/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);
// Exercise
// Apply another filter to the collection below to filter images
// collected in the last 1-month
// Do not hard-code the dates, it should always show images
// from the past 1-month whenever you run the script
// Hint: Use ee.Date.advance() function
// to compute the date 1 month before now
var filtered = s2
.filter(ee.Filter.bounds(geometry))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 22/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');
// For more complex indices, you can use the expression() function
// Note:
// For the SAVI formula, the pixel values need to converted to reflectances
// Multiplyng the pixel values by 'scale' gives us the reflectance value
// The scale value is 0.0001 for Sentinel-2 dataset
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
var ndviVis = {min:0, max:1, palette: ['white', 'green']};
var ndwiVis = {min:0, max:0.5, palette: ['white', 'blue']};
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-Engine-
Intermediate%2F02c_Calculating_Indices_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 23/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2');
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
Map.addLayer(image.clip(geometry), rgbVis, 'Image');
// Exercise
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 24/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Open in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-
Engine-Intermediate%2F03b_Computation_on_Image_Collections_(complete))
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin1 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level1');
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}
var palette = [
'FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
'74A901', '66A000', '529400', '3E8601', '207401', '056201',
'004C00', '023B01', '012E01', '011D01', '011301'];
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-Engine-
Intermediate%2F03c_Computation_on_Image_Collections_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 25/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var admin1 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level1');
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
// Composite
var composite = withIndices.median();
print(composite);
// Exercise
// Display a map of NDWI for the region
// Select the 'ndwi' band and clip it before displaying
// Use a color palette from https://colorbrewer2.org/
Most remote sensing datasets come with a QA or Cloud Mask band that contains the information on whether pixels is cloudy or not. Your
Code Editor contains pre-defined functions for masking clouds for popular datasets under Scripts Tab → Examples → Cloud Masking. To
understand how cloud-masking functions work and learn advanced techniques for bitmasking, please refer to our article on Working with
QA Bands and Bitmasks in Google Earth Engine (https://spatialthoughts.com/2021/08/19/qa-bands-bitmasks-gee/).
The script below takes the Sentinel-2 masking function and shows how to apply it on an image.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 26/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);
var filteredS2 = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 35))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
Map.centerObject(image);
Map.addLayer(image, rgbVis, 'Full Image', false);
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 27/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-Engine-
Intermediate%2F04c_Cloud_Masking_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 28/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Point([77.60412933051538, 12.952912912328241]);
var filteredS2 = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 35))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
Map.centerObject(image);
Map.addLayer(image, rgbVis, 'Full Image', false);
// Exercise
// Delete the 'geometry' variable and add a point at your chosen location
// Run the script and compare the results of the CloudScore+ mask with
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 29/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// the QA60 mask
// Adjust the 'clearThreshold' value to suit your scene
// Hint:
// clearThreshold values between 0.50 and 0.65 generally work well
// Higher values will remove thin clouds, haze & cirrus shadows.
05. Reducers
When writing parallel computing code, a Reduce operation allows you to compute statistics on a large amount of inputs. In Earth Engine, you
need to run reduction operation when creating composites, calculating statistics, doing regression analysis etc. The Earth Engine API comes
with a large number of built-in reducer functions (such as ee.Reducer.sum() , ee.Reducer.histogram() , ee.Reducer.linearFit() etc.)
that can perform a variety of statistical operations on input data. You can run reducers using the reduce() function. Earth Engine supports
running reducers on all data structures that can hold multiple values, such as Images (reducers run on different bands), ImageCollection,
FeatureCollection, List, Dictionary etc. The script below introduces basic concepts related to reducers.
print(filtered.size());
var collMean = filtered.reduce(ee.Reducer.mean());
print('Reducer on Collection', collMean);
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 30/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-Engine-
Intermediate%2F05c_Reducers_(exercise))
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
var image = ee.Image('COPERNICUS/S2_HARMONIZED/20190223T050811_20190223T051829_T44RPR');
Map.addLayer(image, rgbVis, 'Image');
Map.addLayer(geometry, {color: 'red'}, 'Farm');
Map.centerObject(geometry);
// Exercise
// Compute the average NDVI for the farm from the given image
// Hint: Use the reduceRegion() function
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 31/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 32/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var geometry = ee.Geometry.Polygon([[
[82.60642647743225, 27.16350437805251],
[82.60984897613525, 27.1618529901377],
[82.61088967323303, 27.163695288375266],
[82.60757446289062, 27.16517483230927]
]]);
Map.addLayer(geometry, {color: 'red'}, 'Farm');
Map.centerObject(geometry);
var filtered = s2
.filter(ee.Filter.date('2017-01-01', '2018-01-01'))
.filter(ee.Filter.bounds(geometry));
// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}
print(chart);
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A02-Earth-Engine-
Intermediate%2F06c_Time_Series_Charts_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 33/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Assignment 2
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 34/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Assignment
// Use TerraClimate dataset to chart a 50 year time series
// of temparature at any location
// Workflow
// Load the TerraClimate collection
// Select the 'tmmx' band
// Scale the band values
// Filter the scaled collection to the desired date range
// Use ui.Chart.image.series() function to create the chart
// Hint1
// The 'tmnx' band has a scaling factor of 0.1 as per
// https://developers.google.com/earth-engine/datasets/catalog/IDAHO_EPSCOR_TERRACLIMATE#bands
// This means that we need to multiply each pixel value by 0.1
// to obtain the actual temparature value
// Multiplying creates a new image that doesn't have the same properties
// Use copyProperties() function to copy timestamp to new image
var scaleImage = function(image) {
return image.multiply(0.1)
.copyProperties(image,['system:time_start']);
};
var tmaxScaled = tmax.map(scaleImage);
// Hint2
// You will need to specify pixel resolution as the scale parameter
// in the charting function
// Use projection().nominalScale() to find the
// image resolution in meters
var image = ee.Image(terraclimate.first())
print(image.projection().nominalScale())
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 35/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
(https://www.youtube.com/watch?
v=lULwcRpkMv8&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=4)
Fun fact: The classifiers in Earth Engine API have names starting with smile - such as
ee.Classifier.smileRandomForest() . The smile part refers to the Statistical Machine Intelligence and
Learning Engine (SMILE) (https://haifengl.github.io/index.html) JAVA library which is used by Google Earth
Engine to implement these algorithms.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 36/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
// The following collections were created using the
// Drawing Tools in the code editor
var urban = ee.FeatureCollection('users/ujavalgandhi/e2e/urban_gcps');
var bare = ee.FeatureCollection('users/ujavalgandhi/e2e/bare_gcps');
var water = ee.FeatureCollection('users/ujavalgandhi/e2e/water_gcps');
var vegetation = ee.FeatureCollection('users/ujavalgandhi/e2e/vegetation_gcps');
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
// // Classify the image.
var classified = composite.classify(classifier);
// Choose a 4-color palette
// Assign a color for each class in the following order
// Urban, Bare, Water, Vegetation
var palette = ['#cc6d8f', '#ffc107', '#1e88e5', '#004d40' ];
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 37/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-Supervised-
Classification%2F01c_Basic_Supervised_Classification_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 38/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
Map.centerObject(geometry);
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
Map.addLayer(composite.clip(geometry), rgbVis, 'image');
// Exercise
// Add training points for 4 classes
// Assign the 'landcover' property as follows
// urban: 0
// bare: 1
// water: 2
// vegetation: 3
// // Train a classifier.
// var classifier = ee.Classifier.smileRandomForest(50).train({
// features: training,
// classProperty: 'landcover',
// inputProperties: composite.bandNames()
// });
// // // Classify the image.
// var classified = composite.classify(classifier);
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 39/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Accuracy Assessment
Don’t get carried away tweaking your model to give you the highest validation accuracy. You must use
both qualitative measures (such as visual inspection of results) along with quantitative measures to assess
the results.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 40/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection("WWF/HydroSHEDS/v1/Basins/hybas_7");
var gcp = ee.FeatureCollection("users/ujavalgandhi/e2e/arkavathy_gcps");
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');
// Add a random column and split the GCPs into training and validation set
var gcp = gcp.randomColumn();
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 41/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var testConfusionMatrix = test.errorMatrix('landcover', 'classification')
// Printing of confusion matrix may time out. Alternatively, you can export it as CSV
print('Confusion Matrix', testConfusionMatrix);
print('Test Accuracy', testConfusionMatrix.accuracy());
// Alternate workflow
// This is similar to machine learning practice
var validation = composite.sampleRegions({
collection: validationGcp,
properties: ['landcover'],
scale: 10,
tileScale: 16
});
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-Supervised-
Classification%2F02c_Accuracy_Assessment_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 42/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
//**************************************************************************
// Accuracy Assessment
//**************************************************************************
// Exercise
Our training features have more parameters and contain values of the same scale. The result is a much improved classification.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 43/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Improved Classification Accuracy with use of Spectral Indices and Elevation Data
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 44/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');
var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var alos = ee.ImageCollection('JAXA/ALOS/AW3D30/V3_2');
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry));
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 45/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
.rename('slope');
// Machine learning algorithms work best on images when all features have
// the same range
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 46/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
//**************************************************************************
// Accuracy Assessment
//**************************************************************************
// Printing of confusion matrix may time out. Alternatively, you can export it as CSV
print('Confusion Matrix', testConfusionMatrix);
print('Test Accuracy', testConfusionMatrix.accuracy());
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-Supervised-
Classification%2F03c_Improving_the_Classification_(exercise))
// Exercise
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 47/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 48/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');
var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var alos = ee.ImageCollection('JAXA/ALOS/AW3D30/V3_2');
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 49/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var proj = alos.first().projection();
// Machine learning algorithms work best on images when all features have
// the same range
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 50/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
features: training,
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
//**************************************************************************
// Accuracy Assessment
//**************************************************************************
//**************************************************************************
// Exporting Results
//**************************************************************************
// Create a Feature with null geometry and the value we want to export.
// Use .array() to convert Confusion Matrix to an Array so it can be
// exported in a CSV file
var fc = ee.FeatureCollection([
ee.Feature(null, {
'accuracy': testConfusionMatrix.accuracy(),
'matrix': testConfusionMatrix.array()
})
]);
print(fc);
Export.table.toDrive({
collection: fc,
description: 'Accuracy_Assessment_Export',
folder: 'earthengine',
fileNamePrefix: 'accuracy',
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 51/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
fileFormat: 'CSV'
});
Exercise
It is also a good idea to export the classified image as an Asset. This will allows you to import the classified image in another script without
running the whole classification workflow. Use the Export.image.toAsset() function to export the classified image as an asset.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 52/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var basin = ee.FeatureCollection('WWF/HydroSHEDS/v1/Basins/hybas_7');
var gcp = ee.FeatureCollection('users/ujavalgandhi/e2e/arkavathy_gcps');
var alos = ee.ImageCollection('JAXA/ALOS/AW3D30/V3_2');
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 53/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var proj = alos.first().projection();
// Machine learning algorithms work best on images when all features have
// the same range
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50)
.train({
features: training,
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 54/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
classProperty: 'landcover',
inputProperties: composite.bandNames()
});
// Exercise
// Hint: For images with discrete pixel values, we must set the
// pyramidingPolicy to 'mode'.
// The pyramidingPolicy parameter should a dictionary specifying
// the policy for each band. A simpler way to specify it for all
// bands is to use {'.default': 'mode'}
Area of Polygons: Calculating area for polygons is done using the area() function. It computes area on a sphere (ignoring the
ellipsoid flattening) and gives you the area in square meters. You can optionally supply proj and a non-zero maxError parameters to
calculate area in a specific projected CRS. For example, area({proj:'EPSG:32643', maxError: 1}) will calculate the area of the
polygon after reprojecting it to the WGS 84/UTM Zone 43 CRS with a tolerance of 1 meter.
Area of Image Pixels: Area of image pixels is computed using the ee.Image.pixelArea() function. This function computes the area
inside the 4 corners of each pixel using the WGS84 ellipsoid. The ee.Image.pixelArea() function uses a custom equal-area
projection for area calculation. The result is area in square meters regardless of the projection of the input image. Learn more
(https://groups.google.com/g/google-earth-engine-developers/c/Ccaorx-obVw/m/_ZQdP2wVAgAJ).
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 55/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Since our image has only 0 and 1 pixel values, the vegetation
// pixels will have values equal to their area
var areaImage = vegetation.multiply(ee.Image.pixelArea());
// Now that each pixel for vegetation class in the image has the value
// equal to its area, we can sum up all the values in the region
// to get the total green cover.
If you want to compute area covered by each class, you can use a Grouped Reducer
(https://developers.google.com/earth-engine/reducers_grouping). See the Supplement (end-to-end-gee-
supplement.html#calculating-area-by-class) to see a code snippet.
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A03-Supervised-
Classification%2F05c_Calculating_Area_(exercise))
// Exercise
// Compute and print the percentage green cover of the city
Assignment 3
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-
GEE%3AAssignments%2FAssignment3)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 56/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Choose a city of your choice and create land use land classification
// using supervised classification technique.
// [Optional]
// Accuracy Assessment
// Post-processing Classification
Single Band Change: Measuring change in a single band image or a spectral index using a threshold
Multi Band Change: Measuring spectral distance and spectral angle between two multiband images
Classification of Change: One-pass classification using stacked image containing bands from before and after an event
Post Classification Comparison: Comparing two classified images and computing class transitions
(https://www.youtube.com/watch?
v=ybHxiygVVz0&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=5)
Here we apply this technique to map the extent and severity of a forest fire. The Normalized Burn Ratio (NBR) is an index that is designed to
highlight burnt vegetation areas. We compute the NBR for before and after images. Then we apply a suitable threshold to find burnt areas.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 57/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 58/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Map.centerObject(geometry, 10)
var s2 = ee.ImageCollection("COPERNICUS/S2")
// Apply filters
var filtered = s2
.filter(ee.Filter.bounds(geometry))
.select('B.*')
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 59/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Write a function to calculate Normalized Burn Ratio (NBR)
// 'NIR' (B8) and 'SWIR-2' (B12)
var addNBR = function(image) {
var nbr = image.normalizedDifference(['B8', 'B12']).rename(['nbr']);
return image.addBands(nbr)
}
// Apply a threshold
var threshold = 0.3
Exercise
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 60/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Map.centerObject(geometry, 10)
var s2 = ee.ImageCollection("COPERNICUS/S2")
// Apply filters
var filtered = s2
.filter(ee.Filter.bounds(geometry))
.select('B.*')
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 61/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
'Change in NBR')
// Exercise
Here we use this technique to detect landslides using before/after composites. You may learn more about this technique at Craig D’Souza’s
Change Detection (https://goo.gl/xotYhk) presentation.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 62/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 63/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var filtered = s2
.filter(ee.Filter.bounds(geometry))
.select('B.*');
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 64/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-Change-
Detection%2F01c_Spectral_Distance_Change_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 65/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var filtered = s2
.filter(ee.Filter.bounds(geometry))
.select('B.*');
// Exercise
// Inspect the angle image and find a suitable threshold
// that signifies damage after the landslides
// Apply the threshold and create a new image showing landslides
// Display the results
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 66/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 67/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2
.filter(ee.Filter.bounds(geometry))
// January 2019
var filtered2019 = filteredMasked.filter(ee.Filter.date('2019-01-01','2019-02-01'))
var image2019 = filtered2019.median();
// Display the input composite.
Map.addLayer(image2019.clip(geometry), rgbVis, '2019');
// January 2020
var filtered2020 = filteredMasked.filter(ee.Filter.date('2020-01-01','2020-02-01'))
var image2020 = filtered2020.median();
// Display the input composite.
Map.addLayer(image2020.clip(geometry), rgbVis, '2020');
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'class',
inputProperties: stackedImage.bandNames()
});
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 68/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Classify the image.
var classified = stackedImage.classify(classifier);
Map.addLayer(classified.clip(geometry), {min: 0, max: 1, palette: ['white', 'red']}, 'change');
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?accept_repo=users%2Fujavalgandhi%2FEnd-to-End-
GEE&scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A04-Change-Detection%2F03c_Classifying_Change_(exercise))
// use addNDBI() function to add the NDBI band to both 2019 and 2020 composite images
// Hint1: You can save the resulting image in the same variable to avoid changing
// a lot of code.
// var image = addNDBI(image)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 69/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
// 2019 Jan
var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2019-02-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*');
// Train a classifier.
var classifier = ee.Classifier.smileRandomForest(50).train({
features: training,
classProperty: 'landcover',
inputProperties: before.bandNames()
});
// 2020 Jan
var after = s2
.filter(ee.Filter.date('2020-01-01', '2020-02-01'))
.filter(ee.Filter.bounds(geometry))
.select('B.*')
.median();
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 70/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Map.addLayer(changed.clip(geometry), {min:0, max:1, palette: ['white', 'red']}, 'Change');
// We multiply the before image with 100 and add the after image
// The resulting pixel values will be unique and will represent each unique transition
// i.e. 102 is urban to bare, 103 urban to water etc.
var merged = beforeClasses.multiply(100).add(afterClasses).rename('transitions');
Exercise
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 71/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// Exercise
// Show all areas where water became other classes and display the result
// Hint1: Select class 3 pixels from before image and NOT class 3 pixels from after image
// Hint2: use the .and() operation to select pixels matching both conditions
(https://www.youtube.com/watch?
v=Y4lM7Wtckhs&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=6)
To convert client-side objects to server-side objects, you can use the appropriate API function. Server-side functions start with ee. ,
such ee.Date() , ee.Image() etc.
To convert server-side objects to client-side objects, you can call .getInfo() on am Earth Engine object. For the Python API, this is
the only way to extract information from a server-side object, but the Javascript API provides a better (and preferred) - method for
bring server-side objects to client-side using the evaluate() method. This method asynchronously retrieves the value of the object,
without blocking the user interface - meaning it will let your code continue to execute while it fetches the value.
Tip: You can use ee.Algorithms.ObjectType() to get the type of a server-side object
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 72/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
// getInfo() blocks the execution of your code till the value is fetched
// If the value takes time to compute, your code editor will freeze
// This is not a good user experience
var s2 = ee.ImageCollection("COPERNICUS/S2_SR")
var filtered = s2.filter(ee.Filter.date('2020-01-01', '2020-02-01'))
// You need to define a 'callback' function which will be called once the
// value has been computed and ready to be used.
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-Engine-
Apps%2F01c_Client_vs_Server_(exercise))
// Exercise
// The print statement below combines a client-side string
// with a server-side string - resulting in an error.
// Hint:
// Convert the client-side string to a server-side string
// Use ee.String() to create a server-side string
// Use the .cat() function instead of + to combine 2 strings
The Earth Engine API provides a library of User Interface (UI) widgets - such as Buttons, Drop-down Menus, Sliders etc. - that can be used to
create interactive apps. All the user interface functions are contained in the ui. package - such as ui.Select() , ui.Button() . You can
create those elements by calling these functions with appropriate parameters. Learn more in the Earth Engine User Interface API
(https://developers.google.com/earth-engine/guides/ui) section of the Earth Engine User Guide.
This section shows how to build a drop-down selector using the ui.Select() widget.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 73/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Open in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-
Engine-Apps%2F02b_Using_UI_Elements_(complete))
// You can add any widgets from the ui.* module to the map
var years = ['2014', '2015', '2016', '2017'];
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-Engine-
Apps%2F02c_Using_UI_Elements_(exercise))
// Exercise
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 74/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
types of widgets.
The code below shows how to build an app called Night Lights Explorer (https://santhosh-m.users.earthengine.app/view/night-lights-
explorer) that allows anyone to pick a year/month and load the VIIRS Nighttime Day/Night Band Composite for the selected month. Copy/paste
the code below to your Code Editor and click Run.
You will see a panel on the right-hand side with 2 drop-down boxes and a button. These are User Interface (UI) widgets provided by the Earth
Engine API that allows the user to interactively select the values. You can select the values for year and month and click Load button to see
the image for the selected month.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 75/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
monthStrings.evaluate(function(monthList) {
monthSelector.items().reset(monthList);
monthSelector.setPlaceholder('select a month');
});
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 76/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var layerName = 'Night Lights ' + year + '-' + month;
Map.addLayer(image, nighttimeVis, layerName);
};
button.onClick(loadComposite);
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-Engine-
Apps%2F03c_Building_an_App_with_UI_Widgets_(exercise))
// Exercise
// Add a button called 'Reset'
// Clicking the button should remove all loaded layers
Select the existing project or create a new project. The app will be hosted on Google Cloud, so you will need to create and link a Google
Cloud project with the app. If you don’t have a Google Cloud account, you can select the Register a New Project option to create a new project.
You can provide an edit access based on the project selection.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 77/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Give the name of your app and see the URL created for your app.
Select code to use for the app. It can be from the current content or choose any repository path where the code is saved. We will go ahead
with Current contents of editor
Click next and in the Publish New App dialog, leave all other settings to default and click Publish.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 78/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
The app will be hosted on Google Cloud and you can access it by clicking on the App Name of your app in the Manage Apps dialog.
You will see your Earth Engine powered app running in the browser. Anyone can access and interact with the app by just visiting the App
URL.
The app publishing process takes a few minutes. So if you get an error that your app is not yet ready, check
back in a few minutes.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 79/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-Engine-
Apps%2F04c_Publishing_the_App_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 80/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
monthStrings.evaluate(function(monthList) {
monthSelector.items().reset(monthList)
monthSelector.setPlaceholder('select a month')
})
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 81/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var layerName = 'Night Lights ' + year + '-' + month
Map.addLayer(image, nighttimeVis, layerName)
}
button.onClick(loadComposite)
// Exercise
// Set the map center to your area of interest
// Replace the author label with your name
// Publish the app.
Map.setCenter(76.43, 12.41, 8)
var authorLabel = ui.Label('App by: Ujaval Gandhi');
mainPanel.add(authorLabel);
ui.root.add(mainPanel);
On the left-hand panel, we will load a Sentinel-2 composite for the year 2020. On the right-hand panel, we will load the 11-class landcover
classification of the same region.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 82/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection("COPERNICUS/S2_HARMONIZED");
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.filter(ee.Filter.date('2020-01-01', '2021-01-01'));
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 83/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
wipe: true
});
Exercise
Try in Code Editor ↗ (https://code.earthengine.google.co.in/?scriptPath=users%2Fujavalgandhi%2FEnd-to-End-GEE%3A05-Earth-Engine-
Apps%2F05c_Split_Panel_App_(exercise))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 84/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var s2 = ee.ImageCollection("COPERNICUS/S2_HARMONIZED");
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.bounds(geometry))
.filter(ee.Filter.date('2020-01-01', '2021-01-01'));
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 85/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
orientation: 'horizontal',
wipe: true
});
return ui.Panel({
widgets: [colorBox, description],
layout: ui.Panel.Layout.Flow('horizontal')
});
};
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 86/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
classification.toDictionary().select([BAND_NAME + ".*"]).evaluate(function(result) {
var palette = result[BAND_NAME + "_class_palette"];
var names = result[BAND_NAME + "_class_names"];
loading.style().set('shown', false);
// Exercise
// Hint: UI Widgets can only be shown once in the app. Remove the
// print statement before adding the legend to the map.
// Hint: Load the legend in the right-hand side map.
(https://www.youtube.com/watch?
v=34yNkLmEHAI&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=7)
Google Colab
An easy way to start using the Google Earth Engine Python API is via Google Colab (https://colab.research.google.com/). Google
Colaboratory provides a hosted environment to run Python notebooks without having to install Python locally. It also comes pre-installed
with many useful packages - including the Google Earth Engine Python API. You can simply visit https://colab.research.google.com/
(https://colab.research.google.com/) and start a new notebook.
Coming from the programming in Earth Engine through the Code Editor, you will need to slightly adapt your scripts to be able to run in
Python. For the bulk of your code, you will be using Earth Engine API’s server-side objects and functions - which will be exactly the same in
Python. You only need to make a few syntactical changes.
Initialization
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 87/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
First of all, you need to run the following cells to initialize the API and authorize your account. You must have a Google Cloud Project
associated with your GEE account. Replace the cloud_project with your own project from Google Cloud Console
(https://console.cloud.google.com/).
You will be prompted to allow the notebook to access your Google credentials to sign-in to the account and allow access to Google Drive and
Google Cloud data. Once you approve, it will proceed to initialize the Earth Engine API. This step needs to be done just once per session.
import ee
cloud_project = 'spatialthoughts'
try:
ee.Initialize(project=cloud_project)
except:
ee.Authenticate()
ee.Initialize(project=cloud_project)
Variables
Python code doesn’t use the ‘var’ keyword
javascript code:
population = 881549
print(population)
s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
geometry = ee.Geometry.Polygon([[
[82.60642647743225, 27.16350437805251],
[82.60984897613525, 27.1618529901377],
[82.61088967323303, 27.163695288375266],
[82.60757446289062, 27.16517483230927]
]])
Line Continuation
Python doesn’t use a semi-colon for line ending. To indicate line-continuation you need to use the \ character
javascript code:
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var filtered = s2.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2019-02-01', '2019-03-01'))
.filter(ee.Filter.bounds(geometry));
filtered = s2 \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30)) \
.filter(ee.Filter.date('2019-02-01', '2019-03-01')) \
.filter(ee.Filter.bounds(geometry))
Functions
Instead of the function keyword, Python uses the def keyword. Also the in-line functions are defined using lambda anonymous functions.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 88/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
In the example below, also now the and operator - which is capitalized as And in Python version to avoid conflict with the built-in and
operator. The same applies to Or and Not operators. true , false , null in Python are also spelled as True , False and None .
javascript code:
function maskS2clouds(image) {
var qa = image.select('QA60')
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask)//.divide(10000)
.select("B.*")
.copyProperties(image, ["system:time_start"])
}
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}
def maskS2clouds(image):
qa = image.select('QA60')
cloudBitMask = 1 << 10
cirrusBitMask = 1 << 11
mask = qa.bitwiseAnd(cloudBitMask).eq(0).And(
qa.bitwiseAnd(cirrusBitMask).eq(0))
return image.updateMask(mask) \
.select("B.*") \
.copyProperties(image, ["system:time_start"])
def addNDVI(image):
ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi')
return image.addBands(ndvi)
withNdvi = filtered \
.map(maskS2clouds) \
.map(addNDVI)
Function Arguments
Named arguments to Earth Engine functions need to be in quotes. Also when passing the named arguments as a dictionary, it needs to be
passed using the ** keyword.
javascript code:
composite = withNdvi.median()
ndvi = composite.select('ndvi')
stats = ndvi.reduceRegion(**{
'reducer': ee.Reducer.mean(),
'geometry': geometry,
'scale': 10,
'maxPixels': 1e10
})
Printing Values
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 89/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
The print() function syntax is the same. But you must remember that in the Code Editor when you cann print , the value of the server
object is fetched and then printed. You must do that explicitely by calling getInfo() on any server-side object.
javascript code:
print(stats.get('ndvi')
print(stats.get('ndvi').getInfo())
In-line functions
The syntax for defining in-line functions is also slightly different. You need to use the lambda keyword.
javascript code:
Exercise
Take the Javascript code snippet below and write the equiavalent Python code in the cell below.
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
print(filtered.size());
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 90/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
geemap (https://github.com/giswqs/geemap) is an open-source Python package that comes with many helpful features that help you use
Earth Engine effectively in Python.
It comes with a function that can help you translate your javascript earth engine code to Python automatically.
import geemap
import ee
Initialization
First of all, you need to run the following cells to initialize the API and authorize your account. You must have a Google Cloud Project
associated with your GEE account. Replace the cloud_project with your own project from Google Cloud Console
(https://console.cloud.google.com/).
cloud_project = 'spatialthoughts'
try:
ee.Initialize(project=cloud_project)
except:
ee.Authenticate()
ee.Initialize(project=cloud_project)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 91/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
var rgbVis = {min: 0.0, max: 3000, bands: ['B4', 'B3', 'B2']};
Map.centerObject(geometry, 10);
Map.addLayer(medianComposite, rgbVis, 'Median Composite');
Run the cell below to load the map widget. Once the map widget loads, click the Toolbar icon in the top-right corner and select the Convert
Earth Engine Javascript to Python tool. Paste your Javascript code and click Convert.
m = geemap.Map(width=800)
m
You will see the auto-converted code displayed. Copy and paste it into a new cell and run it. Your code will be run using the GEE Python API.
medianComposite = filtered.median()
m.centerObject(geometry, 10)
m.addLayer(medianComposite, rgbVis, 'Median Composite')
If your code loads any layers, they will be loaded on the map widget. To display it, open a new code cell and just type m to display the widget.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 92/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
javascript_code = """
var geometry = ee.Geometry.Point([107.61303468448624, 12.130969369851766]);
Map.centerObject(geometry, 12)
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
var rgbVis = {
min: 0.0,
max: 3000,
bands: ['B4', 'B3', 'B2'],
};
var filtered = s2
.filter(ee.Filter.date('2019-01-01', '2020-01-01'))
.filter(ee.Filter.bounds(geometry))
// Write a function that computes NDVI for an image and adds it as a band
function addNDVI(image) {
var ndvi = image.normalizedDifference(['B5', 'B4']).rename('ndvi');
return image.addBands(ndvi);
}
"""
lines = geemap.js_snippet_to_py(
javascript_code, add_new_cell=False,
import_ee=True, import_geemap=True, show_map=True)
for line in lines:
print(line.rstrip())
The automatic conversion works great. Review it and paste it to the cell below.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 93/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
import ee
import geemap
m = geemap.Map()
filtered = s2 \
.filter(ee.Filter.date('2019-01-01', '2020-01-01')) \
.filter(ee.Filter.bounds(geometry))
filteredMasked = filteredS2WithCs \
.map(maskLowQA)
# Write a function that computes NDVI for an image and adds it as a band
def addNDVI(image):
ndvi = image.normalizedDifference(['B5', 'B4']).rename('ndvi')
return image.addBands(ndvi)
withNdvi = filteredMasked.map(addNDVI)
composite = withNdvi.median()
palette = [
'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
'74A901', '66A000', '529400', '3E8601', '207401', '056201',
'004C00', '023B01', '012E01', '011D01', '011301']
Exercise
Take the Javascript code snippet below and use geemap to automatically convert it to Python.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 94/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
One of the most commonly asked questions by Earth Engine users is - How do I download all images in a collection? The Earth Engine Python
API comes with a ee.batch module that allows you to launch batch exports and manage tasks. The recommended way to do batch exports
like this is to use the Python API’s ee.batch.Export functions and use a Python for-loop to iterate and export each image. The ee.batch
module also gives you ability to control Tasks - allowing you to automate exports.
You can also export images in a collection using Javascript API in the Code Editor but this requires you to
manually start the tasks for each image. This approach is fine for small number of images. You can check
out the recommended script (https://code.earthengine.google.co.in/?
scriptPath=users%2Fujavalgandhi%2FEnd-to-End-
GEE%3ASupplement%2FImage_Collections%2FExporting_ImageCollections).
Initialization
First of all, you need to run the following cells to initialize the API and authorize your account. You must have a Google Cloud Project
associated with your GEE account. Replace the cloud_project with your own project from Google Cloud Console
(https://console.cloud.google.com/).
import ee
cloud_project = 'spatialthoughts'
try:
ee.Initialize(project=cloud_project)
except:
ee.Authenticate()
ee.Initialize(project=cloud_project)
Create a Collection
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 95/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
filtered = s2 \
.filter(ee.Filter.date('2019-01-01', '2020-01-01')) \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30)) \
.filter(ee.Filter.bounds(geometry)) \
filteredMasked = filteredS2WithCs \
.map(maskLowQA)
# Write a function that computes NDVI for an image and adds it as a band
def addNDVI(image):
ndvi = image.normalizedDifference(['B5', 'B4']).rename('ndvi')
return image.addBands(ndvi)
withNdvi = filteredMasked.map(addNDVI)
image_ids = withNdvi.aggregate_array('system:index').getInfo()
print('Total images: ', len(image_ids))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 96/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
tasks = ee.batch.Task.list()
for task in tasks:
task_id = task.status()['id']
task_state = task.status()['state']
print(task_id, task_state)
tasks = ee.batch.Task.list()
for task in tasks:
task_id = task.status()['id']
task_state = task.status()['state']
if task_state == 'RUNNING' or task_state == 'READY':
task.cancel()
print('Task {} canceled'.format(task_id))
else:
print('Task {} state is {}'.format(task_id, task_state))
Exercise
The code below uses the TerraClimate data and creates an ImageCollection with 12 monthly images of maximum temperature. It also
extract the geometry for Australia from the LSIB collection. Add the code to start an export task for each image in the collection for australia.
import ee
lsib = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')
australia = lsib.filter(ee.Filter.eq('country_na', 'Australia'))
geometry = australia.geometry()
terraclimate = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE')
tmax = terraclimate.select('tmmx')
def scale(image):
return image.multiply(0.1) \
.copyProperties(image,['system:time_start'])
tmaxScaled = tmax.map(scale)
filtered = tmaxScaled \
.filter(ee.Filter.date('2020-01-01', '2021-01-01')) \
.filter(ee.Filter.bounds(geometry))
image_ids = filtered.aggregate_array('system:index').getInfo()
print('Total images: ', len(image_ids))
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 97/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
XEE (https://github.com/google/Xee) is an python package for working with Google Earth Engine data with XArray
(https://docs.xarray.dev/en/stable/). XEE makes it possible to leverage the strengths of both GEE and the Python ecosystem around XArray.
We will learn how to use XEE to extract and process a NDVI time-series for a single point location.
If you want to download processed time-series images as GeoTIFF files, pleasee see this notebook
(https://courses.spatialthoughts.com/python-remote-sensing.html#processing-time-series).
Installation
Let’s install the required packages in the Colab environment.
%%capture
if 'google.colab' in str(get_ipython()):
!pip install --upgrade xee
import ee
import xarray
import matplotlib.pyplot as plt
Initialization
First of all, you need to run the following cells to initialize the API and authorize your account. You must have a Google Cloud Project
associated with your GEE account. Replace the cloud_project with your own project from Google Cloud Console
(https://console.cloud.google.com/).
We are using the High-volume Endpoint (https://developers.google.com/earth-engine/cloud/highvolume) which supports large number of
concurrent requests and is recommended when working with XEE.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 98/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
cloud_project = 'spatialthoughts'
try:
ee.Initialize(
project=cloud_project,
opt_url='https://earthengine-highvolume.googleapis.com'
)
except:
ee.Authenticate()
ee.Initialize(
project=cloud_project,
opt_url='https://earthengine-highvolume.googleapis.com'
)
Select a location.
s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
filtered = s2 \
.filter(ee.Filter.date('2017-01-01', '2018-01-01')) \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30)) \
.filter(ee.Filter.bounds(geometry))
filteredMasked = filteredS2WithCs \
.map(maskLowQA)
# Write a function that computes NDVI for an image and adds it as a band
# Create a new image to overcome https://github.com/google/Xee/issues/88
def addNDVI(image):
ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi')
return image.multiply(0.0001).addBands(ndvi)\
.copyProperties(image, ['system:time_start'])
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 99/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
ds = xarray.open_dataset(
withNdvi,
engine='ee',
crs='EPSG:3857',
scale=10,
geometry=geometry,
ee_mask_value=-9999,
)
ds
ndvi_time_series = ds.ndvi
Run compute() to fetch the pixels from Earth Engine. This may take some time depending on the size of the request. This is a time-series at a
single pixel, so we also squeeze() to remove the X and Y dimensions and get an array of NDVI values.
original_time_series = ndvi_time_series.compute()
original_time_series = original_time_series.squeeze()
original_time_series
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(10, 5)
original_time_series.plot.line(
ax=ax, x='time',
marker='o', color='#66c2a4', linestyle='--', linewidth=1, markersize=4)
plt.show()
time_series_resampled = original_time_series\
.resample(time='5d').mean(dim='time')
time_series_resampled
Next we fill the cloud-masked pixels with linearly interpolated values from temporal neighbors.
time_series_interpolated = time_series_resampled\
.interpolate_na('time', use_coordinate=False)
time_series_interpolated
time_series_smooth = time_series_interpolated\
.rolling(time=3, center=True).mean()
time_series_smooth
A moving-window smoothing removed the first and last values of the time-series. We anchor the smoothed time-series with the values from
the original time-series.
time_series_smooth[0] = original_time_series[0]
time_series_smooth[-1] = original_time_series[-1]
time_series_smooth
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 100/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(10, 5)
original_time_series.plot.line(
ax=ax, x='time',
marker='^', color='#66c2a4', linestyle='--', linewidth=1, markersize=2)
time_series_smooth.plot.line(
ax=ax, x='time',
marker='o', color='#238b45', linestyle='-', linewidth=1, markersize=4)
plt.show()
df = time_series_smooth.to_dataframe('ndvi').reset_index()
df
output_filename = 'smoothed_time_series.csv'
df[['time', 'ndvi']].to_csv(output_filename, index=False)
Exercise
Replace the geometry with the location of your choice. Extract and download the smoothed time-series as a CSV file.
This script below provides a complete example of automating a download using Google Earth Engine API. It uses the Google Earth Engine
API to compute the average soil moisture for the given time period over all districts in a state. The result is then downloaded as a CSV file
and saved locally.
Before running the script, please install the Earth Engine Python Client Library and commplete the
authentication workflow on your machine using our step-by-step instructions.
Once you have finished the authentication, follow the steps below to create a script to download data from GEE.
1. Create a new file named download_data.py with the content shown below.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 101/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
import datetime
import ee
import csv
import os
cloud_project = 'spatialthoughts'
try:
ee.Initialize(project=cloud_project)
except:
ee.Authenticate()
ee.Initialize(project=cloud_project)
date_string = end_date.format('YYYY_MM_dd')
filename = 'ssm_{}.csv'.format(date_string.getInfo())
# Saving to current directory. You can change the path to appropriate location
output_path = os.path.join(filename)
soilmoisture = ee.ImageCollection('NASA/SMAP/SPL4SMGP/007')
admin2 = ee.FeatureCollection('FAO/GAUL_SIMPLIFIED_500m/2015/level2')
# Filter to a state
karnataka = admin2.filter(ee.Filter.eq('ADM1_NAME', 'Karnataka'))
mean = filtered.mean()
stats = mean.reduceRegions(**{
'collection': karnataka,
'reducer': ee.Reducer.mean().setOutputs(['meanssm']),
'scale': 11000,
'crs': 'EPSG:32643'
})
# Select columns to keep and remove geometry to make the result lightweight
# Change column names to match your uploaded shapefile
columns = ['ADM2_NAME', 'meanssm']
exportCollection = stats.select(**{
'propertySelectors': columns,
'retainGeometry': False})
features = exportCollection.getInfo()['features']
data = []
for f in features:
data.append(f['properties'])
2. From the terminal, navigate to the directory where you have created the file and type the command below to run the script.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 102/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
python download_data.py
3. The script will download the data from GEE and save a file to your current directory.
Before running the script, please install the Earth Engine Python Client Library on the server using our
step-by-step instructions.
The below code example shows how to authenticate an Earth Engine Export job using a service account. The code snippet assumes that the
private key file is in the same directory as the scirpt and saved as a file named .private_key.json .
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 103/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
import ee
# Replace the service account with your service account email
service_account = 'export-data-gee@spatialthoughts.iam.gserviceaccount.com'
# Replace the value with the path to your private key json file
private_key_path = '.private_key.json'
credentials = ee.ServiceAccountCredentials(service_account, private_key_path)
ee.Initialize(credentials)
lsib = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017')
australia = lsib.filter(ee.Filter.eq('country_na', 'Australia'))
geometry = australia.geometry()
terraclimate = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE')
tmax = terraclimate.select('tmmx')
def scale(image):
return image.multiply(0.1) \
.copyProperties(image,['system:time_start'])
tmaxScaled = tmax.map(scale)
filtered = tmaxScaled \
.filter(ee.Filter.date('2020-01-01', '2021-01-01')) \
.filter(ee.Filter.bounds(geometry))
image_ids = filtered.aggregate_array('system:index').getInfo()
clippedImage = exportImage.clip(geometry)
task = ee.batch.Export.image.toDrive(**{
'image': clippedImage,
'description': 'Terraclimate Image Export {}'.format(i+1),
'fileNamePrefix': image_id,
'folder':'earthengine',
'scale': 4638.3,
'region': geometry,
'maxPixels': 1e10
})
task.start()
print('Started Task: ', i+1)
We will run a script that processes and visualizes a future climate scenario using the CMIP6 Climate Models
(https://www.carbonbrief.org/cmip6-the-next-generation-of-climate-models-explained/).
Open the QGIS Python Console from Plugins → Python Console and click the Show Editor button. Paste the following code and click the
Run button. Once the code runs, the resulting image computed by Earth Engine and will be streamed to QGIS as a new layer.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 104/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
import ee
from ee_plugin import Map
model = 'ACCESS-CM2'
scenario = 'ssp245'
filtered = cmip6 \
.filter(ee.Filter.date(startDate, endDate)) \
.filter(ee.Filter.eq('model', model)) \
.filter(ee.Filter.eq('scenario', scenario)) \
.select(band)
def scaleValues(image):
return image \
.subtract(273.15) \
.copyProperties(image,
['system:time_start', 'model', 'scenario'])
scaled = filtered.map(scaleValues)
tempVis = {
'min': 10,
'max': 40,
'palette': ['blue', 'purple', 'cyan', 'green', 'yellow', 'red'],
}
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 105/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
You can now use this layer in your QGIS project or Print Layout. Here’s an example of visualizing the layer on a custom globe using the Globe
Builder Plugin (https://plugins.qgis.org/plugins/GlobeBuilder/).
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 106/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Supplement
We have a large collection of scripts that accompany this course. Visit the Supplement (https://courses.spatialthoughts.com/end-to-end-gee-
supplement.html).
Guided Projects
Below are step-by-step video-based walkthrough of implementing real-world projects using Earth Engine. You can continue their learning
journey by implementing these projects for their region of interest after the class.
(https://www.youtube.com/watch?
v=uoOvOPK0iro&list=PLppGmFLhQ1HJuIb7qMiKIv11HiEQhy3ha&index=8)
If you do not see the repository in the Reader section, click Refresh repository cache button in your Scripts tab and it will show up.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 107/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
(https://www.youtube.com/watch?
v=zHUCM3XLc6k&list=PLppGmFLhQ1HJ5VhW6BZfhPX6spUcTY7SR)
(https://www.youtube.com/watch?
v=jYsK9Y4ICrY&list=PLppGmFLhQ1HJzzKVS_4v8nBiXLYxAu100)
(https://www.youtube.com/watch?
v=LqSClCXrMl4&list=PLppGmFLhQ1HJV1CctqanQvXQI1JmqGDDD)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 108/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
(https://www.youtube.com/watch?
v=B0E_dzO1J4g&list=PLppGmFLhQ1HLl0St2wiOPePr58sKu0Vh1)
(https://www.youtube.com/watch?v=_c-
_UU9J6s0&list=PLppGmFLhQ1HLN6ivbJJHLZbEYLdq5zgns)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 109/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Learning Resources
Cloud-Based Remote Sensing with Google Earth Engine: Fundamentals and Applications (https://www.eefabook.org/): A free and
open-access book with 55-chapters covering fundamentals and applications of GEE. Also includes YouTube videos summarizing each
chapter.
Awesome Earth Engine (https://github.com/giswqs/Awesome-GEE): A curated list of Google Earth Engine resources.
Google Earth Engine for Water Resources Management (https://courses.spatialthoughts.com/gee-water-resources-
management.html): Application-focused Introduction to Google Earth Engine.
Creating Publication Quality Charts with GEE (https://courses.spatialthoughts.com/gee-charts.html): A comprehesive guide on
creating high-quality data visualizations with Google Earth Engine and Google Charts.
We also have a few recommendations of a few selected packages, which have very useful functions to make you productive in Earth Engine.
These are usually the result of inefficient code and structure of your script. Below are my recommendations for improving your coding style
and utilizing the full power of the Earth Engine infrastructure.
If/else statements and for-loops should be avoided completely and replaced with filter/map/reduce. The former are sequential and will
be slow. Refer to the Function Programming Concepts (https://developers.google.com/earth-engine/tutorials/tutorial_js_03) guide on
how to restructure your code to utilize the parallel computing infratructure provided by GEE.
Remove any reprojection or resampling calls fro your script. Barring a few exceptions, you should not be reprojecting or resampling
data. Earth Engine is designed to take care of it internally.
For complex analysis involving large volumes of data, you should break down your workflow into logical steps and export intermediate
results. For example, if you are implementing a complex supervised classification workflow - do it in multiple stages. A) Create your
composite, add required bands, normalize them and export it as an Asset. B) Import the exported composite into another script and
train a classifier. Export the classified image as an Asset. C) Import the exported classified image and do accuracy assessment and
visualize the results. You will be able to run your analysis much faster, experiment easily and avoid scaling errors. Refer to the Export
Intermediate Result (https://developers.google.com/earth-engine/guides/best_practices?hl=en#export_intermediate_results) guide to
learn more.
You may also break your export region into smaller regions (typically by admin units) and Export each region separately. For example,
if you want to export results for an entire country at 30m resolution - rather than a running a large Export - you can export each
Admin1 unit in your country separately. This helps with computation timed-out errors and improves overall processing time. You can
start as many exports from your account as you want and they will be executed in a queue. But do not split your exports over multiple
Earth Engine accounts. This is a violation of GEE Terms of Service (https://developers.google.com/earth-engine/batch-task-
restrictions) and may result in your account getting blocked.
The Earth Engine User Guide also has tips and examples of best practices. You can review the following articles to learn them.
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 110/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
The following videos are highly recommended and contain good advice on debugging and scaling workflows.
(https://www.youtube.com/watch?v=3AtnpkZTvnk)
(https://www.youtube.com/watch?v=fsez4HiOc8k)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 111/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Data Credits
Sentinel-2 Level-1C, Level-2A and Sentinel-1 SAR GRD: Contains Copernicus Sentinel data.
TerraClimate: Monthly Climate and Climatic Water Balance for Global Terrestrial Surfaces, University of Idaho: Abatzoglou, J.T., S.Z.
Dobrowski, S.A. Parks, K.C. Hegewisch, 2018, Terraclimate, a high-resolution global dataset of monthly climate and climatic water
balance from 1958-2015, Scientific Data 5:170191, doi:10.1038/sdata.2017.191 (doi:10.1038/sdata.2017.191)
VIIRS Stray Light Corrected Nighttime Day/Night Band Composites Version 1: C. D. Elvidge, K. E. Baugh, M. Zhizhin, and F.-C. Hsu,
“Why VIIRS data are superior to DMSP for mapping nighttime lights,” Asia-Pacific Advanced Network 35, vol. 35, p. 62, 2013.
FAO GAUL 500m: Global Administrative Unit Layers 2015, Second-Level Administrative Units: Source of Administrative
boundaries: The Global Administrative Unit Layers (GAUL) dataset, implemented by FAO within the CountrySTAT and Agricultural
Market Information System (AMIS) projects.
CHIRPS Pentad: Climate Hazards Group InfraRed Precipitation with Station Data (version 2.0 final): Funk, Chris, Pete Peterson,
Martin Landsfeld, Diego Pedreros, James Verdin, Shraddhanand Shukla, Gregory Husak, James Rowland, Laura Harrison, Andrew
Hoell & Joel Michaelsen. “The climate hazards infrared precipitation with stations—a new environmental record for monitoring
extremes”. Scientific Data 2, 150066. doi:10.1038/sdata.2015.66 (doi:10.1038/sdata.2015.66) 2015.
MOD13Q1.006 Terra Vegetation Indices 16-Day Global 250m: Didan, K. (2015). MOD13Q1 MODIS/Terra Vegetation Indices 16-Day L3
Global 250m SIN Grid V006 [Data set]. NASA EOSDIS Land Processes DAAC. Accessed 2021-05-06 from
https://doi.org/10.5067/MODIS/MOD13Q1.006 (https://doi.org/10.5067/MODIS/MOD13Q1.006)
GHS Urban Centre Database 2015 multitemporal and multidimensional attributes R2019A: Florczyk A., Corbane C,. Schiavina M.,
Pesaresi M., Maffenini L., Melchiorri, M., Politis P., Sabo F., Freire S., Ehrlich D., Kemper T., Tommasi P., Airaghi D., Zanchetta L. (2019)
European Commission, Joint Research Centre (JRC) PID (https://data.jrc.ec.europa.eu/dataset/53473144-b88c-44bc-b4a3-
4583ed1f547e)
ESA WorldCover v100: Zanaga, D., Van De Kerchove, R., De Keersmaecker, W., Souverijns, N., Brockmann, C., Quast, R., Wevers, J.,
Grosu, A., Paccini, A., Vergnaud, S., Cartus, O., Santoro, M., Fritz, S., Georgieva, I., Lesiv, M., Carter, S., Herold, M., Li, Linlin, Tsendbazar,
N.E., Ramoino, F., Arino, O., 2021. ESA WorldCover 10 m 2020 v100. doi:10.5281/zenodo.5571936
(https://doi.org/10.5281/zenodo.5571936)
References
Composites
Phan, T.N.; Kuch, V.; Lehnert, L.W. Land Cover Classification using Google Earth Engine and Random Forest Classifier—The Role of
Image Composition. Remote Sens. 2020, 12, 2411. https://doi.org/10.3390/rs12152411 (https://doi.org/10.3390/rs12152411)
D. Simonetti, U. Pimple, A. Langner, A. Marelli, Pan-tropical Sentinel-2 cloud-free annual composite datasets, Data in Brief, Volume 39,
2021, 107488, ISSN 2352-3409,https://doi.org/10.1016/j.dib.2021.107488 (https://doi.org/10.1016/j.dib.2021.107488).
Supervised Classification
Shetty, S.; Gupta, P.K.; Belgiu, M.; Srivastav, S.K. Assessing the Effect of Training Sampling Design on the Performance of Machine
Learning Classifiers for Land Cover Mapping Using Multi-Temporal Remote Sensing Data and Google Earth Engine. Remote Sens.
2021, 13, 1433. https://doi.org/10.3390/rs13081433 (https://doi.org/10.3390/rs13081433)
Kelley, L.C.; Pitcher, L.; Bacon, C. Using Google Earth Engine to Map Complex Shade-Grown Coffee Landscapes in Northern Nicaragua.
Remote Sens. 2018, 10, 952. https://doi.org/10.3390/rs10060952 (https://doi.org/10.3390/rs10060952)
Arsalan Ghorbanian, Mohammad Kakooei, Meisam Amani, Sahel Mahdavi, Ali Mohammadzadeh, Mahdi Hasanlou, Improved land
cover map of Iran using Sentinel imagery within Google Earth Engine and a novel automatic workflow for land cover classification
using migrated training samples, ISPRS Journal of Photogrammetry and Remote Sensing, Volume 167, 2020,
https://doi.org/10.1016/j.isprsjprs.2020.07.013 (https://doi.org/10.1016/j.isprsjprs.2020.07.013)
Tassi, A.; Vizzari, M. Object-Oriented LULC Classification in Google Earth Engine Combining SNIC, GLCM, and Machine Learning
Algorithms. Remote Sens. 2020, 12, 3776. https://doi.org/10.3390/rs12223776 (https://doi.org/10.3390/rs12223776)
Cristina Gómez, Joanne C. White, Michael A. Wulder, Optical remotely sensed time series data for land cover classification: A review,
ISPRS Journal of Photogrammetry and Remote Sensing, Volume 116, 2016, Pages 55-72, ISSN 0924-2716,
https://doi.org/10.1016/j.isprsjprs.2016.03.008 (https://doi.org/10.1016/j.isprsjprs.2016.03.008)
Sherrie Wang, George Azzari, David B. Lobell, Crop type mapping without field-level labels: Random forest transfer and unsupervised
clustering techniques, Remote Sensing of Environment, Volume 222, 2019, Pages 303-317, ISSN 0034-4257,
https://doi.org/10.1016/j.rse.2018.12.026 (https://doi.org/10.1016/j.rse.2018.12.026)
License
The course material (text, images, presentation, videos) is licensed under a Creative Commons Attribution 4.0 International License
(https://creativecommons.org/licenses/by/4.0/).
The code (scripts, Jupyter notebooks) is licensed under the MIT License. For a copy, see https://opensource.org/licenses/MIT
(https://opensource.org/licenses/MIT)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 112/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
Gandhi, Ujaval, 2021. End-to-End Google Earth Engine Course. Spatial Thoughts. https://courses.spatialthoughts.com/end-to-end-
gee.html (https://courses.spatialthoughts.com/end-to-end-gee.html)
This course is offered as an instructor-led online class. Visit Spatial Thoughts (https://spatialthoughts.com/events/) to know details of
upcoming sessions.
If you want to report any issues with this page, please comment below.
Different image collections yield a significantly different number of images for the same time period over a ROI.
How do we decide which is the most appropriate collection for a given application, for example monitoring crop
health via vegetation indices. Is there a guideline that can help in this.
You choose the image collection that is best suited for the purpose.
For example, Sentinel-2 provides higher resolution but has limited historic coverage. Landsat has thermal bands that
can help certain crop analysis and has a 50 year archive but has a lower spatial resolution. Temporal resolution of
Sentinel-2 is much better than Landsat, so it allows you to monitor more frequently.
Once you know the sensor you want to use, the preference is always to work with Surface Reflectance (Level 2) data
whenever possible. But again this is not always a choice. For example, Sentinel-2 has Level 1 data from 2015
onwards but Level 2 data from 2017 onwards.
So as of now, my recommendation for crop health via vegetation indices is to use Sentinel-2 Level 2 data if you can
deal with a limited historical archive.
Another dataset to keep in mind for future is HLS - which is a fusion of both Landsat and Sentinel-2 which maybe a
good choice for vegetation monitoring. But it's still not fully available and may take a while for examples to be
developed and tested.
Good afternoon i need your help here , I tried to follow the instructions for assigning the google earth engine in
Qgis and i got this errors "import ee
Traceback (most recent call last):
File "C:\OSGeo4W\apps\Python312\Lib\code.py", line 90, in runcode
exec(code, self.locals)
File "", line 1, in
File "C:\Users/DELL/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\ee_plugin_init_.py", line 38,
in wrapping_ee_import
module = builtin import(name, *args, **kwargs)
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 113/114
12/25/24, 2:13 PM End-to-End Google Earth Engine (Full Course)
module builtin_import(name, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\OSGeo4W/apps/qgis-ltr/./python\qgis\utils.py", line 892, in _import
mod = builtin_import(name, globals, locals, fromlist, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\OSGeo4W\apps\Python312\Lib\site-packages\ee_init.py", line 15, in
from ee import utils
File "C:\Users/DELL/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\ee_plugin_init.py", line 40, in
wrapping_ee_import
if not module.data.credentials:
^^^^^^^^^^^^^
AttributeError: partially initialized module 'ee' has no attribute 'data' (most likely due to a circular import)
exec(Path('C:/Users/DELL/AppData/Local/Temp/tmpq9rfmlyz') read text())
@halieute Install the EE Python API on your system using conda. Follow the steps at
https://courses.spatialthoughts.com/install-gee-python-api.html
Create a new environment and once installed, authenticate using earthengine authenticate . Re-install the plugin
and it should work. If you still get the error, create a new QGIS Profile and install the plugin there. Watch this video
on how to create a new profile.
Thank you for sharing valuable content on GEE. Using markers, we can mark certain locations. However, I don't have
an option to save them to a physical location. But I could see you have used them from repository as shown in the
above line. Can you please share the process to save them to certain locations as GCPs and refer them as you
referred in the above code?
Write Preview
Sign in to comment
https://courses.spatialthoughts.com/end-to-end-gee.html#introduction 114/114