|
6 | 6 |
|
7 | 7 | Live weather on a deck.gl map, served almost entirely from free tiers: the whole bill is thirteen dollars a year of vanity domain plus a few cents a month for Route 53 and SES email. |
8 | 8 |
|
9 | | -OpenStreetMap basemap tiles come from [martin](https://github.com/maplibre/martin) running **inside AWS Lambda**, reading [PMTiles](https://docs.protomaps.com/pmtiles/) extracts straight from a private S3 bucket. A scheduled Rust lambda ([cargo-lambda](https://www.cargo-lambda.info/)) snapshots US-wide NWS alerts and decodes NOAA GFS GRIB2 straight from NOAA's open-data S3 bucket into global 2 m temperatures (a planet-wide lattice when zoomed out, per-city point forecasts when zoomed in), animated global wind, and a global precipitation forecast (GFS composite reflectivity) — city list from GeoNames. Precipitation shows the live RainViewer global radar composite (IEM NEXRAD as fallback) while the timeline is parked at *now*, then swaps to the GFS forecast raster as you scrub forward, so the whole map moves on one timeline. The web app is React + deck.gl + MapLibre, served from the same CloudFront distribution as the tiles and weather: one origin, no CORS, hashed assets cached immutable at the edge. Map views mirror into the URL hash, so any view is a link. |
| 9 | +OpenStreetMap basemap tiles come from [martin](https://github.com/maplibre/martin) running **inside AWS Lambda**, reading [PMTiles](https://docs.protomaps.com/pmtiles/) extracts straight from a private S3 bucket. A scheduled Rust lambda ([cargo-lambda](https://www.cargo-lambda.info/)) snapshots US-wide NWS alerts and decodes NOAA GFS GRIB2 straight from NOAA's open-data S3 bucket into global 2 m temperatures (a planet-wide lattice when zoomed out, per-city point forecasts when zoomed in), animated global wind, a global precipitation forecast (GFS composite reflectivity), and a global storm-potential overlay (GFS surface CAPE) — city list from GeoNames. Precipitation shows the live RainViewer global radar composite (IEM NEXRAD as fallback) while the timeline is parked at *now*, then swaps to the GFS forecast raster as you scrub forward, so the whole map moves on one timeline. Storm potential (off by default) shades where the atmosphere is primed for thunderstorms, alongside the live NWS alerts. The web app is React + deck.gl + MapLibre, served from the same CloudFront distribution as the tiles and weather: one origin, no CORS, hashed assets cached immutable at the edge. Map views mirror into the URL hash, so any view is a link. |
10 | 10 |
|
11 | 11 | > **Not an official weather source.** This is a hobby map on shoestring infrastructure: alerts refresh on a schedule, live radar lags by several minutes, the forecast precipitation is coarse 0.25° model output (it smooths over individual cells), zone-based NWS alerts (no polygon geometry) are not shown, and any piece can fail silently with no on-map indication. For decisions involving life or property, use [weather.gov](https://www.weather.gov/) and local emergency guidance. |
12 | 12 |
|
@@ -35,7 +35,7 @@ flowchart LR |
35 | 35 | sched -->|"invokes"| ingest |
36 | 36 | nws --> ingest |
37 | 37 | gfs --> ingest |
38 | | - ingest -->|"PUT JSON + wind/precip PNGs"| bucket |
| 38 | + ingest -->|"PUT JSON + wind/precip/cape PNGs"| bucket |
39 | 39 | ``` |
40 | 40 |
|
41 | 41 | ## What it costs |
@@ -135,19 +135,19 @@ One piece lives outside CloudFormation: the stormdeck.live certificate was reque |
135 | 135 | | Lattice ↔ city temp switch | `GRID_ZOOM_SPLIT` in `web/src/config.ts` | z6.5 | |
136 | 136 | | Map start view | `web/src/config.ts` (URL hash wins) | world, z0 | |
137 | 137 | | World context detail | `WORLD_MAXZOOM` env for `just tiles extract` | z0-6 | |
138 | | -| Schedules | `cdk/lib/stormdeck-stack.ts` | alerts 5 min, temp 6 h, windtex 6 h | |
| 138 | +| Schedules | `cdk/lib/stormdeck-stack.ts` | alerts 5 min; temp / windtex / refc / cape 6 h | |
139 | 139 |
|
140 | | -The `bbox` sets the full-detail basemap region; outside it the map shows the coarse world tiles. Temperature, wind, and the precipitation forecast are global (GFS), so they ignore it. |
| 140 | +The `bbox` sets the full-detail basemap region; outside it the map shows the coarse world tiles. Temperature, wind, the precipitation forecast, and storm potential are global (GFS), so they ignore it. |
141 | 141 |
|
142 | 142 | ## Notes |
143 | 143 |
|
144 | 144 | - **martin-in-Lambda**: martin ≥ v0.14 detects `AWS_LAMBDA_RUNTIME_API` and serves Lambda events natively. The zip is just the upstream `aarch64-musl` binary plus a two-line `bootstrap`. The function URL is IAM-auth; only CloudFront (OAC SigV4) may invoke it. |
145 | 145 | - **No aws-sdk in the ingester**: it only PUTs a handful of small objects, JSON snapshots plus the GFS wind PNGs, so it signs the request itself (SigV4, ~80 lines, test vector included). As of June 2026 the SDK also doesn't compile (aws-runtime 1.7.4 vs aws-smithy-runtime-api 1.12.3 skew). Check back later if you need more S3 surface. |
146 | 146 | - **Zone-based NWS alerts** (no polygon geometry) are dropped; rendering them would mean shipping zone shapefiles. Counted in the lambda logs. |
147 | | -- **GFS straight from GRIB2**: temperature, wind, and the precipitation forecast all come from NOAA GFS with no per-point API metering: the ingester pulls 0.25° UGRD/VGRD/TMP/REFC fields from NOAA's public `noaa-gfs-bdp-pds` S3 bucket and decodes the GRIB2 itself, so one ~0.9 MB field covers the whole planet (1440×721) and any number of points sample for free. One pass writes a whole-planet `lattice.json` (the zoomed-out grid) plus per-city tiles (zoomed in), sampled from the same TMP fields, so the grid costs no extra fetches; the wind u/v PNGs (±40 m/s); and the precipitation PNGs (composite reflectivity, a grayscale dBZ texture over −20…75 dBZ — GFS floors clear sky at ~−20 dBZ, so the web renders that transparent). All carry the model run's snapshot so a new run refetches cleanly, and all share one forecast-hour axis so the timeline scrubs grid, cities, wind, and precipitation together. |
| 147 | +- **GFS straight from GRIB2**: temperature, wind, the precipitation forecast, and storm potential all come from NOAA GFS with no per-point API metering: the ingester pulls 0.25° UGRD/VGRD/TMP/REFC/CAPE fields from NOAA's public `noaa-gfs-bdp-pds` S3 bucket and decodes the GRIB2 itself, so one ~0.9 MB field covers the whole planet (1440×721) and any number of points sample for free. One pass writes a whole-planet `lattice.json` (the zoomed-out grid) plus per-city tiles (zoomed in), sampled from the same TMP fields, so the grid costs no extra fetches; the wind u/v PNGs (±40 m/s); the precipitation PNGs (composite reflectivity, a grayscale dBZ texture over −20…75 dBZ — GFS floors clear sky at ~−20 dBZ, so the web renders that transparent); and the storm-potential PNGs (surface CAPE, a grayscale texture over 0…5000 J/kg, faded out below ~250 J/kg). All carry the model run's snapshot so a new run refetches cleanly, and all share one forecast-hour axis so the timeline scrubs grid, cities, wind, precipitation, and storm potential together. |
148 | 148 |
|
149 | 149 | ## Attribution |
150 | 150 |
|
151 | | -Map data © [OpenStreetMap](https://openstreetmap.org/copyright) contributors, tiles via [Protomaps](https://protomaps.com) builds (ODbL). Radar: [RainViewer](https://www.rainviewer.com/) global composite (free tier, attribution required), falling back to NOAA NEXRAD via the [Iowa Environmental Mesonet](https://mesonet.agron.iastate.edu/). Alerts: [National Weather Service](https://www.weather.gov/) (public domain). Temperatures, wind, and the precipitation forecast (composite reflectivity): [NOAA GFS](https://registry.opendata.aws/noaa-gfs-bdp-pds/) via NOAA Open Data Dissemination (public domain). City list: [GeoNames](https://www.geonames.org/) (CC-BY 4.0). |
| 151 | +Map data © [OpenStreetMap](https://openstreetmap.org/copyright) contributors, tiles via [Protomaps](https://protomaps.com) builds (ODbL). Radar: [RainViewer](https://www.rainviewer.com/) global composite (free tier, attribution required), falling back to NOAA NEXRAD via the [Iowa Environmental Mesonet](https://mesonet.agron.iastate.edu/). Alerts: [National Weather Service](https://www.weather.gov/) (public domain). Temperatures, wind, the precipitation forecast (composite reflectivity), and storm potential (surface CAPE): [NOAA GFS](https://registry.opendata.aws/noaa-gfs-bdp-pds/) via NOAA Open Data Dissemination (public domain). City list: [GeoNames](https://www.geonames.org/) (CC-BY 4.0). |
152 | 152 |
|
153 | 153 | MIT. |
0 commit comments