From 2bcd27c1e0adcbed777e4ec0e748e8c9bebd9cd4 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sat, 24 Oct 2026 00:23:44 -0400 Subject: [PATCH 1/3] fix(ais): keep a tracked vessel's motion when static data arrives --- server/providers/vessels/ais-store.js | 5 ++++ src/data/aisStreamSentinels.test.mjs | 36 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/server/providers/vessels/ais-store.js b/server/providers/vessels/ais-store.js index 297081b38..a456e19f4 100644 --- a/server/providers/vessels/ais-store.js +++ b/server/providers/vessels/ais-store.js @@ -51,6 +51,11 @@ export function ingestAisStreamEnvelope(envelope) { }; _aisStreamStatic.set(mmsi, staticData); mergeAisStaticIntoLiveVessel(mmsi, staticData); + // AISStream repeats the last known position in a static message's + // MetaData, but the message has no speed, course or heading and is not a + // new fix. Rebuilding a tracked vessel from it would blank its motion and + // restamp its position time, so only merge the static fields. + if (_aisStreamVessels.has(mmsi)) return true; } const lat = numberValue( diff --git a/src/data/aisStreamSentinels.test.mjs b/src/data/aisStreamSentinels.test.mjs index 6595b795e..49572c05d 100644 --- a/src/data/aisStreamSentinels.test.mjs +++ b/src/data/aisStreamSentinels.test.mjs @@ -63,3 +63,39 @@ test('the fastest encodable reading below the unavailable code is kept', () => { assert.equal(row.speed, 102.2); assert.equal(row.course, 359.9); }); + +test('a static data report keeps the motion and fix time of a tracked vessel', () => { + ingestAisStreamEnvelope( + positionReport('244690000', { Sog: 12.4, Cog: 86.7, TrueHeading: 87 }), + ); + // AISStream's MetaData carries the last known position on every message, + // static data included. + assert.equal( + ingestAisStreamEnvelope({ + MessageType: 'ShipStaticData', + Message: { + ShipStaticData: { + UserID: 244690000, + Name: 'EXAMPLE VESSEL', + Type: 70, + Destination: 'ROTTERDAM', + ImoNumber: 9876543, + }, + }, + MetaData: { + MMSI: '244690000', + latitude: 53.40879, + longitude: 6.19741, + time_utc: '2026-09-17 20:05:00.000000000 +0000 UTC', + }, + }), + true, + ); + const row = rowFor('244690000'); + assert.equal(row.speed, 12.4); + assert.equal(row.course, 86.7); + assert.equal(row.heading, 87); + assert.equal(row.last_position_UTC, '2026-09-17T20:00:00.000Z'); + assert.equal(row.destination, 'ROTTERDAM'); + assert.equal(row.imo, '9876543'); +}); From b8f8a8685dea49d6cb8eba1fcb2e09c940d644f0 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sat, 24 Oct 2026 00:24:08 -0400 Subject: [PATCH 2/3] docs(changelog): add AIS static data fragment --- changelog.d/1351-ais-static-keeps-motion.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/1351-ais-static-keeps-motion.md diff --git a/changelog.d/1351-ais-static-keeps-motion.md b/changelog.d/1351-ais-static-keeps-motion.md new file mode 100644 index 000000000..78b0804f7 --- /dev/null +++ b/changelog.d/1351-ais-static-keeps-motion.md @@ -0,0 +1,3 @@ +- Keep a tracked vessel's speed, course, heading and fix time when AISStream + static data arrives; the static message only merges name, type, destination + and IMO (rudycelekli, #1351). From 77b485d619e79fa59994a07909c78de4ff023590 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sat, 24 Oct 2026 21:10:12 -0700 Subject: [PATCH 3/3] fix(ais): refresh changed static metadata without advancing position --- changelog.d/1351-ais-static-keeps-motion.md | 3 + docs/CURRENT-STATE.md | 8 + server/providers/vessels/ais-store.js | 60 ++++++-- src/data/aisStreamSentinels.test.mjs | 159 ++++++++++++++++++++ 4 files changed, 220 insertions(+), 10 deletions(-) diff --git a/changelog.d/1351-ais-static-keeps-motion.md b/changelog.d/1351-ais-static-keeps-motion.md index 78b0804f7..52dd9bede 100644 --- a/changelog.d/1351-ais-static-keeps-motion.md +++ b/changelog.d/1351-ais-static-keeps-motion.md @@ -1,3 +1,6 @@ - Keep a tracked vessel's speed, course, heading and fix time when AISStream static data arrives; the static message only merges name, type, destination and IMO (rudycelekli, #1351). +- Apply changed static metadata immediately, retaining known fields when a + later static report omits them, without refreshing the vessel's position + (samehkhamis, #1351). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index ede889fdc..282181df3 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -459,6 +459,14 @@ than a reading. Heading keeps its existing separate check for its own sentinel. Genuine readings are unchanged, including a stopped vessel's zero and the highest speed and course the fields can encode. +For an already tracked vessel, AISStream static messages update name, type, +destination and IMO without changing its position, motion, fix timestamps, +position freshness or trail. Non-empty static values replace earlier values; +omitted, empty or unavailable fields retain known metadata across Class B's +separate reports and in the cache used by the next position update. Position +reports still prefer their own metadata name. Invalid Class B parts are ignored; +absent fields prefer the latest live value over the static cache. + Native `