store stats outside of gpx object

This commit is contained in:
vcoppe
2024-05-03 22:15:47 +02:00
parent 22884b3a8b
commit 619849f987
6 changed files with 170 additions and 223 deletions

View File

@@ -19,9 +19,8 @@ export abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
abstract getStartTimestamp(): Date; abstract getStartTimestamp(): Date;
abstract getEndTimestamp(): Date; abstract getEndTimestamp(): Date;
abstract getStatistics(): GPXStatistics;
abstract getTrackPoints(): TrackPoint[]; abstract getTrackPoints(): TrackPoint[];
abstract getTrackPointsAndStatistics(): { points: TrackPoint[], point_statistics: TrackPointStatistics, statistics: GPXStatistics }; abstract getStatistics(): GPXStatistics;
abstract getSegments(): TrackSegment[]; abstract getSegments(): TrackSegment[];
abstract toGeoJSON(): GeoJSON.Feature | GeoJSON.Feature[] | GeoJSON.FeatureCollection | GeoJSON.FeatureCollection[]; abstract toGeoJSON(): GeoJSON.Feature | GeoJSON.Feature[] | GeoJSON.FeatureCollection | GeoJSON.FeatureCollection[];
@@ -85,40 +84,6 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
return statistics; return statistics;
} }
getTrackPointsAndStatistics(): { points: TrackPoint[], point_statistics: TrackPointStatistics, statistics: GPXStatistics } {
let points: TrackPoint[] = [];
let point_statistics: TrackPointStatistics = {
distance: [],
time: [],
speed: [],
elevation: {
smoothed: [],
gain: [],
loss: [],
},
slope: [],
};
let statistics = new GPXStatistics();
for (let child of this.getChildren()) {
let childData = child.getTrackPointsAndStatistics();
points = points.concat(childData.points);
point_statistics.distance = point_statistics.distance.concat(childData.point_statistics.distance.map((distance) => distance + statistics.distance.total));
point_statistics.time = point_statistics.time.concat(childData.point_statistics.time.map((time) => time + statistics.time.total));
point_statistics.elevation.gain = point_statistics.elevation.gain.concat(childData.point_statistics.elevation.gain.map((gain) => gain + statistics.elevation.gain));
point_statistics.elevation.loss = point_statistics.elevation.loss.concat(childData.point_statistics.elevation.loss.map((loss) => loss + statistics.elevation.loss));
point_statistics.speed = point_statistics.speed.concat(childData.point_statistics.speed);
point_statistics.elevation.smoothed = point_statistics.elevation.smoothed.concat(childData.point_statistics.elevation.smoothed);
point_statistics.slope = point_statistics.slope.concat(childData.point_statistics.slope);
statistics.mergeWith(childData.statistics);
}
return { points, point_statistics, statistics };
}
getSegments(): TrackSegment[] { getSegments(): TrackSegment[] {
return this.getChildren().flatMap((child) => child.getSegments()); return this.getChildren().flatMap((child) => child.getSegments());
} }
@@ -165,13 +130,8 @@ export class GPXFile extends GPXTreeNode<Track>{
if (gpx) { if (gpx) {
this.attributes = gpx.attributes this.attributes = gpx.attributes
this.metadata = gpx.metadata; this.metadata = gpx.metadata;
if (gpx instanceof GPXFile) { this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : [];
this.wpt = gpx.wpt; this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : [];
this.trk = gpx.trk;
} else {
this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : [];
this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : [];
}
if (gpx.hasOwnProperty('_data')) { if (gpx.hasOwnProperty('_data')) {
this._data = gpx._data; this._data = gpx._data;
} }
@@ -234,11 +194,7 @@ export class Track extends GPXTreeNode<TrackSegment> {
this.src = track.src; this.src = track.src;
this.link = track.link; this.link = track.link;
this.type = track.type; this.type = track.type;
if (track instanceof Track) { this.trkseg = track.trkseg ? track.trkseg.map((seg) => new TrackSegment(seg)) : [];
this.trkseg = track.trkseg;
} else {
this.trkseg = track.trkseg ? track.trkseg.map((seg) => new TrackSegment(seg)) : [];
}
this.extensions = cloneJSON(track.extensions); this.extensions = cloneJSON(track.extensions);
if (track.hasOwnProperty('_data')) { if (track.hasOwnProperty('_data')) {
this._data = cloneJSON(track._data); this._data = cloneJSON(track._data);
@@ -301,43 +257,26 @@ export class Track extends GPXTreeNode<TrackSegment> {
// A class that represents a TrackSegment in a GPX file // A class that represents a TrackSegment in a GPX file
export class TrackSegment extends GPXTreeLeaf { export class TrackSegment extends GPXTreeLeaf {
trkpt: TrackPoint[]; trkpt: TrackPoint[];
trkptStatistics: TrackPointStatistics;
statistics: GPXStatistics;
constructor(segment?: TrackSegmentType | TrackSegment) { constructor(segment?: TrackSegmentType | TrackSegment) {
super(); super();
if (segment) { if (segment) {
if (segment instanceof TrackSegment) { this.trkpt = segment.trkpt.map((point) => new TrackPoint(point));
this.trkpt = segment.trkpt;
} else {
this.trkpt = segment.trkpt.map((point) => new TrackPoint(point));
}
if (segment.hasOwnProperty('_data')) { if (segment.hasOwnProperty('_data')) {
this._data = cloneJSON(segment._data); this._data = cloneJSON(segment._data);
} }
} else { } else {
this.trkpt = []; this.trkpt = [];
} }
this._computeStatistics();
} }
_computeStatistics(): void { _computeStatistics(): GPXStatistics {
let statistics = new GPXStatistics(); let statistics = new GPXStatistics();
let trkptStatistics: TrackPointStatistics = {
distance: [],
time: [],
speed: [],
elevation: {
smoothed: [],
gain: [],
loss: [],
},
slope: [],
};
trkptStatistics.elevation.smoothed = this._computeSmoothedElevation(); statistics.local.points = this.trkpt;
trkptStatistics.slope = this._computeSlope();
statistics.local.elevation.smoothed = this._computeSmoothedElevation();
statistics.local.slope = this._computeSlope();
const points = this.trkpt; const points = this.trkpt;
for (let i = 0; i < points.length; i++) { for (let i = 0; i < points.length; i++) {
@@ -348,29 +287,29 @@ export class TrackSegment extends GPXTreeLeaf {
if (i > 0) { if (i > 0) {
dist = distance(points[i - 1].getCoordinates(), points[i].getCoordinates()) / 1000; dist = distance(points[i - 1].getCoordinates(), points[i].getCoordinates()) / 1000;
statistics.distance.total += dist; statistics.global.distance.total += dist;
} }
trkptStatistics.distance.push(statistics.distance.total); statistics.local.distance.push(statistics.global.distance.total);
// elevation // elevation
if (i > 0) { if (i > 0) {
const ele = trkptStatistics.elevation.smoothed[i] - trkptStatistics.elevation.smoothed[i - 1]; const ele = statistics.local.elevation.smoothed[i] - statistics.local.elevation.smoothed[i - 1];
if (ele > 0) { if (ele > 0) {
statistics.elevation.gain += ele; statistics.global.elevation.gain += ele;
} else { } else {
statistics.elevation.loss -= ele; statistics.global.elevation.loss -= ele;
} }
} }
trkptStatistics.elevation.gain.push(statistics.elevation.gain); statistics.local.elevation.gain.push(statistics.global.elevation.gain);
trkptStatistics.elevation.loss.push(statistics.elevation.loss); statistics.local.elevation.loss.push(statistics.global.elevation.loss);
// time // time
if (points[0].time !== undefined && points[i].time !== undefined) { if (points[0].time !== undefined && points[i].time !== undefined) {
const time = (points[i].time.getTime() - points[0].time.getTime()) / 1000; const time = (points[i].time.getTime() - points[0].time.getTime()) / 1000;
trkptStatistics.time.push(time); statistics.local.time.push(time);
} }
// speed // speed
@@ -380,26 +319,25 @@ export class TrackSegment extends GPXTreeLeaf {
speed = dist / (time / 3600); speed = dist / (time / 3600);
if (speed >= 0.5) { if (speed >= 0.5) {
statistics.distance.moving += dist; statistics.global.distance.moving += dist;
statistics.time.moving += time; statistics.global.time.moving += time;
} }
} }
// bounds // bounds
statistics.bounds.southWest.lat = Math.min(statistics.bounds.southWest.lat, points[i].attributes.lat); statistics.global.bounds.southWest.lat = Math.min(statistics.global.bounds.southWest.lat, points[i].attributes.lat);
statistics.bounds.southWest.lon = Math.max(statistics.bounds.southWest.lon, points[i].attributes.lon); statistics.global.bounds.southWest.lon = Math.max(statistics.global.bounds.southWest.lon, points[i].attributes.lon);
statistics.bounds.northEast.lat = Math.max(statistics.bounds.northEast.lat, points[i].attributes.lat); statistics.global.bounds.northEast.lat = Math.max(statistics.global.bounds.northEast.lat, points[i].attributes.lat);
statistics.bounds.northEast.lon = Math.min(statistics.bounds.northEast.lon, points[i].attributes.lon); statistics.global.bounds.northEast.lon = Math.min(statistics.global.bounds.northEast.lon, points[i].attributes.lon);
} }
statistics.time.total = trkptStatistics.time[trkptStatistics.time.length - 1]; statistics.global.time.total = statistics.local.time[statistics.local.time.length - 1];
statistics.speed.total = statistics.distance.total / (statistics.time.total / 3600); statistics.global.speed.total = statistics.global.distance.total / (statistics.global.time.total / 3600);
statistics.speed.moving = statistics.distance.moving / (statistics.time.moving / 3600); statistics.global.speed.moving = statistics.global.distance.moving / (statistics.global.time.moving / 3600);
trkptStatistics.speed = distanceWindowSmoothingWithDistanceAccumulator(points, 200, (accumulated, start, end) => (points[start].time && points[end].time) ? 3600 * accumulated / (points[end].time.getTime() - points[start].time.getTime()) : undefined); statistics.local.speed = distanceWindowSmoothingWithDistanceAccumulator(points, 200, (accumulated, start, end) => (points[start].time && points[end].time) ? 3600 * accumulated / (points[end].time.getTime() - points[start].time.getTime()) : undefined);
this.statistics = statistics; return statistics;
this.trkptStatistics = trkptStatistics;
} }
_computeSmoothedElevation(): number[] { _computeSmoothedElevation(): number[] {
@@ -461,15 +399,7 @@ export class TrackSegment extends GPXTreeLeaf {
} }
getStatistics(): GPXStatistics { getStatistics(): GPXStatistics {
return this.statistics; return this._computeStatistics();
}
getTrackPointsAndStatistics(): { points: TrackPoint[], point_statistics: TrackPointStatistics, statistics: GPXStatistics } {
return {
points: this.trkpt,
point_statistics: this.trkptStatistics,
statistics: this.statistics,
};
} }
getSegments(): TrackSegment[] { getSegments(): TrackSegment[] {
@@ -509,7 +439,6 @@ export class TrackPoint {
_data: { [key: string]: any } = {}; _data: { [key: string]: any } = {};
constructor(point: TrackPointType | TrackPoint) { constructor(point: TrackPointType | TrackPoint) {
this.attributes = point.attributes;
this.attributes = point.attributes; this.attributes = point.attributes;
this.ele = point.ele; this.ele = point.ele;
this.time = point.time; this.time = point.time;
@@ -602,9 +531,7 @@ export class Waypoint {
constructor(waypoint: WaypointType | Waypoint) { constructor(waypoint: WaypointType | Waypoint) {
this.attributes = waypoint.attributes; this.attributes = waypoint.attributes;
this.ele = waypoint.ele; this.ele = waypoint.ele;
if (waypoint.time) { this.time = waypoint.time;
this.time = new Date(waypoint.time.getTime());
}
this.name = waypoint.name; this.name = waypoint.name;
this.cmt = waypoint.cmt; this.cmt = waypoint.cmt;
this.desc = waypoint.desc; this.desc = waypoint.desc;
@@ -637,88 +564,116 @@ export class Waypoint {
} }
export class GPXStatistics { export class GPXStatistics {
distance: { global: {
moving: number; distance: {
total: number; moving: number,
total: number,
},
time: {
moving: number,
total: number,
},
speed: {
moving: number,
total: number,
},
elevation: {
gain: number,
loss: number,
},
bounds: {
southWest: Coordinates,
northEast: Coordinates,
},
}; };
time: { local: {
moving: number; points: TrackPoint[],
total: number; distance: number[],
time: number[],
speed: number[],
elevation: {
smoothed: number[],
gain: number[],
loss: number[],
},
slope: number[],
}; };
speed: {
moving: number;
total: number;
};
elevation: {
gain: number;
loss: number;
};
bounds: {
southWest: Coordinates;
northEast: Coordinates;
}
constructor() { constructor() {
this.distance = { this.global = {
moving: 0, distance: {
total: 0, moving: 0,
}; total: 0,
this.time = {
moving: 0,
total: 0,
};
this.speed = {
moving: 0,
total: 0,
};
this.elevation = {
gain: 0,
loss: 0,
};
this.bounds = {
southWest: {
lat: 90,
lon: -180,
}, },
northEast: { time: {
lat: -90, moving: 0,
lon: 180, total: 0,
}, },
speed: {
moving: 0,
total: 0,
},
elevation: {
gain: 0,
loss: 0,
},
bounds: {
southWest: {
lat: 90,
lon: -180,
},
northEast: {
lat: -90,
lon: 180,
},
},
};
this.local = {
points: [],
distance: [],
time: [],
speed: [],
elevation: {
smoothed: [],
gain: [],
loss: [],
},
slope: [],
}; };
} }
mergeWith(other: GPXStatistics): void { mergeWith(other: GPXStatistics): void {
this.distance.total += other.distance.total;
this.distance.moving += other.distance.moving;
this.time.total += other.time.total; this.local.points = this.local.points.concat(other.local.points);
this.time.moving += other.time.moving;
this.speed.moving = this.distance.moving / (this.time.moving / 3600); this.local.distance = this.local.distance.concat(other.local.distance.map((distance) => distance + this.global.distance.total));
this.speed.total = this.distance.total / (this.time.total / 3600); this.local.time = this.local.time.concat(other.local.time.map((time) => time + this.global.time.total));
this.local.elevation.gain = this.local.elevation.gain.concat(other.local.elevation.gain.map((gain) => gain + this.global.elevation.gain));
this.local.elevation.loss = this.local.elevation.loss.concat(other.local.elevation.loss.map((loss) => loss + this.global.elevation.loss));
this.elevation.gain += other.elevation.gain; this.local.speed = this.local.speed.concat(other.local.speed);
this.elevation.loss += other.elevation.loss; this.local.elevation.smoothed = this.local.elevation.smoothed.concat(other.local.elevation.smoothed);
this.local.slope = this.local.slope.concat(other.local.slope);
this.bounds.southWest.lat = Math.min(this.bounds.southWest.lat, other.bounds.southWest.lat); this.global.distance.total += other.global.distance.total;
this.bounds.southWest.lon = Math.max(this.bounds.southWest.lon, other.bounds.southWest.lon); this.global.distance.moving += other.global.distance.moving;
this.bounds.northEast.lat = Math.max(this.bounds.northEast.lat, other.bounds.northEast.lat);
this.bounds.northEast.lon = Math.min(this.bounds.northEast.lon, other.bounds.northEast.lon); this.global.time.total += other.global.time.total;
this.global.time.moving += other.global.time.moving;
this.global.speed.moving = this.global.distance.moving / (this.global.time.moving / 3600);
this.global.speed.total = this.global.distance.total / (this.global.time.total / 3600);
this.global.elevation.gain += other.global.elevation.gain;
this.global.elevation.loss += other.global.elevation.loss;
this.global.bounds.southWest.lat = Math.min(this.global.bounds.southWest.lat, other.global.bounds.southWest.lat);
this.global.bounds.southWest.lon = Math.max(this.global.bounds.southWest.lon, other.global.bounds.southWest.lon);
this.global.bounds.northEast.lat = Math.max(this.global.bounds.northEast.lat, other.global.bounds.northEast.lat);
this.global.bounds.northEast.lon = Math.min(this.global.bounds.northEast.lon, other.global.bounds.northEast.lon);
} }
} }
export type TrackPointStatistics = {
distance: number[],
time: number[],
speed: number[],
elevation: {
smoothed: number[],
gain: number[],
loss: number[],
},
slope: number[],
}
const earthRadius = 6371008.8; const earthRadius = 6371008.8;
export function distance(coord1: Coordinates, coord2: Coordinates): number { export function distance(coord1: Coordinates, coord2: Coordinates): number {
const rad = Math.PI / 180; const rad = Math.PI / 180;

View File

@@ -2,7 +2,7 @@
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte'; import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte'; import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import FileList from '$lib/components/FileList.svelte'; import FileList from '$lib/components/FileList.svelte';
import GPXData from '$lib/components/GPXData.svelte'; import GPXStatistics from '$lib/components/GPXStatistics.svelte';
import Map from '$lib/components/Map.svelte'; import Map from '$lib/components/Map.svelte';
import Menu from '$lib/components/Menu.svelte'; import Menu from '$lib/components/Menu.svelte';
import Toolbar from '$lib/components/toolbar/Toolbar.svelte'; import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
@@ -21,7 +21,7 @@
<Toaster richColors /> <Toaster richColors />
</div> </div>
<div class="h-48 flex flex-row gap-2 overflow-hidden"> <div class="h-48 flex flex-row gap-2 overflow-hidden">
<GPXData /> <GPXStatistics />
<ElevationProfile /> <ElevationProfile />
</div> </div>
</div> </div>

View File

@@ -6,7 +6,7 @@
import Chart from 'chart.js/auto'; import Chart from 'chart.js/auto';
import mapboxgl from 'mapbox-gl'; import mapboxgl from 'mapbox-gl';
import { map, settings, gpxData } from '$lib/stores'; import { map, settings, gpxStatistics } from '$lib/stores';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { import {
@@ -233,16 +233,16 @@
}); });
$: if (chart && $settings) { $: if (chart && $settings) {
let data = $gpxData; let data = $gpxStatistics;
// update data // update data
chart.data.datasets[0] = { chart.data.datasets[0] = {
label: $_('quantities.elevation'), label: $_('quantities.elevation'),
data: data.points.map((point, index) => { data: data.local.points.map((point, index) => {
return { return {
x: getConvertedDistance(data.point_statistics.distance[index]), x: getConvertedDistance(data.local.distance[index]),
y: point.ele ? getConvertedElevation(point.ele) : 0, y: point.ele ? getConvertedElevation(point.ele) : 0,
slope: data.point_statistics.slope[index], slope: data.local.slope[index],
surface: point.getSurface(), surface: point.getSurface(),
coordinates: point.getCoordinates() coordinates: point.getCoordinates()
}; };
@@ -253,10 +253,10 @@
}; };
chart.data.datasets[1] = { chart.data.datasets[1] = {
label: datasets.speed.getLabel(), label: datasets.speed.getLabel(),
data: data.points.map((point, index) => { data: data.local.points.map((point, index) => {
return { return {
x: getConvertedDistance(data.point_statistics.distance[index]), x: getConvertedDistance(data.local.distance[index]),
y: getConvertedVelocity(data.point_statistics.speed[index]) y: getConvertedVelocity(data.local.speed[index])
}; };
}), }),
normalized: true, normalized: true,
@@ -265,9 +265,9 @@
}; };
chart.data.datasets[2] = { chart.data.datasets[2] = {
label: datasets.hr.getLabel(), label: datasets.hr.getLabel(),
data: data.points.map((point, index) => { data: data.local.points.map((point, index) => {
return { return {
x: getConvertedDistance(data.point_statistics.distance[index]), x: getConvertedDistance(data.local.distance[index]),
y: point.getHeartRate() y: point.getHeartRate()
}; };
}), }),
@@ -277,9 +277,9 @@
}; };
chart.data.datasets[3] = { chart.data.datasets[3] = {
label: datasets.cad.getLabel(), label: datasets.cad.getLabel(),
data: data.points.map((point, index) => { data: data.local.points.map((point, index) => {
return { return {
x: getConvertedDistance(data.point_statistics.distance[index]), x: getConvertedDistance(data.local.distance[index]),
y: point.getCadence() y: point.getCadence()
}; };
}), }),
@@ -289,9 +289,9 @@
}; };
chart.data.datasets[4] = { chart.data.datasets[4] = {
label: datasets.atemp.getLabel(), label: datasets.atemp.getLabel(),
data: data.points.map((point, index) => { data: data.local.points.map((point, index) => {
return { return {
x: getConvertedDistance(data.point_statistics.distance[index]), x: getConvertedDistance(data.local.distance[index]),
y: getConvertedTemperature(point.getTemperature()) y: getConvertedTemperature(point.getTemperature())
}; };
}), }),
@@ -301,9 +301,9 @@
}; };
chart.data.datasets[5] = { chart.data.datasets[5] = {
label: datasets.power.getLabel(), label: datasets.power.getLabel(),
data: data.points.map((point, index) => { data: data.local.points.map((point, index) => {
return { return {
x: getConvertedDistance(data.point_statistics.distance[index]), x: getConvertedDistance(data.local.distance[index]),
y: point.getPower() y: point.getPower()
}; };
}), }),
@@ -312,7 +312,7 @@
hidden: true hidden: true
}; };
chart.options.scales.x['min'] = 0; chart.options.scales.x['min'] = 0;
chart.options.scales.x['max'] = getConvertedDistance(data.statistics.distance.total); chart.options.scales.x['max'] = getConvertedDistance(data.global.distance.total);
// update units // update units
for (let [id, dataset] of Object.entries(datasets)) { for (let [id, dataset] of Object.entries(datasets)) {

View File

@@ -3,17 +3,11 @@
import Tooltip from '$lib/components/Tooltip.svelte'; import Tooltip from '$lib/components/Tooltip.svelte';
import WithUnits from '$lib/components/WithUnits.svelte'; import WithUnits from '$lib/components/WithUnits.svelte';
import { GPXStatistics } from 'gpx'; import { gpxStatistics, settings } from '$lib/stores';
import { gpxData, settings } from '$lib/stores';
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from 'lucide-svelte'; import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from 'lucide-svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
let data: GPXStatistics;
$: data = $gpxData.statistics;
</script> </script>
<Card.Root class="h-full overflow-hidden border-none shadow-none min-w-48 pl-4"> <Card.Root class="h-full overflow-hidden border-none shadow-none min-w-48 pl-4">
@@ -21,25 +15,25 @@
<Tooltip> <Tooltip>
<span slot="data" class="flex flex-row items-center"> <span slot="data" class="flex flex-row items-center">
<Ruler size="18" class="mr-1" /> <Ruler size="18" class="mr-1" />
<WithUnits value={data.distance.total} type="distance" /> <WithUnits value={$gpxStatistics.global.distance.total} type="distance" />
</span> </span>
<span slot="tooltip">{$_('quantities.distance')}</span> <span slot="tooltip">{$_('quantities.distance')}</span>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<span slot="data" class="flex flex-row items-center"> <span slot="data" class="flex flex-row items-center">
<MoveUpRight size="18" class="mr-1" /> <MoveUpRight size="18" class="mr-1" />
<WithUnits value={data.elevation.gain} type="elevation" /> <WithUnits value={$gpxStatistics.global.elevation.gain} type="elevation" />
<MoveDownRight size="18" class="mx-1" /> <MoveDownRight size="18" class="mx-1" />
<WithUnits value={data.elevation.loss} type="elevation" /> <WithUnits value={$gpxStatistics.global.elevation.loss} type="elevation" />
</span> </span>
<span slot="tooltip">{$_('quantities.elevation')}</span> <span slot="tooltip">{$_('quantities.elevation')}</span>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<span slot="data" class="flex flex-row items-center"> <span slot="data" class="flex flex-row items-center">
<Zap size="18" class="mr-1" /> <Zap size="18" class="mr-1" />
<WithUnits value={data.speed.total} type="speed" showUnits={false} /> <WithUnits value={$gpxStatistics.global.speed.total} type="speed" showUnits={false} />
<span class="mx-1">/</span> <span class="mx-1">/</span>
<WithUnits value={data.speed.moving} type="speed" /> <WithUnits value={$gpxStatistics.global.speed.moving} type="speed" />
</span> </span>
<span slot="tooltip" <span slot="tooltip"
>{$settings.velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_( >{$settings.velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_(
@@ -50,9 +44,9 @@
<Tooltip> <Tooltip>
<span slot="data" class="flex flex-row items-center"> <span slot="data" class="flex flex-row items-center">
<Timer size="18" class="mr-1" /> <Timer size="18" class="mr-1" />
<WithUnits value={data.time.total} type="time" /> <WithUnits value={$gpxStatistics.global.time.total} type="time" />
<span class="mx-1">/</span> <span class="mx-1">/</span>
<WithUnits value={data.time.moving} type="time" /> <WithUnits value={$gpxStatistics.global.time.moving} type="time" />
</span> </span>
<span slot="tooltip" <span slot="tooltip"
>{$_('quantities.time')} ({$_('quantities.total')} / {$_('quantities.moving')})</span >{$_('quantities.time')} ({$_('quantities.total')} / {$_('quantities.moving')})</span

View File

@@ -1,8 +1,7 @@
import type { GPXFile } from "gpx"; import type { GPXFile } from "gpx";
import { map, selectFiles, currentTool, Tool } from "$lib/stores"; import { map, selectFiles, currentTool, Tool } from "$lib/stores";
import { get, type Readable, type Writable } from "svelte/store"; import { get, type Readable } from "svelte/store";
import mapboxgl from "mapbox-gl"; import mapboxgl from "mapbox-gl";
import type { FreezedObject } from "structurajs";
let defaultWeight = 6; let defaultWeight = 6;
let defaultOpacity = 1; let defaultOpacity = 1;

View File

@@ -1,12 +1,11 @@
import { writable, get, type Writable } from 'svelte/store'; import { writable, get, type Writable } from 'svelte/store';
import mapboxgl from 'mapbox-gl'; import mapboxgl from 'mapbox-gl';
import { GPXFile, buildGPX, parseGPX, GPXFiles } from 'gpx'; import { GPXFile, buildGPX, parseGPX, GPXStatistics } from 'gpx';
import { tick } from 'svelte'; import { tick } from 'svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer'; import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
import { dbUtils, fileObservers } from './db'; import { dbUtils, fileObservers } from './db';
import type { FreezedObject } from 'structurajs';
export const map = writable<mapboxgl.Map | null>(null); export const map = writable<mapboxgl.Map | null>(null);
@@ -29,18 +28,15 @@ fileObservers.subscribe((files) => { // Update selectedFiles automatically when
} }
}); });
export const gpxData = writable(new GPXFiles([]).getTrackPointsAndStatistics()); const fileStatistics: Map<string, GPXStatistics> = new Map();
export const gpxStatistics: Writable<GPXStatistics> = writable(new GPXStatistics());
function updateGPXData() { function updateGPXData() {
let fileIds: string[] = get(fileOrder).filter((f) => get(selectedFiles).has(f)); let fileIds: string[] = get(fileOrder).filter((f) => fileStatistics.has(f) && get(selectedFiles).has(f));
let files: GPXFile[] = fileIds gpxStatistics.set(fileIds.reduce((stats: GPXStatistics, fileId: string) => {
.map((id) => { stats.mergeWith(fileStatistics.get(fileId) ?? new GPXStatistics());
let fileObserver = get(fileObservers).get(id); return stats;
return fileObserver ? get(fileObserver) : null; }, new GPXStatistics()));
})
.filter((f) => f) as GPXFile[];
let gpxFiles = new GPXFiles(files);
gpxData.set(gpxFiles.getTrackPointsAndStatistics());
} }
let selectedFilesUnsubscribe: Function[] = []; let selectedFilesUnsubscribe: Function[] = [];
@@ -49,8 +45,11 @@ selectedFiles.subscribe((selectedFiles) => {
selectedFiles.forEach((fileId) => { selectedFiles.forEach((fileId) => {
let fileObserver = get(fileObservers).get(fileId); let fileObserver = get(fileObservers).get(fileId);
if (fileObserver) { if (fileObserver) {
let unsubscribe = fileObserver.subscribe(() => { let unsubscribe = fileObserver.subscribe((file) => {
updateGPXData(); if (file) {
fileStatistics.set(fileId, file.getStatistics());
updateGPXData();
}
}); });
selectedFilesUnsubscribe.push(unsubscribe); selectedFilesUnsubscribe.push(unsubscribe);
} }
@@ -116,23 +115,23 @@ export async function loadFiles(list: FileList) {
if (file) { if (file) {
files.push(file); files.push(file);
let fileBounds = file.getStatistics().bounds; /*let fileBounds = file.getStatistics().bounds;
bounds.extend(fileBounds.southWest); bounds.extend(fileBounds.southWest);
bounds.extend(fileBounds.northEast); bounds.extend(fileBounds.northEast);
bounds.extend([fileBounds.southWest.lon, fileBounds.northEast.lat]); bounds.extend([fileBounds.southWest.lon, fileBounds.northEast.lat]);
bounds.extend([fileBounds.northEast.lon, fileBounds.southWest.lat]); bounds.extend([fileBounds.northEast.lon, fileBounds.southWest.lat]);*/
} }
} }
dbUtils.addMultiple(files); dbUtils.addMultiple(files);
if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast()) || !mapBounds.contains(bounds.getSouthEast()) || !mapBounds.contains(bounds.getNorthWest())) { /*if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast()) || !mapBounds.contains(bounds.getSouthEast()) || !mapBounds.contains(bounds.getNorthWest())) {
get(map)?.fitBounds(bounds, { get(map)?.fitBounds(bounds, {
padding: 80, padding: 80,
linear: true, linear: true,
easing: () => 1 easing: () => 1
}); });
} }*/
selectFileWhenLoaded(files[0]._data.id); selectFileWhenLoaded(files[0]._data.id);
} }
@@ -190,7 +189,7 @@ export function exportAllFiles() {
}); });
} }
export function exportFile(file: FreezedObject<GPXFile>) { export function exportFile(file: GPXFile) {
let blob = new Blob([buildGPX(file)], { type: 'application/gpx+xml' }); let blob = new Blob([buildGPX(file)], { type: 'application/gpx+xml' });
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
let a = document.createElement('a'); let a = document.createElement('a');