screentinker/Examples/PIP-Weather-Radar/README.md
ScreenTinker b8c127f766 Examples/weather-radar: keep the map centred and bounded
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.
2026-07-27 12:26:08 -05:00

5.4 KiB
Raw Blame History

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.

  1. Vendor Leaflet (downloads leaflet.js + leaflet.css into this dir):
    ./vendor-leaflet.sh
    
  2. 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/
    
  3. 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
    
  4. 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), while radar.js decides show/clear from the ?point= at your lat/lon. Set lat/lon inside the area you care about and list its states.

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.