Spatial Intelligence Atlas for Hazard & Resilience Analytics
แผนที่ข้อมูลเชิงพื้นที่เพื่อการเฝ้าระวังภัยพิบัติของประเทศไทย A 3D geospatial hazard-intelligence platform for Thailand — real-time flood, rainfall, dam and earthquake monitoring rendered as an interactive provincial digital environment.
thailand disaster-response geospatial webgl three-js react typescript cloudflare-workers durable-objects flood-monitoring earthquake-monitoring digital-twin 3d-visualization open-data
Nakhon Ratchasima in the live 3D viewport — GISTDA satellite flood extent over real terrain, a TMD radar sweep, 49 gauge stations reporting measured levels, and every panel stamped with when its data was fetched.
|
|
The same province on a phone-sized viewport (390 × 844) — the desktop panels collapse into a bottom tab bar and a swipe-up sheet, so the map keeps the whole screen; the timeline, exaggeration switch and source-health dots stay reachable with one thumb.
- About
- Features
- Tech stack
- Architecture
- Monorepo layout
- Getting started
- API
- CI & contributing
- Data sources & attribution
- Project status
- Disclaimer
- License
SIAHRA renders Thailand's 77 provinces as navigable 3D terrain — real elevation, buildings, roads, rivers and land cover — and overlays it with live hazard intelligence: flood extents, river/rainfall telemetry, dam levels, weather radar and earthquake activity. It exists to make the question "is my area at risk right now, and how did we get here?" answerable in a single interactive view instead of scattered across a dozen agency dashboards.
The project follows a clean separation between three data classes: base geospatial data (terrain, buildings, land cover — slow-changing, pre-processed offline), observation data (rainfall, river levels, radar, seismic events — polled continuously from official sources), and derived data (flood extents, alerts, aggregates — computed by SIAHRA's own edge services). Every panel in the UI shows its data's age and source so nothing is presented as more current or more certain than it actually is.
- 🗺️ Interactive 3D provincial terrain — Three.js quadtree-tiled terrain with satellite imagery, building extrusions, vegetation and road networks, streamed progressively as you pan and zoom.
- 🌊 Real-time flood intelligence — GISTDA satellite-derived flood extent overlaid on the terrain, plus live water-level readings from ThaiWater (สสน.) gauge stations with 72-hour history.
- 🌧️ Rainfall & weather radar — TMD radar composites and rainfall telemetry with a scrubbable timeline.
- 🏔️ Dam & reservoir monitoring — storage levels and capacity for reservoirs reporting in the selected province.
- 🌏 Earthquake feed — near real-time seismic events cross-checked across USGS, EMSC and TMD.
- 📡 Live source-health footer — every panel discloses upstream freshness ("updated N minutes ago") and falls back to "unknown" instead of failing silently when a source is down.
- 🔗 Shareable permalinks — camera position, province and layer state are encoded into the URL.
- 📱 Responsive layout — a full desktop control-room view and a condensed mobile sheet for field use.
| Layer | Technology |
|---|---|
| 3D rendering / client | Three.js, React 19, TypeScript, Vite, Tailwind CSS 4 |
| Edge API | Cloudflare Workers, Durable Objects (live state / fan-out, SQLite-backed), R2 (immutable geodata artifacts) |
| ETL / geodata pipeline | GDAL-driven Node/TypeScript scripts (tsx), Turf.js, Copernicus GLO-30 DEM, OpenStreetMap |
| Tooling | npm workspaces monorepo, Wrangler, oxlint |
Rendered from docs/diagrams/architecture.svg with node docs/diagrams/render.mjs — edit the SVG and re-render.
The Worker's scheduled handler polls upstream sources on a cron and keeps each Durable Object's cache warm, so the first browser request after a quiet period never pays a multi-megabyte upstream fetch inline.
SIAHRA/
├── apps/
│ ├── web/ # React + Three.js client (Vite)
│ ├── api/ # Cloudflare Worker — routes, Durable Objects, ingestion
│ └── etl/ # Offline geodata pipeline (terrain/building/feature/land-cover tiles)
├── packages/
│ └── shared-types/ # Types shared between web and api
└── docs/ # Plan, audit & deploy guide
Prerequisites: Node.js 20+, npm 10+.
# Install workspace dependencies
npm install
# Run the API (Wrangler, port 8787) and the web client (Vite, port 5173) together
npm run dev
# ...or independently
npm run dev:api
npm run dev:webTerrain/building tile pyramids are generated by the ETL pipeline and served locally by a Vite middleware:
npm run build:all -w apps/etl # build tiles for all provincesThe SPA and the API are two separately deployed Cloudflare Workers sharing one origin
(siahra-radar.co): siahra-web holds the hostname as a Custom Domain, and siahra-api layers a
/api/* route in front of it — Workers on routes run before the origin Worker. So the browser still
sees a single origin (no CORS), while a UI change and an API change never have to ship together:
npm run deploy:web # build apps/web → dist, deploy siahra-web (static assets only)
npm run deploy:api # deploy siahra-api (Durable Objects, cron, R2) — dist not neededSee docs/deploy.md for the full deploy guide.
The Worker exposes a versioned JSON API under /api/v1:
| Endpoint | Description |
|---|---|
GET /api/v1/health |
Freshness/status of every upstream source |
GET /api/v1/observations |
Live rainfall/water-level observations |
GET /api/v1/earthquakes/recent · /live |
Recent and live-polled seismic events |
GET /api/v1/flood-extent/summary |
Nationwide flood-extent summary |
GET /api/v1/provinces/:code/flood-extent |
Flood extent for one province |
GET /api/v1/provinces/:code/hazards/latest |
Latest hazard snapshot for one province |
GET /api/v1/dams |
Reservoir storage levels |
GET /api/v1/radar/frames · /radar/frame/:ts.png |
Weather radar composite frames |
GET /api/v1/stations/:id/history |
Historical readings for one gauge station |
GET /api/v1/local-authorities · /:id |
National local-authority (อปท.) registry, sourced from DLA — static-reference, baked into the build, not live-polled |
ci.yml runs on every push to main and every pull request:
Lint ─────────────┐
TypeScript ───────┼─ independent, always run → required status checks on main
Build ────────────┘
Detect affected ──┬─ Test (api, apps/api) ┐
├─ Test (web, apps/web) ├─ only the affected workspaces
└─ Test (etl, apps/etl) ┘
└─ Test ─────────────── always reports, gates the legs
- Lint —
oxlintoverapps/web/srcandapps/web/worker - TypeScript —
tsc -b(apps/web),tsc --noEmit(apps/api + itstest/project, apps/etl); the same commands as the pre-push checklist inAGENTS.md - Build — production web build, then
wrangler deploy --dry-runto bundle both Workers against it, plus a guard on the Workers static-asset limits (≤ 20,000 files, ≤ 25 MiB each) - Detect affected — diffs the branch against its merge base and emits the matrix of workspaces whose tests need to run. A change to the lockfile, root
package.json,packages/shared-typesorci.ymlitself marks all three; an unusable diff base does the same, so the failure mode is "run everything", never "run nothing" - Test (name, path) — one vitest run per affected workspace:
apps/apiinworkerd,apps/webandapps/etlas pure modules in node. These names are generated and a leg can be absent, so none of them may ever be a required check - Test — the aggregate gate,
if: always(). It reports pass/fail for whatever the matrix did, including "nothing was affected", which makes it the only test check that is safe to require. It is not required yet — promoting it is an owner action
Two conventions a branch ruleset can't express — a PR that touches UI files must embed a screenshot (or carry the no-screenshot label), and PR titles/descriptions are English-only — used to live in a pr-rules.yml workflow. They are still the rules, but they are now checked locally by the /implement loop instead of burning Actions minutes on every edit to a PR description. pr-image-cleanup.yml / pr-cache-cleanup.yml tidy up screenshot releases and npm caches when a PR closes; dependabot.yml batches minor/patch bumps weekly and opens majors one-by-one.
Rules on main (.github/rulesets/main.json, applied with scripts/apply-branch-rules.sh): no direct pushes, no force-push or deletion, every change via a pull request with Lint, TypeScript and Build green.
Warning
Only jobs that always run may be required checks. A path-filtered job that never triggers leaves the PR waiting forever.
Agent loop: .claude/ holds the working loop this repo is built with — /implement runs a senior-engineer agent, gates it on a QA agent that runs the same commands as CI plus a headless screenshot pass, loops until the verdict is green, syncs the docs, and then asks before opening a PR (a PreToolUse hook makes sure it never opens one on its own). /review-fix addresses a Codex review in a single batch — P1/P2 only — and closes every thread with a reaction, a reply and a resolve. See AGENTS.md.
The diagram is rendered from docs/diagrams/loop-engineering.svg with node docs/diagrams/render.mjs — edit the SVG and re-render.
Social preview / Open Graph image: docs/images/og-image.png (1280 × 640) is rendered from docs/og/og-image.html with node docs/og/render.mjs — edit the HTML, re-render, and upload the PNG in Settings → General → Social preview.
Contributing: branch, open a PR, and — for anything that touches the UI — put a screenshot in the description (drag-and-drop, or scripts/pr-media.sh "$(git branch --show-current)" shot.png from the CLI and paste the Markdown it prints). Delete the branch once merged. AGENTS.md has the full working agreement, including the data-honesty rules every layer must follow.
ข้อมูลจาก สสน. (ThaiWater), กรมอุตุนิยมวิทยา (TMD), USGS, EMSC, Copernicus DEM และ OpenStreetMap
| Layer | Source | License |
|---|---|---|
| Terrain (DEM) | Copernicus GLO-30 | Open, attribution required |
| Land cover | ESA WorldCover | CC BY 4.0 |
| Buildings / roads | OpenStreetMap / Geofabrik Thailand extract | ODbL |
| Rainfall / radar / seismic | Thai Meteorological Department (TMD) | Official API terms |
| River / rainfall telemetry, dams | Hydro-Informatics Institute / ThaiWater (สสน.) | Official API terms |
| Satellite flood extent | GISTDA | Open Data Commons |
| Earthquake cross-check | USGS, EMSC | Public data policy |
| Local-authority (อปท.) registry | Department of Local Administration (DLA) | Open Data Common |
This is an attribution list, not a claim of authorship or endorsement — these agencies supply the data; they did not build or endorse this application. Several upstream layers referenced in docs/SIAHRA-implement-plan.md (e.g. some LDD soil datasets) carry non-commercial or share-alike terms and are deliberately not wired into the live product; verify licensing before adding any new source.
SIAHRA is deployed on siahra-radar.co as two independently released Cloudflare Workers — siahra-web (the static SPA and tile proxy) and siahra-api (bound to /api/*). The hazard layers listed in Features above are live: 3D terrain for all 77 provinces, GISTDA flood extent, ThaiWater levels and dams, TMD radar, the earthquake feed and the source-health footer. The Durable-Object-backed API endpoints require a Workers Paid plan — see docs/deploy.md for the deployment prerequisites.
What comes next — the ordered task list, milestones and the work deliberately deferred — is in docs/roadmap.md. docs/SIAHRA-implement-plan.md remains the original research blueprint and data-source inventory; it is not a schedule.
SIAHRA aggregates and visualizes official hazard data; it is not an early-warning system and does not predict earthquakes. Earthquake science can detect and rapidly characterize events after rupture begins — it cannot forecast the time, place or magnitude of a future quake. Flood extents and levels reflect the freshness and resolution of upstream sources (shown next to every panel) and should not be the sole basis for evacuation or safety decisions — always follow official guidance from TMD, DDPM and local authorities.
The code in this repository is released under the MIT License — see LICENSE. That covers the source of the web app, the API Worker and the ETL pipelines, and nothing else.
The data is not ours to license. Every observation, tile and imagery layer SIAHRA fetches or derives stays under the terms of the agency that published it — ThaiWater/HII, TMD, GISTDA, USGS, EMSC, Copernicus DEM, OpenStreetMap, ESA WorldCover and DLA — listed one by one above. MIT applies to the code only; it grants you no right to redistribute those sources, and reusing this code does not transfer their terms to you. If you fork SIAHRA, check each upstream licence for your own use, and keep the attributions.




