Skip to content

Commit 6c97f23

Browse files
committed
feat: vibe it up
0 parents  commit 6c97f23

16 files changed

Lines changed: 3424 additions & 0 deletions

.github/workflows/deploy.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
deploy:
19+
runs-on: ubuntu-latest
20+
environment:
21+
name: github-pages
22+
url: ${{ steps.deployment.outputs.page_url }}
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: 22
29+
cache: npm
30+
31+
- run: npm ci
32+
33+
- run: npm run build
34+
35+
- uses: actions/configure-pages@v5
36+
37+
- uses: actions/upload-pages-artifact@v3
38+
with:
39+
path: dist
40+
41+
- uses: actions/deploy-pages@v4
42+
id: deployment

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

CLAUDE.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Goal
6+
7+
Create a minimal, beautiful map application showcasing [titiler-cmr](https://developmentseed.org/titiler-cmr)'s data rendering capability, hosted on GitHub Pages. The map should cover as much of the browser window as possible, with a default globe view, and expose basic controls like start/end datetime filtering.
8+
9+
Key UX requirements:
10+
- Per-layer minimum zoom enforcement with a visual indicator prompting users to zoom in
11+
- Tile loading indicator (tile fetches can be slow)
12+
- Globe view as default projection
13+
14+
## Tech Stack
15+
16+
- **Build tool:** Vite
17+
- **Language:** TypeScript
18+
- **Mapping:** MapLibre GL JS (supports globe projection natively)
19+
- **Deployment:** GitHub Pages (static site, `gh-pages` branch)
20+
21+
## Development Commands
22+
23+
```bash
24+
# Install dependencies
25+
npm install
26+
27+
# Start dev server
28+
npm run dev
29+
30+
# Build for production (runs tsc then vite build)
31+
npm run build
32+
33+
# Preview production build locally
34+
npm run preview
35+
36+
# Deploy to GitHub Pages (builds then pushes dist/ to gh-pages branch)
37+
npm run deploy
38+
```
39+
40+
## titiler-cmr API Integration
41+
42+
The backend API is documented at https://developmentseed.org/titiler-cmr. Key endpoints used by this app:
43+
44+
- **TileJSON:** `GET /{backend}/tilejson.json` — returns metadata including tile URL template; pass as a `raster-source` in MapLibre
45+
- **Tiles:** `GET /{backend}/tiles/{z}/{x}/{y}` — the actual tile imagery (`rasterio` backend for GeoTIFF/COG, `xarray` for NetCDF/HDF5)
46+
- **Granule search:** `GET /bbox/{minx},{miny},{maxx},{maxy}/granules` — find available granules in a bounding box
47+
48+
Common query parameters for tile requests: `concept_id`, `datetime`, `assets`/`bands`, `rescale`, `colormap_name`.
49+
50+
The hosted titiler-cmr instance URL should be stored in a single config constant (e.g., `src/config.ts`) so it can be easily swapped between environments.
51+
52+
## Architecture
53+
54+
Single-page static site, no server-side code. Globe projection via MapLibre GL JS v5 (`map.setProjection({ type: 'globe' })` called on `load`).
55+
56+
```
57+
src/
58+
config.ts # TITILER_ENDPOINT, DatasetConfig/CollectionConfig/RenderConfig types, DATASETS array
59+
main.ts # Map init, wires controls → layers → zoom-guard → loading
60+
controls.ts # Dataset/Collection/Render selects + date picker; exports getState()
61+
layers.ts # updateLayer(): removes old source/layer, builds TileJSON URL, adds new raster source
62+
loading.ts # Shows #loading spinner on map `dataloading`, hides on `idle`
63+
zoom-guard.ts # Shows #zoom-guard message when zoom < collection.minzoom
64+
style.css # Full-viewport map, absolute-positioned overlay panels
65+
```
66+
67+
### Adding a new dataset
68+
69+
Add a new `DatasetConfig` entry to the `DATASETS` array in `src/config.ts`. No other code changes are needed. Each collection needs: `conceptId`, `assetsRegex`, `backend` (`rasterio` | `xarray`), `minzoom`/`maxzoom`, `defaultDate`, and a `renders` array of `RenderConfig` objects.
70+
71+
### Layer update flow
72+
73+
Controls emit a `ControlState` on any change → `updateLayer()` removes the previous `cmr-layer`/`cmr-source` and adds a new raster source pointing at the TileJSON endpoint with all render params encoded in the URL. MapLibre fetches TileJSON natively when `type: 'raster'` with a `url` field is used.

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# titiler-cmr Showcase
2+
3+
A minimal map application showcasing [titiler-cmr](https://developmentseed.org/titiler-cmr)'s data rendering capability, hosted on GitHub Pages.
4+
5+
## Features
6+
7+
- Globe projection by default (MapLibre GL JS v5)
8+
- Tile loading indicator (tile fetches from titiler-cmr can be slow)
9+
- Per-layer minimum zoom enforcement with a visual prompt to zoom in
10+
- Dataset / collection / render switching with live tile updates
11+
- Date and date-range pickers
12+
- Advanced query parameter controls (cloud cover, orbit direction, etc.)
13+
- Mobile-friendly collapsible controls panel
14+
15+
## Datasets
16+
17+
| Dataset | Collections | Backend |
18+
|---|---|---|
19+
| HLS (Harmonized Landsat Sentinel-2) | HLSL30 (Landsat 8/9), HLSS30 (Sentinel-2) | rasterio |
20+
| NISAR Beta GCOV | NISAR L2 GCOV | xarray |
21+
| MUR Sea Surface Temperature | MUR SST | xarray |
22+
23+
## Development
24+
25+
```bash
26+
npm install
27+
npm run dev # start dev server
28+
npm run build # TypeScript check + Vite production build
29+
npm run preview # preview production build locally
30+
npm run deploy # build and push dist/ to gh-pages branch
31+
```
32+
33+
## Architecture
34+
35+
Single-page static site — no server-side code.
36+
37+
```
38+
src/
39+
config.ts # TITILER_ENDPOINT, type definitions, DATASETS array
40+
main.ts # Map init; wires controls → layers → zoom-guard → loading
41+
controls.ts # Dataset/collection/render selects, date picker, extra params
42+
layers.ts # updateLayer(): removes old source/layer, builds TileJSON URL, adds raster source
43+
loading.ts # Shows spinner on map dataloading, hides on idle
44+
zoom-guard.ts # Shows message when zoom < collection.minzoom
45+
style.css # Full-viewport map, absolute-positioned overlay panels
46+
```
47+
48+
The titiler-cmr endpoint is configured in `src/config.ts` as `TITILER_ENDPOINT`. Swap this value to point at a different environment.
49+
50+
## Adding a Dataset
51+
52+
Add a new `DatasetConfig` entry to the `DATASETS` array in `src/config.ts`. No other code changes are needed.
53+
54+
Each `CollectionConfig` requires:
55+
56+
| Field | Description |
57+
|---|---|
58+
| `collectionConceptId` | NASA CMR collection concept ID |
59+
| `backend` | `rasterio` (GeoTIFF/COG) or `xarray` (NetCDF/HDF5) |
60+
| `minzoom` / `maxzoom` | Zoom range for tile requests |
61+
| `date` | `{ mode: "single", default: "YYYY-MM-DD" }` or `{ mode: "range", default: ["YYYY-MM-DD", "YYYY-MM-DD"] }` |
62+
| `renders` | Array of `RenderConfig` objects (label, assets/variables, query params) |
63+
| `queryParams` | Optional extra controls: `range`, `select`, `text`, or `attribute` |
64+
65+
A dataset with a single collection hides the collection selector in the UI. A dataset with multiple collections shows it.
66+
67+
## Layer Update Flow
68+
69+
1. Any control change calls `updateLayer(map, state)`.
70+
2. All previous CMR sources/layers (`cmr-0``cmr-7`) are removed.
71+
3. A TileJSON URL is built from the current state and added as a MapLibre `raster` source. MapLibre fetches the TileJSON natively and renders the tiles.
72+
4. Renders that define `subLayers` produce one map layer per sub-layer (used by NISAR to switch between frequency-A and frequency-B grids based on zoom level).

index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>titiler-cmr showcase</title>
7+
</head>
8+
<body>
9+
<div id="map"></div>
10+
11+
<button id="controls-toggle" aria-label="Toggle controls" aria-expanded="false"></button>
12+
<div id="controls"></div>
13+
14+
<div id="zoom-guard">Zoom in further to see data</div>
15+
16+
<div id="loading">
17+
<div class="spinner"></div>
18+
Loading tiles&hellip;
19+
</div>
20+
21+
<script type="module" src="/src/main.ts"></script>
22+
</body>
23+
</html>

0 commit comments

Comments
 (0)