Skip to content

Latest commit

 

History

902 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

example workflow codecov

Slackwater

Tide and current predictions. This repository holds the Slackwater engine for TypeScript, Swift and Kotlin, and the packages built on it. The engine predicts tides and currents in TypeScript, Swift and Kotlin; the CLI predicts both, and the React package predicts tides. Documentation lives at openwaters.io/tides/slackwater.

Warning

Not for navigational use

Do not use calculations from this project for navigation, or depend on them in any situation where inaccuracies could result in harm to a person or property. Tide predictions are only as good as the harmonics data available, and these can be inconsistent and vary widely based on the accuracy of the source data and local conditions. The tide predictions do not factor events such as storm surge, wind waves, uplift, tsunamis, or sadly, climate change. 😢

Slackwater family

Slackwater is one name for everything Open Waters publishes about tides and currents.

Surface Package What it is
App Slackwater for iPhone Tide and current predictions on iPhone, offline (source)
Engine slackwater Tide and current predictions with the station database built in, for TypeScript
@slackwater/engine The harmonic tide and current engine for TypeScript, with no station data attached
SlackwaterIWLS Device-side CHS fetching with a shared contract and synthetic fixtures
SlackwaterKit The harmonic tide and current engine for Swift
Slackwater for Kotlin The harmonic tide and current engine for Kotlin/JVM and Android, consumed from source
slackwater-iwls Device-side CHS fetching for Kotlin/JVM and Android, on the same contract as SlackwaterIWLS
CLI @slackwater/cli Tide and current predictions in your terminal: brew install openwatersio/tap/slackwater
API @slackwater/api HTTP API for tide and current predictions, hosted at api.openwaters.io/tides
React @slackwater/react React components for tide predictions
Database @slackwater/database The tide and current station database
Docs openwaters.io/tides/slackwater Developer documentation

The name, logo, and colors are in brand.

Installation

CLI

Install the command line tool to get tide and current predictions from your terminal with Homebrew on macOS or Linux, or with npm (npm install -g @slackwater/cli).

brew install openwatersio/tap/slackwater
slackwater help                             # Show help and available commands
slackwater extremes --near 37.8,-122.5      # High/low tides near San Francisco
slackwater timeline --station noaa/9414290  # Water level timeline
slackwater stations "portland"              # Search for stations
slackwater currents --near 48.406,-122.643  # Slack water and max currents at Deception Pass
slackwater currents stations "deception"    # Search for current stations
slackwater serve                            # Start the REST API server for tides and currents

See the CLI README for full usage and installation options.

Library

npm install slackwater

Usage

Tide Extremes Prediction

import { getExtremesPrediction } from "slackwater";

const prediction = getExtremesPrediction({
  latitude: 26.7, // or `lat`
  longitude: -80.05, // or `lng` or `lon`
  start: new Date("2025-12-17"),
  end: new Date("2025-12-18"),
  datum: "MLLW", // optional, defaults to station's chart datum (usually LAT or MLLW)
  units: "meters", // optional, defaults to 'meters', can also be 'feet'
});

console.log(prediction);
// {
//   datum: 'MLLW',
//   units: 'meters',
//   station: {
//     id: '8723214',
//     name: 'Fort Lauderdale, FL',
//     // ...
//   },
//   distance: 12.3,
//   extremes: [
//     { time: 2019-01-01T03:12:00.000Z, level: 3.2, high: true, low: false, label: 'High' },
//     { time: 2019-01-01T09:45:00.000Z, level: 0.5, high: false, low: true, label: 'Low' },
//   ]
// }

Get Timeline Prediction

import { getTimelinePrediction } from "slackwater";

const timeline = getTimelinePrediction({
  lat: 26.77,
  lon: -80.05,
  start: new Date("2025-12-19T00:00:00-05:00"),
  end: new Date("2025-12-19T01:00:00-05:00"),
  timeFidelity: 5 * 60, // seconds, defaults to `10 * 60`
  units: "meters", // optional, defaults to 'meters', can also be 'feet'
});

console.log(timeline);
// {
//   datum: 'MLLW',
//   units: 'meters',
//   station: {
//     id: 'noaa/8722588',
//     name: 'Port of Palm Beach',
//     // ...
//   },
//   timeline: [
//     { time: 2025-12-19T05:00:00.000Z, hour: 0, level: 0.7232836255650095 },
//     { time: 2025-12-19T05:06:00.000Z, hour: 0.1, level: 0.7231644154595303 },
//     { time: 2025-12-19T05:12:00.000Z, hour: 0.2, level: 0.7221097788490487 },
//     { time: 2025-12-19T05:18:00.000Z, hour: 0.3, level: 0.7201242512580206 },
//     // ...
//   ]
// }

Get Water Level at Specific Time

import { getWaterLevelAtTime } from "slackwater";

const prediction = getWaterLevelAtTime({
  lat: 26.77,
  lon: -80.05,
  time: new Date("2025-12-19T00:30:00-05:00"),
  datum: "MSL",
  units: "meters", // optional, defaults to 'meters', can also be 'feet'
});

console.log(prediction);
// {
//   datum: 'MSL',
//   units: 'meters',
//   station: {
//     id: 'noaa/8722588',
//     name: 'Port of Palm Beach',
//     // ...
//   },
//   time: 2025-12-19T05:30:00.000Z,
//   hour: 0,
//   level: -0.43840585181640557
// }

Finding stations

Slackwater uses @slackwater/database to find station data. You can find stations by location or ID.

Nearest Station

import { nearestStation } from "slackwater";

const station = nearestStation({ lat: 26.7, lon: -80.05 });
console.log(`${station.name} (${station.source.id})`); // Fort Lauderdale, FL (8722588)

