mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 23:53:25 +00:00
realistic timestamps option
This commit is contained in:
@@ -350,6 +350,15 @@ export class GPXFile extends GPXTreeNode<Track>{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
setStyle(style: LineStyleExtension) {
|
||||||
this.trk.forEach((track) => {
|
this.trk.forEach((track) => {
|
||||||
track.setStyle(style);
|
track.setStyle(style);
|
||||||
@@ -581,6 +590,17 @@ export class Track extends GPXTreeNode<TrackSegment> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
setStyle(style: LineStyleExtension, force: boolean = true) {
|
||||||
if (!this.extensions) {
|
if (!this.extensions) {
|
||||||
this.extensions = {};
|
this.extensions = {};
|
||||||
@@ -944,6 +964,14 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
|
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) {
|
setHidden(hidden: boolean) {
|
||||||
this._data.hidden = hidden;
|
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 {
|
function getTimestamp(a: TrackPoint, b: TrackPoint, speed: number): Date {
|
||||||
let dist = distance(a.getCoordinates(), b.getCoordinates()) / 1000;
|
let dist = distance(a.getCoordinates(), b.getCoordinates()) / 1000;
|
||||||
return new Date(a.time.getTime() + 1000 * 3600 * dist / speed);
|
return new Date(a.time.getTime() + 1000 * 3600 * dist / speed);
|
||||||
|
@@ -1,363 +1,396 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DatePicker from '$lib/components/ui/date-picker/DatePicker.svelte';
|
import DatePicker from '$lib/components/ui/date-picker/DatePicker.svelte';
|
||||||
import { Input } from '$lib/components/ui/input';
|
import { Input } from '$lib/components/ui/input';
|
||||||
import { Label } from '$lib/components/ui/label/index.js';
|
import { Label } from '$lib/components/ui/label/index.js';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||||
import TimePicker from '$lib/components/ui/time-picker/TimePicker.svelte';
|
import TimePicker from '$lib/components/ui/time-picker/TimePicker.svelte';
|
||||||
import { dbUtils, settings } from '$lib/db';
|
import { dbUtils, settings } from '$lib/db';
|
||||||
import { gpxStatistics } from '$lib/stores';
|
import { gpxStatistics } from '$lib/stores';
|
||||||
import {
|
import {
|
||||||
distancePerHourToSecondsPerDistance,
|
distancePerHourToSecondsPerDistance,
|
||||||
getConvertedVelocity,
|
getConvertedVelocity,
|
||||||
milesToKilometers,
|
milesToKilometers,
|
||||||
nauticalMilesToKilometers
|
nauticalMilesToKilometers
|
||||||
} from '$lib/units';
|
} from '$lib/units';
|
||||||
import { CalendarDate, type DateValue } from '@internationalized/date';
|
import { CalendarDate, type DateValue } from '@internationalized/date';
|
||||||
import { CalendarClock, CirclePlay, CircleStop, CircleX, Timer, Zap } from 'lucide-svelte';
|
import { CalendarClock, CirclePlay, CircleStop, CircleX, Timer, Zap } from 'lucide-svelte';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { _, locale } from 'svelte-i18n';
|
import { _, locale } from 'svelte-i18n';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { selection } from '$lib/components/file-list/Selection';
|
import { selection } from '$lib/components/file-list/Selection';
|
||||||
import {
|
import {
|
||||||
ListFileItem,
|
ListFileItem,
|
||||||
ListRootItem,
|
ListRootItem,
|
||||||
ListTrackItem,
|
ListTrackItem,
|
||||||
ListTrackSegmentItem
|
ListTrackSegmentItem
|
||||||
} from '$lib/components/file-list/FileList';
|
} from '$lib/components/file-list/FileList';
|
||||||
import Help from '$lib/components/Help.svelte';
|
import Help from '$lib/components/Help.svelte';
|
||||||
|
|
||||||
let startDate: DateValue | undefined = undefined;
|
let startDate: DateValue | undefined = undefined;
|
||||||
let startTime: string | undefined = undefined;
|
let startTime: string | undefined = undefined;
|
||||||
let endDate: DateValue | undefined = undefined;
|
let endDate: DateValue | undefined = undefined;
|
||||||
let endTime: string | undefined = undefined;
|
let endTime: string | undefined = undefined;
|
||||||
let movingTime: number | undefined = undefined;
|
let movingTime: number | undefined = undefined;
|
||||||
let speed: number | undefined = undefined;
|
let speed: number | undefined = undefined;
|
||||||
|
let artificial = false;
|
||||||
|
|
||||||
function toCalendarDate(date: Date): CalendarDate {
|
function toCalendarDate(date: Date): CalendarDate {
|
||||||
return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
|
return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
const { velocityUnits, distanceUnits } = settings;
|
const { velocityUnits, distanceUnits } = settings;
|
||||||
|
|
||||||
function setSpeed(value: number) {
|
function setSpeed(value: number) {
|
||||||
let speedValue = getConvertedVelocity(value);
|
let speedValue = getConvertedVelocity(value);
|
||||||
if ($velocityUnits === 'speed') {
|
if ($velocityUnits === 'speed') {
|
||||||
speedValue = parseFloat(speedValue.toFixed(2));
|
speedValue = parseFloat(speedValue.toFixed(2));
|
||||||
}
|
}
|
||||||
speed = speedValue;
|
speed = speedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGPXData() {
|
function setGPXData() {
|
||||||
if ($gpxStatistics.global.time.start) {
|
if ($gpxStatistics.global.time.start) {
|
||||||
startDate = toCalendarDate($gpxStatistics.global.time.start);
|
startDate = toCalendarDate($gpxStatistics.global.time.start);
|
||||||
startTime = $gpxStatistics.global.time.start.toLocaleTimeString();
|
startTime = $gpxStatistics.global.time.start.toLocaleTimeString();
|
||||||
} else {
|
} else {
|
||||||
startDate = undefined;
|
startDate = undefined;
|
||||||
startTime = undefined;
|
startTime = undefined;
|
||||||
}
|
}
|
||||||
if ($gpxStatistics.global.time.end) {
|
if ($gpxStatistics.global.time.end) {
|
||||||
endDate = toCalendarDate($gpxStatistics.global.time.end);
|
endDate = toCalendarDate($gpxStatistics.global.time.end);
|
||||||
endTime = $gpxStatistics.global.time.end.toLocaleTimeString();
|
endTime = $gpxStatistics.global.time.end.toLocaleTimeString();
|
||||||
} else {
|
} else {
|
||||||
endDate = undefined;
|
endDate = undefined;
|
||||||
endTime = undefined;
|
endTime = undefined;
|
||||||
}
|
}
|
||||||
if ($gpxStatistics.global.time.moving) {
|
if ($gpxStatistics.global.time.moving) {
|
||||||
movingTime = $gpxStatistics.global.time.moving;
|
movingTime = $gpxStatistics.global.time.moving;
|
||||||
} else {
|
} else {
|
||||||
movingTime = undefined;
|
movingTime = undefined;
|
||||||
}
|
}
|
||||||
if ($gpxStatistics.global.speed.moving) {
|
if ($gpxStatistics.global.speed.moving) {
|
||||||
setSpeed($gpxStatistics.global.speed.moving);
|
setSpeed($gpxStatistics.global.speed.moving);
|
||||||
} else {
|
} else {
|
||||||
speed = undefined;
|
speed = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if ($gpxStatistics && $velocityUnits && $distanceUnits) {
|
$: if ($gpxStatistics && $velocityUnits && $distanceUnits) {
|
||||||
setGPXData();
|
setGPXData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDate(date: DateValue, time: string): Date {
|
function getDate(date: DateValue, time: string): Date {
|
||||||
if (date === undefined) {
|
if (date === undefined) {
|
||||||
return new Date();
|
return new Date();
|
||||||
}
|
}
|
||||||
let [hours, minutes, seconds] = time.split(':').map((x) => parseInt(x));
|
let [hours, minutes, seconds] = time.split(':').map((x) => parseInt(x));
|
||||||
return new Date(date.year, date.month - 1, date.day, hours, minutes, seconds);
|
return new Date(date.year, date.month - 1, date.day, hours, minutes, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateEnd() {
|
function updateEnd() {
|
||||||
if (startDate && movingTime !== undefined) {
|
if (startDate && movingTime !== undefined) {
|
||||||
if (startTime === undefined) {
|
if (startTime === undefined) {
|
||||||
startTime = '00:00:00';
|
startTime = '00:00:00';
|
||||||
}
|
}
|
||||||
let start = getDate(startDate, startTime);
|
let start = getDate(startDate, startTime);
|
||||||
let ratio =
|
let ratio =
|
||||||
$gpxStatistics.global.time.moving > 0
|
$gpxStatistics.global.time.moving > 0
|
||||||
? $gpxStatistics.global.time.total / $gpxStatistics.global.time.moving
|
? $gpxStatistics.global.time.total / $gpxStatistics.global.time.moving
|
||||||
: 1;
|
: 1;
|
||||||
let end = new Date(start.getTime() + ratio * movingTime * 1000);
|
let end = new Date(start.getTime() + ratio * movingTime * 1000);
|
||||||
endDate = toCalendarDate(end);
|
endDate = toCalendarDate(end);
|
||||||
endTime = end.toLocaleTimeString();
|
endTime = end.toLocaleTimeString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStart() {
|
function updateStart() {
|
||||||
if (endDate && movingTime !== undefined) {
|
if (endDate && movingTime !== undefined) {
|
||||||
if (endTime === undefined) {
|
if (endTime === undefined) {
|
||||||
endTime = '00:00:00';
|
endTime = '00:00:00';
|
||||||
}
|
}
|
||||||
let end = getDate(endDate, endTime);
|
let end = getDate(endDate, endTime);
|
||||||
let ratio =
|
let ratio =
|
||||||
$gpxStatistics.global.time.moving > 0
|
$gpxStatistics.global.time.moving > 0
|
||||||
? $gpxStatistics.global.time.total / $gpxStatistics.global.time.moving
|
? $gpxStatistics.global.time.total / $gpxStatistics.global.time.moving
|
||||||
: 1;
|
: 1;
|
||||||
let start = new Date(end.getTime() - ratio * movingTime * 1000);
|
let start = new Date(end.getTime() - ratio * movingTime * 1000);
|
||||||
startDate = toCalendarDate(start);
|
startDate = toCalendarDate(start);
|
||||||
startTime = start.toLocaleTimeString();
|
startTime = start.toLocaleTimeString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpeed() {
|
function getSpeed() {
|
||||||
if (speed === undefined) {
|
if (speed === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let speedValue = speed;
|
let speedValue = speed;
|
||||||
if ($velocityUnits === 'pace') {
|
if ($velocityUnits === 'pace') {
|
||||||
speedValue = distancePerHourToSecondsPerDistance(speed);
|
speedValue = distancePerHourToSecondsPerDistance(speed);
|
||||||
}
|
}
|
||||||
if ($distanceUnits === 'imperial') {
|
if ($distanceUnits === 'imperial') {
|
||||||
speedValue = milesToKilometers(speedValue);
|
speedValue = milesToKilometers(speedValue);
|
||||||
} else if ($distanceUnits === 'nautical') {
|
} else if ($distanceUnits === 'nautical') {
|
||||||
speedValue = nauticalMilesToKilometers(speedValue);
|
speedValue = nauticalMilesToKilometers(speedValue);
|
||||||
}
|
}
|
||||||
return speedValue;
|
return speedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDataFromSpeed() {
|
function updateDataFromSpeed() {
|
||||||
let speedValue = getSpeed();
|
let speedValue = getSpeed();
|
||||||
if (speedValue === undefined) {
|
if (speedValue === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let distance =
|
let distance =
|
||||||
$gpxStatistics.global.distance.moving > 0
|
$gpxStatistics.global.distance.moving > 0
|
||||||
? $gpxStatistics.global.distance.moving
|
? $gpxStatistics.global.distance.moving
|
||||||
: $gpxStatistics.global.distance.total;
|
: $gpxStatistics.global.distance.total;
|
||||||
movingTime = (distance / speedValue) * 3600;
|
movingTime = (distance / speedValue) * 3600;
|
||||||
|
|
||||||
updateEnd();
|
updateEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDataFromTotalTime() {
|
function updateDataFromTotalTime() {
|
||||||
if (movingTime === undefined) {
|
if (movingTime === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let distance =
|
let distance =
|
||||||
$gpxStatistics.global.distance.moving > 0
|
$gpxStatistics.global.distance.moving > 0
|
||||||
? $gpxStatistics.global.distance.moving
|
? $gpxStatistics.global.distance.moving
|
||||||
: $gpxStatistics.global.distance.total;
|
: $gpxStatistics.global.distance.total;
|
||||||
setSpeed(distance / (movingTime / 3600));
|
setSpeed(distance / (movingTime / 3600));
|
||||||
updateEnd();
|
updateEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
$: canUpdate =
|
$: canUpdate =
|
||||||
$selection.size === 1 && $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']);
|
$selection.size === 1 && $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||||
<fieldset class="flex flex-col gap-2">
|
<fieldset class="flex flex-col gap-2">
|
||||||
<div class="flex flex-row gap-2 justify-center">
|
<div class="flex flex-row gap-2 justify-center">
|
||||||
<div class="flex flex-col gap-2 grow">
|
<div class="flex flex-col gap-2 grow">
|
||||||
<Label for="speed" class="flex flex-row">
|
<Label for="speed" class="flex flex-row">
|
||||||
<Zap size="16" class="mr-1" />
|
<Zap size="16" class="mr-1" />
|
||||||
{#if $velocityUnits === 'speed'}
|
{#if $velocityUnits === 'speed'}
|
||||||
{$_('quantities.speed')}
|
{$_('quantities.speed')}
|
||||||
{:else}
|
{:else}
|
||||||
{$_('quantities.pace')}
|
{$_('quantities.pace')}
|
||||||
{/if}
|
{/if}
|
||||||
</Label>
|
</Label>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
{#if $velocityUnits === 'speed'}
|
{#if $velocityUnits === 'speed'}
|
||||||
<Input
|
<Input
|
||||||
id="speed"
|
id="speed"
|
||||||
type="number"
|
type="number"
|
||||||
step={0.01}
|
step={0.01}
|
||||||
min={0.01}
|
min={0.01}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
bind:value={speed}
|
bind:value={speed}
|
||||||
on:change={updateDataFromSpeed}
|
on:change={updateDataFromSpeed}
|
||||||
/>
|
/>
|
||||||
<span class="text-sm shrink-0">
|
<span class="text-sm shrink-0">
|
||||||
{#if $distanceUnits === 'imperial'}
|
{#if $distanceUnits === 'imperial'}
|
||||||
{$_('units.miles_per_hour')}
|
{$_('units.miles_per_hour')}
|
||||||
{:else if $distanceUnits === 'metric'}
|
{:else if $distanceUnits === 'metric'}
|
||||||
{$_('units.kilometers_per_hour')}
|
{$_('units.kilometers_per_hour')}
|
||||||
{:else if $distanceUnits === 'nautical'}
|
{:else if $distanceUnits === 'nautical'}
|
||||||
{$_('units.knots')}
|
{$_('units.knots')}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<TimePicker
|
<TimePicker
|
||||||
bind:value={speed}
|
bind:value={speed}
|
||||||
showHours={false}
|
showHours={false}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
onChange={updateDataFromSpeed}
|
onChange={updateDataFromSpeed}
|
||||||
/>
|
/>
|
||||||
<span class="text-sm shrink-0">
|
<span class="text-sm shrink-0">
|
||||||
{#if $distanceUnits === 'imperial'}
|
{#if $distanceUnits === 'imperial'}
|
||||||
{$_('units.minutes_per_mile')}
|
{$_('units.minutes_per_mile')}
|
||||||
{:else if $distanceUnits === 'metric'}
|
{:else if $distanceUnits === 'metric'}
|
||||||
{$_('units.minutes_per_kilometer')}
|
{$_('units.minutes_per_kilometer')}
|
||||||
{:else if $distanceUnits === 'nautical'}
|
{:else if $distanceUnits === 'nautical'}
|
||||||
{$_('units.minutes_per_nautical_mile')}
|
{$_('units.minutes_per_nautical_mile')}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 grow">
|
<div class="flex flex-col gap-2 grow">
|
||||||
<Label for="duration" class="flex flex-row">
|
<Label for="duration" class="flex flex-row">
|
||||||
<Timer size="16" class="mr-1" />
|
<Timer size="16" class="mr-1" />
|
||||||
{$_('toolbar.time.total_time')}
|
{$_('toolbar.time.total_time')}
|
||||||
</Label>
|
</Label>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
bind:value={movingTime}
|
bind:value={movingTime}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
onChange={updateDataFromTotalTime}
|
onChange={updateDataFromTotalTime}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Label class="flex flex-row">
|
<Label class="flex flex-row">
|
||||||
<CirclePlay size="16" class="mr-1" />
|
<CirclePlay size="16" class="mr-1" />
|
||||||
{$_('toolbar.time.start')}
|
{$_('toolbar.time.start')}
|
||||||
</Label>
|
</Label>
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
bind:value={startDate}
|
bind:value={startDate}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
locale={get(locale) ?? 'en'}
|
locale={get(locale) ?? 'en'}
|
||||||
placeholder={$_('toolbar.time.pick_date')}
|
placeholder={$_('toolbar.time.pick_date')}
|
||||||
class="w-fit grow"
|
class="w-fit grow"
|
||||||
onValueChange={async () => {
|
onValueChange={async () => {
|
||||||
await tick();
|
await tick();
|
||||||
updateEnd();
|
updateEnd();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
step={1}
|
step={1}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
bind:value={startTime}
|
bind:value={startTime}
|
||||||
class="w-fit"
|
class="w-fit"
|
||||||
on:change={updateEnd}
|
on:change={updateEnd}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Label class="flex flex-row">
|
<Label class="flex flex-row">
|
||||||
<CircleStop size="16" class="mr-1" />
|
<CircleStop size="16" class="mr-1" />
|
||||||
{$_('toolbar.time.end')}
|
{$_('toolbar.time.end')}
|
||||||
</Label>
|
</Label>
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
bind:value={endDate}
|
bind:value={endDate}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
locale={get(locale) ?? 'en'}
|
locale={get(locale) ?? 'en'}
|
||||||
placeholder={$_('toolbar.time.pick_date')}
|
placeholder={$_('toolbar.time.pick_date')}
|
||||||
class="w-fit grow"
|
class="w-fit grow"
|
||||||
onValueChange={async () => {
|
onValueChange={async () => {
|
||||||
await tick();
|
await tick();
|
||||||
updateStart();
|
updateStart();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
step={1}
|
step={1}
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
bind:value={endTime}
|
bind:value={endTime}
|
||||||
class="w-fit"
|
class="w-fit"
|
||||||
on:change={updateStart}
|
on:change={updateStart}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
|
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
|
||||||
<div class="mt-0.5 flex flex-row gap-1 items-center hidden">
|
<div class="mt-0.5 flex flex-row gap-1 items-center">
|
||||||
<Checkbox id="artificial-time" disabled={!canUpdate} />
|
<Checkbox id="artificial-time" bind:checked={artificial} disabled={!canUpdate} />
|
||||||
<Label for="artificial-time">
|
<Label for="artificial-time">
|
||||||
{$_('toolbar.time.artificial')}
|
{$_('toolbar.time.artificial')}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={!canUpdate}
|
disabled={!canUpdate}
|
||||||
class="grow"
|
class="grow"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
let effectiveSpeed = getSpeed();
|
let effectiveSpeed = getSpeed();
|
||||||
if (startDate === undefined || startTime === undefined || effectiveSpeed === undefined) {
|
if (
|
||||||
return;
|
startDate === undefined ||
|
||||||
}
|
startTime === undefined ||
|
||||||
|
effectiveSpeed === undefined
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Math.abs(effectiveSpeed - $gpxStatistics.global.speed.moving) < 0.01) {
|
if (Math.abs(effectiveSpeed - $gpxStatistics.global.speed.moving) < 0.01) {
|
||||||
effectiveSpeed = $gpxStatistics.global.speed.moving;
|
effectiveSpeed = $gpxStatistics.global.speed.moving;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ratio = 1;
|
let ratio = 1;
|
||||||
if (
|
if (
|
||||||
$gpxStatistics.global.speed.moving > 0 &&
|
$gpxStatistics.global.speed.moving > 0 &&
|
||||||
$gpxStatistics.global.speed.moving !== effectiveSpeed
|
$gpxStatistics.global.speed.moving !== effectiveSpeed
|
||||||
) {
|
) {
|
||||||
ratio = $gpxStatistics.global.speed.moving / effectiveSpeed;
|
ratio = $gpxStatistics.global.speed.moving / effectiveSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item = $selection.getSelected()[0];
|
let item = $selection.getSelected()[0];
|
||||||
let fileId = item.getFileId();
|
let fileId = item.getFileId();
|
||||||
dbUtils.applyToFile(fileId, (file) => {
|
dbUtils.applyToFile(fileId, (file) => {
|
||||||
if (item instanceof ListFileItem) {
|
if (item instanceof ListFileItem) {
|
||||||
file.changeTimestamps(getDate(startDate, startTime), effectiveSpeed, ratio);
|
if (artificial) {
|
||||||
} else if (item instanceof ListTrackItem) {
|
file.createArtificialTimestamps(
|
||||||
file.changeTimestamps(
|
getDate(startDate, startTime),
|
||||||
getDate(startDate, startTime),
|
movingTime
|
||||||
effectiveSpeed,
|
);
|
||||||
ratio,
|
} else {
|
||||||
item.getTrackIndex()
|
file.changeTimestamps(
|
||||||
);
|
getDate(startDate, startTime),
|
||||||
} else if (item instanceof ListTrackSegmentItem) {
|
effectiveSpeed,
|
||||||
file.changeTimestamps(
|
ratio
|
||||||
getDate(startDate, startTime),
|
);
|
||||||
effectiveSpeed,
|
}
|
||||||
ratio,
|
} else if (item instanceof ListTrackItem) {
|
||||||
item.getTrackIndex(),
|
if (artificial) {
|
||||||
item.getSegmentIndex()
|
file.createArtificialTimestamps(
|
||||||
);
|
getDate(startDate, startTime),
|
||||||
}
|
movingTime,
|
||||||
});
|
item.getTrackIndex()
|
||||||
}}
|
);
|
||||||
>
|
} else {
|
||||||
<CalendarClock size="16" class="mr-1" />
|
file.changeTimestamps(
|
||||||
{$_('toolbar.time.update')}
|
getDate(startDate, startTime),
|
||||||
</Button>
|
effectiveSpeed,
|
||||||
<Button variant="outline" on:click={setGPXData}>
|
ratio,
|
||||||
<CircleX size="16" />
|
item.getTrackIndex()
|
||||||
</Button>
|
);
|
||||||
</div>
|
}
|
||||||
<Help link="./help/toolbar/time">
|
} else if (item instanceof ListTrackSegmentItem) {
|
||||||
{#if canUpdate}
|
if (artificial) {
|
||||||
{$_('toolbar.time.help')}
|
file.createArtificialTimestamps(
|
||||||
{:else}
|
getDate(startDate, startTime),
|
||||||
{$_('toolbar.time.help_invalid_selection')}
|
movingTime,
|
||||||
{/if}
|
item.getTrackIndex(),
|
||||||
</Help>
|
item.getSegmentIndex()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
file.changeTimestamps(
|
||||||
|
getDate(startDate, startTime),
|
||||||
|
effectiveSpeed,
|
||||||
|
ratio,
|
||||||
|
item.getTrackIndex(),
|
||||||
|
item.getSegmentIndex()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CalendarClock size="16" class="mr-1" />
|
||||||
|
{$_('toolbar.time.update')}
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" on:click={setGPXData}>
|
||||||
|
<CircleX size="16" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Help link="./help/toolbar/time">
|
||||||
|
{#if canUpdate}
|
||||||
|
{$_('toolbar.time.help')}
|
||||||
|
{:else}
|
||||||
|
{$_('toolbar.time.help_invalid_selection')}
|
||||||
|
{/if}
|
||||||
|
</Help>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
div :global(input[type='time']) {
|
div :global(input[type='time']) {
|
||||||
/*
|
/*
|
||||||
Style copy-pasted from shadcn-svelte Input.
|
Style copy-pasted from shadcn-svelte Input.
|
||||||
Needed to use native time input to avoid a bug with 2-level bind:value.
|
Needed to use native time input to avoid a bug with 2-level bind:value.
|
||||||
*/
|
*/
|
||||||
@apply flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
|
@apply flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Reference in New Issue
Block a user