Draw a LiveTrack session's planned course alongside the actual track
CI / check (push) Successful in 19s
CI / deploy (push) Successful in 13s

Garmin's own LiveTrack page already shows both when the activity was
started from a saved course — the session payload carries the course's
geoPoints right alongside the track points (LiveTrackClient found it
nested under the same React Query cache the session/points come from).

Rendered as a second, dashed LineString distinct from the actual
track's own line ("kind": "actual" vs "planned" on each GeoJSON
feature) — muted gray rather than matching Garmin's own purple-for-
planned, since this app's actual track is already purple and reusing
it would make the two indistinguishable.
This commit is contained in:
randy committed 2026-09-20 16:54:24 +02:00
1 parent 8eada07869
commit 3106f8c103
4 files changed
+66 -21

No files matched your search

+17 -1
View File
@@ -5,6 +5,11 @@ import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
const SESSIONS_URL = "/api/livetrack/sessions";
const TRACK_SOURCE = "livetrack-track";
const TRACK_COLOR = "#7209b7";
// The planned route (only present when the activity was started from a
// saved Garmin course, see LiveTrackClient#fetch) — dashed and muted so it
// reads as a reference line, not more recorded data next to the actual
// track's own color.
const PLANNED_COLOR = "#888";
// Garmin devices post a new point roughly every 15s (postTrackPointFrequency
// in the session data) — polling twice that often would just re-fetch the
// same 400+KB page for nothing most of the time.
@@ -171,11 +176,22 @@ export class LiveTrackPanelControl {
if (this._map.getSource(TRACK_SOURCE)) return;
this._map.addSource(TRACK_SOURCE, { type: "geojson", data: geojson });
// Added before the actual track's own line layer so it paints
// underneath — the recorded track is the thing that actually matters
// when the two overlap.
this._map.addLayer({
id: `${TRACK_SOURCE}-planned`,
type: "line",
source: TRACK_SOURCE,
filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "kind"], "planned"]],
layout: { "line-cap": "round", "line-join": "round" },
paint: { "line-color": PLANNED_COLOR, "line-width": 2, "line-dasharray": [2, 2] },
});
this._map.addLayer({
id: `${TRACK_SOURCE}-line`,
type: "line",
source: TRACK_SOURCE,
filter: ["==", ["geometry-type"], "LineString"],
filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "kind"], "actual"]],
layout: { "line-cap": "round", "line-join": "round" },
paint: { "line-color": TRACK_COLOR, "line-width": 3 },
});