Advanced NEXRAD weather radar viewer — an open-source homage to supercell-wx, built from scratch in Rust with wgpu + egui. Deep per-site Level 2 / Level 3 analysis plus national situational awareness, forecast environment overlays, and warning intelligence — on Windows, Linux, and Android.
README
Hook Echo-WX
Advanced NEXRAD weather radar viewer — an open-source homage to
supercell-wx, built from scratch in Rust
with wgpu + egui. Deep per-site Level 2 / Level 3 analysis plus national
situational awareness, forecast environment overlays, and warning intelligence —
on Windows, Linux, and Android.
The whole app as wasm, on live data, with nothing to install.
KTLX 0.5° reflectivity — Moore, Oklahoma, 20 May 2013. Replayed from the
public archive inside the app, one scan every few seconds, with the tornado
warning that was in force at the time.
Animated wind. HRRR 10 m wind as drifting particles coloured by speed —
built from NOAA's free GRIB grids, so it needs no API key.
Install
Linux: download Hook_Echo-WX-x86_64.AppImage from
Releases, chmod +x, run. Debian/Ubuntu users can install
hookecho__amd64.deb from the same place
(sudo apt install ./hookecho_*.deb) to get the menu entry and icon.
Windows: grab the installer from Releases —
Hook_Echo-WX-setup-x86_64.exe (setup wizard) or Hook_Echo-WX-x86_64.msi
(MSI, for scripted/enterprise installs). A portable
hookecho-windows-x86_64.zip is there too: unzip, run hookecho.exe.
Android (arm64, Android 10+): sideload Hook_Echo-WX-arm64-v8a.apk from
Releases (adb install -r …, or open it on-device with
"install unknown apps" enabled). The same Rust app as desktop,
with a Material 3 phone UI — see android/README.md.
macOS (experimental): Hook_Echo-WX-macos.zip from
Releases. The build is made and smoke-tested in CI but has
never been run on real Apple hardware, and it is ad-hoc signed, so Gatekeeper
will ask before it opens. Homebrew users can build it instead:
brew install --HEAD d4vid87/hookecho/hookecho (formula in
packaging/homebrew). macOS testers wanted — there
is no Apple hardware behind this project, so an
issue saying what did or did not work is the only way this
build stops being experimental.
Package managers: manifests for
Flatpak, Snap,
Homebrew, winget and the
AUR live in the repo. None are published to their stores
yet — building from these files works today; a flatpak install hookecho does not.
From source: cargo run --release (needs a Rust toolchain; on Linux also
ALSA/Wayland/GTK dev headers — see .github/workflows/ci.yml). Android builds
via android/build.sh (NDK + cargo-ndk).
Versioned v* releases are the stable channel. Every push to main also
refreshes a latest rolling prerelease carrying the
same artifacts, if you want the newest work without waiting for a tag.
First launch opens a four-card setup: home radar site, map and theme (13 built
in), and how warnings should reach you (chime and/or ntfy.sh
push to your phone). The last card offers a 60-second tour — four stops
spotlighted on the live map, two of which you finish by doing the thing rather
than reading about it. Neither is forced, and both re-run from the sidebar's
App section, Ctrl+K, or Settings → General.
The interface
Hook Echo-WX is a map with its controls docked around it — no menu bar, no
floating cards over the weather:
The left sidebar holds everything: the site, the current product and its
tilt, the expert knobs for that product, then every layer, window and tool —
one icon-led row each, described in plain English, filed under collapsible
categories that carry their own count, with the layer options, map settings and
app commands under them. Search it with Ctrl+K; Enter runs the top match.
Drag a row by its icon to pin it above the rest of its category, and collapse
the whole sidebar to a single button when you want the map and nothing else.
Its Alerts tab lists every alert covering your view, worst first, badged
with the count. Click a row to fly there and read the bulletin.
The bottom bar is the timeline: site, clock, play, scrub, and a LIVE badge
that snaps back to the newest scan. Right-click the badge for the archive day,
loop and playback speed.
The right edge carries the color scale for the product each pane is
showing — a thin bar floating over the map, spanning only the values the color
table actually paints.
That's the whole of it. Searching for a place is the same box — type a name and
take the Fly to row at the bottom of the results.
Search everything
One sidebar for everything
Joplin, MO — 22 May 2011
Mayfield, KY — 11 Dec 2021
Radar times read in the selected radar's local time, not Zulu — a KEPZ pane
shows MDT while a KTLX pane shows CDT, side by side. Settings → Units puts it
back to UTC if you'd rather work in Zulu.
Every expert control is still there, in the sidebar's Layer options section,
which shows a layer's settings only once that layer is on — so the forecast-hour
slider appears when you turn on future radar, and stays out of the way when you
haven't.
Search a place in the sidebar and it flies there, with a Save marker button
if you want to keep it. Markers are what the warning, lightning and rain-arrival
alerts watch. Tap one on the map to rename or remove it.
Walkthrough
Storm screenshots are historic events replayed through the app's own archive
timeline — the same scrubbing you'd do live. Forecast and national layers are
live data, captured as they came in. Everything is on the Mapbox Satellite
Streets basemap; a dozen others ship too, from a dark vector map to GOES
satellite imagery.
Watching a storm
All six Level 2 moments on a GPU polar pipeline, with VCP-aware tilt selection,
velocity dealiasing, storm-relative velocity, and GRLevelX .pal color tables.
Reflectivity shows you the storm's structure; velocity shows you what it's doing —
the tight red-against-green couplet below is a tornado on the ground.
Reflectivity
Velocity (dealiased)
KBMX — Tuscaloosa, AL, 27 Apr 2011
KTLX — El Reno, OK, 31 May 2013
Split the window into four panes and give each one its own product, cameras
linked — reflectivity, velocity, correlation coefficient and differential
reflectivity on the same storm at the same second. One action does the same
trick with four tilts instead, so you can see how a storm leans with height.
Cross-sections slice it vertically in any product — click two points and you get
the storm in profile, core and overhang and all.
Four products at once
Cross-section
KTLX — Moore, OK, 20 May 2013
KTLX — Moore, OK, 20 May 2013
Every screenshot in this section is a replay. The timeline reaches back to June
1991, so any archived storm loads the same way the live one does — a supercell
from fifteen years ago, or a hurricane coming ashore:
KTBW 0.5° reflectivity — Hurricane Ian, 28 September 2022.
And because the archive is there, you can ask how the warnings did. The
verification lab scores an office's warnings for a day against the storm reports
that came in — POD, FAR, CSI, lead times, and the individual rows behind them,
including the reports nobody warned for (the red dots on the map). The
arbitration is the Iowa Environmental Mesonet's, so the numbers agree with the
published ones instead of being a private opinion.
KTLX — 20 May 2013. OUN's warnings scored against that day's reports.
Deciding which storm matters
Every tracked cell in one sortable table — hail size, tops, VIL, rotation flags —
and clicking a row flies you there. Alongside it, an active-alerts panel sorted
worst-first, with clickable NWS bulletins and parsed storm-motion vectors. Scrub
into the archive and the panel fills with the warnings that were actually in
force at that moment.
Storm attributes
Active alerts
KTBW — live
KBMX — 27 Apr 2011 outbreak
Looking ahead
HRRR future radar rides the timeline's forecast tail, so scrubbing past the
present keeps going into the model. It is labeled the whole time it is on —
model output should never be mistaken for something a radar actually saw.
HRRR future radar, +1 h. The banner stays up for as long as the layer is
on: forecast, not observed.
Any point on the map gives you the plain NWS forecast for that spot, and the WPC
surface analysis draws the national weather map — fronts with their proper pips,
highs and lows with pressures.
Surface analysis
Point forecast
WPC coded surface analysis, live
Tap anywhere — KBMX, live
Also on the forecast tail: rotation tracks (HRRR max updraft-helicity swaths
— where storms are forecast to rotate, accumulated across the hours you scrub
to) and near-surface wildfire smoke.
The national picture
Every radar in the country stitched into one mosaic, updated every couple of
minutes — the view for "what's happening everywhere" rather than "what's
happening here" — plus rainfall accumulated over the last hour or day.
National mosaic
24-hour rainfall
MRMS composite reflectivity, live
MRMS gauge-corrected precipitation, live
There is also a multi-radar mosaic built the other way round: instead of a
national 1 km product, every radar covering your view contributes its own base
reflectivity at full resolution, composited nearest-radar-wins so neighbouring
sites join without a seam. It shows which radars it used and how old the oldest
scan in it is, because radars scan on their own schedules and a composite is
never one instant.
Six radars, one picture — live N0B composite
Alongside them: individual lightning flashes from the GOES satellite's lightning
mapper (fading as they age), cloud-to-ground lightning density, rotation tracks
and azimuthal shear, MESH hail size and 24-hour hail swaths, surface
precipitation type, and FLASH flash-flood recurrence intervals. Every gridded
layer draws its own scale and units.
Features
Warnings and alerting
Each warning's eventMotionDescription is parsed into a storm-motion vector —
warned-storm dot, projected 15/30/45/60-minute path, and ETA readouts to your
saved locations.
Home marker with a watch radius: mark one saved location as home and set
how close a warning has to come (20 miles by default) — you're alerted when
the polygon's edge reaches that ring, not only when it swallows your house.
Every marker carries its own radius; home draws its ring on the map.
Escalation tiers (CONSIDERABLE → DESTRUCTIVE/observed tornado → Tornado
Emergency / PDS) drive a pulsing polygon outline, priority sorting with red
threat chips, a dedicated emergency siren, and urgent-priority phone push.
Warnings are read aloud through the system voice — the desktop speech engine,
or Android's own TTS on the phone.
Watched-location monitoring, a lightning proximity alarm (a strike within
~15 km of a saved spot), rain-arrival alerts ("rain in about 20 minutes"), and
live NWS Local Storm Reports, minutes fresh.
Storm analysis
SCIT cell tracks, past tracks and arrival-time cones; hail and mesocyclone
flags; a sortable attributes table for every tracked cell at once.
Automatic tornado debris signature detection (low correlation coefficient
collocated with high reflectivity) and client-side azimuthal-shear couplet
detection on the live volume — a rotation flag that doesn't wait for a Level 3
product.
Cross-sections in any moment, a CAPPI altitude slicer, and a 3D volume view.
Copy CSV or save it to a file from the cell table, the cross-section, the
verification report, the sounding — indices and profile both — and the tornado
climatology, so the numbers can leave the app and be checked somewhere else.
Environment and forecast
CAPE (surface-based or mixed-layer parcel) and storm-relative helicity (0–1 or
0–3 km) as map overlays, from either the HRRR forecast (3 km) or the RAP
f00 analysis (13 km) — the observed mesoanalysis, assimilated from obs rather
than projected forward. Same switch drives the contour fields and the
STP/SCP/EHI composites.
Point soundings — Skew-T and hodograph — with derived SBCAPE / LCL / SRH /
SCP / STP / EHI indices from real parcel ascent and Bunkers storm motion,
and the observed radiosonde ascent from the nearest station drawn dashed
beside the model profile (University of Wyoming archive, so an event replay
gets that morning's real balloon, back to 1973).
The VAD wind-profile hodograph, plus a time-height panel of wind barbs
accumulated while the app runs, since the radar only publishes the latest.
HRRR future radar (0–18 h), forecast rotation tracks, near-surface wildfire
smoke, and 0–45 minute optical-flow extrapolation of the radar you're watching.
A model difference layer: GFS minus ECMWF (MSLP, 500 hPa height, 2 m
temperature, 10 m wind) and HRRR minus RAP (surface CAPE, storm-relative
helicity), on one map. Where the two models agree it draws nothing, so what
you see is the disagreement — and the layer names both valid times, because
the two feeds rarely share a cycle.
Effective-layer bulk shear, SRH and STP as gridded fields, solved per
column on the HRRR's 25 hPa pressure ladder up to 100 hPa — deep enough that a
buoyant parcel has an equilibrium level to be measured against, which is what
the depth-dependent parameters need to exist at all.
SPC Day 1–3 categorical outlooks plus Day-1 probabilistic tornado/wind/hail
grids with the significant-severe hatch, and the WFO's Area Forecast
Discussion in-app.
Products computed from the volume itself
VIL density over the Mayfield, KY supercell — computed here from the 11 Dec
2021 volume, four years after it was recorded.
VIL, VIL density and echo tops, integrated here from the stacked tilts
rather than read from a Level 3 grid — so they work in any archive replay,
at the volume's own resolution, with an echo-top threshold you can move.
Maximum expected hail size and probability of severe hail (Witt et al.
1998), with the melting-level and −20 °C heights the algorithm needs sourced
automatically from HRRR instead of typed in by hand. Live only: those heights
come from the current analysis, and applying today's freezing level to a storm
from 2021 would be an answer with no meaning.
Data the app decodes itself
Gridded Level 3: Digital VIL, Enhanced Echo Tops and Hydrometeor
Classification (rain / snow / hail / graupel / biological), via a
from-scratch packet-16 decoder — BZIP2 symbology blocks, ICD float16
thresholds, 0.25-km and 1-km bins, golden-tested against MetPy.
GOES satellite lightning: individual flashes from the GLM, ~40 seconds
behind real time. GLM sees in-cloud flashes a ground network never reports.
The netCDF-4 granules go through a from-scratch minimal HDF5 reader
(crates/hdf5lite), because the reference C library can't ship in the Android
build.
METAR station plots with US-convention wind barbs, flight-category colors
and greedy decluttering — extended offshore and over the Great Lakes by the
NDBC buoy network, where the airport network simply has no stations. Buoys
also print their wave height and dominant period, which is the reason to
look at one.
Wildfires: active perimeters and incident points from the interagency
WFIGS feed, with acres and containment on tap.
Air quality: every AirNow monitor in view as an EPA-category dot with
its AQI (free key in Settings).
TDWR terminal radars: the FAA's airport radars, synthesized into volumes
from their Level 3 tilt products, so all 44 of them behave like any other site.
NHC tropical storms with forecast cones and Saffir–Simpson-colored track
points, the forecast wind field at 34/50/64 kt, potential storm surge,
and hurricane-hunter flight tracks with the measured SFMR surface wind.
SIGMET/AIRMET hazard polygons and PIREPs — what pilots actually flew
through, rather than what was forecast.
Winter: HRRR forecast snowfall, the NOHRSC observed snowfall analysis
(6/24/48/72 h), the Winter Storm Severity Index — how disruptive, not just
how much — and mPING crowd reports of what is actually falling.
WPC Excessive Rainfall Outlook, the flood half of a severe-weather day.
Time machine
Archive playback of any date since June 1991, with the warning polygons and
the local storm reports that were actually in effect at the scrubbed instant.
Volumes from before the dual-polarization upgrade simply don't offer ZDR/CC.
A curated library of historic events, plus your own bookmarks.
An in-RAM decode buffer with one-touch instant replay (R), and
screenshot / GIF / MP4 loop export.
Workspaces
A pane layout is a dozen clicks to rebuild — two sites, their products and
tilts, the overlays and field layers that go with them — and it is the same
dozen clicks every time. Save the arrangement from the command palette and
restore it in one.
Three are there on first run: Chase (reflectivity beside storm-relative
velocity, cameras locked), National overview (the MRMS mosaic under the
warnings), and Analysis (one storm at four heights). They adopt whichever
radar is on screen, and deleting them is final.
What is on your disk
Everything the app downloads is cached and every cache is trimmed to a cap at
startup. Settings → Storage shows each one's size against that cap, with a
button to clear it and a button to open it. Nothing there is irreplaceable —
clearing a cache costs the next fetch and nothing else.
Across your machines
Sign in with Google (Settings → Sync) and your settings, saved locations,
placefiles, palettes and API keys follow you to every machine. The data lives
in your own Drive's hidden per-app folder — no Hook Echo account, no server,
and a scope that cannot see the rest of your Drive. Screen scale and device
name stay local. One-time OAuth client setup: docs/sync.md.
Out in the field
Chase mode: live GPS (gpsd on desktop, the system location service on
Android) drawn as a blue dot on the radar, a storm-relative HUD with closest
approach and escape bearing, and offline "chase packs" of pre-downloaded
basemap tiles.
Position sharing: opt in and every Hook Echo on the same network sees
everyone else's dot, no account and no configuration (UDP broadcast on
:41777). For devices that aren't on one network — the phone chasing on
cellular, the desktop at home — set a relay URL in the same panel: any HTTP
endpoint you host that takes POST of one position and returns GET of the
list. There is no default relay; the endpoint sees your live position.
Streamer/OBS mode: chrome-free UI (F8) and an auto-tour of active
warnings (F9).
Click anywhere for historical tornado tracks near that point (SPC 1950–2022)
with an EF-scale histogram, plus how often that spot has actually been warned —
warning counts by type, first year on record, busiest year and worst day.
NWS damage surveys: the EF-rated damage points and surveyed track a survey
crew filed after the storm, with photos, on the day your timeline is showing.
Ground truth to lay over the hook you were watching.
Listen to a NOAA Weather Radio relay while you watch (bring your county's
stream URL — NOAA broadcasts on VHF and streams nothing itself).
Draw on the map freehand to circle the storm you're talking about, and look
through webcams to see what the sky actually looks like. The FAA's ~2,600
airport cameras need no key but stop at the US border; a free Windy key in
Settings adds their global network, which returns the 50 most popular cameras
in view rather than every one of them.
Animated wind: HRRR 10 m wind drawn as drifting particles, coloured by
speed — the Windy look, built from NOAA's own free GRIB grids rather than
licensed, so it needs no key and works offline of any paid API. CONUS only,
because HRRR is. Model output, not observation, and the layer says so.
Minute-by-minute rain for the spot you tapped: the next hour advected off
the current scan, above the hourly forecast — when it starts, when it stops.
Share the view: the palette copies a hookecho:// link carrying the site,
place, zoom and (when you're scrubbed back) the time. Opens the app right
there — on Android, straight from the link.
Android home-screen widget: what's warned at your saved locations, tap to
open the map on it.
Per-layer opacity in the Layer Manager, for field layers as well as
placefiles, and the scrub bar now shades its forecast tail so a model hour
never reads as radar.
Open this view in Windy from the command palette when you want their
models next to ours — same place, same zoom, matching overlay.
Live station cards: click a surface station and get a floating,
draggable card — a live highway camera on top, then the station's local clock
and how stale its reading is, temperature with humidity and dewpoint, a wind
dial with the trailing-gust ladder (10 s through 24 h), and the electric field.
Open as many as you want, one per station.
Airport METARs need no key; a WeatherFlow Tempest token or a Weather
Underground key adds personal weather stations. Camera video is MJPEG decoded
in-app, or HLS if you have ffmpeg installed — the card says so plainly when
you don't, and everything else on it keeps working.
Cameras come from the FAA and from Caltrans, whose twelve districts publish
~3,300 cameras keyless and stream video from most of them. No other state DOT
publishes an open camera API, so that is the coverage, not a national map.
The electric field is NOAA's PPEF model: the equatorial ionospheric field
in mV/m, predicted from solar wind. That is space weather, not the storm over
your head — for the kV/m a chaser means, point the card at a ground field
mill's JSON in Settings and it charts that instead.
On Android the cards work the same, minus live video: the camera shows its
newest still instead, refreshed on the poll clock (a phone can't spawn ffmpeg,
and a video decoder per open card is not what its battery is for).
Multi-pane layouts, placefiles with icon sheets and a layer manager, a sensor
dashboard, range rings, 15 themes, and tray-based background alerting.
On your phone
The same Rust app, rebuilt around the phone. KTLX 0.5° reflectivity,
20 May 2013, playing out of the archive at half speed with the tornado warnings
of the day counting up on the alert badge.
Android is not the desktop squeezed onto a smaller screen — it is a Material 3
layout over the same renderer, the same data paths and the same action registry:
Map first. The radar owns the screen; one chip names the site and VCP.
One sheet, three snaps. Drag it up for the scrubber, speed, products and tilts; drag it to full for the archive.
Everything else is a sheet. The same described, categorized action registry the desktop sidebar uses.
Alerts in view, tap to fly there and read the full text.
A docked five-action toolbar along the sheet's bottom edge — no second bar
stacked under it.
Every tool window (soundings, cross-sections, settings, the site picker) is a
full-screen surface, because that is what the compact width class is for.
Real WindowInsets, including the keyboard, so a focused field rises above it.
The real system IME through GameActivity: autocorrect, suggestions, and every
language the phone has, instead of NativeActivity's raw ASCII key events.
Predictive back: back dismisses the innermost surface, and with nothing open
Android draws its own home-screen preview as you drag.
In landscape the sheet becomes a full-height side rail.
Native GPS for chase mode, and opt-in background alerting: a foreground
service watches your saved locations and notifies you with the app closed,
tiered by watch / warning / emergency, tapping through to the storm.
Without opening the app
hookecho --status prints what's happening at your saved locations — current
conditions from the nearest station and any active alert whose polygon comes
within that location's watch radius:
conditions and nearby alerts for every saved location
/alerts.json
alerts only
/obs.json
conditions only
/cells.json?site=KTLX
every storm cell the radar's algorithms track — hail size, tops, VIL, TVS, forecast track
/health.json
version, uptime and how stale the answers are, for a container health check
/snapshot.png?site=KTLX
a radar render — &product=VEL, &basemap=none, &size=512 (256–2048), &zoom=6.5, &tilt=1
JSON answers are cached for a minute and snapshots for five, so polling it every
30 seconds costs the upstream services nothing extra.
The server binds loopback and answers anyone who asks. Putting it on a network
(--bind 0.0.0.0, or a container port that isn't 127.0.0.1:) means publishing
where you live, so set a token first — serve_token in settings.json, or
--serve-token on the command line, which wins:
Every route is behind it, /metrics and the CORS proxy included. The
?token= form is there for dashboards that can only fetch a URL and have no
place to put a header.
For a desktop widget rather than a server, --snapshot writes the same render
straight to a file:
It renders, renames the finished file into place (so a widget polling on its own
clock never catches a half-written PNG), and repeats. Point conky, feh --reload, or a wallpaper script at the file.
There's a container for it, if that's easier than a systemd unit:
:latest is rebuilt from main on every push; released versions are tagged
(:0.7.0). docker build -t hookecho . still works if you'd rather build it.
Mount your settings so it reports on your saved locations. The image is ~660 MB
because it carries Mesa's lavapipe software Vulkan driver — that's what renders
/snapshot.png with no GPU and no display attached.
It binds loopback unless you tell it otherwise. This endpoint says where you
live and what is warned there; --bind 0.0.0.0 exposes that to everything on
your network, and there is no authentication in front of it. Put it behind a
reverse proxy if it needs to leave the machine.
In Home Assistant
The command_line sensor above is the no-install path. For something richer
there's a custom component in custom_components/hookecho:
point it at a machine running --serve and you get, per saved location, a
device carrying temperature, dewpoint, humidity, wind, gust and pressure
sensors, an alert count sensor and an alert active binary sensor
(attributes: event, expiry, distance, escalation tier), a nearest storm
sensor giving the distance to the closest cell the radar is tracking
(attributes: cell id, bearing, max dBZ, hail size, TVS), plus one radar
camera entity showing the live snapshot.
The nearest-storm sensor is the one to automate on: it answers "is a storm
coming" minutes before anybody issues a warning, and goes unavailable — never
zero — when nothing is being tracked.
Install via HACS as a custom repository, or copy the custom_components/hookecho
directory into your Home Assistant config/custom_components/ and restart. Then
add the integration and give it the host and port. Polling is once a minute,
which is the server's own cache window.
Remember the server has to be reachable from Home Assistant — that means
--bind 0.0.0.0 (or the container), and a network you're willing to expose your
saved locations on. Set serve_token if that network is not one you control.
In a browser
A hosted build of main runs at **** if you only
want to look at it.
The app also builds for wasm and runs on a canvas — the same HookEchoApp, not
a cut-down viewer. Build it and serve it:
cargo install wasm-bindgen-cli # once
./scripts/web/build.sh
cargo run --release -- --serve 8080 --web-root web
Then open . Radar, the vector basemap, alerts and point
forecasts all work: the Level 2 buckets and api.weather.gov allow cross-origin
requests. Live chunk streaming works too, so a browser tab updates sweep by
sweep during a scan rather than waiting out the whole volume. Settings persist to
localStorage, and alert sounds and spoken warnings play through the browser's
own audio and speechSynthesis. This is a core viewer, though, not parity.
There is no filesystem, so caches are memory-only and imports and exports are
out; there is no camera, plugin or GPS support; and any feed whose host refuses
cross-origin requests simply doesn't load. Anything that needs those is on the
desktop and Android builds.
Hosting it yourself
The bundle is static files plus one CORS proxy — the feeds NOAA serves without
Access-Control-Allow-Origin need an origin of your own in front of them, or the
page loads and draws nothing. The proxy's rules (exact-match allowlist, GET only,
64 MB cap, narrowed content types) live once in web/_worker.js/proxy-core.js;
each host is a few lines of wiring around it.
The Worker also answers /geo.json with the visitor's rough position from
Cloudflare's own geo-IP, which is how the demo opens on the radar nearest you
instead of the configured default site. No third-party lookup and no browser
permission prompt; a #goto= deep link still wins. A host without it just opens
on the default.
Cloudflare Pages — web/_worker.js/, deployed by
.github/workflows/demo.yml. That's what hookecho.pages.dev is, and it is the
only host this repo deploys to.
Any other host works the same way if it can run one small function on
/proxy/*. A purely static host cannot: without the proxy the app opens, and
then sits empty.
Extending it
Placefiles work as they do everywhere else — URLs, icon sheets, a layer manager with per-file
opacity and paint order. Beyond that, anything that can print a placefile can be a plugin: the app
runs your command on a cadence, hands it the current site, view box, product and the instant on
screen (so it works during an archive replay too), and draws what comes back. No SDK, no build
step, no language requirement — the shipped examples are a Python script and twenty lines of
shell. See docs/plugins.md.
Deep links
hookecho://goto/… opens the app at a place and, optionally, a time. The
desktop registers the scheme on install (Linux .desktop, Windows registry,
macOS bundle); on Android a notification tap uses the same path.
hookecho://goto/KTLX # a radar site
hookecho://goto/KTLX,-97.28,35.33,9 # site, lon, lat, zoom
hookecho://goto/,-97.28,35.33,9 # no site: just the view
hookecho://goto/KTLX,-97.28,35.33,9,2013-05-20T20:00:00Z # and an instant
hookecho://goto/KTLX,-97.28,35.33,9,VEL,1 # and a product and tilt
The timestamp is RFC 3339 and puts the timeline where it was, so a link can
point at a moment in the archive rather than at "now". The trailing fields are
recognized by shape, not position: a timestamp, a product code (VEL, ZDR,
CC, …) and a tilt index can appear in any order, and any you leave out keep
whatever the person opening the link already had.
The browser build takes the same thing in the URL fragment, which never leaves
the client — no server sees where you are looking:
"Copy link to this view" in the command palette writes the right form for
whichever build you are running.
Keyboard
Every action in the command palette is bindable, and the defaults are listed in
Settings → Hotkeys. ? opens the cheat sheet over whatever is on screen. The
ones worth knowing before you look:
Key
What it does
←→
step one frame
R
instant replay from the in-RAM buffer
Page Up / Page Down
tilt up / down
1–6
products: reflectivity, velocity, spectrum width, ZDR, PHI, CC
Ctrl+K
command palette
L
the drawer
A
the alert panel
Z
cycle the basemap
M
mute
F3
change site
?
cheat sheet
The whole interface is reachable from the keyboard, and the widget tree is
published to screen readers (AT-SPI on Linux, UI Automation on Windows). There
is a High contrast theme in Settings → General for low vision and for
reading the screen in direct sun, an OLED black theme for night use, and
colorblind-safe reflectivity and velocity color tables in Settings → Palettes
(a viridis ramp whose brightness rises with dBZ, and a blue/orange diverging
velocity table — red/green diverging tables do not survive protan or deutan
vision).
Repository layout
crates/hookecho — the app: egui UI and wgpu render pipelines.
crates/wxdata — data plumbing: Level 2 (AWS), MRMS, HRRR (future radar and
environment fields), NWS alerts with storm motion and escalation, IEM archived
warnings and live/archived storm reports, SPC outlooks and climatology, METAR,
NHC tropical, aviation SIGMETs, Area Forecast Discussions, ProbSevere,
placefiles, spotters, GOES GLM lightning, WPC surface fronts, NWS point
forecasts, TDS detection, sounding indices, and CAPPI/cross-section/3D
resampling.
crates/nexrad-level3 — from-scratch NEXRAD Level 3 (RPG) product decoder:
storm-cell packets (15/19/20/23) and digital radial arrays (packet 16 —
DVL/EET/HHC), golden-tested against MetPy.
crates/hdf5lite — minimal read-only HDF5 reader, just enough for the
netCDF-4 files NOAA publishes. Exists because the reference reader is a C
library the Android build can't take; checked against h5py on real granules.
scripts/shots — the screenshot harness that produced everything above.
Also worth reading: docs/GUIDE.md — the task-by-task user
guide, "how do I actually do this"; docs/DATA.md — every feed the
app decodes, with its cadence, latency and whether it needs a key;
ROADMAP.md — what's next and what isn't planned;
ARCHITECTURE.md — how the code is shaped;
CONTRIBUTING.md — how the
workspace fits together and what a patch has to clear;
CHANGELOG.md — what changed per release;
docs/promotion.md — how a release is cut and announced.
Verification
Every data-backed feature has a headless CLI verifier (renders a PNG or prints a
report without opening a window), e.g.:
cargo run --release -- --headless out.png KTLX --moment VEL --dealias
cargo run --release -- --headless-mrms mosaic.png
cargo run --release -- --headless-alerts # motion + escalation lines
cargo run --release -- --headless-archwarn 2013-05-20T20:00:00Z
cargo run --release -- --headless-env sbcape cape.png # sbcape|mlcape|srh1|srh3
cargo run --release -- --headless-field preciptype ptype.png
cargo run --release -- --headless-l3grid hhc KTLX hca.png # dvl|eet|hhc
cargo run --release -- --headless-metar KTLX
cargo run --release -- --headless-tropical
cargo run --release -- --headless-cappi KTLX 3 cappi.png
cargo run --release -- --headless-reports 2013-05-20T19:00Z 2013-05-20T21:00Z
cargo run --release -- --headless-afd KTLX
cargo run --release -- --headless-aviation
cargo run --release -- --headless-indices -97.5 35.3
cargo run --release -- --headless-glm # GOES satellite lightning
cargo run --release -- --headless-fronts # WPC surface analysis
cargo run --release -- --headless-hrrr uh 3 uh.png # refc|uh|smoke
cargo run --release -- --headless-rules KTLX # would your alert rules fire?
cargo test # 303 offline unit tests
The screenshots in this README are regenerated by scripts/shots/shoot.sh,
which drives the real binary on a nested X display — see
scripts/shots/README.md.