By the end of this two-hour lab, your AI-EO application will ingest real Sentinel-2 imagery, compute vegetation indices on the fly, and let your AI assistant interpret the results for users.
From static tiles to live satellite intelligence
Your current app shows a basemap. That is like having a telescope pointed at the ceiling. Today we point it at Earth, live.
Add Sentinel-2 true-color, false-color, and NDVI layers to your Leaflet map with live WMS tiles.
Use the Copernicus Process API to request custom analysis (any band combination, any date range, any area).
Give your Gemini-powered assistant a "satellite analysis" tool so it can fetch and interpret real Earth observation data.
/templates/day6-starter/.
Choose the level of control you need
Add satellite layers as standard WMS tiles. Works like any other Leaflet tile layer. Perfect for visualization.
Write custom evalscripts (server-side JavaScript) to compute any index, apply any color ramp, filter by cloud cover.
Access Google Earth Engine's entire catalog and computation engine via REST. Best for time-series analysis at scale.
| Feature | WMS | Process API | GEE REST |
|---|---|---|---|
| Setup time | ~10 min | ~30 min | ~45 min |
| Auth required | API key | OAuth2 token | Service account |
| Custom analysis | Limited (presets) | Full (evalscript) | Full (Python/JS) |
| Time-series | Manual date params | Built-in | Native |
| Data catalog | Sentinel only | Sentinel + Landsat | 1,000+ datasets |
Add instant, free global Sentinel-2 imagery with no configuration
Before configuring complex APIs, students can add beautiful, cloud-free global Sentinel-2 imagery to Leaflet in under 60 seconds. This uses public WMTS tiles served by EOxCloudless, a reliable service that requires zero authentication or API tokens.
// EOxCloudless Sentinel-2 Cloudless global composite
const s2cloudless = L.tileLayer(
'https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2023_3857/default/GoogleMapsCompatible/{z}/{y}/{x}.jpeg',
{
attribution: 'Sentinel-2 cloudless by EOX IT Services GmbH (Contains modified Copernicus Sentinel data)',
maxZoom: 14,
minZoom: 1
}
);
// Add it to your Leaflet map
s2cloudless.addTo(map);
Create a configuration for WMS access
Sentinel Hub (by Sinergise, now part of the Copernicus Data Space Ecosystem) provides a standard OGC WMS endpoint that serves pre-rendered satellite imagery tiles. Your Leaflet map can consume these tiles just like OpenStreetMap tiles.1
Go to dataspace.copernicus.eu and create a free account. You get 30,000 free processing units per month.
In the Dashboard, go to "Configuration Utility" and create a new configuration. Select Sentinel-2 L2A as the data source.
Add three layers: TRUE-COLOR-S2-L2A, NDVI, and FALSE-COLOR. Each uses a predefined evalscript that Sentinel Hub provides.
Your instance ID is the unique string in your WMS URL. It looks like: https://sh.dataspace.copernicus.eu/ogc/wms/YOUR-INSTANCE-ID
.env file excluded by .gitignore.
Three lines of code to see real satellite data
Leaflet's built-in L.tileLayer.wms() method speaks the OGC Web Map Service protocol natively.
All you need is the endpoint URL and the layer name.
// Your Sentinel Hub WMS endpoint
const SH_URL = 'https://sh.dataspace.copernicus.eu/ogc/wms/YOUR-INSTANCE-ID';
// Add a true-color Sentinel-2 layer
const trueColor = L.tileLayer.wms(SH_URL, {
layers: 'TRUE-COLOR-S2-L2A',
format: 'image/png',
transparent: true,
maxcc: 20, // max cloud cover 20%
time: '2026-06-01/2026-06-15',
attribution: 'Sentinel-2 | Copernicus'
});
trueColor.addTo(map);
The maxcc parameter filters scenes by cloud cover percentage. The time parameter accepts ISO 8601 date ranges. Sentinel Hub automatically mosaics multiple scenes within your time window and returns the least-cloudy composite.
Switch between true-color, NDVI, and false-color on the fly
// ββ Sentinel Hub WMS base URL ββ
const SH_URL = 'https://sh.dataspace.copernicus.eu/ogc/wms/YOUR-INSTANCE-ID';
// ββ Shared WMS options ββ
const sharedOpts = {
format: 'image/png',
transparent: true,
maxcc: 20,
time: '2026-06-01/2026-06-15',
attribution: 'Sentinel-2 L2A | Copernicus'
};
// ββ Define overlay layers ββ
const layers = {
'True Color': L.tileLayer.wms(SH_URL, { ...sharedOpts, layers: 'TRUE-COLOR-S2-L2A' }),
'NDVI': L.tileLayer.wms(SH_URL, { ...sharedOpts, layers: 'NDVI' }),
'False Color': L.tileLayer.wms(SH_URL, { ...sharedOpts, layers: 'FALSE-COLOR' })
};
// ββ Add the layer control ββ
L.control.layers(null, layers).addTo(map);
// ββ Start with true color visible ββ
layers['True Color'].addTo(map);
The first argument to L.control.layers() is for base layers (radio buttons);
the second is for overlays (checkboxes). We pass null for base layers because
we keep the OSM basemap separate. Users can toggle satellite overlays on and off independently.
YOUR-INSTANCE-ID with the ID from
your Sentinel Hub dashboard. Pan to your team's study area and toggle between layers.
Why different layers reveal different features
| Layer | Bands Used | What It Shows | Use Case |
|---|---|---|---|
| True Color | B04 (Red), B03 (Green), B02 (Blue) | Natural colors as the human eye sees them | Orientation, urban areas, water bodies |
| False Color | B08 (NIR), B04 (Red), B03 (Green) | Vegetation appears bright red | Vegetation mapping, crop health assessment |
| NDVI | (B08 - B04) / (B08 + B04) | Vegetation index: green = healthy, brown = stressed | Agriculture, deforestation, drought monitoring |
| SWIR | B12, B8A, B04 | Moisture content highlighted | Burn scars, soil moisture, geology |
| NDWI | (B03 - B08) / (B03 + B08) | Water bodies appear bright | Flood mapping, water resource monitoring |
Each "layer" in Sentinel Hub is really a server-side rendering script (called an evalscript) that selects specific bands, applies arithmetic, and maps the result to RGB values. WMS delivers the rendered output as standard image tiles.2
Let users browse historical satellite imagery by date
A static satellite layer is already useful, but the real power of EO comes from temporal comparison. Adding a date picker lets users scrub through time and observe change.
// ββ Date picker for satellite imagery ββ
const datePicker = document.getElementById('sat-date');
datePicker.addEventListener('change', (e) => {
const selectedDate = e.target.value; // e.g. "2026-06-10"
// Create a 5-day window around the selected date
const d = new Date(selectedDate);
const from = new Date(d - 5 * 86400000).toISOString().slice(0, 10);
const to = selectedDate;
// Update WMS time parameter on all layers
Object.values(layers).forEach(layer => {
layer.setParams({ time: `${from}/${to}` });
});
});
// HTML: <input type="date" id="sat-date" value="2026-06-15" />
The setParams() method on a Leaflet WMS layer updates the query parameters
and automatically reloads the tiles. The 5-day window ensures we find a cloud-free acquisition
even in cloudy regions.
Getting a token from the Copernicus Data Space
The Process API requires OAuth2 client credentials authentication. Unlike the WMS instance ID (which is embedded in the URL), OAuth2 uses a proper token exchange flow that is more secure and provides fine-grained access control.3
Authorization headerasync function getAccessToken() {
const response = await fetch(
'https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.SH_CLIENT_ID,
client_secret: process.env.SH_CLIENT_SECRET
})
}
);
const data = await response.json();
return data.access_token; // valid for ~600 seconds
}
Anatomy of a Sentinel Hub Process API call
The Process API is a single POST endpoint that accepts a JSON body with three main sections: input (what data and where), evalscript (what computation to run), and output (what format to return).4
{
"input": {
"bounds": {
"bbox": [7.74, 48.57, 7.78, 48.59], // Strasbourg area
"properties": { "crs": "http://www.opengis.net/def/crs/EPSG/0/4326" }
},
"data": [{
"type": "sentinel-2-l2a",
"dataFilter": {
"timeRange": { "from": "2026-06-01T00:00:00Z", "to": "2026-06-15T00:00:00Z" },
"maxCloudCoverage": 30
}
}]
},
"evalscript": "// ... your custom script (next slide)",
"output": {
"width": 512,
"height": 512,
"responses": [{ "identifier": "default", "format": { "type": "image/png" } }]
}
}
Server-side band math with visualization
An evalscript is a small JavaScript program that runs on Sentinel Hub's servers for each pixel. It receives raw band values and returns RGB (or any custom output). This means all computation happens in the cloud; your app receives only the rendered result.
//VERSION=3
function setup() {
return {
input: ["B04", "B08", "dataMask"],
output: { bands: 4 } // RGBA
};
}
function evaluatePixel(sample) {
let ndvi = (sample.B08 - sample.B04) / (sample.B08 + sample.B04);
// Color ramp: brown (-1 to 0) -> yellow (0 to 0.3) -> green (0.3 to 1)
if (ndvi < 0) return [0.5, 0.2, 0.1, sample.dataMask];
if (ndvi < 0.1) return [0.75, 0.6, 0.2, sample.dataMask];
if (ndvi < 0.3) return [0.9, 0.9, 0.2, sample.dataMask];
if (ndvi < 0.5) return [0.4, 0.8, 0.2, sample.dataMask];
return [0.1, 0.6, 0.1, sample.dataMask]; // deep green
}
setup() function declares which bands to request and the output formatevaluatePixel() runs for every pixel; sample.B08 is the NIR reflectance value (0 to 1)dataMask band is 0 for no-data pixels (outside the scene footprint) and 1 for valid pixelsTrack vegetation change over six months
A single NDVI image is a snapshot. A time series reveals the story: seasonal growth cycles, drought onset, harvest dates, or urban expansion eating into farmland. The Sentinel Hub Statistical API provides aggregated values over an area across multiple dates.5
| Metric | What It Shows | Example Signal |
|---|---|---|
| Mean NDVI | Average greenness of the area | 0.25 in winter, 0.75 in summer |
| NDVI Trend | Direction of change over time | Declining = drought or deforestation |
| Phenology | Seasonal growth curve shape | Double peak = two crop cycles/year |
| Anomalies | Deviation from historical average | Sudden drop = fire, flood, or harvest |
Instead of downloading a full image for each date (expensive), the Statistical API computes aggregate statistics (mean, median, std, percentiles) server-side and returns only the numbers. For a 6-month time series with biweekly sampling, that is 12 small JSON responses instead of 12 high-resolution images.
[0.32, 0.41, 0.55, 0.71, 0.68, 0.45].
Time-series data is the bridge between satellite imagery and language-based AI reasoning.
Complete function to get NDVI values for any location
// ββ NDVI evalscript that returns raw values ββ
const ndviScript = `
//VERSION=3
function setup() {
return { input: ["B04","B08","dataMask"], output: { bands: 1, sampleType: "FLOAT32" } };
}
function evaluatePixel(s) {
if (s.dataMask === 0) return [-9999];
return [(s.B08 - s.B04) / (s.B08 + s.B04)];
}
`;
// ββ Fetch NDVI for a location over a date range ββ
async function getNDVIForLocation(lat, lng, fromDate, toDate) {
const delta = 0.01; // ~1 km box
const bbox = [lng - delta, lat - delta, lng + delta, lat + delta];
const token = await getAccessToken();
const response = await fetch(
'https://sh.dataspace.copernicus.eu/api/v1/process',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
input: {
bounds: { bbox, properties: { crs: 'http://www.opengis.net/def/crs/EPSG/0/4326' } },
data: [{
type: 'sentinel-2-l2a',
dataFilter: {
timeRange: { from: fromDate, to: toDate },
maxCloudCoverage: 30
}
}]
},
evalscript: ndviScript,
output: { width: 64, height: 64,
responses: [{ identifier: 'default',
format: { type: 'image/tiff' } }] }
})
}
);
return response;
}
/api/v1/statistics) which returns
aggregated JSON values directly, avoiding the need to parse raster data in the browser.
Bridging natural language and the Process API via function calling
In Day 5, you gave your Gemini assistant tools like panMap() and addMarker().
Now we add a new category of tools: satellite data retrieval.
When a user asks "How green is this area?", the AI can autonomously call a function to fetch
NDVI data, receive numeric values, and respond with a natural-language interpretation.6
tools array in the Gemini API request you set up on Day 5.
You are simply adding new function declarations.
See Gemini Function Calling docs.
analyzeSatelliteImage ToolDeclaring and implementing the satellite analysis function
// ββ Tool declaration for Gemini ββ
const satelliteTool = {
name: 'analyzeSatelliteImage',
description: `Analyzes satellite imagery for a location. Returns
vegetation index (NDVI), land cover type, and temporal trends.
Use when the user asks about greenness, vegetation health,
land use, or environmental conditions at a specific place.`,
parameters: {
type: 'object',
properties: {
lat: { type: 'number', description: 'Latitude in decimal degrees' },
lng: { type: 'number', description: 'Longitude in decimal degrees' },
analysis: { type: 'string', enum: ['ndvi', 'truecolor', 'moisture'] },
dateRange: {
type: 'object',
properties: {
from: { type: 'string', description: 'ISO date' },
to: { type: 'string', description: 'ISO date' }
}
}
},
required: ['lat', 'lng', 'analysis']
}
};
// ββ Implementation (called when AI invokes the tool) ββ
async function handleSatelliteAnalysis({ lat, lng, analysis, dateRange }) {
const from = dateRange?.from || getDefaultFrom();
const to = dateRange?.to || getDefaultTo();
const ndviData = await getNDVIForLocation(lat, lng, from, to);
const avgNDVI = computeMeanNDVI(ndviData);
return {
location: { lat, lng },
dateRange: { from, to },
meanNDVI: avgNDVI.toFixed(3),
interpretation: interpretNDVI(avgNDVI),
cloudCover: ndviData.cloudPercentage
};
}
Tracing a user query through the full AI-satellite pipeline
The message goes to Gemini along with the list of available tools.
analyzeSatelliteImage({ lat: 40.7829, lng: -73.9654, analysis: "ndvi", dateRange: { from: "2026-06-01", to: "2026-06-15" } })
Fetches NDVI data for a ~2 km box around Central Park. Returns: { meanNDVI: 0.72, cloudCover: 8 }
The function result is injected into the conversation as a tool response message.
"Central Park is very green right now! The average NDVI is 0.72 (based on Sentinel-2 data from June 1-15, 2026), indicating healthy, dense vegetation. Cloud cover was only 8%, so this is a reliable measurement."
For even richer analysis, you can capture a screenshot of the map view
(using html2canvas or the Leaflet image export plugin) and send it to
Gemini Vision alongside the NDVI values. The AI can then describe
spatial patterns it observes in the imagery: "The northern section of the park shows
higher NDVI than the southern section near Columbus Circle."
WMS integration takes ~10 minutes. If your app has a Leaflet map, you can show live Sentinel-2 imagery today.
Custom band math runs on Sentinel Hub's servers. Your browser receives only the rendered result, keeping the app lightweight.
An AI model cannot interpret a raw raster, but it excels at reasoning over a JSON array of NDVI values with timestamps.
By declaring satellite tools in the Gemini API, users can query Earth observation data in plain English.
OAuth2 tokens and API secrets must stay server-side. Never expose credentials in browser-facing code.
Before 16:00 today, implement at least one satellite data integration in your team's project. Choose the path that matches your current progress.
Add a Sentinel-2 true-color layer and an NDVI layer with a toggle control to your Leaflet map.
Implement the getNDVIForLocation() function and display NDVI values when the user clicks the map.
Wire up the analyzeSatelliteImage tool so your AI assistant can answer questions about satellite data.
/templates/day6-starter/. The Sentinel Hub EO Browser is your best friend
for debugging evalscripts.
NASA Mathematician
Her calculations of orbital mechanics were critical to the success of the first and subsequent U.S. crewed spaceflights.
Earth Observation provides a macroscopic view of environmental trends, but its true power lies in downscaling this data to affect local policy and design, such as urban planning and sustainable workplaces.
Your startup needs to establish a new hybrid work hub. You must balance employee commute times, environmental impact (using the IPAT equation), and existing green infrastructure.
Interact with the live map below to explore live satellite overlays over Strasbourg.
What was your biggest takeaway from this session, and how does it apply to the TERRA project? Write your response below. Your instructor will review this to track your progress.