Learn by example! This module provides complete, working app templates you can customize for your own projects. Each example demonstrates a different app pattern.
Learning objectives
- Build an NDVI time series app with date selection.
- Create a split-panel comparison viewer.
- Implement a location search with point inspection.
- Combine multiple widgets into a complete application.
Example 1: NDVI Time Series Explorer
Users can select a location and view NDVI trends over time:
// NDVI Time Series App
ui.root.clear();
// Create map
var map = ui.Map();
map.setCenter(-82.3, 29.6, 10);
map.style().set('cursor', 'crosshair');
// Create chart panel
var chartPanel = ui.Panel({style: {width: '400px'}});
// Instructions
var instructions = ui.Label(
'Click anywhere on the map to view NDVI time series for that location.',
{fontSize: '14px', margin: '10px'}
);
// Title
var title = ui.Label('NDVI Time Series Explorer', {
fontWeight: 'bold',
fontSize: '24px',
margin: '10px'
});
// Year selector
var yearSelect = ui.Select({
items: ['2020', '2021', '2022', '2023'],
value: '2023',
style: {margin: '10px'}
});
// Function to create chart
var createChart = function(point, year) {
chartPanel.clear();
chartPanel.add(ui.Label('Loading...'));
var startDate = year + '-01-01';
var endDate = year + '-12-31';
// Get NDVI collection
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.filterDate(startDate, endDate)
.filterBounds(point)
.filter(ee.Filter.lt('CLOUD_COVER', 30))
.map(function(img) {
var ndvi = img.multiply(0.0000275).add(-0.2)
.normalizedDifference(['SR_B5', 'SR_B4'])
.rename('NDVI');
return ndvi.set('system:time_start', img.get('system:time_start'));
});
// Create chart
var chart = ui.Chart.image.series({
imageCollection: collection,
region: point.buffer(30),
reducer: ee.Reducer.mean(),
scale: 30
}).setOptions({
title: 'NDVI Time Series - ' + year,
vAxis: {title: 'NDVI', viewWindow: {min: 0, max: 1}},
hAxis: {title: 'Date'},
lineWidth: 2,
pointSize: 4
});
chartPanel.clear();
chartPanel.add(title);
chartPanel.add(yearSelect);
chartPanel.add(instructions);
chartPanel.add(chart);
};
// Map click handler
map.onClick(function(coords) {
var point = ee.Geometry.Point([coords.lon, coords.lat]);
map.layers().reset();
map.addLayer(point, {color: 'red'}, 'Selected Location');
createChart(point, yearSelect.getValue());
});
// Year change handler
yearSelect.onChange(function(year) {
var layers = map.layers();
if (layers.length() > 0) {
var layer = layers.get(0);
var point = ee.Geometry(layer.getEeObject());
createChart(point, year);
}
});
// Initial panel content
chartPanel.add(title);
chartPanel.add(yearSelect);
chartPanel.add(instructions);
// Layout
ui.root.add(ui.SplitPanel(chartPanel, map));
What you should see
An app with a sidebar showing a year selector. Click anywhere on the map to generate an NDVI time series chart for that location.
Example 2: Before/After Comparison
Split-screen comparison with swipe functionality:
// Split-Map Comparison App
ui.root.clear();
// Create two maps
var leftMap = ui.Map();
var rightMap = ui.Map();
// Link the maps
var linker = ui.Map.Linker([leftMap, rightMap]);
leftMap.setCenter(-82.3, 29.6, 11);
// Load images for different years
var image2015 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.filterDate('2015-06-01', '2015-08-31')
.filterBounds(ee.Geometry.Point([-82.3, 29.6]))
.median()
.multiply(0.0000275).add(-0.2);
var image2023 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.filterDate('2023-06-01', '2023-08-31')
.filterBounds(ee.Geometry.Point([-82.3, 29.6]))
.median()
.multiply(0.0000275).add(-0.2);
// Visualization
var vis = {bands: ['SR_B4', 'SR_B3', 'SR_B2'], min: 0, max: 0.3};
// Add layers
leftMap.addLayer(image2015, vis, 'Summer 2015');
rightMap.addLayer(image2023, vis, 'Summer 2023');
// Add labels
leftMap.add(ui.Label('2015', {
position: 'top-center',
fontSize: '20px',
fontWeight: 'bold',
backgroundColor: 'rgba(255,255,255,0.8)'
}));
rightMap.add(ui.Label('2023', {
position: 'top-center',
fontSize: '20px',
fontWeight: 'bold',
backgroundColor: 'rgba(255,255,255,0.8)'
}));
// Create split panel with wipe
var splitPanel = ui.SplitPanel({
firstPanel: leftMap,
secondPanel: rightMap,
orientation: 'horizontal',
wipe: true // Enable swipe!
});
ui.root.add(splitPanel);
What you should see
A swipeable split view comparing 2015 and 2023 imagery. Drag the divider left/right to compare the same location across time.
Example 3: Layer Toggle Dashboard
Multiple layers with visibility controls:
// Layer Toggle Dashboard
ui.root.clear();
var map = ui.Map();
map.setCenter(-82.3, 29.6, 10);
// Load and prepare data
var image = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.filterDate('2023-06-01', '2023-08-31')
.filterBounds(ee.Geometry.Point([-82.3, 29.6]))
.median()
.multiply(0.0000275).add(-0.2);
var ndvi = image.normalizedDifference(['SR_B5', 'SR_B4']);
var ndwi = image.normalizedDifference(['SR_B3', 'SR_B5']);
// Add layers (initially hidden except first)
map.addLayer(image, {bands: ['SR_B4','SR_B3','SR_B2'], min:0, max:0.3}, 'True Color', true);
map.addLayer(image, {bands: ['SR_B5','SR_B4','SR_B3'], min:0, max:0.4}, 'False Color', false);
map.addLayer(ndvi, {min: 0, max: 0.8, palette: ['brown','yellow','green']}, 'NDVI', false);
map.addLayer(ndwi, {min: -0.5, max: 0.5, palette: ['brown','white','blue']}, 'NDWI', false);
// Create layer controls
var makeCheckbox = function(label, index) {
var isVisible = index === 0;
var checkbox = ui.Checkbox(label, isVisible);
checkbox.onChange(function(checked) {
map.layers().get(index).setShown(checked);
});
return checkbox;
};
// Build control panel
var controlPanel = ui.Panel({
widgets: [
ui.Label('Landsat 8 Layer Dashboard', {fontWeight: 'bold', fontSize: '20px'}),
ui.Label('Toggle layers on/off:', {fontSize: '14px'}),
makeCheckbox('True Color', 0),
makeCheckbox('False Color', 1),
makeCheckbox('NDVI', 2),
makeCheckbox('NDWI', 3),
ui.Label(''),
ui.Label('Tip: Compare NDVI and NDWI to distinguish vegetation from water.',
{fontSize: '12px', color: 'gray'})
],
style: {width: '250px', padding: '15px'}
});
ui.root.add(ui.SplitPanel(controlPanel, map));
Example 4: Location Search with Inspector
Search by coordinates and inspect pixel values:
// Location Search + Inspector App
ui.root.clear();
var map = ui.Map();
map.style().set('cursor', 'crosshair');
// Load NDVI layer
var ndvi = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.filterDate('2023-06-01', '2023-08-31')
.median()
.multiply(0.0000275).add(-0.2)
.normalizedDifference(['SR_B5', 'SR_B4'])
.rename('NDVI');
map.addLayer(ndvi, {min: 0, max: 0.8, palette: ['brown','yellow','green']}, 'NDVI');
map.setCenter(-82.3, 29.6, 8);
// Create input fields
var lonInput = ui.Textbox({placeholder: 'Longitude (e.g., -82.3)'});
var latInput = ui.Textbox({placeholder: 'Latitude (e.g., 29.6)'});
// Result label
var resultLabel = ui.Label('Click map or enter coordinates to inspect NDVI');
// Go to location function
var goToLocation = function() {
var lon = parseFloat(lonInput.getValue());
var lat = parseFloat(latInput.getValue());
if (!isNaN(lon) && !isNaN(lat)) {
var point = ee.Geometry.Point([lon, lat]);
map.centerObject(point, 12);
inspectPoint(point, lon, lat);
}
};
// Inspect point function
var inspectPoint = function(point, lon, lat) {
resultLabel.setValue('Loading...');
ndvi.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: point.buffer(30),
scale: 30
}).get('NDVI').evaluate(function(value) {
if (value !== null) {
resultLabel.setValue(
'Location: ' + lon.toFixed(4) + ', ' + lat.toFixed(4) +
'\nNDVI: ' + value.toFixed(4)
);
} else {
resultLabel.setValue('No data at this location');
}
});
// Add marker
map.layers().set(1, ui.Map.Layer(point, {color: 'red'}, 'Selected'));
};
// Map click handler
map.onClick(function(coords) {
var point = ee.Geometry.Point([coords.lon, coords.lat]);
lonInput.setValue(coords.lon.toFixed(4));
latInput.setValue(coords.lat.toFixed(4));
inspectPoint(point, coords.lon, coords.lat);
});
// Build panel
var panel = ui.Panel({
widgets: [
ui.Label('NDVI Point Inspector', {fontWeight: 'bold', fontSize: '20px'}),
ui.Label('Enter coordinates:'),
lonInput,
latInput,
ui.Button('Go to Location', goToLocation),
ui.Label(''),
resultLabel
],
style: {width: '280px', padding: '15px'}
});
ui.root.add(ui.SplitPanel(panel, map));
Pro tips
- Start simple: Get core functionality working before adding features.
- Use evaluate(): For displaying server values in UI, use
.evaluate(callback). - Loading indicators: Show "Loading..." while async operations run.
- Error handling: Check for null values in callbacks.
- Mobile testing: Check layout on narrow screens before publishing.
Try it: Customize an example
- Take Example 3 (Layer Toggle) and add an NDBI (Built-up Index) layer.
- Modify Example 1 to show a different spectral index.
- Combine elements from multiple examples into your own app.
Common mistakes
- Forgetting to use
.evaluate()for async server values. - Not providing feedback when operations are loading.
- Making panels too wide for comfortable map viewing.
- Not testing the full workflow before publishing.
Quick self-check
- How do you create a split-screen comparison with swipe?
- What callback pattern gets server values into the UI?
- How do you show/hide map layers programmatically?