The auto-framing fitted the view to whatever warning polygons were active, so a storm a few counties away pulled the frame out to cover it and the configured area shrank to an unreadable corner of a half-state view. On signage that is read at a glance, a map that wanders is worse than one that shows less. Framing is now centred and bounded: - The map never pans. The centre stays on the configured point and only the zoom responds, because the box handed to fitBounds is symmetric about home. - Zoom-out is capped at `max_counties` (default 2) county-widths in every direction, with longitude scaled by cos(lat) so the budget is the same distance on the ground north and south. - Warnings entirely outside that box are not chased at all; the configured view is held. Warnings clearing returns to it rather than staying parked on the last storm. - A floor on the frame keeps one small cell overhead from zooming to street level, and fit padding drops to 24px, which on a PiP-sized overlay was discarding a third of the width per side. The overlay assets are served max-age=14400, so a player that had already loaded them kept the old copy for four hours and silently ignored a redeploy. The page URL and its script tag now carry a version, documented to be bumped together. Tests cover the invariants against the shipped frameFor source rather than a copy of it: centred after reframing, capped at the county budget, small cells floored, distant storms not chased.
5.4 KiB
PIP-Weather-Radar
A TV-news-style live weather radar PiP overlay for ScreenTinker — a dark county map with animated precipitation radar and live NWS warning polygons drawn on top (tornado = red, severe thunderstorm = yellow, flash flood = teal, flood = green), exactly like a local station's radar.
Its headline trick is mode: "on_warning": it watches the National Weather Service
and only "cuts to radar" when a qualifying warning actually covers your area — then it
clears itself when the warnings expire or drop. (Or run mode: "always" to keep the
radar up permanently, e.g. for an ops/EOC wall.)
radar.js (Node) radar-overlay.html (player iframe)
────────────── ─────────────────────────────────
poll NWS for warnings ── show/clear ─▶ CARTO dark basemap
at your point + animated RainViewer radar loop
(mode on_warning) + live NWS warning polygons + HUD
Everything is keyless and has zero Node dependencies. Map rendering uses Leaflet (MIT), vendored locally.
Data sources & attribution
The overlay shows attribution on-map; please keep it. Sources:
- Basemap: © OpenStreetMap contributors, © CARTO
- Radar: RainViewer public weather-maps API
- Warnings/alerts: US National Weather Service / NOAA (
api.weather.gov)
⚠️ Disclaimer: this is an informational visualization, not an official warning system. Radar and alert data can be delayed or incomplete. Do not rely on it for life-safety decisions — follow official NWS alerts and local emergency guidance.
Why it works (CSP)
The overlay is served from your signage server, whose CSP is script-src 'self' — so the
map library is vendored (loaded same-origin), not from a CDN. The same CSP allows
img-src https: and connect-src https:, so the overlay can pull tiles and fetch() the
radar + alert JSON directly (both send Access-Control-Allow-Origin: *). No server change
needed.
Files
| File | Purpose |
|---|---|
radar.js |
Poller/pusher: decides when to show/clear the radar PiP; exports pure helpers |
radar-overlay.html / radar-overlay.js |
The map overlay (served same-origin, external JS per CSP) |
vendor-leaflet.sh |
Downloads leaflet.js + leaflet.css into this dir |
config.example.json |
Copy to config.json and fill in |
test.js |
Offline unit test (npm test) |
Setup
Note: Leaflet is not committed to this repo (it's third-party, BSD-2-licensed). The script below downloads it locally — run it once before deploying. Nothing else to install.
- Vendor Leaflet (downloads
leaflet.js+leaflet.cssinto this dir):./vendor-leaflet.sh - Copy the overlay + Leaflet into your signage server's frontend dir (so they're
served same-origin as the player):
cp radar-overlay.html radar-overlay.js leaflet.js leaflet.css /path/to/screentinker/frontend/ - Configure:
cp config.example.json config.json # edit: api_base, api_token (st_ token with 'full' scope), overlay_base_url # (https://<server>/radar-overlay.html), device_id, and your area: # area_label, lat, lon, zoom, states (for the alert query), events - Run:
npm start # or: node radar.js
Local quick-start (self-signed dev server)
./vendor-leaflet.sh
cp radar-overlay.html radar-overlay.js leaflet.js leaflet.css ../../frontend/
cp config.example.json config.json
# set in config.json:
# api_base="https://localhost:3443/"
# api_token="<your st_ full-scope token>"
# overlay_base_url="https://localhost:3443/radar-overlay.html"
# device_id="<your device or group id>"
NODE_TLS_REJECT_UNAUTHORIZED=0 node radar.js
Config
| Key | Default | Notes |
|---|---|---|
mode |
"on_warning" |
"on_warning" = show only during qualifying warnings; "always" = always on |
lat, lon |
— | Map center and the NWS ?point= used to detect warnings |
zoom |
8 |
Leaflet zoom; ~8 ≈ a county/metro |
max_counties |
2 |
How far the auto-framing may pull back from the centre, in county-widths. Warnings inside that box are framed; one entirely outside it is not chased, so a distant storm can't zoom your area down to nothing. |
area_label |
— | Shown in the overlay header |
states |
[] |
2-letter codes used to fetch warning polygons (?area=ST). Empty → ?point= |
events |
Tornado/Severe Tstorm/Flash Flood/Flood Warning | Which warnings qualify & are drawn |
poll_interval_sec |
60 |
How often radar.js checks NWS |
position/width/height/border_radius |
center / 1100×720 / 12 | PiP box |
noaa_user_agent |
— | NWS asks for a contact in the User-Agent |
The overlay fetches warnings by
states(so the polygons stay visible across the map), whileradar.jsdecides show/clear from the?point=at yourlat/lon. Setlat/loninside the area you care about and list itsstates.
Test
npm test # RESULT: PASS ✅
Covers the warning gate (event/expiry/geometry), the color map, the RainViewer tile-URL builder, and the overlay-URI round-trip. No network.