diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 2d06ae6e..b75e1c99 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -350,6 +350,15 @@ export class GPXFile extends GPXTreeNode{ }); } + createArtificialTimestamps(startTime: Date, totalTime: number, trackIndex?: number, segmentIndex?: number) { + let lastPoint = undefined; + this.trk.forEach((track, index) => { + if (trackIndex === undefined || trackIndex === index) { + track.createArtificialTimestamps(startTime, totalTime, lastPoint, segmentIndex); + } + }); + } + setStyle(style: LineStyleExtension) { this.trk.forEach((track) => { track.setStyle(style); @@ -581,6 +590,17 @@ export class Track extends GPXTreeNode { }); } + createArtificialTimestamps(startTime: Date, totalTime: number, lastPoint: TrackPoint | undefined, segmentIndex?: number) { + this.trkseg.forEach((segment, index) => { + if (segmentIndex === undefined || segmentIndex === index) { + segment.createArtificialTimestamps(startTime, totalTime, lastPoint); + if (segment.trkpt.length > 0) { + lastPoint = segment.trkpt[segment.trkpt.length - 1]; + } + } + }); + } + setStyle(style: LineStyleExtension, force: boolean = true) { if (!this.extensions) { this.extensions = {}; @@ -944,6 +964,14 @@ export class TrackSegment extends GPXTreeLeaf { this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well } } + + createArtificialTimestamps(startTime: Date, totalTime: number, lastPoint: TrackPoint | undefined) { + let og = getOriginal(this); // Read as much as possible from the original object because it is faster + let slope = og._computeSlope(); + let trkpt = withArtificialTimestamps(og.trkpt, totalTime, lastPoint, startTime, slope); + this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well + } + setHidden(hidden: boolean) { this._data.hidden = hidden; } @@ -1442,6 +1470,30 @@ function withShiftedAndCompressedTimestamps(points: TrackPoint[], speed: number, }); } +function withArtificialTimestamps(points: TrackPoint[], totalTime: number, lastPoint: TrackPoint | undefined, startTime: Date, slope: number[]): TrackPoint[] { + let weight = []; + let totalWeight = 0; + + for (let i = 0; i < points.length - 1; i++) { + let dist = distance(points[i].getCoordinates(), points[i + 1].getCoordinates()); + let w = dist * (0.5 + 1 / (1 + Math.exp(- 0.2 * slope[i]))); + weight.push(w); + totalWeight += w; + } + + let last = lastPoint; + return points.map((point, i) => { + let pt = point.clone(); + if (i === 0) { + pt.time = lastPoint?.time ?? startTime; + } else { + pt.time = new Date(last.time.getTime() + totalTime * 1000 * weight[i - 1] / totalWeight); + } + last = pt; + 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); diff --git a/website/src/lib/components/toolbar/tools/Time.svelte b/website/src/lib/components/toolbar/tools/Time.svelte index e26774fc..ac4f070e 100644 --- a/website/src/lib/components/toolbar/tools/Time.svelte +++ b/website/src/lib/components/toolbar/tools/Time.svelte @@ -1,363 +1,396 @@
-
-
-
- -
- {#if $velocityUnits === 'speed'} - - - {#if $distanceUnits === 'imperial'} - {$_('units.miles_per_hour')} - {:else if $distanceUnits === 'metric'} - {$_('units.kilometers_per_hour')} - {:else if $distanceUnits === 'nautical'} - {$_('units.knots')} - {/if} - - {:else} - - - {#if $distanceUnits === 'imperial'} - {$_('units.minutes_per_mile')} - {:else if $distanceUnits === 'metric'} - {$_('units.minutes_per_kilometer')} - {:else if $distanceUnits === 'nautical'} - {$_('units.minutes_per_nautical_mile')} - {/if} - - {/if} -
-
-
- - -
-
- -
- { - await tick(); - updateEnd(); - }} - /> - -
- -
- { - await tick(); - updateStart(); - }} - /> - -
- {#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined} - - {/if} -
-
- - -
- - {#if canUpdate} - {$_('toolbar.time.help')} - {:else} - {$_('toolbar.time.help_invalid_selection')} - {/if} - + let item = $selection.getSelected()[0]; + let fileId = item.getFileId(); + dbUtils.applyToFile(fileId, (file) => { + if (item instanceof ListFileItem) { + if (artificial) { + file.createArtificialTimestamps( + getDate(startDate, startTime), + movingTime + ); + } else { + file.changeTimestamps( + getDate(startDate, startTime), + effectiveSpeed, + ratio + ); + } + } else if (item instanceof ListTrackItem) { + if (artificial) { + file.createArtificialTimestamps( + getDate(startDate, startTime), + movingTime, + item.getTrackIndex() + ); + } else { + file.changeTimestamps( + getDate(startDate, startTime), + effectiveSpeed, + ratio, + item.getTrackIndex() + ); + } + } else if (item instanceof ListTrackSegmentItem) { + if (artificial) { + file.createArtificialTimestamps( + getDate(startDate, startTime), + movingTime, + item.getTrackIndex(), + item.getSegmentIndex() + ); + } else { + file.changeTimestamps( + getDate(startDate, startTime), + effectiveSpeed, + ratio, + item.getTrackIndex(), + item.getSegmentIndex() + ); + } + } + }); + }} + > + + {$_('toolbar.time.update')} + + +
+ + {#if canUpdate} + {$_('toolbar.time.help')} + {:else} + {$_('toolbar.time.help_invalid_selection')} + {/if} +