Skip to content

Tides ​

sextant/tides gives the boat a tide page: for every port near the current position, what the water is doing now, when the next high and low water are, how big the tide is today, and a curve to plot.

It publishes all the stations it found, not one, sorted nearest first. The screen picks — the crew usually wants the port they are heading for, not the one they happen to be closest to.

Read this before using the numbers ​

Two properties of this data set are not negotiable and change how the numbers must be read.

1. The heights are not referenced to a chart datum. Every station's height is a harmonic sum with no constant term, so it oscillates about zero, and roughly half the time it is negative. A published height of −1.8 m does not mean 1.8 m below the charted sounding; it means 1.8 m below the local mean water level. The database has a datum column and it is empty for every one of the 8117 stations. There is no offset anywhere in the daemon that would let you convert.

So: do not add a published height to a charted depth. For under-keel clearance, use the official tide table for the port.

2. What is trustworthy is everything datum-independent — the timing of high and low water, the range between them, and the tidal coefficient. Those are what the tide page is genuinely good for, and they are what MUXEN validates against official predictions.

What a station carries ​

FieldUnitNotes
name—port name from the source station list
countryISO alpha-3optional; 129 of the 8117 stations have none
latitude, longitudedegreesthe station's own position
distanceNmnautical milesgreat-circle distance from the boat
heightmetresthe level now, about local mean water — see above
nextHighTideISO-8601 UTCoptional
nextHighTideHeightmetresoptional, present with nextHighTide
nextLowTideISO-8601 UTCoptional
nextLowTideHeightmetresoptional, present with nextLowTide
tidalCoefficientintegeroptional
curvearray of 216 pointsalways present

Unlike the sun and moon reports, the tide times are published only as ISO-8601 strings; there is no …Timestamp sibling here.

json
{
  "data": {
    "stations": [
      {
        "name": "Brest",
        "country": "FRA",
        "latitude": 48.3833,
        "longitude": -4.495,
        "distanceNm": 2.3,
        "height": 1.42,
        "nextHighTide": "2026-08-16T16:42:00Z",
        "nextHighTideHeight": 2.84,
        "nextLowTide": "2026-08-16T22:58:00Z",
        "nextLowTideHeight": -2.31,
        "tidalCoefficient": 95,
        "curve": [
          { "t": "2026-08-16T02:30:00Z", "h": -0.87 },
          { "t": "2026-08-16T02:40:00Z", "h": -0.62 }
        ]
      }
    ]
  },
  "metadata": { "rxDate": "2026-08-16T14:30:00Z", "expireAfterSec": 3600 }
}

Which stations are published ​

The daemon queries an R-tree spatial index for every station inside a bounding box, then filters exactly by great-circle distance against --tide-radius-nm (default 50.0). Stations are returned sorted by distanceNm, nearest first.

The bounding box is built with the cosine of the latitude applied to the longitude half-width, so a 50 NM radius really is 50 NM east-west at 60° north and not 25. Near the poles the longitude sweep is widened to the full circle and the exact distance filter does the work. A box that would straddle the antimeridian is likewise widened rather than split.

How many stations you get depends entirely on the coast. Mid-ocean returns none. A fjord coast returns a great many: the test suite pins 83 stations within 50 NM of Bergen. Each one carries a 216-point curve, so the payload grows linearly with the count — a few hundred kilobytes is normal on a busy coast, and that is the reason --tide-radius-nm exists.

If no station is inside the radius, nothing is published at all — not an empty array. The previous retained payload therefore stays on the broker, which on a long offshore passage means the tide page keeps showing the last coast you left. The log line, with --verbose-tides, is:

Tides: No tide stations found within 50 NM

height and the curve ​

height is the harmonic sum evaluated at the moment of the recompute.

curve is the same sum evaluated at 216 points: from −12 h to +24 h around that moment, every 10 minutes. Each point is { "t": "<ISO-8601 UTC>", "h": <metres> }, with t an absolute timestamp so a plot needs no arithmetic. The window is deliberately asymmetric — enough history to show the shape you have just come through, and a full day ahead to plan against.

The curve is what makes the report expensive: 216 evaluations of a harmonic sum — up to 34 constituents, 25 on average — per station, for every station in radius. Hence the separate throttle.

