fix: live-update GPS position marker and distance during tracking
All checks were successful
CI / update (push) Successful in 2m33s

- Make map variables reactive ($state) so effects fire when map initializes
- Split single effect into polyline update + marker/view tracking
- Marker now always follows latestPoint instead of staying at start position
- Reset prevTrackLen on GPS restart to avoid skipping points
- Hide marker until real GPS position arrives
- Round saved distance to nearest 10m to avoid long floating-point values
This commit is contained in:
2026-03-26 09:56:15 +01:00
parent 8b63812734
commit fcccd4e060

View File

@@ -101,11 +101,11 @@
} }
/** @type {any} */ /** @type {any} */
let liveMap = null; let liveMap = $state(null);
/** @type {any} */ /** @type {any} */
let livePolyline = null; let livePolyline = $state(null);
/** @type {any} */ /** @type {any} */
let liveMarker = null; let liveMarker = $state(null);
/** @type {any} */ /** @type {any} */
let leafletLib = null; let leafletLib = null;
let prevTrackLen = 0; let prevTrackLen = 0;
@@ -117,13 +117,13 @@
destroy() { destroy() {
if (liveMap) { if (liveMap) {
liveMap.remove(); liveMap.remove();
}
liveMap = null; liveMap = null;
livePolyline = null; livePolyline = null;
liveMarker = null; liveMarker = null;
leafletLib = null; leafletLib = null;
prevTrackLen = 0; prevTrackLen = 0;
} }
}
}; };
} }
@@ -140,25 +140,28 @@
}).addTo(liveMap); }).addTo(liveMap);
livePolyline = leafletLib.polyline([], { color: '#88c0d0', weight: 3 }).addTo(liveMap); livePolyline = leafletLib.polyline([], { color: '#88c0d0', weight: 3 }).addTo(liveMap);
liveMarker = leafletLib.circleMarker([0, 0], { liveMarker = leafletLib.circleMarker([0, 0], {
radius: 6, fillColor: '#a3be8c', fillOpacity: 1, color: '#fff', weight: 2 radius: 6, fillColor: '#a3be8c', fillOpacity: 1, color: '#fff', weight: 2, opacity: 0, fillOpacity: 0
}).addTo(liveMap); }).addTo(liveMap);
if (gps.track.length > 0) { if (gps.track.length > 0) {
// Restore existing trail on the polyline
if (gpsStarted) {
const pts = gps.track.map((/** @type {any} */ p) => [p.lat, p.lng]); const pts = gps.track.map((/** @type {any} */ p) => [p.lat, p.lng]);
livePolyline.setLatLngs(pts); livePolyline.setLatLngs(pts);
liveMarker.setLatLng(pts[pts.length - 1]); }
liveMap.setView(pts[pts.length - 1], 16); // Center on latest point — the marker $effect will also kick in
const last = gps.track[gps.track.length - 1];
liveMap.setView([last.lat, last.lng], 16);
liveMarker.setLatLng([last.lat, last.lng]);
prevTrackLen = gps.track.length; prevTrackLen = gps.track.length;
} else { } else {
// No track yet — show fallback until GPS kicks in // No track yet — get current position to center the map
liveMap.setView([51.5, 10], 16); liveMap.setView([51.5, 10], 5);
if ('geolocation' in navigator) { if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(pos) => { (pos) => {
if (liveMap) { if (liveMap) {
const ll = [pos.coords.latitude, pos.coords.longitude]; liveMap.setView([pos.coords.latitude, pos.coords.longitude], 16);
liveMap.setView(ll, 16);
liveMarker.setLatLng(ll);
} }
}, },
() => {}, () => {},
@@ -204,25 +207,33 @@
} }
}); });
// Update polyline incrementally when new track points arrive
$effect(() => { $effect(() => {
const len = gps.track.length; const len = gps.track.length;
if (len > prevTrackLen && liveMap && gps.latestPoint) { if (len > prevTrackLen && liveMap && livePolyline && gpsStarted) {
if (gpsStarted) {
// Only draw the trail once the workout has actually started
for (let i = prevTrackLen; i < len; i++) { for (let i = prevTrackLen; i < len; i++) {
const p = gps.track[i]; const p = gps.track[i];
livePolyline.addLatLng([p.lat, p.lng]); livePolyline.addLatLng([p.lat, p.lng]);
} }
} }
// Always update the position marker // Always sync prevTrackLen even if we didn't draw (e.g. pre-start)
const pt = [gps.latestPoint.lat, gps.latestPoint.lng]; if (len > prevTrackLen) {
liveMarker.setLatLng(pt);
const zoom = liveMap.getZoom() || 16;
liveMap.setView(pt, zoom);
prevTrackLen = len; prevTrackLen = len;
} }
}); });
// Always keep marker and map view centered on the latest GPS position
$effect(() => {
const pt = gps.latestPoint;
if (pt && liveMap && liveMarker) {
const ll = [pt.lat, pt.lng];
liveMarker.setLatLng(ll);
liveMarker.setStyle({ opacity: 1, fillOpacity: 1 });
const zoom = liveMap.getZoom() || 16;
liveMap.setView(ll, zoom);
}
});
/** Check if any exercise in the workout is cardio */ /** Check if any exercise in the workout is cardio */
function hasCardioExercise() { function hasCardioExercise() {
return workout.exercises.some((/** @type {any} */ e) => { return workout.exercises.some((/** @type {any} */ e) => {
@@ -285,6 +296,7 @@
// so the native service resets time/distance to zero // so the native service resets time/distance to zero
await gps.stop(); await gps.stop();
gps.reset(); gps.reset();
prevTrackLen = 0;
} }
const started = await gps.start(getVoiceGuidanceConfig()); const started = await gps.start(getVoiceGuidanceConfig());
if (started) { if (started) {
@@ -317,7 +329,7 @@
if (wasGpsMode && gpsTrack.length >= 2) { if (wasGpsMode && gpsTrack.length >= 2) {
// GPS workout: create a cardio exercise entry with the track attached, // GPS workout: create a cardio exercise entry with the track attached,
// just like a manually-added workout with GPX upload // just like a manually-added workout with GPX upload
const filteredDistance = trackDistance(gpsTrack); const filteredDistance = Math.round(trackDistance(gpsTrack) * 100) / 100;
const durationMin = (gpsTrack[gpsTrack.length - 1].timestamp - gpsTrack[0].timestamp) / 60000; const durationMin = (gpsTrack[gpsTrack.length - 1].timestamp - gpsTrack[0].timestamp) / 60000;
const exerciseId = ACTIVITY_EXERCISE_MAP[actType ?? 'running'] ?? 'running'; const exerciseId = ACTIVITY_EXERCISE_MAP[actType ?? 'running'] ?? 'running';
const exerciseName = getExerciseById(exerciseId)?.name ?? exerciseId; const exerciseName = getExerciseById(exerciseId)?.name ?? exerciseId;
@@ -343,7 +355,7 @@
// Manual workout: attach GPS to cardio exercises // Manual workout: attach GPS to cardio exercises
const workoutStart = new Date(sessionData.startTime).getTime(); const workoutStart = new Date(sessionData.startTime).getTime();
const filteredTrack = gpsTrack.filter((/** @type {any} */ p) => p.timestamp >= workoutStart); const filteredTrack = gpsTrack.filter((/** @type {any} */ p) => p.timestamp >= workoutStart);
const filteredDistance = trackDistance(filteredTrack); const filteredDistance = Math.round(trackDistance(filteredTrack) * 100) / 100;
if (filteredTrack.length > 0) { if (filteredTrack.length > 0) {
for (const ex of sessionData.exercises) { for (const ex of sessionData.exercises) {