terra-lens: because Europe launches satellites and nobody uses them

In Auvergne, I wanted to track water bodies across the Puy-de-Dome over several years. Field surveys, when they exist, happen once a year, maybe twice. Not enough to see a trend.

The ESA's Copernicus programme operates Sentinel-2, a constellation that passes over every point on the globe every five days -- thirteen spectral bands, ten-metre resolution, all under an open European licence. It's accessible to anyone with a free account and some patience for the API.

I wrote a pipeline to download images via the Copernicus CDSE API, clip to the study area, and compute spectral indices that make water stand out. NDWI, MNDWI, AWEI -- ratios between bands that exploit the fact that water, vegetation, and soil reflect light very differently depending on wavelength.

What water looks like from space

A satellite image isn't a photograph. Sentinel-2 captures thirteen bands, from visible to shortwave infrared, and each surface type has its own spectral signature. Water absorbs heavily in the near-infrared and SWIR -- where vegetation, on the other hand, reflects strongly. This difference is what makes spectral indices possible.

NDWI, for example, is the ratio (Green - NIR) / (Green + NIR). A water pixel comes out positive, a forest pixel comes out negative. Simple in theory, except that clouds also respond in the visible range, bare soil has its own signature, and shadows throw everything off. MNDWI replaces NIR with SWIR1 to better separate water from built-up surfaces. AWEI weights four different bands to reduce noise. You stack indices because none of them works alone in every case.

From hydrology to generic analysis

While writing all this, something became obvious fairly quickly: the same bands feed pretty much every type of remote sensing analysis. NDVI for vegetation, NBR for burn scars, NDBI for urban areas, NDSI for snow -- it's always ratios between bands, algebra on pixel matrices.

Rather than locking the code to hydrology, I set up a profile system:

hydrology   -> NDWI, MNDWI, AWEI, NDMI, NDVI  -> water bodies, wetlands
urban       -> NDBI, BSI, MNDWI, NDVI          -> built-up surfaces
fire        -> NBR, BSI, NDVI                   -> burn areas
vegetation  -> NDVI, SAVI, EVI, NDRE, NDMI     -> vegetation health
snow        -> NDSI, NDVI, NDWI                 -> snow cover
conflict    -> NBR, NDVI, NDBI, BSI, TAI, IOR  -> structural damage

A profile is a list of indices and detection thresholds. Pick one, the pipeline computes what it needs and ignores the rest. Twelve indices in total, all computable from Sentinel-2 standard bands.

The twelve indices

Index Formula Detects
NDVI (NIR-Red)/(NIR+Red) Vegetation density
NDWI (Green-NIR)/(Green+NIR) Water
MNDWI (Green-SWIR1)/(Green+SWIR1) Water (modified)
NDMI (NIR-SWIR1)/(NIR+SWIR1) Moisture content
AWEI 4(Green-SWIR1)-(0.25NIR+2.75SWIR2) Automated water extraction
SAVI ((NIR-Red)/(NIR+Red+L))(1+L) Soil-adjusted vegetation
EVI 2.5((NIR-Red)/(NIR+6Red-7.5Blue+1)) Enhanced vegetation
NDRE (NIR-RedEdge)/(NIR+RedEdge) Crop stress
NDBI (SWIR1-NIR)/(SWIR1+NIR) Urban / built-up
BSI ((SWIR1+Red)-(NIR+Blue))/((SWIR1+Red)+(NIR+Blue)) Bare soil
NBR (NIR-SWIR2)/(NIR+SWIR2) Burn scars
NDSI (Green-SWIR1)/(Green+SWIR1) Snow and ice

Most of these indices date back to the 1980s-90s. What's less common is having all of them in a single pipeline that also handles downloading, spatial clipping, cloud masking, and report generation.

Beyond water: conflict damage assessment

The same spectral engine works for entirely different problems. By switching to the conflict profile, the pipeline computes dNBR (differenced Normalized Burn Ratio) between two dates to quantify structural damage. Here's Mariupol, Ukraine -- before and after the 2022 siege, as seen by Sentinel-2:

Drag the slider to compare Mariupol before and after the 2022 siege. The seasonal green-up is visible, but urban and industrial texture has changed dramatically.

dNBR severity overlay -- green = no damage, yellow = low, orange = moderate, red = high severity. Concentrated damage visible around the Azovstal complex and agricultural areas.

Ten-metre resolution won't show individual buildings, but it picks up patterns: areas where the spectral signature shifted between the two acquisition dates. The same approach works for wildfire damage assessment, deforestation monitoring, or tracking construction activity.

The stack

Rasterio and GDAL for raster data, GeoPandas for vector, NumPy for index computation, scikit-image for morphological mask cleanup, Folium for interactive maps, ReportLab for PDFs. The standard Python stack for this kind of work, nothing exotic.

Data comes from Copernicus' CDSE API -- free account, pass your coordinates and time window, get back multi-band GeoTIFF. The pipeline filters by cloud cover, defaulting to reject anything above 10%.

Interactive study area

The pipeline also generates interactive maps. Here's the Puy-de-Dome study area on a Leaflet map -- you can pan, zoom, switch between satellite and topographic base layers, and click the study boundary for summary statistics:

Adapting to another area

The case study shipped with the repo is the Puy-de-Dome. But the system isn't tied to any location -- change the bounding box in the config, drop your GeoJSON boundary in data/raw/, pick a profile, and run. It works on an Auvergne watershed, a German peri-urban zone, or a Portuguese forest after a fire.

The code is on the Marklar GitLab: dev.marklar.systems/jo/terra-lens. MIT.