What it is
ATS River Pulse brings USGS streamflow into the browser, live, through the modern USGS Water Data API (an OGC API · Features service at api.waterdata.usgs.gov). Pick any of the 50 states and every reporting stream gauge appears on a topographic basemap with its latest continuous discharge (cfs) and gage height (ft), parsed from a single latest-continuous request — a few hundred to roughly 900 gauges per state (Colorado, the smoke-test state, returns 628 gauges, of which 331 have reported within the last seven days). Gauges are classified by flow within the state using quantile buckets over the log-flow range: dry gray through low cyan to high blue, with the top decile in bold ATS blue.
Selecting any gauge opens a glass detail card with its live readings, a seven-day hydrograph drawn on a canvas (minimum/maximum labels and a gold "now" marker), and a deep link to the official USGS station page. A stats row tracks the gauge count, the state's highest-flow site and the median flow in cfs — and everything silently refreshes every five minutes without disturbing the selection.
The application is zero-credential end to end: the basemap is anonymous, the USGS Water Data API is public and CORS-enabled, and there is no server anywhere — the browser talks to the federal API directly. An api.data.gov key would raise the hourly request quota, but a key shipped in a browser bundle is shared by every visitor, so the app sends none and handles a rate-limit response gracefully instead.
The experience
Choose a state and within seconds its river network lights up — hundreds of classed dots over a topographic base, the biggest rivers unmistakable in bold blue. The interface is a working hydrologist's view with a consumer finish:
- One picker, fifty states. Each state loads from a single latest-continuous request; a search box filters the gauge list by name, and the list caps at 600 rendered items so California-scale states never tank the DOM — the map always shows every live gauge.
- Flow classification that adapts to the state. Quantile buckets computed over the state's log-flow range make the symbology meaningful in both Arizona and Louisiana: dry gray, low cyan, high blue, and the top decile in bold #0061FF. The selected gauge carries a gold ring.
- A real hydrograph. The detail card draws seven days of 15-minute discharge (~672 points) with min/max labels and a gold marker at the newest reading, alongside live cfs and gage height and a launch link to the USGS station page.
- Live, quietly. Values refresh every five minutes in the background — the selected site is re-found by ID in the new payload, its card numbers update, and the gold ring stays put. A UTC clock in the chrome keeps the timestamps honest.
The data — where it comes from
One live federal API and one anonymous basemap — no key, no server, no cost.
| Source | What it provides | Refresh | Cost | Attribution |
|---|---|---|---|---|
| USGS Water Data API — OGC API · Features (api.waterdata.usgs.gov/ogcapi/v0 — live) | Latest discharge (parameter 00060) and gage height (00065) for every reporting gauge in a state, from the latest-continuous collection; the continuous collection supplies the 7-day hydrograph and monitoring-locations supplies site names | Live at load and every 5 minutes; hydrograph on demand; upstream readings at 15-minute cadence | Free | U.S. Geological Survey — provisional data subject to revision |
| Esri topo-vector basemap (anonymous) | Light topographic basemap under the gauge layer | Vendor-managed | Free (no API key) | Esri attribution renders in-map automatically |
How it was assembled
The application is a fully static single-page app — no backend, no database, no server-side rendering. The stack: TypeScript, ArcGIS Maps SDK for JavaScript 5.0.x, Calcite Design System 5.0.x, and Vite 8.
Module map — the codebase is small and deliberate; each file owns one concern:
Key engineering decisions
- The Water Data API's quirks are handled — verifiably. The OGC service returns one GeoJSON feature per site × parameter × statistic: Colorado returns 1,006 features for 628 unique gauges, so parsing groups by monitoring_location_id. Values arrive as strings ("7.30") and are converted explicitly, and can be null. Site ids are agency-prefixed ("USGS-09371010"), so the prefix is stripped before building a USGS station link.
- Silent gauges are filtered, and counted. latest-continuous returns each gauge's most recent reading whenever it happened — Colorado alone carries 268 gauges whose latest discharge predates 2025, the oldest from 1987. Plotting them would show a river that has not been measured in a generation, so the map draws only gauges read within the last seven days (Colorado: 331 of 618) and the stats row says in plain words how many went silent.
- Names come from a second collection. The value collections carry no site name, so names are fetched from monitoring-locations by id — one to three requests per state, cached, so the five-minute refresh costs nothing extra. Not every gauge reports both parameters, so every reading is nullable and renders "—" when absent.
- Timestamps compared as instants, not strings. The API returns UTC times ("2026-09-19T16:00:00+00:00") where the legacy service returned local offsets — freshness comparisons go through Date.parse, never string order.
- Rate limiting is handled as weather, not failure. USGS publishes no numeric quota for unkeyed clients and sends no X-RateLimit header to warn one; the first sign of trouble is HTTP 429. The app keeps the last good readings on screen, says quietly that USGS is busy, and retries on the next refresh rather than emptying the map.
- Refresh that respects the user. Request-sequence guards prevent a slow state fetch from overwriting a newer one after rapid state switching, and the five-minute silent refresh re-binds the selection by site ID rather than clobbering it.
- Small traps, recorded. Importing @arcgis/core/Map.js shadows the global Map — imported as EsriMap because the app uses real Map<string, number> buckets. view.goTo() needs a real Extent instance, not a plain object. Setting canvas.width resets the 2D transform, so the devicePixelRatio scale is reapplied on every draw. SDK 5.x attribution and Calcite 5 asset-path conventions follow the portfolio baseline.