feat(vessels): key vessels by their source's stable id, and share a selected vessel

AIS Vessels are keyed by the id their live source gives, with the MMSI as an
attribute: an MMSI can be shared, spoofed or reused, so two vessels with one
MMSI are now two vessels. Looking a vessel up or selecting it by MMSI still
finds it.

A share link made while a vessel is selected opens that vessel. A UUID vessel
id travels as 16 bytes in a new thing field (13). A shared thing that is not
reporting now says so in a sentence per kind, with its id (ICAO, NORAD, MMSI
or vessel UUID) on a second line.
This commit is contained in:
Sameh Khamis committed 2026-11-10 18:12:53 -08:00
1 parent 57ff81f723
commit cbbf55cacd
74 files changed
+2481 -586

No files matched your search

+14 -6
View File
@@ -279,6 +279,13 @@ export function createSnapshotTimeline({
} else keepUp(timeMs, state);
}
/** Load the time shown afresh. */
function reloadShown() {
if (!started || clock.isLive()) return;
failedUntil = -Infinity;
jumpTo(clock.getState().timeMs);
}
return {
/** The source's timeline while the layer follows the clock, else null. */
get timeline() {
@@ -304,16 +311,17 @@ export function createSnapshotTimeline({
clock.register({ id, coverage: () => span() }),
clock.subscribe(follow),
];
const unsubscribe = timeline.subscribe?.(() => clock.coverageChanged());
// A source tells of new coverage, or of a better answer for times it
// has answered; paused, a time already shown is loaded again.
const unsubscribe = timeline.subscribe?.(() => {
clock.coverageChanged();
if (!clock.isPlaying() && lead !== null && !inflight) reloadShown();
});
if (typeof unsubscribe === 'function') releases.push(unsubscribe);
follow(clock.getState());
},
/** Load the time shown afresh, as when the area the layer asks for moved. */
reload() {
if (!started || clock.isLive()) return;
failedUntil = -Infinity;
jumpTo(clock.getState().timeMs);
},
reload: () => reloadShown(),
/** The layer is off: stop following and drop what loads. */
stop() {
if (!started) return;