The chips are labelled "in view" but were tallied from the alert query result. Alerts are fetched per state — one request instead of one per county — so the feed routinely carries warnings hundreds of miles away. A Kenosha-centred map reported "2x Tornado Warning" for tornadoes in Calumet and Winnebago, neither of them on screen and neither reachable, since the view is bounded to two counties. Warnings are now sorted into three states rather than two: - Unreachable: outside the bounded frame the map can ever show. Not drawn, not counted. This is what produced the phantom tornado count. - Reachable but off-screen: drawn, so it can slide into view at the edge as the frame widens, but not claimed as "in view". - On screen: tallied into the chips, recomputed on moveend/zoomend, because framing settles asynchronously and it is the settled zoom that decides what "in view" means. Bounds come off the GeoJSON coordinates directly, covering every ring of a MultiPolygon, rather than building a throwaway layer per feature to ask Leaflet for an extent. Asset version bumped to 3 so players holding the cached copy pick this up. |
||
|---|---|---|
| .. | ||
| .gitignore | ||
| config.example.json | ||
| package.json | ||
| radar-overlay.html | ||
| radar-overlay.js | ||
| radar.js | ||
| README.md | ||
| test.js | ||
| vendor-leaflet.sh | ||
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.