Skip to main content

Sentinel-2 Composites for Any Map View

Pick three bands and a date range, and this canvas builds a 10 m Sentinel-2 composite of the current map view. The view is split into blocks that run on up to 50 machines at once. Each finished block is saved as a COG, so panning and zooming afterwards only stream tiles.

Try it out​

Open the Canvas →

Start small

The dashboard runs on load using the area in view. Start zoomed in on a city; a view already computed with the same settings loads straight from storage.

1 · Dashboard​

A MapLibre map with a control panel:

  • Bands: any three Sentinel-2 bands, with presets for true colour, false colour (nir,red,green) and SWIR (swir22,nir,red).
  • Dates and Quality: date range, max cloud cover, max scenes per block, and the composite method (median, mean, min, max, first).
  • Run composite uses the current map view. Pan or zoom, then run again.

A table below the map lists the matching scenes; Footprints draws their outlines.

s2_scenes queries the tge-labs Sentinel-2 L2A STAC-GeoParquet mirror with DuckDB, filtering by bounding box, date range and cloud cover. It returns one row per scene with a href_<band> COG URL for each band.

curl "https://udf.ai/fc_fused/Sentinel2_Composite/s2_scenes/run/file?format=csv&aoi=-74.05,40.68,-73.90,40.82&date_start=2025-06-01&date_end=2025-09-30&max_cloud=10&limit=5"

3 · Composite blocks​

The dashboard splits the view into zoom-12 tiles of 1024 px each and sends every block to s2_composite_block, up to 50 in parallel. Each call loads its scenes onto the block's grid with odc.stac.load, reduces over time with xarray, and writes the result as a COG:

# doctest: skip
da = ds.to_array("band").where(lambda v: v > 0)
comp = getattr(da, method)("time", skipna=True) # median / mean / min / max

Blocks are stored under fd://s2_composite_blocks/<job>/, where the job ID is a hash of the settings. Re-running the same settings skips blocks that already exist.

4 · Tile server​

s2_composite_tiles serves the stored blocks as XYZ tiles with rio-tiler. It only reads the saved COGs, never the source imagery. Pass job=all for every stored composite, or a job ID for one:

curl -o tile.png "https://udf.ai/fc_fused/Sentinel2_Composite/s2_composite_tiles/run/tiles/12/1206/1539?dtype_out_raster=png&job=all"

About the data​

PropertyValue
ImagerySentinel-2 Collection 1 L2A COGs, AWS Open Data (anonymous)
Scene catalogtge-labs s2-stac-geoparquet on Source Cooperative
Bandsblue … swir22 (B02–B12), any three at a time
Output10 m RGBA COGs, served as XYZ tiles

See also​