What it is
Tide Watch plots all 301 active NOAA CO-OPS water-level stations on the Esri oceans basemap and, on selection, opens a live station card: the latest observed water level in feet above MLLW (auto-refreshing every 60 seconds), live countdowns to the next high and next low tide, and a 72-hour tide curve that overlays the last 24 hours of observed levels against the predicted six-minute harmonic curve on a hand-built canvas chart.
Its distinction is fidelity to how NOAA's data actually behaves. The app fetches real six-minute observations and harmonic predictions, parses NOAA's zone-less GMT timestamps correctly, and — importantly — degrades gracefully on the 52 Great Lakes gauges, which carry no MLLW datum and no tide predictions at all. Those stations fall back to lake level on the LWD datum with a plain notice that tide predictions do not apply, rather than showing a broken or misleading card.
It is built for anyone working on or near the water who needs an at-a-glance answer: mariners and harbor operators, coastal engineers and surveyors, fishers and paddlers planning around the tide, and coastal-hazard analysts. No login, no API key, no app store.
The experience
The app boots on The Battery, New York (station 8518750), selected with a gold ring at zoom 8.
- The station network. All 301 stations render as cyan dots on the Esri oceans basemap. Click a marker — or a row in the list — to select a station; a gold selection ring marks the active choice.
- Filtering. Narrow the network by state or territory, or type into a free-text search. NOAA's state field also holds territories and odd values (American Samoa, Bermuda, United States of America), which the filter handles.
- Latest level. The station card leads with the most recent observed water level in feet above MLLW, refreshed on a 60-second loop.
- Next high / next low tiles. Two tiles count down in "in 2 h 14 m" style, updated every second, each showing the event's UTC time and predicted height.
- The 72-hour tide chart. A DPR-aware canvas chart spans −24 h to +48 h: a solid cyan observed line, a dashed white predicted curve, a gold now-line, labeled high/low dots, foot gridlines, and 12-hour ticks.
- Great Lakes fallback. Select a Great Lakes gauge and the card shows lake level on the LWD datum with a clear notice that tide predictions do not apply there.
The data — where it comes from
All data comes from NOAA CO-OPS public APIs and an anonymous Esri basemap.
| Source | What it provides | Refresh | Cost | Attribution |
|---|---|---|---|---|
| NOAA CO-OPS station metadata (mdapi) | 301 water-level stations with lat/lng, state, greatlakes & tidal flags | On load | Free / public | NOAA CO-OPS |
| NOAA CO-OPS data API — water_level | Latest observation + last 24 h of 6-min samples | Every 60 s (latest); on selection (24 h) | Free / public | NOAA CO-OPS |
| NOAA CO-OPS data API — predictions | 6-min harmonic curve and hi/lo events (−24 h…+48 h) | On station selection | Free / public | NOAA CO-OPS |
| Esri oceans basemap | Anonymous bathymetric base | Static | Free (no LP cost) | Esri |
How it was assembled
Tide Watch is a Calcite Design System application on the ArcGIS Maps SDK for JavaScript, built with Vite and strict TypeScript.
Engineering decisions worth calling out
- Zero-credential design. The app runs with an empty .env. Every service is either an anonymous Esri basemap or a public CORS-enabled API, so there are no keys to provision, rotate, or leak. An optional Location Platform basemap upgrade is documented in .env.example but never required.
- Great Lakes datum fallback. The 52 Great Lakes stations have no MLLW datum and no predictions — datum=MLLW and product=predictions both return in-body errors. The app reads the stations feed's greatlakes flag, falls back to the LWD datum for water level, skips predictions entirely, and surfaces a card notice.
- Errors arrive as HTTP 200. NOAA's data API returns failures inside a 200 response body as an error object, so the code inspects the body rather than trusting the status code.
- Zone-less timestamp parsing. Timestamps are "yyyy-MM-dd HH:mm" GMT with no zone suffix; they are parsed as Date.parse(t.replace(' ','T')+'Z') to avoid local-time drift. Values arrive as strings.
- Selection-race tokens. A monotonically increasing load token guards every async render path so a stale fetch can never paint over a newer selection's card.
- HiDPI canvas. The chart is sized from clientWidth × devicePixelRatio on every draw, or it blurs on HiDPI screens; the observed line breaks into segments where sample gaps exceed 30 minutes rather than drawing a false straight line.