Give the ownship marker the same 6-minute COG/SOG projection vector the
AIS targets got in #25, so the skipper's own vessel is no longer the only
one on the Live map without one.
- lib/ownshipVector.ts: pure GeoJSON builder on the shared projectionLine
(never inlined); suppresses on missing COG/SOG and below a documented
0.5 kn noise floor (AIS uses sog > 0 — quantized shipborne data — but
raw device GPS jitters a nonzero SOG with a noise course at rest).
- lib/projectionVector.ts: PROJECTION_VECTOR_MINUTES = 6 as the single
shared length convention; aisGeoJson's AIS_VECTOR_MINUTES now aliases
it so the two vectors can never drift apart.
- BoatMarker: dedicated sc-boat-vector GeoJSON source + line layer in the
AIS vector line-style family (width 1.5, opacity 0.85) but the ownship
blue; per-fix setData draws and clears via the empty-collection
suppression. Applies at the single structural marker render site
(OwnshipMarker), covering both the Live view and the standalone
show-my-position toggle (#104).
- OwnshipMarker forwards fix.sogKn; GPS-only, zero AIS coupling.
Tests pin hand-derived great-circle endpoint literals (computed
independently in Python, cross-checked against flat-earth approximations)
plus every suppression case at both the lib and the render-site level.
Closes #141
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXvHWVP7iALk2rScg3CtkT
Implements #141: the ownship (GPS) boat marker now renders the same 6-minute COG/SOG projection vector the AIS targets got in #25 — the skipper's own vessel is no longer the only one on the Live map without one.
What
lib/ownshipVector.ts(new): pure GeoJSON builder for the ownship vector, built on the sharedprojectionLinefromlib/projectionVector.ts— never inlined (design pin). Returns an empty collection (suppress) when the device reports no COG or no SOG, or SOG is below the noise floor.sog > 0— quantized shipborne SOG, where any nonzero value is real motion — which is no usable threshold for raw device GPS that jitters a small nonzero SOG (with a noise course) at rest. Per the issue, picked and documentedOWNSHIP_VECTOR_MIN_SOG_KN = 0.5(inclusive: exactly 0.5 kn renders).PROJECTION_VECTOR_MINUTES = 6inlib/projectionVector.tsis now the single shared length convention;AIS_VECTOR_MINUTESaliases it — parity by construction, pinned by a test, so the two vectors can never drift apart.BoatMarker: dedicatedsc-boat-vectorGeoJSON source +sc-boat-vector-lineline layer — the AIS vector line-style family (line-width1.5,line-opacity0.85) but the existing ownship blue (BOAT_COLOR), so it cannot read as a traffic vector. No minzoom: a single own vector needs no decluttering, and the skipper's own projection matters most. A per-fix unconditionalsetDataboth draws and clears.OwnshipMarkerforwardsfix.sogKn. Since the AIS: live online traffic overlay with ownship recognition #25 refactor this is the single structural marker render site, so the vector applies on the Live view AND the standalone show-my-position toggle (feat(map): standalone ownship marker toggle, decoupled from Live View (#25) #104) with no duplication.useOwnshipGpspublish path — no new AppState churn.[Unreleased]entry. No new UI strings (line layer only).Tests (TDD, red first)
lib/ownshipVector.test.ts: projection geometry with hand-derived great-circle endpoint literals (computed independently in Python from the movable-type destination formula with R = 3440.065 nm, cross-checked against flat-earth approximations — not read off the implementation); every suppression case; the inclusive 0.5 kn boundary; the shared-constant parity pins.components/BoatMarker.test.tsx(new): render-site wiring against a fake map — source/layer setup with the pinned paint literals, draw → suppress → re-draw transitions per fix, teardown. The pre-existing marker/accuracy-circle behavior stays "not unit-tested" per the component's own note.components/OwnshipMarker.test.tsx:sogKnforwarded verbatim (incl. null).Verification
npm --prefix app run typecheck/lint: clean.npm --prefix app run test: green (see PR checks).navigator.geolocation): vector renders SE at COG 135 @ 6.0 kn, follows COG to due east at 90, disappears at 0.19 kn while the marker stays, disappears when the device reports no COG (marker falls back to north), and renders identically on the Live tab surface (AIS off, key-less — zero sockets).Deferred-minor assessment (dual
watchPosition, from the #104 review)Assessed, not consolidated: with Live view and the ownship toggle both active there are two
navigator.geolocation.watchPositionsubscriptions (LiveView's readout watch +useOwnshipGps). Consolidating needs a refcounted shared watch inservices/geolocation.tsplus rewiring both consumers' injectable test seams (~3 source + 3 test files) — a medium-size, lifecycle-sensitive change for negligible benefit (the browser runs one position engine regardless of watcher count). Left as is.Closes #141
🤖 Generated with Claude Code
https://claude.ai/code/session_01UXvHWVP7iALk2rScg3CtkT