Satellite Data Viewer is a hybrid React app with client-side viewing and optional serverless backend for downloads. Queries Microsoft Planetary Computer's STAC API and renders COG tiles via TiTiler. Built on a config-driven pattern with centralized collection metadata.
flowchart TD
User([User Browser]) --> SearchBar[SearchBar Component]
SearchBar --> AppState[App State Manager]
subgraph Configuration
Config[collections.js]
end
subgraph Data Layer
AppState --> StacApi[stacApi.js]
StacApi --> Config
end
subgraph External Services
StacApi --> STAC[Microsoft STAC API]
MapComponent --> TiTiler[Microsoft TiTiler]
end
STAC --> Results[Search Results]
Results --> AppState
AppState --> ImageList[ImageList Component]
ImageList --> Selection[User Selection]
Selection --> AppState
AppState --> MapComponent[MapLeaflet Component]
Config --> MapComponent
TiTiler --> Tiles[COG Tiles]
Tiles --> Display([Rendered Map])
Display --> User
sat-data-viewer/
├── src/
│ ├── components/
│ │ ├── MapLeaflet.jsx # Leaflet map with tile overlays and measurements
│ │ ├── Map.css # Map styling, legends, and controls
│ │ ├── SearchBar.jsx # Search form with collection selector
│ │ ├── SearchBar.css # Search bar styling
│ │ ├── ImageList.jsx # Results list with band selectors
│ │ ├── ImageList.css # Image list and band button styling
│ │ └── DownloadModal.jsx # Download modal with Turnstile verification
│ ├── config/
│ │ └── collections.js # Centralized satellite collection config
│ ├── utils/
│ │ ├── stacApi.js # STAC API integration & formatting
│ │ └── downloadApi.js # Backend download API client
│ ├── App.jsx # Main application & state management
│ ├── App.css # App layout with Earth background
│ ├── main.jsx # React entry point
│ └── index.css # Global styles and CSS variables
├── public/
│ └── earth_clean.png # Background image
├── vite.config.js # Vite configuration
├── package.json # Dependencies
├── README.md
├── ARCHITECTURE.md # This file
├── LICENSE.md
└── CONTRIBUTING.md
Main state container. Manages search results, selected images, band selections, and coordinate ranges. Orchestrates data flow between child components.
Collection selector and search parameters. Dynamically shows/hides filters based on collection type (cloud cover for optical, date range for temporal data, etc.). Reads from collections.js config.
Displays search results with thumbnails and metadata. Band selector layout is driven by config. Handles image selection and band switching.
Leaflet map with COG tile rendering, legends, measurement tools, and base layer switching. Uses buildTileUrl() from config to construct TiTiler URLs. Measurement tools use leaflet-draw + Turf.js for geodesic calculations.
Download modal with band selection and Cloudflare Turnstile bot protection. Communicates with serverless backend (AWS Lambda) to process and download full-resolution GeoTIFF files. Files auto-delete after 10 minutes.
Single source of truth for all satellite collections. Defines bands, rescale values, colormaps, legends, filters, and metadata display options. Components read this config instead of hardcoding collection logic.
Handles STAC API queries. Reads collection config to determine which filters to include (datetime, cloud cover). Formats raw STAC items for app consumption.
Backend API client for downloads. Sends requests to AWS Lambda with Turnstile tokens. Handles file downloads and error states.
App (state management)
├── SearchBar (collection selector, date range, filters, sliders)
├── ImageList (results with band selectors, metadata, action buttons)
├── MapLeaflet (Leaflet map with COG tile overlays, legends, measurements)
└── DownloadModal (download UI with Turnstile, band selection)
- User clicks "Set Search Point" → Map places blue rectangle
- User selects collection and parameters → SearchBar controls
- User submits search → App queries STAC API with collection filter
- Results returned → App formats items and updates ImageList
- User selects images and bands → App passes to MapLeaflet
- Map renders COG tiles → TiTiler serves tiles with band/colormap parameters
- User adjusts ranges → Map re-renders with new rescale values
- User clicks info button → Map displays STAC metadata overlay
- User clicks download → DownloadModal opens with Turnstile verification
- User completes Turnstile and submits → Backend processes and returns GeoTIFF
- File auto-deleted from S3 after 10 minutes
The app queries different collections based on user selection. Example for Sentinel-2:
POST https://planetarycomputer.microsoft.com/api/stac/v1/search
{
"collections": ["sentinel-2-l2a"],
"bbox": [minLon, minLat, maxLon, maxLat],
"datetime": "2024-01-01/2024-12-31",
"query": { "eo:cloud_cover": { "lt": 20 } },
"limit": 10
}Collection values: sentinel-2-l2a, landsat-c2-l2, sentinel-1-rtc, modis-13Q1-061, cop-dem-glo-30
Response is a GeoJSON FeatureCollection with STAC items.
TiTiler URLs are built from config:
https://planetarycomputer.microsoft.com/api/data/v1/item/tiles/
WebMercatorQuad/{z}/{x}/{y}@1x?
collection={collection}&
item={item_id}&
assets={band}&
rescale={min},{max}&
colormap_name={colormap}
Visual (TCI) bands have no rescale/colormap. Single bands (NIR, SWIR, etc.) use rescale and colormap from config.
Edit src/config/collections.js:
'new-collection-id': {
name: 'Collection Name',
displayName: 'Dropdown Label',
type: 'optical',
hasCloudFilter: true,
hasDateFilter: true,
defaultBand: 'visual',
bands: {
'band-id': {
label: 'Button Label',
assets: ['asset-name'],
rescale: '0,4000',
colormap: 'inferno',
legend: { title: 'Title', min: '0', max: '4000', gradient: '...' }
}
},
bandLayout: 'grid',
metadata: { showCloudCover: true, showTileId: true }
}The UI automatically updates. No component code changes needed.
- React 19 + Vite 7.2.4
- Leaflet + leaflet-draw
- Turf.js (@turf/area, @turf/length)
- Axios 1.13.4
- Cloudflare Turnstile (for bot protection)
- CSS custom properties for theming
Backend (serverless):
- AWS Lambda (FastAPI + Mangum)
- See sat-data-viewer-backend repository