Recompute throttle ​

The tides report has its own minimum interval, --tide-recompute-min, default 30 minutes, checked inside the report itself. The central scheduler still fires on drift or on --recompute-interval-min (default 15), but the tides report ignores triggers that arrive too soon after the last computation.

The practical effects:

  • With the defaults, the tide page updates every 30 minutes, while the sun and moon pages update every 15.
  • height on the page can be up to half an hour old. On a 6 m range that is a meaningful amount of water.
  • A boat oscillating across a station-set boundary — anchored near the edge of the radius, say — does not churn the payload.

The tidal coefficient ​

tidalCoefficient is the French convention for "how big is today's tide", and it is the number a French almanac prints:

CoefficientTide
~20smallest neap
45mean neap
100mean spring
~120large spring

Values normally fall between about 20 and 120; the daemon clamps to 0…200 so an extreme station cannot emit nonsense.

It is computed as

coefficient = (nextHighTideHeight − nextLowTideHeight) / meanSpringRange × 100

clamped to the range 0…200, where the mean spring range is 2 × (A_M2 + A_S2) from the station's own harmonic constants. Because it is a ratio of ranges, it is datum-independent and therefore one of the trustworthy numbers.

It is absent when it cannot be formed honestly:

  • when either the next high or the next low was not found — a range built from one real extremum and one missing one is not a tidal range;
  • when the station has no usable mean spring range, which is what a diurnal-dominant or micro-tidal port looks like. Two of the 8117 stations have no mean spring range recorded at all.

Finding the extremes ​

High and low water are found by scanning the computed curve for turning points, with a prominence filter: an extremum is accepted only if it stands out from its neighbourhood by more than the greater of 10 % of the curve's full range and 0.05 m.

That filter exists because shallow-water overtides — M4, MS4, M6 — put small wobbles on the curve near slack water. Without it, a double high water at a place like Southampton, or ordinary harmonic noise in a micro-tidal Mediterranean port, produces a "high tide" 20 minutes after the real one and 3 cm lower.

Consequences to expect:

  • In a genuinely micro-tidal port the whole curve can fail the prominence floor, and both nextHighTide and nextLowTide are absent. The curve is still published and still correct.
  • The search covers only the 36-hour window of the curve, so a very long interval between extremes can leave one of them unfound.

Accuracy ​

The harmonic constants are extracted from FES2022b, a global ocean tide model distributed by AVISO/CNES, interpolated at each port's coordinates. That is the essential caveat: they are a global model sampled at a port, not the port's own observed harmonic constants from a hydrographic office.

MUXEN validates the result against official predictions — SHOM for French ports, NOAA for American ones — for Brest, Saint-Nazaire, La Rochelle, Saint-Malo, Lorient, Canet, Palermo, Fethiye, Bermuda and Fort Lauderdale, on a common reference date. The tolerances those tests enforce are the honest statement of accuracy:

QuantityTolerance against the official prediction
Time of high waterwithin 60 minutes
Time of low waterwithin 60 minutes
Tidal rangewithin 0.50 m
Tidal coefficientwithin 15

Absolute height is not validated at all, and cannot be, because of the datum.

Sixty minutes is a wide tolerance, and it is a test bound rather than a typical error — but it is the number MUXEN stands behind. For anything where the timing of the tide is a safety matter — a bar, a sill, a lock, a tidal gate — use the official tide table for that port.

The other structural limitation is that FES2022b is an ocean model. It represents the open-sea tide well and does not resolve estuaries, rivers, narrow inlets or the shallow-water distortion that dominates inside them. A station at the head of an estuary is being given the tide of the sea outside it.

When the report does not appear ​

SymptomCause
Tides: Database not found: <path> at startup, no sextant/tides evermuxen-sextant-database is not installed, or --tides-db-path is wrong. The report is not created at all; the rest of the daemon runs normally.
Startup line present, but no topicno station within --tide-radius-nm, or no position yet
Topic present but stalethe 30-minute throttle, or the retained payload from a previous run

The database is opened read-only on every computation and closed again; the daemon never writes to it.

Its schema and the harmonic arithmetic are documented in internal/tide-harmonics.md.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.