Project · 2025

The Tide Clock

A browser-based tide clock that tracks where the tide actually is — not where a gear thinks it should be.

Vanilla JS HTML Canvas NOAA CO-OPS API spatial-utils Vercel
§ Live clock
Requests your location to select the nearest NOAA station. Open full screen ↗

A mechanical tide clock has one gear that turns once every 24 hours and 50 minutes — the average lunar day. Set it to a local high tide, and it approximates the next one. It's elegant. It's also wrong.

Tides don't run on averages. Local geography shapes them: inlet width, basin depth, river mouth backpressure. The difference between the ocean beach and the intracoastal waterway a quarter-mile away can be two hours and several feet. A gear can't know that. A fixed calibration can't know that. Live data can.

"Replace the mechanical approximation with live data and location-aware station selection."

On load, the clock requests your GPS coordinates and passes them through a coordinate transformation pipeline — WGS-84 geodetic to ECEF to ENU — to compute true metre-accurate distances to every NOAA tide prediction station in the network. That's roughly 3,400 stations.

The two nearest stations within 30km are fetched and their predictions blended using inverse-distance weighting with wrap-safe angle averaging to handle the 0°/360° clock face boundary. An exponential moving average smoother prevents the hand from jumping as blend weights shift.

The key insight is that NOAA's subordinate station network already encodes the hydraulic corrections for inlets, back bays, tidal rivers, and the ICW — inlet lag, basin attenuation, height ratio — for every named water body in the US. By ranking stations by physical distance rather than ID or region, a user standing on the ICW gets ICW-corrected predictions automatically. A user on the ocean beach gets ocean predictions. No hardcoded offsets. No geometry classification logic. The same code works on the Chesapeake, Puget Sound, San Francisco Bay, or anywhere else NOAA has subordinate coverage.

The clock is built on spatial-utils, a custom open-source JavaScript navigation library for WGS-84 geodetic math. It's the geometric foundation that makes location-aware prediction possible without region-specific logic.

spatial-utils · what it contributes to the clock
  • wgs84ToEcef Converts GPS coordinates and NOAA station positions from geodetic angles into 3D Cartesian space anchored at Earth's center. Necessary first step — degree arithmetic is geometrically invalid for distance comparisons because a degree of longitude shrinks from 111km at the equator to zero at the poles.
  • ecefToEnu Projects the vector between the user and each station into a local East-North-Up frame. Produces a distance that means what it says: metres along the ground, accounting for the WGS-84 ellipsoid rather than a sphere approximation.
  • getHorizontalDistance Collapses the ENU vector to a single ground-plane distance in metres, used to rank all ~3,400 NOAA stations and compute inverse-distance blend weights between the nearest ones.
  • ema / smoothAngle Exponential moving average smoother and wrap-safe angle interpolation. Prevents the clock hand from jumping as station blend weights shift. Handles the 0°/360° boundary without branch logic.

spatial-utils is inlined into the clock rather than imported as a module to avoid ES module CORS restrictions when opening as a local file. Its role is purely geometric: it answers which station is relevant to your position. NOAA answers what the tide is doing at that station. The division of responsibility is intentional.

The clock face is a custom aerial photograph rendered as a tiny planet — a stereographic projection of a 360° equirectangular image that wraps the full panorama into a circular world, ocean at the center and the horizon curling around the edge.

The hand completes one rotation every 24 hours and 50 minutes. Alongside it: current tide phase (rising or falling), countdown to next high and next low, current water height in feet above MLLW, a water level arc on the rim showing fill proportional to height, a spring/neap indicator driven by moon phase math, and a station blend card showing which NOAA stations are active, their type, distance, and blend weight.

Started as a single widget demonstration of how a tide clock works mechanically. Expanded into a full location-aware prediction system through an iterative build covering coordinate math, NOAA API integration, multi-station blending, ICW/ocean differential, geolocation edge cases on macOS, Vercel deployment, and iframe embedding.

The geolocation flow uses the Permissions API to detect blocked access before firing getCurrentPosition, retries with enableHighAccuracy: false on macOS Core Location failures, and surfaces OS-level fix instructions when the browser permission is granted but the system is blocking. Data refreshes every 6 hours.

The narrative arc is simple: decorative approximation → location-aware precision → universally applicable without hardcoding.