Once you've found a station, you can get predictions, timeline, or water level at a specific time for that station:

// Get extremes prediction for the nearest station
station.getExtremesPrediction({
  start: new Date("2025-12-17"),
  end: new Date("2025-12-18"),
});

// Get timeline prediction for the nearest station
station.getTimelinePrediction({
  start: new Date("2025-12-19"),
  end: new Date("2025-12-20"),
});

// Get timeline prediction for the nearest station
station.getWaterLevelAt({ time: new Date("2025-12-19T00:30:00-00:00") });

List Nearby Stations

import { stationsNear } from "slackwater";

stationsNear({ latitude: 45.6, longitude: -122.7 }, 5).forEach((s) => {
  console.log(`${s.name} (${s.source.id}) - ${(s.distance / 1000).toFixed(2)} km away`);
});
// Vancouver (9440083) - 3.49 km away
// Portland Morrison Street Bridge (9439221) - 10.24 km away
// KNAPP(THORNES)LNDG, WILLOW BAR (9440171) - 16.34 km away
// Rocky Point (9439189) - 16.93 km away
// WASHOUGAL, COLUMBIA RIVER (9440047) - 24.89 km away

Find station by ID

import { findStation } from "slackwater";

// Find station by Slackwater ID
findStation("noaa/8443970"); // Boston

// Find station by source ID (e.g. NOAA)
findStation("9440083"); // Vancouver

Current predictions

The database also carries tidal current stations. Current predictions are signed speeds in knots along the station's flood axis: positive is flood, negative is ebb, zero is slack.

import { getCurrentEventsPrediction, getCurrentTimelinePrediction, slackWindows } from "slackwater";

const options = {
  latitude: 48.406, // Deception Pass, WA
  longitude: -122.643,
  start: new Date("2026-06-01T00:00:00Z"),
  end: new Date("2026-06-02T00:00:00Z"),
};

// Slack, max flood, and max ebb events in time order. Flood and ebb events
// carry the station's direction in degrees true when it publishes one.
const { station, events } = getCurrentEventsPrediction(options);
// { time: Date, speed: -5.2, kind: "maxEbb", direction: 281.5 }

// The signed speed curve, sampled every 10 minutes by default.
const { timeline } = getCurrentTimelinePrediction(options);

// The windows where |speed| stays under a threshold around a reversal.
const windows = slackWindows(timeline, 0.5);
// [{ start: Date, end: Date }, ...]

Current stations can also be found with nearestCurrentStation, currentStationsNear, and findCurrentStation, mirroring the tide station functions. Subordinate current stations (time offsets and speed ratios against a reference station) are resolved automatically.

Lookups by position only return stations that can be predicted, and only a station's primary depth bin. findCurrentStation also finds the rest by id: secondary bins such as noaa/EPT0003@11, and stations that can't be predicted, whose prediction methods throw the reason. currentStationUnavailable(station) gives that reason up front: redistribution for Canadian Hydrographic Service stations, whose terms don't allow their predictions to be redistributed (get them from tides.gc.ca), or no-model for a station with no constituents or offsets. parseCurrentBin(id) splits a bin id into its station and bin number.

Accuracy & Validation

Slackwater is continuously validated against NOAA tidal predictions, comparing the time and height of predicted high and low tides for all NOAA tide stations.

Methodology

  • High/low predictions over a full year extracted from both NOAA and Slackwater for 3300+ NOAA reference and subordinate stations
  • One-to-one event matching using a ±180-minute window
  • Metrics computed per station and aggregated across all stations
  • Evaluated automatically in CI on every change

Results

As of Feb 2026:

  • Stations evaluated: 3,370
  • High/low events compared: 4,522,258
  • Median |Δt|: 0.6 minutes
  • 95% of stations have median |Δt| < 4.3 minutes
  • Median MAE: 5 mm
  • 95% of stations have MAE < 17 mm

Summary

Slackwater reproduces high and low tide timing with sub-minute accuracy and tide heights with millimeter-level accuracy compared to NOAA's tide predictions. Validation is automated and enforced in CI to prevent regressions as the model evolves.

Fitted CHS tide ports

Canadian Hydrographic Service ports have no redistributable constants, so clients fit them on device from CHS's own predictions with chsTideFit. benchmarks/chs-fit.ts scores that recipe monthly on 64 ports at held-out weeks 28 to 270 days out. On the run recorded in the validation record, fitted from the samples a device fetches, about half the ports met the accuracy bar at the first held-out week, and the median held-out RMSE was 5–8 cm. Most misses come from the seasonal mean level, which a 60-day fit cannot carry (#428).

Fitted CHS current gates

CHS current gates are fitted the same way from CHS's speed and direction predictions with chsCurrentFit, over 60 or 210 days per gate. benchmarks/chs-current-fit.ts scores both windows on a year-round grid of held-out weeks against CHS's published slack and peak times. openwatersio/chs-current-validation runs it every month, pools a year of rows per gate, and publishes each gate's tier and bounds in its releases. On this repository's run under scorer version 1 with the fit ending October 1, 2026, 10 to 19 gates met the accuracy bar at 210 days depending on the week, and verdicts for gates near the bar change with the season.

License

The libraries in this repository (slackwater, @slackwater/engine, @slackwater/cli, @slackwater/api, and @slackwater/react) are MIT licensed, and so are SlackwaterKit (swift/LICENSE), SlackwaterIWLS (module license), the Kotlin engine and its IWLS client. The iOS app is GPL-3.0. The station database code is MIT, and its station data carries a license per station: CC BY 4.0 unless noted, NOAA stations in the public domain, and some stations CC BY-NC 4.0, marked commercial_use: false. Every station has an attribution string to display wherever you show its data.