This workshop is under active development and is not complete.
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.
The course material and exercises are in the form of Earth Engine scripts shared via a code repository.
users/ujavalgandhi/GEE-Charts
in the Scripts tab
in the Reader section.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.
Refresh repository cache
In this section, we will explore various built-in functions to create time-series charts from ImageCollections. We will also explore the customization options provided by Google Charts to make high-quality functional graphics.
We start by using the time-series charting function
ui.Chart.image.series()
that allows you to create a
time-series plot from an ImageCollection at a single location. You get
one time-series per band of the input dataset. We take the TerraClimate
dataset and select the bands for monthly maximum and minimum
temperatures. The resulting chart is a Line
Chart that can be further customized using the
.setOptions()
method.
Here are the customization applied to the default time-series chart:
lineWidth
: Sets the thickness of the linepointSize
: Sets the size of the data pointtitle
: Sets the chart titlevAxis
: Sets the options for Y-Axis. Axis label is
specified using the title
option.hAxis
: Sets the options for X-Axis. Grid lines are
specified using the gridlines
option. Date format for tick
labels is specified with format
option.series
: Sets the options for each individual
time-series. Series count starts from 0.Time-Series Chart
// Select a location
var geometry = ee.Geometry.Point([77.57738128916243, 12.964758918835752]);
// We use TerraClimate Dataset
var terraclimate = ee.ImageCollection('IDAHO_EPSCOR/TERRACLIMATE');
// Select the temerature bands
// 'tmmx' = Maximum temperature
// 'tmmn' (Minimum temperature)
var temp = terraclimate.select(['tmmx', 'tmmn']);
// The pixel values have a scale factor of 0.1
// We must multiply the pixel values with the scale factor
// to get the temperature values in °C
var tempScaled = temp.map(function(image) {
return image.multiply(0.1)
.copyProperties(image,['system:time_start']);
;
})
// Filter the collection
var startYear = 2022;
var endYear = 2022;
var startDate = ee.Date.fromYMD(startYear, 1, 1);
var endDate = ee.Date.fromYMD(endYear + 1, 1, 1);
var filtered = tempScaled
.filter(ee.Filter.date(startDate, endDate));
// Create a time-series chart
var chart = ui.Chart.image.series({
imageCollection: filtered.select(['tmmx']),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 4638.3
;
})
// Print the chart
print(chart);
// We can use .setOptions() to customize the chart
var chart = ui.Chart.image.series({
imageCollection: filtered.select(['tmmx']),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 4638.3
;
})
var chart = ui.Chart.image.series({
imageCollection: filtered.select(['tmmx']),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 4638.3
.setOptions({
})lineWidth: 1,
pointSize: 2,
title: 'Monthly Temperature Time-Series',
vAxis: {title: 'Temparature (°C)'},
hAxis: {title: '', format: 'YYYY-MMM', gridlines: {count: 12}}
})print(chart);
// We can select multiple bands and get a time-series for each band
// Additionally, we can specify the 'series' options
// to specify styling options for each series.
var chart = ui.Chart.image.series({
imageCollection: filtered.select(['tmmx', 'tmmn'], ['maximum', 'minimum']),
region: geometry,
reducer: ee.Reducer.mean(),
scale: 4638.3
.setOptions({
})lineWidth: 1,
pointSize: 2,
title: 'Monthly Temperature Time-Series',
vAxis: {title: 'Temparature (°C)'},
hAxis: {title: '', format: 'YYYY-MMM', gridlines: {count: 12}},
series: {
0: {color: 'red'},
1: {color: 'blue'}
,
}
})
// Print the chart
print(chart);
// Exercise
// a) Delete the 'geometry' and add a new point at your chosen location
// b) Modify the chart options display the series with dashed lines
// c) Print the chart.
// See reference:
// https://developers.google.com/chart/interactive/docs/lines#dashed
Google Charts can dynamically compute and display Trendlines on the chart. You can choose from linear, polynomial or exponential trendlines. The linear trendline fit a least-square regression model on the dataset. Here we take a time-series of precipitation data, aggregate it to yearly precipitation and then display a linear trendline to indicate whether we see an increasing or decreasing rainfall in the region.
Here are the styling options applied to the time-series chart:
vAxis.ticks
: Sets the tick positions for Y-Axis. We
manually specify the exact tick marks we want.gridlines.color
: Sets the color of the grid lines.legend
: Sets the position of the legend. The
in
options makes the legend appear inside the chart.series.visibleInLegend
: Sets whether a particular
series label is visible in the legend.trendlines
: Sets the option for trendlines. We override
the default label using labelInLegend
option.Time-Series Chart with Trendline
// Select a region
var geometry = ee.Geometry.Point([77.6045, 12.8992]);
// We use the CHIRPS Rainfall Dataset
var chirps = ee.ImageCollection('UCSB-CHG/CHIRPS/PENTAD');
// We will compute the trend of total annual precipitation
var createAnnualImage = function(year) {
var startDate = ee.Date.fromYMD(year, 1, 1);
var endDate = startDate.advance(1, 'year');
var seasonFiltered = chirps
.filter(ee.Filter.date(startDate, endDate));
// Calculate total precipitation
var total = seasonFiltered.reduce(ee.Reducer.sum()).rename('Precipitation');
return total.set({
'system:time_start': startDate.millis(),
'system:time_end': endDate.millis(),
'year': year,
;
});
}
// Aggregate Precipitation Data over 40 years
var years = ee.List.sequence(1981, 2020);
var yearlyImages = years.map(createAnnualImage);
var yearlyCol = ee.ImageCollection.fromImages(yearlyImages);
// Create a time-series with sens slope trend
var chart = ui.Chart.image.series({
imageCollection: yearlyCol,
region: geometry,
reducer: ee.Reducer.mean(),
scale: 5566,
.setOptions({
})title: 'Annual Total Precipitation',
color: 'blue',
pointSize: 3,
lineWidth: 1,
vAxis: {
title: 'Rainfall (mm)',
ticks: [0, 200, 400, 600, 800, 1000, 1200, 1400],
gridlines: {color: '#f0f0f0'}
,
}hAxis: {
title: 'Year',
gridlines: {
color: '#f0f0f0',
units: {years: { format: 'YYYY'}}
},
}
legend: {
position: 'in'
,
}series: {
0: {
visibleInLegend: false
},
}trendlines: {
0: {
type: 'linear',
color: 'black',
lineWidth: 1,
pointSize: 0,
visibleInLegend: true,
labelInLegend: 'Precipitation Trend',
},
};
})print(chart);
// Exercise
// a) Delete the 'geometry' and add a new point at your chosen location
// b) Modify the chart options to remove the legend from the chart.
// c) Print the chart.
// Hint: Use legend 'position' option
// See reference:
// https://developers.google.com/chart/interactive/docs/gallery/linechart
So far, we have learnt how to display time-series of one or more
variables at a single location using the
ui.Chart.image.series()
function. If you wanted to plot
time-series of multiple locations in a single chart, you can use the
ui.Chart.image.series.byRegion()
function. This function
takes a FeatureCollection with one or more locations and extract the
time-series at each geometry.
Here we take the Global Forecast System (GFS) dataset and create a chart of 16-day temperature-forecasts at 2 cities.
Time-Series Chart at Multiple Locations
// Select the locations
var geometry1 = ee.Geometry.Point([72.57, 23.04]);
var geometry2 = ee.Geometry.Point([77.58, 12.97]);
// We use the NOAA GFS dataset
var gfs = ee.ImageCollection('NOAA/GFS0P25');
// Select the temperature band
var forecast = gfs.select('temperature_2m_above_ground');
// Get the forecasts for today
var now = Date.now();
var now = ee.Date(now).advance(-1, 'day');
var filtered = forecast
.filterDate(now, now.advance(1, 'day'));
// All forecast images have a timestamp of the current day
// As we want a time-series of forecasts, we update the
// timestamp to the date the image is forecasting.
var filtered = filtered.select('temperature_2m_above_ground')
.map(function(image) {
var forecastTime = image.get('forecast_time');
return image.set('system:time_start', forecastTime);
;
})
// Create a chart of forecast at a single location
var chart = ui.Chart.image.series({
imageCollection: filtered,
region: geometry1,
reducer: ee.Reducer.first(),
scale: 27830}).setOptions({
lineWidth: 1,
pointSize: 2,
title: 'Temperature Forecast at a Single Location',
vAxis: {title: 'Temparature (°C)'},
hAxis: {title: '', format: 'YYYY-MM-dd'},
series: {
0: {color: '#fc8d62'},
,
}legend: {
position: 'none'
};
})print(chart);
// For plotting multiple locations, we need a FeatureCollection
var locations = ee.FeatureCollection([
.Feature(geometry1, {'name': 'Ahmedabad'}),
ee.Feature(geometry2, {'name': 'Bengaluru'})
ee;
])
// Create a chart of forecasted temperatures
var chart = ui.Chart.image.seriesByRegion({
imageCollection: filtered,
regions: locations,
reducer: ee.Reducer.first(),
scale: 27830,
seriesProperty: 'name'
.setOptions({
})lineWidth: 1,
pointSize: 2,
title: 'Temperature Forecast at Multiple Locations',
vAxis: {title: 'Temparature (°C)'},
hAxis: {title: '', format: 'YYYY-MM-dd'},
series: {
0: {color: '#fc8d62'},
1: {color: '#8da0cb'}
,
}legend: {
position: 'top'
};
})print(chart);
// Exercise
// a) Replace the 'geometry1' and 'geometry2' points with your chosen locations.
// b) Modify the chart options to limit the Y-Axis range to the
// actual range of temperatures at your chosen locations (i.e. between 20-45 degrees)
// c) Print the chart.
Another useful function to plot time-series is
`ui.Chart.image.doySeriesByYear()
that extracts and plots
values from an image band at different Day-Of-Year (DOY) over many
years. This type of chart is helpful visualize both inter-annual and
inter-annual variations in a single chart.
Here we take the MODIS 16-day Vegetation Indices (VI) dataset and create a chart of NDVI Time-Series over 4 years.
Here are the styling options applied to the time-series chart:
interpolateNulls
: Sets whether to fill missing (i.e
masked) time-series valuescurveType
: Apply smoothing on the time-series by fitting
a function.DOY Time-Series Chart
// Select a location
var geometry = ee.Geometry.Point([81.73099978484261, 27.371459793533507]);
// We use the MODIS 16-day Vegetation Indicies dataset
var modis = ee.ImageCollection('MODIS/061/MOD13Q1');
// Filter the collection
var startYear = 2019;
var endYear = 2022;
var startDate = ee.Date.fromYMD(startYear, 1, 1);
var endDate = ee.Date.fromYMD(endYear + 1, 1, 1);
var filtered = modis
.filter(ee.Filter.date(startDate, endDate))
// Pre-Processing: Cloud Masking and Scaling
// Function for Cloud Masking
var bitwiseExtract = function(input, fromBit, toBit) {
var maskSize = ee.Number(1).add(toBit).subtract(fromBit)
var mask = ee.Number(1).leftShift(maskSize).subtract(1)
return input.rightShift(fromBit).bitwiseAnd(mask)
}
var maskSnowAndClouds = function(image) {
var summaryQa = image.select('SummaryQA')
// Select pixels which are less than or equals to 1 (0 or 1)
var qaMask = bitwiseExtract(summaryQa, 0, 1).lte(1)
var maskedImage = image.updateMask(qaMask)
return maskedImage.copyProperties(
, ['system:index', 'system:time_start'])
image
}
// Function for Scaling Pixel Values
// MODIS NDVI values come as NDVI x 10000
// that need to be scaled by 0.0001
var ndviScaled = function(image) {
var scaled = image.divide(10000)
return scaled.copyProperties(
, ['system:index', 'system:time_start'])
image;
}
// Apply the functions and select the 'NDVI' band
var processedCol = filtered
.map(maskSnowAndClouds)
.map(ndviScaled)
.select('NDVI');
// Plot a time-series
var chart = ui.Chart.image.series({
imageCollection: processedCol,
region: geometry,
reducer: ee.Reducer.mean(),
scale: 250
.setOptions({
})interpolateNulls: true,
lineWidth: 1,
pointSize: 2,
title: 'NDVI Time-Series',
vAxis: {title: 'NDVI'},
})print(chart);
// We can plot a yearly time-series
// that allows us to compare changes over time
var chart = ui.Chart.image.doySeriesByYear({
imageCollection: processedCol,
region: geometry,
regionReducer: ee.Reducer.mean(),
scale: 250,
bandName: 'NDVI'
.setOptions({
})interpolateNulls: true,
curveType: 'function',
lineWidth: 1,
pointSize: 2,
title: 'Multi-Year NDVI Time-Series',
vAxis: {title: 'NDVI'},
hAxis: {title: 'Day-of-Year'},
legend: {position: 'top'}
})print(chart)
Exercise 04c
// Exercise
// a) Replace the 'geometry' with your chosen location.
// b) Modify the chart to specify custom colors for each year.
// Use color codes from https://colorbrewer2.org/
// c) Modify the chart to plot only the time-series
// with lines without any points.
// c) Print the chart.
This section covers charting functions and techniques to plot values from an image. We will also learn how to deal with limitations of the charting API and create plots by extracting data from large regions.
A histogram plot is a bar chart showing count of pixel values. Typically the pixel values are grouped into range of values called buckets on the X-Axis and the total count of pixels is shown on the Y-Axis.
Here we take the Harmonized Night Time Lights dataset that contains images from both DMSP and VIIRS sensors.
Below is the list of styling options applied to the histogram:
hAxis.ticks
: Sets the tick labels on the X-Axis.bar.gap
: Sets the gap between each histogram barImage Histogram
// We use the Harmonized Global Night Time Lights (1992-2020) dataset
var dmsp = ee.ImageCollection('projects/sat-io/open-datasets/Harmonized_NTL/dmsp');
var viirs = ee.ImageCollection('projects/sat-io/open-datasets/Harmonized_NTL/viirs');
// Merge both collections to create a single Night Lights Collection
var ntlCol = dmsp.merge(viirs);
// Using LSIB for country boundaries
var lsib = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
var country = 'Japan';
var selected = lsib.filter(ee.Filter.eq('country_na', country));
var geometry = selected.geometry();
var year = 2009;
var startDate = ee.Date.fromYMD(year, 1, 1);
var endDate = startDate.advance(1, 'year')
// We filter for the selected year
var filtered = ntlCol
.filter(ee.Filter.date(startDate, endDate))
// Extract the image and set the masked pixels to 0
var ntlImage = ee.Image(filtered.first()).unmask(0);
var palette =['#253494','#2c7fb8','#41b6c4','#a1dab4','#ffffcc' ];
var ntlVis = {min:0, max: 63, palette: palette}
Map.centerObject(geometry, 6);
Map.addLayer(ntlImage.clip(geometry), ntlVis, 'Night Time Lights ' + year);
// Extract the native resolution of the image
var resolution = ntlImage.projection().nominalScale();
// NTL images have DN values from 0-63
// We can create a histogram to show pixel counts
// for each DN value
var chart = ui.Chart.image.histogram({
image: ntlImage,
region: geometry,
scale: resolution,
maxBuckets: 63,
minBucketWidth: 1})
print(chart);
// Add options to add labels and ticks
var chart = ui.Chart.image.histogram({
image: ntlImage,
region: geometry,
scale: resolution,
maxBuckets: 63,
minBucketWidth: 1
.setOptions({
})title: 'Night Time Lights Distribution for ' + country + ' ' + year,
vAxis: {
title: 'Number of Grids',
gridlines: {color: 'transparent'}
,
}hAxis: {
title: 'Level of Observed Nighttime Lights',
ticks: [0, 6, 13, 21, 29, 37, 45, 53, 61],
gridlines: {color: 'transparent'}
,
}bar: { gap: 1 },
legend: { position: 'none' },
colors: ['#525252']
})
print(chart);
Exercise 01c
// Exercise
// The code now has a function createChart that creates a chart
// for the given year
// a) Change the name of the country to your chosen country
// b) Call the function to create histograms for the year 2010 and 2020
// c) Print the charts.
A scatter plot is useful to explore the relationship between 2
variables. In Earth Engine, you can extract the pixel values from an
image using any of the sampling functions such as sample()
or stratifiedSample()
to get a FeatureCollection with pixel
values for a random subset of the pixels. We can then plot the results
using the built-in charting functions for FeatureCollections.
Here we use the Sentinel-2 Surface Reflectance dataset along with
Global Surface Water Yearly Water History dataset to get reflectance
values of water and non-water pixels within the chosen region. We then
use the ui.Chart.feature.groups()
function to plot the
results. Note that you can explicitly set the desired chart type using
the setChartType()
function.
Below is the list of new styling options applied to the scatter plot:
titleTextStyle
: Sets the style of the title text.dataOpacity
: Sets the transparency for the data points.
Useful when you have overlapping data points.pointShape
: Sets the shape of the marker from the available
marker shapes.Scatter Plot
// We want to plot the relationship between
// 2 spectral bands for different classes
// Select a region
var geometry = ee.Geometry.Polygon([[
76.816, 13.006],[76.816, 12.901],
[76.899, 12.901],[76.899, 13.006]
[;
]])
// We use the Sentinel-2 SR data
var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
// Add function for cloud masking
function maskS2clouds(image) {
var qa = image.select('QA60');
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
var mask = qa.bitwiseAnd(cloudBitMask).eq(0).and(
.bitwiseAnd(cirrusBitMask).eq(0));
qareturn image.updateMask(mask)
.select('B.*')
.multiply(0.0001)
.copyProperties(image, ['system:time_start']);
}
// Filter and apply cloud mask
var filtered = s2
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 30))
.filter(ee.Filter.date('2020-01-01', '2021-01-01'))
.filter(ee.Filter.bounds(geometry))
.map(maskS2clouds)
.select('B.*');
// Create a composite
var composite = filtered.median();
var rgbVis = {bands: ['B4', 'B3', 'B2'], min: 0, max: 0.3, gamma: 1.2};
Map.centerObject(geometry, 12);
Map.addLayer(composite.clip(geometry), rgbVis, 'RGB');
// Use the Global Surface Water Yearly dataset
var gswYearly = ee.ImageCollection('JRC/GSW1_4/YearlyHistory');
// Extract the image for the chosen year
var filtered = gswYearly.filter(ee.Filter.eq('year', 2020));
var gsw2020 = ee.Image(filtered.first());
// Select permanent or seasonal water
var water = gsw2020.eq(3).or(gsw2020.eq(2)).rename('water');
var waterVis = {min:0, max:1, palette: ['white','blue']};
Map.addLayer(water.clip(geometry).selfMask(), waterVis, 'Water', false);
// We want to splot the relationship between
// 'NIR' (B8) and 'GREEN' (B3) band reflectance
// for water and non-water pixels
// Select the bands
var bands = composite.select(['B8', 'B3']);
// Extract samples for both classes
var samples = bands.addBands(water).stratifiedSample({
numPoints: 50,
classBand: 'water',
region: geometry,
scale: 10})
print(samples.first());
// Create a chart and set the chart type
var chart = ui.Chart.feature.groups({
features: samples,
xProperty: 'B3',
yProperty: 'B8',
seriesProperty: 'water'
.setChartType('ScatterChart');
})
print(chart);
// Customize the style
var chart = ui.Chart.feature.groups({
features: samples,
xProperty: 'B3',
yProperty: 'B8',
seriesProperty: 'water'
.setChartType('ScatterChart')
}).setOptions({
title: 'Relationship Among Spectral Values ' +
'for Water and Non-Water Pixels',
titleTextStyle: {bold: true},
dataOpacity: 0.8,
hAxis: {
'title': 'Green reflectance',
titleTextStyle: {italic: true},
,
}vAxis: {
'title': 'NIR Reflectance',
titleTextStyle: {italic: true},
,
}series: {
0: {
pointShape: 'triangle',
pointSize: 4,
color: '#2c7bb6',
labelInLegend: 'Water',
,
}1: {
pointShape: 'triangle',
pointSize: 4,
color: '#f46d43',
labelInLegend: 'Non-Water'
},
}legend: {position: 'in'}
;
})print(chart);
// Exercise
// The code now contains a function createChart() that creates a scatter plot
// between the chosen bands
// a) Delete the 'geometry' and add a new polygon at your chosen location
// b) Create a chart for B3 and B11
// c) Print the chart.
Many analysts would want to create a chart or a table showing areas
of different landcover classes in an image. The EE API has a dedicated
function ui.Chart.Image.byClass()
that can tabulate image
pixel values by class.
Here we use the ESA Landcover 2021 dataset and create a
Table chart of areas within the buffer zone of a
location. Note that we are using setChartType()
function
with the option Table to create a table. Such tables
are useful when you are creating apps and want to display a formatted
table. The Global
Population Explorer is a good example where you can switch between a
Bar Chart and a Table to display the results.
Table Chart
// Select a region
var geometry = ee.Geometry.Point([77.6045, 12.8992]);
// We use the ESA WorldCover 2021 dataset
var worldcover = ee.ImageCollection('ESA/WorldCover/v200').first();
// The image has 11 classes
// Remap the class values to have continuous values
// from 0 to 10
var classified = worldcover.remap(
10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100],
[0, 1 , 2, 3, 4, 5, 6, 7, 8, 9, 10]).rename('classification');
[
// Define a list of class names
var worldCoverClassNames= [
'Tree Cover', 'Shrubland', 'Grassland', 'Cropland', 'Built-up',
'Bare / sparse Vegetation', 'Snow and Ice',
'Permanent Water Bodies', 'Herbaceous Wetland',
'Mangroves', 'Moss and Lichen'];
// Define a list of class colors
var worldCoverPalette = [
'006400', 'ffbb22', 'ffff4c', 'f096ff', 'fa0000',
'b4b4b4', 'f0f0f0', '0064c8', '0096a0', '00cf75',
'fae6a0'];
var visParams = {min:0, max:10, palette: worldCoverPalette};
Map.addLayer(classified, visParams, 'Landcover');
// We want to compute the class areas in a buffer zone
var bufferDistance = 1000;
var buffer = geometry.buffer(bufferDistance);
Map.centerObject(buffer, 12);
Map.addLayer(buffer, {color: 'gray'}, 'Buffer Zone');
// Create an area image and convert to Hectares
var areaImage = ee.Image.pixelArea().divide(1e5);
// Add the band containing classes
var areaImageWithClass = areaImage.addBands(classified);
// Create a chart
var chart = ui.Chart.image.byClass({
image: areaImageWithClass,
classBand: 'classification',
region: buffer,
reducer: ee.Reducer.sum(),
scale: 10,
;
})print(chart);
// Set the chart type and add styling options
var chart = ui.Chart.image.byClass({
image: areaImageWithClass,
classBand: 'classification',
region: buffer,
reducer: ee.Reducer.sum(),
scale: 10,
classLabels: worldCoverClassNames,
xLabels: ['Area (Hectares)']
.setChartType('Table');
})
print(chart);
// Exercise
// a) Delete the 'geometry' and add a new point at your chosen location
// b) Change the buffer distance to 10km and Area units to Square Kilometers
// c) Print the chart.
One of the biggest limitations of the GEE Charting API is that it cannot create charts from more than 10000000 pixels. While this may seem like a big number, you can easily run into this limit when working with images that cover large areas. If you try creating charts for large regions, you may run into an error such as below:
Image.reduceRegion: Too many pixels in the region. Found 159578190, but maxPixels allows only 10000000. Ensure that you are not aggregating at a higher resolution than you intended; that is a frequent cause of this error. If not, then you may set the ‘maxPixels’ argument to a limit suitable for your computation; set ‘bestEffort’ to true to aggregate at whatever scale results in ‘maxPixels’ total pixels; or both.
Chart Error
Fortunately, there is a way around it. Earth Engine allows you to
aggregate values from very large regions using reducers that have an
option to specify a maxPixels
parameter. You will need to
use the appropriate reducer and create a FeatureCollection with the
results. The resulting value can then be plotted easily using the
charting functions.
Here we take the same dataset as the previous section, but try to summarize the area by class over a much larger region. We use a Grouped Reducer to compute the class areas and post-process the result into a FeatureCollection. If you find the code hard to understand, please review our article on Calculating Area in Google Earth Engine for explanation.
We set the chart type to PieChart
and plot the
percentage of area of each class in the region.
Below is the list of new styling options applied to the pie chart:
pieSliceBorderColor
: Sets the edge color of each pie
slice.pieSliceTextStyle
: Sets the text style of pie slice
labels.pieSliceText
: Sets the format of the text.sliceVisibilityThreshold
: Sets the threshold below
which to group small slices into others category.Pie Chart
// Select a region
var geometry = ee.Geometry.Point([77.6045, 12.8992]);
// We use the ESA WorldCover 2021 dataset
var worldcover = ee.ImageCollection('ESA/WorldCover/v200').first();
// The image has 11 classes
// Remap the class values to have continuous values
// from 0 to 10
var classified = worldcover.remap(
10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100],
[0, 1 , 2, 3, 4, 5, 6, 7, 8, 9, 10]).rename('classification');
[
// Define a list of class names
var worldCoverClassNames= [
'Tree Cover', 'Shrubland', 'Grassland', 'Cropland', 'Built-up',
'Bare / sparse Vegetation', 'Snow and Ice',
'Permanent Water Bodies', 'Herbaceous Wetland',
'Mangroves', 'Moss and Lichen'];
// Define a list of class colors
var worldCoverPalette = [
'006400', 'ffbb22', 'ffff4c', 'f096ff', 'fa0000',
'b4b4b4', 'f0f0f0', '0064c8', '0096a0', '00cf75',
'fae6a0'];
// We define a dictionary with class names
var classNames = ee.Dictionary.fromLists(
'0','1','2','3','4','5','6','7','8','9', '10'],
[
worldCoverClassNames;
)// We define a dictionary with class colors
var classColors = ee.Dictionary.fromLists(
'0','1','2','3','4','5','6','7','8','9', '10'],
[
worldCoverPalette;
)var visParams = {min:0, max:10, palette: worldCoverPalette};
Map.addLayer(classified, visParams, 'Landcover');
// We want to compute the class areas in a buffer zone
var bufferDistance = 50000;
var buffer = geometry.buffer(bufferDistance);
Map.centerObject(buffer, 12);
Map.addLayer(buffer, {color: 'gray'}, 'Buffer Zone');
// Create an area image and convert to Hectares
var areaImage = ee.Image.pixelArea().divide(1e5);
// Add the band containing classes
var areaImageWithClass = areaImage.addBands(classified);
// As charting functions do not work on more than
// 10000000 pixels, we need to extract the areas using
// a reducer and create a FeatureCollection first
// Use a Grouped Reducer to calculate areas
var areas = areaImageWithClass.reduceRegion({
reducer: ee.Reducer.sum().group({
groupField: 1,
groupName: 'classification',
,
})geometry: buffer,
scale: 10,
maxPixels: 1e10
;
})
var classAreas = ee.List(areas.get('groups'))
// Process results to extract the areas and
// create a FeatureCollection
var classAreasList = classAreas.map(function(item) {
var areaDict = ee.Dictionary(item)
var classNumber = ee.Number(areaDict.get('classification')).format();
var area = ee.Number(
.get('sum'))
areaDictreturn ee.List([classNumber, area])
})
var classAreasDict = ee.Dictionary(classAreasList.flatten());
var classList = classAreasDict.keys();
var classAreaFc = ee.FeatureCollection(classList.map(function(classNumber) {
var classArea = classAreasDict.get(classNumber);
var className = classNames.get(classNumber);
var classColor = classColors.get(classNumber);
return ee.Feature(null, {
'class': classNumber,
'class_name': className,
'Area': classArea,
'color': classColor
;
});
}))print('Class Area (FeatureCollection)', classAreaFc)
// We can now chart the resulting FeatureCollection
// If your area is large, it is advisable to first Export
// the featurecolleciton as an Asset and import it once
// the export is finished.
var chart = ui.Chart.feature.byFeature({
features: classAreaFc,
xProperty: 'class_name',
yProperties: ['Area']
.setChartType('PieChart')
}).setOptions({
title: 'Area by class',
})
print(chart);
// The pie colors do not match the class colors
// We need to create a list of colors for
// all the classes present in the FeatureCollection
var colors = classAreaFc.aggregate_array('color');
print(colors);
// The variable 'colors' is a server-side object
// Use evaluate() to convert it to client-side
// and use the results in the chart
.evaluate(function(colorlist) {
colors// Let's create a Pie Chart
var areaChart = ui.Chart.feature.byFeature({
features: classAreaFc,
xProperty: 'class_name',
yProperties: ['Area']
.setChartType('PieChart')
}).setOptions({
title: 'Area by class',
colors: colorlist,
pieSliceBorderColor: '#fafafa',
pieSliceTextStyle: {'color': '#252525'},
pieSliceText: 'percentage',
sliceVisibilityThreshold: .10
;
})print(areaChart);
})
/ Exercise
// a) Delete the 'geometry' and add a new point at your chosen location
// b) Modify the chart options to show one of the slices separated from the pie.
// c) Print the chart.
// Hint: Use the 'offset' property
// https://developers.google.com/chart/interactive/docs/gallery/piechart#exploding-a-slice
Exercise 04c
In the previous example, we used the
ui.Chart.feature.byFeature()
function to create a plot from
the properties of each feature. There are fewer built-in functions to
create different plots from FeatureCollections, but we can always use
the GEE API to process our data and create a FeatureCollection to meet
our requirements.
Here we take the WRI Global Power Plant Database and create a plot
showing total installed capacity by fuel type for the chosen country.
The FeatureCollection has one feature for each power plant, so we first
need to process the collection to create one feature for each fuel type
having a property with the total capacity. We use the a Grouped
Reducer with the reduceColumns()
function to calculate
group statistics on a FeatureCollection.
We then use the ui.Chart.feature.byFeature()
function to
create a Bar Chart.
Google Charts uses the term Column Chart for a vertical bar chart, while the term Bar Chart is used for a horizonal bar chart.
Below is the list of new styling options applied to the column chart:
backgroundColor
: Sets the background color for the
whole chart.Column Chart
// Use the WRI Global Power Plant Database
var table = ee.FeatureCollection('projects/sat-io/open-datasets/global_power_plant_DB_1-3');
// Select features for a country
var country = 'Germany';
var filtered = table
.filter(ee.Filter.eq('country_long', country));
print(filtered.first());
// We want to calculate total installed capacity
// by each fuel type
// We use a Grouped Reducer to sum 'capacity_mw'
// values grouped by 'primary_fuel'
var stats = filtered.reduceColumns({
selectors: ['capacity_mw', 'primary_fuel'],
reducer: ee.Reducer.sum().setOutputs(['capacity_mw']).group({
groupField: 1,
groupName: 'primary_fuel',
});
})
// Post-process the result into a FeatureCollection
var groupStats = ee.List(stats.get('groups'));
var groupFc = ee.FeatureCollection(groupStats.map(function(item) {
return ee.Feature(null, item);
;
}))
// Create a chart
var chart = ui.Chart.feature.byFeature({
features: groupFc,
xProperty: 'primary_fuel',
yProperties: ['capacity_mw']
.setChartType('ColumnChart')
}).setOptions({
title: 'Installed Power Generation Capacity by Fuel Type for ' + country,
vAxis: {
title: 'Capacity (MW)',
format: 'short'
,
}hAxis: {
title: 'Type of Fuel'},
backgroundColor: '#feedde',
colors: ['#d94801'],
legend: { position: 'none' },
;
})print(chart);
// a) Change the country name to your chosen country
// b) Sort the groupFc by 'capacity_mw' property so the bars are plotted
// from largest to smallest values
// c) Print the chart
// Hint: Use the .sort() function
Exercise 02c
The workshop material (text, images, presentation, videos) is licensed under a Creative Commons Attribution 4.0 International License.
The code (scripts, Jupyter notebooks) is licensed under the MIT License. For a copy, see https://opensource.org/licenses/MIT
You are free to re-use and adapt the material but are required to give appropriate credit to the original author as below:
Copyright © 2023 Ujaval Gandhi www.spatialthoughts.com
You can cite the course materials as follows