From 5f9d609a044a874a98276c14ac77567032294a8f Mon Sep 17 00:00:00 2001 From: vcoppe Date: Tue, 18 Jun 2024 12:35:24 +0200 Subject: [PATCH] time data management --- gpx/src/gpx.ts | 112 ++++++++++++++++- .../lib/components/ElevationProfile.svelte | 35 ++++-- .../src/lib/components/GPXStatistics.svelte | 4 +- .../lib/components/toolbar/tools/Time.svelte | 116 ++++++++++++++---- .../toolbar/tools/routing/RoutingControls.ts | 35 +++++- .../ui/time-picker/TimePicker.svelte | 7 +- website/src/lib/db.ts | 20 ++- website/src/lib/stores.ts | 4 + website/src/lib/units.ts | 2 +- website/src/locales/en.json | 4 +- 10 files changed, 287 insertions(+), 52 deletions(-) diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index e53ca44f..f5969109 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -205,11 +205,11 @@ export class GPXFile extends GPXTreeNode{ return [result, removed]; } - replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[]) { + replaceTrackPoints(trackIndex: number, segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) { return produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trk = og.trk.slice(); - trk[trackIndex] = trk[trackIndex].replaceTrackPoints(segmentIndex, start, end, points); + trk[trackIndex] = trk[trackIndex].replaceTrackPoints(segmentIndex, start, end, points, speed, startTime); draft.trk = freeze(trk); // Pre-freeze the array, faster as well }); } @@ -310,6 +310,21 @@ export class GPXFile extends GPXTreeNode{ } }); } + + changeTimestamps(startTime: Date, speed: number, ratio: number, trackIndex?: number, segmentIndex?: number) { + let lastPoint = undefined; + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trk = og.trk.map((track, index) => { + if (trackIndex === undefined || trackIndex === index) { + return track.changeTimestamps(startTime, speed, ratio, lastPoint, segmentIndex); + } else { + return track; + } + }); + draft.trk = freeze(trk); // Pre-freeze the array, faster as well + }); + } }; // A class that represents a Track in a GPX file @@ -405,11 +420,11 @@ export class Track extends GPXTreeNode { return [result, removed]; } - replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[]) { + replaceTrackPoints(segmentIndex: number, start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) { return produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trkseg = og.trkseg.slice(); - trkseg[segmentIndex] = trkseg[segmentIndex].replaceTrackPoints(start, end, points); + trkseg[segmentIndex] = trkseg[segmentIndex].replaceTrackPoints(start, end, points, speed, startTime); draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well }); } @@ -473,6 +488,25 @@ export class Track extends GPXTreeNode { draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well }); } + + changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint, segmentIndex?: number) { + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + let trkseg = og.trkseg.slice(); + trkseg = trkseg.map((segment, index) => { + if (segmentIndex === undefined || segmentIndex === index) { + let seg = segment.changeTimestamps(startTime, speed, ratio, lastPoint); + if (seg.trkpt.length > 0) { + lastPoint = seg.trkpt[seg.trkpt.length - 1]; + } + return seg; + } else { + return segment; + } + }); + draft.trkseg = freeze(trkseg); // Pre-freeze the array, faster as well + }); + } }; // A class that represents a TrackSegment in a GPX file @@ -634,10 +668,33 @@ export class TrackSegment extends GPXTreeLeaf { } // Producers - replaceTrackPoints(start: number, end: number, points: TrackPoint[]) { + replaceTrackPoints(start: number, end: number, points: TrackPoint[], speed?: number, startTime?: Date) { return produce(this, (draft) => { let og = getOriginal(draft); // Read as much as possible from the original object because it is faster let trkpt = og.trkpt.slice(); + + if (speed !== undefined || (trkpt.length > 0 && trkpt[0].time !== undefined)) { + if (start > 0 && trkpt[0].time === undefined) { + trkpt.splice(0, 0, withTimestamps(trkpt.splice(0, start), speed, undefined, startTime)); + } + if (points.length > 0) { + let last = start > 0 ? trkpt[start - 1] : undefined; + if (points[0].time === undefined || (points.length > 1 && points[1].time === undefined)) { + points = withTimestamps(points, speed, last, startTime); + } else if (last !== undefined && points[0].time < last.time) { + points = withShiftedAndCompressedTimestamps(points, speed, 1, last); + } + } + if (end < trkpt.length - 1) { + let last = points.length > 0 ? points[points.length - 1] : start > 0 ? trkpt[start - 1] : undefined; + if (trkpt[end + 1].time === undefined) { + trkpt.splice(end + 1, 0, withTimestamps(trkpt.splice(end + 1), speed, last, startTime)); + } else if (last !== undefined && trkpt[end + 1].time < last.time) { + points = withShiftedAndCompressedTimestamps(points, speed, 1, last); + } + } + } + trkpt.splice(start, end - start + 1, ...points); draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well }); @@ -690,6 +747,23 @@ export class TrackSegment extends GPXTreeLeaf { draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well }); } + + changeTimestamps(startTime: Date, speed: number, ratio: number, lastPoint?: TrackPoint) { + if (lastPoint === undefined && this.trkpt.length > 0) { + lastPoint = this.trkpt[0].clone(); + lastPoint.time = startTime; + } + return produce(this, (draft) => { + let og = getOriginal(draft); // Read as much as possible from the original object because it is faster + if (og.trkpt.length > 0 && og.trkpt[0].time === undefined) { + let trkpt = withTimestamps(og.trkpt, speed, lastPoint, startTime); + draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } else { + let trkpt = withShiftedAndCompressedTimestamps(og.trkpt, speed, ratio, lastPoint); + draft.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + }); + } }; export class TrackPoint { @@ -1039,6 +1113,34 @@ function distanceWindowSmoothingWithDistanceAccumulator(points: ReadonlyArray index > 0 ? distance(points[index - 1].getCoordinates(), points[index].getCoordinates()) : 0, compute, (index) => distance(points[index].getCoordinates(), points[index + 1].getCoordinates())); } +function withTimestamps(points: TrackPoint[], speed: number, lastPoint: TrackPoint | undefined, startTime?: Date): TrackPoint[] { + let last = lastPoint; + if (last === undefined) { + last = points[0].clone(); + last.time = startTime; + } + return points.map((point) => { + let time = getTimestamp(last, point, speed); + last = point.clone(); + last.time = time; + return last; + }); +} + +function withShiftedAndCompressedTimestamps(points: TrackPoint[], speed: number, ratio: number, lastPoint: TrackPoint): TrackPoint[] { + let start = getTimestamp(lastPoint, points[0], speed); + return points.map((point) => { + let pt = point.clone(); + pt.time = new Date(start.getTime() + ratio * (point.time.getTime() - points[0].time.getTime())); + return pt; + }); +} + +function getTimestamp(a: TrackPoint, b: TrackPoint, speed: number): Date { + let dist = distance(a.getCoordinates(), b.getCoordinates()) / 1000; + return new Date(a.time.getTime() + 1000 * 3600 * dist / speed); +} + function getOriginal(obj: any): any { while (isDraft(obj)) { obj = original(obj); diff --git a/website/src/lib/components/ElevationProfile.svelte b/website/src/lib/components/ElevationProfile.svelte index 0764465d..445b99d5 100644 --- a/website/src/lib/components/ElevationProfile.svelte +++ b/website/src/lib/components/ElevationProfile.svelte @@ -205,6 +205,7 @@ grid: { display: false }, + reverse: () => id === 'speed' && $velocityUnits === 'pace', display: false }; } @@ -256,17 +257,26 @@ function getIndex(evt) { const points = chart.getElementsAtEventForMode( evt, - 'index', + 'x', { intersect: false }, true ); const rect = canvas.getBoundingClientRect(); - return ( - points.find((point) => point.datasetIndex === 0)?.element.raw.index ?? - (evt.x - rect.left <= chart.chartArea.left ? 0 : get(gpxStatistics).local.points.length - 1) - ); + + if (points.length === 0) { + return evt.x - rect.left <= chart.chartArea.left + ? 0 + : get(gpxStatistics).local.points.length - 1; + } + let point = points.find((point) => point.element.raw); + if (point) { + return point.element.raw.index; + } else { + console.log(points); + return points[0].index; + } } canvas.addEventListener('pointerdown', (evt) => { dragging = true; @@ -323,7 +333,8 @@ data: data.local.points.map((point, index) => { return { x: getConvertedDistance(data.local.distance.total[index]), - y: getConvertedVelocity(data.local.speed[index]) + y: getConvertedVelocity(data.local.speed[index]), + index: index }; }), normalized: true, @@ -335,7 +346,8 @@ data: data.local.points.map((point, index) => { return { x: getConvertedDistance(data.local.distance.total[index]), - y: point.getHeartRate() + y: point.getHeartRate(), + index: index }; }), normalized: true, @@ -347,7 +359,8 @@ data: data.local.points.map((point, index) => { return { x: getConvertedDistance(data.local.distance.total[index]), - y: point.getCadence() + y: point.getCadence(), + index: index }; }), normalized: true, @@ -359,7 +372,8 @@ data: data.local.points.map((point, index) => { return { x: getConvertedDistance(data.local.distance.total[index]), - y: getConvertedTemperature(point.getTemperature()) + y: getConvertedTemperature(point.getTemperature()), + index: index }; }), normalized: true, @@ -371,7 +385,8 @@ data: data.local.points.map((point, index) => { return { x: getConvertedDistance(data.local.distance.total[index]), - y: point.getPower() + y: point.getPower(), + index: index }; }), normalized: true, diff --git a/website/src/lib/components/GPXStatistics.svelte b/website/src/lib/components/GPXStatistics.svelte index 6f3839e9..942c334a 100644 --- a/website/src/lib/components/GPXStatistics.svelte +++ b/website/src/lib/components/GPXStatistics.svelte @@ -3,7 +3,7 @@ import Tooltip from '$lib/components/Tooltip.svelte'; import WithUnits from '$lib/components/WithUnits.svelte'; - import { gpxStatistics, slicedGPXStatistics, currentTool, Tool } from '$lib/stores'; + import { gpxStatistics, slicedGPXStatistics } from '$lib/stores'; import { settings } from '$lib/db'; import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from 'lucide-svelte'; @@ -25,7 +25,7 @@ @@ -244,7 +269,7 @@ on:change={updateStart} /> - {#if $gpxStatistics.global.time.total === 0 || $gpxStatistics.global.time.total === undefined} + {#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}