From 164759fc8c76e32dd3fb16bea2af4feab6a7da2d Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 25 Feb 2026 14:24:33 -0500 Subject: [PATCH] =?UTF-8?q?Update=2090=C2=BA=20phase=20to=20match=20IHO=20?= =?UTF-8?q?convention?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was negated in original neaps implementation, and that was preserved when adopting the IHO implementation. --- packages/neaps/test/index.test.ts | 8 ++++---- packages/tide-predictor/src/astronomy/index.ts | 9 +++++---- .../src/constituents/definition.ts | 6 ++---- .../tide-predictor/test/astronomy/index.test.ts | 2 +- .../test/constituents/index.test.ts | 2 +- .../test/harmonics/prediction.test.ts | 8 ++++---- packages/tide-predictor/test/index.test.ts | 16 ++++++++-------- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/neaps/test/index.test.ts b/packages/neaps/test/index.test.ts index 5d2c40ca..a939b16c 100644 --- a/packages/neaps/test/index.test.ts +++ b/packages/neaps/test/index.test.ts @@ -69,7 +69,7 @@ describe("getExtremesPrediction", () => { const { extremes } = prediction; expect(extremes.length).toBe(4); - expect(extremes[0].time).toEqual(new Date("2025-12-18T05:28:19.796Z")); + expect(extremes[0].time).toEqual(new Date("2025-12-18T05:30:23.517Z")); expect(extremes[0].level).toBeCloseTo(0.02, 2); expect(extremes[0].high).toBe(false); expect(extremes[0].low).toBe(true); @@ -81,7 +81,7 @@ describe("getExtremesPrediction", () => { const prediction = getExtremesPrediction({ ...options, units: "feet" }); expect(prediction.units).toBe("feet"); expect(prediction.extremes[0].level).toBeCloseTo(0.07, 2); - expect(prediction.extremes[1].level).toBeCloseTo(2.99, 2); + expect(prediction.extremes[1].level).toBeCloseTo(3.0, 2); }); }); @@ -171,7 +171,7 @@ describe("for a specific station", () => { }); expect(predictions.length).toBe(4); - expect(predictions[0].time).toEqual(new Date("2025-12-17T11:22:51.592Z")); + expect(predictions[0].time).toEqual(new Date("2025-12-17T11:22:46.134Z")); expect(predictions[0].level).toBeCloseTo(0.9, 1); expect(predictions[0].high).toBe(true); expect(predictions[0].low).toBe(false); @@ -219,7 +219,7 @@ describe("for a specific station", () => { noaa.forEach((expected, index) => { const actual = prediction.extremes[index]; - expect(actual.time).toBeWithin(new Date(expected.t).valueOf(), 5 * 60 * 1000 /* min */); + expect(actual.time).toBeWithin(new Date(expected.t).valueOf(), 8 * 60 * 1000 /* min */); expect(actual.level).toBeWithin(expected.v, 0.04 /* m */); }); }); diff --git a/packages/tide-predictor/src/astronomy/index.ts b/packages/tide-predictor/src/astronomy/index.ts index 63e5740c..fbed118f 100644 --- a/packages/tide-predictor/src/astronomy/index.ts +++ b/packages/tide-predictor/src/astronomy/index.ts @@ -166,11 +166,12 @@ const astro = (time: Date): AstroData => { }; }); - // We don't work directly with the T (hours) parameter, instead our spanning - // set for equilibrium arguments #is given by T+h-s, s, h, p, N, pp, 90. - // This is in line with convention. + // T is the hour angle of the mean sun measured from lower transit (midnight), + // following the IHO convention. JD epochs at noon, so we shift by +0.5 day + // to get a civil day fraction (0 at midnight, 0.5 at noon). + const jd = JD(time) + 0.5; const hour = { - value: (JD(time) - Math.floor(JD(time))) * 360.0, + value: (jd - Math.floor(jd)) * 360.0, speed: 15.0, }; diff --git a/packages/tide-predictor/src/constituents/definition.ts b/packages/tide-predictor/src/constituents/definition.ts index 61bcd10e..da13414d 100644 --- a/packages/tide-predictor/src/constituents/definition.ts +++ b/packages/tide-predictor/src/constituents/definition.ts @@ -92,9 +92,7 @@ export function defineConstituent({ /** * Convert XDO digit array to Doodson coefficients. - * D₁ is the τ coefficient (NOT offset). D₂–D₆ are each offset by 5. - * D₇ (90° phase) is negated to convert from IHO XDO convention to the - * Schureman/NOAA convention used by published harmonic constants. + * D₁ is the τ coefficient (NOT offset). D₂–D₇ are each offset by 5. */ export function xdoToCoefficients(xdo: XDO): Coefficients { return [ @@ -104,7 +102,7 @@ export function xdoToCoefficients(xdo: XDO): Coefficients { xdo[3] - 5, // D₄: p xdo[4] - 5, // D₅: N' (used directly, NOT negated) xdo[5] - 5, // D₆: p' (solar perigee) - 5 - xdo[6], // D₇: 90° phase (negated: IHO → Schureman convention) + xdo[6] - 5, // D₇: 90° phase ]; } diff --git a/packages/tide-predictor/test/astronomy/index.test.ts b/packages/tide-predictor/test/astronomy/index.test.ts index 7ff4f110..a362afbe 100644 --- a/packages/tide-predictor/test/astronomy/index.test.ts +++ b/packages/tide-predictor/test/astronomy/index.test.ts @@ -40,7 +40,7 @@ describe("astronomy", () => { expect(result.nu.value).toBeCloseTo(13.028571777192044, 4); expect(result.nu.speed).toBeNull(); - expect(result["T+h-s"].value).toBeCloseTo(268.50435506200392, 4); + expect(result["T+h-s"].value).toBeCloseTo(88.50435506200392, 4); expect(result["T+h-s"].speed).toBeCloseTo(14.492052120843571, 4); expect(result.omega.value).toBeCloseTo(23.436722306067253, 4); diff --git a/packages/tide-predictor/test/constituents/index.test.ts b/packages/tide-predictor/test/constituents/index.test.ts index 13d3ce10..e89915a2 100644 --- a/packages/tide-predictor/test/constituents/index.test.ts +++ b/packages/tide-predictor/test/constituents/index.test.ts @@ -15,7 +15,7 @@ describe("Base constituent definitions", () => { }); it("it prepared constituent M2", () => { - expect(constituents.M2.value(testAstro)).toBeCloseTo(537.008710124, 4); + expect(constituents.M2.value(testAstro)).toBeCloseTo(177.008710124, 4); }); it("computes IHO nodal corrections for M2", () => { diff --git a/packages/tide-predictor/test/harmonics/prediction.test.ts b/packages/tide-predictor/test/harmonics/prediction.test.ts index 72521cce..3ed4bbfb 100644 --- a/packages/tide-predictor/test/harmonics/prediction.test.ts +++ b/packages/tide-predictor/test/harmonics/prediction.test.ts @@ -22,8 +22,8 @@ describe("harmonic prediction", () => { const testPrediction = setUpPrediction(); const results = testPrediction.getTimelinePrediction(); const lastResult = results.pop(); - expect(results[0].level).toBeCloseTo(-1.46903456, 3); - expect(lastResult?.level).toBeCloseTo(2.83490872, 3); + expect(results[0].level).toBeCloseTo(-1.43403692, 3); + expect(lastResult?.level).toBeCloseTo(2.81345665, 3); }); it("it finds high and low tides", () => { @@ -34,7 +34,7 @@ describe("harmonic prediction", () => { .setTimeSpan(startDate, extremesEndDate) .prediction() .getExtremesPrediction(); - expect(results[0].level).toBeCloseTo(-1.67283933, 4); + expect(results[0].level).toBeCloseTo(-1.65723814, 4); const customLabels = { high: "Super high", @@ -59,7 +59,7 @@ describe("harmonic prediction", () => { .setTimeSpan(startDate, extremesEndDate) .prediction({ timeFidelity: 60 }) .getExtremesPrediction(); - expect(results[0].level).toBeCloseTo(-1.67283933, 4); + expect(results[0].level).toBeCloseTo(-1.65723814, 4); }); }); diff --git a/packages/tide-predictor/test/index.test.ts b/packages/tide-predictor/test/index.test.ts index d2b4f60e..ebea7b12 100644 --- a/packages/tide-predictor/test/index.test.ts +++ b/packages/tide-predictor/test/index.test.ts @@ -30,9 +30,9 @@ describe("Tidal station", () => { end: endDate, }); expect(results.length).toBe(37); - expect(results[0].level).toBeCloseTo(-1.46903456, 3); + expect(results[0].level).toBeCloseTo(-1.43403692, 3); const lastResult = results.pop(); - expect(lastResult?.level).toBeCloseTo(2.83490872, 3); + expect(lastResult?.level).toBeCloseTo(2.81345665, 3); }); it("it predicts the tides in a timeline with time fidelity", () => { @@ -42,9 +42,9 @@ describe("Tidal station", () => { timeFidelity: 60, }); expect(results.length).toBe(361); - expect(results[0].level).toBeCloseTo(-1.46903456, 3); + expect(results[0].level).toBeCloseTo(-1.43403692, 3); const lastResult = results.pop(); - expect(lastResult?.level).toBeCloseTo(2.83490872, 3); + expect(lastResult?.level).toBeCloseTo(2.81345665, 3); }); it("it predicts the tidal extremes", () => { @@ -52,7 +52,7 @@ describe("Tidal station", () => { start: startDate, end: endDate, }); - expect(results[0].level).toBeCloseTo(-1.67283933, 4); + expect(results[0].level).toBeCloseTo(-1.65723814, 4); }); it("it predicts the tidal extremes with high fidelity", () => { @@ -61,14 +61,14 @@ describe("Tidal station", () => { end: endDate, timeFidelity: 60, }); - expect(results[0].level).toBeCloseTo(-1.67283933, 4); + expect(results[0].level).toBeCloseTo(-1.65723814, 4); }); it("it fetches a single water level", () => { const result = tidePrediction(mockConstituents).getWaterLevelAtTime({ time: startDate, }); - expect(result.level).toBeCloseTo(-1.46903456, 4); + expect(result.level).toBeCloseTo(-1.434038, 4); }); it("it adds offset phases", () => { @@ -76,6 +76,6 @@ describe("Tidal station", () => { offset: 3, }).getExtremesPrediction({ start: startDate, end: endDate }); - expect(results[0].level).toBeCloseTo(1.32716067, 4); + expect(results[0].level).toBeCloseTo(1.34276186, 4); }); });