mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 15:43:25 +00:00
store stats outside of gpx object
This commit is contained in:
293
gpx/src/gpx.ts
293
gpx/src/gpx.ts
@@ -19,9 +19,8 @@ export abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
|
||||
|
||||
abstract getStartTimestamp(): Date;
|
||||
abstract getEndTimestamp(): Date;
|
||||
abstract getStatistics(): GPXStatistics;
|
||||
abstract getTrackPoints(): TrackPoint[];
|
||||
abstract getTrackPointsAndStatistics(): { points: TrackPoint[], point_statistics: TrackPointStatistics, statistics: GPXStatistics };
|
||||
abstract getStatistics(): GPXStatistics;
|
||||
abstract getSegments(): TrackSegment[];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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[] {
|
||||
return this.getChildren().flatMap((child) => child.getSegments());
|
||||
}
|
||||
@@ -165,13 +130,8 @@ export class GPXFile extends GPXTreeNode<Track>{
|
||||
if (gpx) {
|
||||
this.attributes = gpx.attributes
|
||||
this.metadata = gpx.metadata;
|
||||
if (gpx instanceof GPXFile) {
|
||||
this.wpt = gpx.wpt;
|
||||
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)) : [];
|
||||
}
|
||||
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')) {
|
||||
this._data = gpx._data;
|
||||
}
|
||||
@@ -234,11 +194,7 @@ export class Track extends GPXTreeNode<TrackSegment> {
|
||||
this.src = track.src;
|
||||
this.link = track.link;
|
||||
this.type = track.type;
|
||||
if (track instanceof Track) {
|
||||
this.trkseg = track.trkseg;
|
||||
} else {
|
||||
this.trkseg = track.trkseg ? track.trkseg.map((seg) => new TrackSegment(seg)) : [];
|
||||
}
|
||||
this.trkseg = track.trkseg ? track.trkseg.map((seg) => new TrackSegment(seg)) : [];
|
||||
this.extensions = cloneJSON(track.extensions);
|
||||
if (track.hasOwnProperty('_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
|
||||
export class TrackSegment extends GPXTreeLeaf {
|
||||
trkpt: TrackPoint[];
|
||||
trkptStatistics: TrackPointStatistics;
|
||||
statistics: GPXStatistics;
|
||||
|
||||
constructor(segment?: TrackSegmentType | TrackSegment) {
|
||||
super();
|
||||
if (segment) {
|
||||
if (segment instanceof TrackSegment) {
|
||||
this.trkpt = segment.trkpt;
|
||||
} else {
|
||||
this.trkpt = segment.trkpt.map((point) => new TrackPoint(point));
|
||||
}
|
||||
this.trkpt = segment.trkpt.map((point) => new TrackPoint(point));
|
||||
if (segment.hasOwnProperty('_data')) {
|
||||
this._data = cloneJSON(segment._data);
|
||||
}
|
||||
} else {
|
||||
this.trkpt = [];
|
||||
}
|
||||
|
||||
this._computeStatistics();
|
||||
}
|
||||
|
||||
_computeStatistics(): void {
|
||||
_computeStatistics(): GPXStatistics {
|
||||
let statistics = new GPXStatistics();
|
||||
let trkptStatistics: TrackPointStatistics = {
|
||||
distance: [],
|
||||
time: [],
|
||||
speed: [],
|
||||
elevation: {
|
||||
smoothed: [],
|
||||
gain: [],
|
||||
loss: [],
|
||||
},
|
||||
slope: [],
|
||||
};
|
||||
|
||||
trkptStatistics.elevation.smoothed = this._computeSmoothedElevation();
|
||||
trkptStatistics.slope = this._computeSlope();
|
||||
statistics.local.points = this.trkpt;
|
||||
|
||||
statistics.local.elevation.smoothed = this._computeSmoothedElevation();
|
||||
statistics.local.slope = this._computeSlope();
|
||||
|
||||
const points = this.trkpt;
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
@@ -348,29 +287,29 @@ export class TrackSegment extends GPXTreeLeaf {
|
||||
if (i > 0) {
|
||||
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
|
||||
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) {
|
||||
statistics.elevation.gain += ele;
|
||||
statistics.global.elevation.gain += ele;
|
||||
} else {
|
||||
statistics.elevation.loss -= ele;
|
||||
statistics.global.elevation.loss -= ele;
|
||||
}
|
||||
}
|
||||
|
||||
trkptStatistics.elevation.gain.push(statistics.elevation.gain);
|
||||
trkptStatistics.elevation.loss.push(statistics.elevation.loss);
|
||||
statistics.local.elevation.gain.push(statistics.global.elevation.gain);
|
||||
statistics.local.elevation.loss.push(statistics.global.elevation.loss);
|
||||
|
||||
// time
|
||||
if (points[0].time !== undefined && points[i].time !== undefined) {
|
||||
const time = (points[i].time.getTime() - points[0].time.getTime()) / 1000;
|
||||
|
||||
trkptStatistics.time.push(time);
|
||||
statistics.local.time.push(time);
|
||||
}
|
||||
|
||||
// speed
|
||||
@@ -380,26 +319,25 @@ export class TrackSegment extends GPXTreeLeaf {
|
||||
speed = dist / (time / 3600);
|
||||
|
||||
if (speed >= 0.5) {
|
||||
statistics.distance.moving += dist;
|
||||
statistics.time.moving += time;
|
||||
statistics.global.distance.moving += dist;
|
||||
statistics.global.time.moving += time;
|
||||
}
|
||||
}
|
||||
|
||||
// bounds
|
||||
statistics.bounds.southWest.lat = Math.min(statistics.bounds.southWest.lat, points[i].attributes.lat);
|
||||
statistics.bounds.southWest.lon = Math.max(statistics.bounds.southWest.lon, points[i].attributes.lon);
|
||||
statistics.bounds.northEast.lat = Math.max(statistics.bounds.northEast.lat, points[i].attributes.lat);
|
||||
statistics.bounds.northEast.lon = Math.min(statistics.bounds.northEast.lon, points[i].attributes.lon);
|
||||
statistics.global.bounds.southWest.lat = Math.min(statistics.global.bounds.southWest.lat, points[i].attributes.lat);
|
||||
statistics.global.bounds.southWest.lon = Math.max(statistics.global.bounds.southWest.lon, points[i].attributes.lon);
|
||||
statistics.global.bounds.northEast.lat = Math.max(statistics.global.bounds.northEast.lat, points[i].attributes.lat);
|
||||
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.speed.total = statistics.distance.total / (statistics.time.total / 3600);
|
||||
statistics.speed.moving = statistics.distance.moving / (statistics.time.moving / 3600);
|
||||
statistics.global.time.total = statistics.local.time[statistics.local.time.length - 1];
|
||||
statistics.global.speed.total = statistics.global.distance.total / (statistics.global.time.total / 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;
|
||||
this.trkptStatistics = trkptStatistics;
|
||||
return statistics;
|
||||
}
|
||||
|
||||
_computeSmoothedElevation(): number[] {
|
||||
@@ -461,15 +399,7 @@ export class TrackSegment extends GPXTreeLeaf {
|
||||
}
|
||||
|
||||
getStatistics(): GPXStatistics {
|
||||
return this.statistics;
|
||||
}
|
||||
|
||||
getTrackPointsAndStatistics(): { points: TrackPoint[], point_statistics: TrackPointStatistics, statistics: GPXStatistics } {
|
||||
return {
|
||||
points: this.trkpt,
|
||||
point_statistics: this.trkptStatistics,
|
||||
statistics: this.statistics,
|
||||
};
|
||||
return this._computeStatistics();
|
||||
}
|
||||
|
||||
getSegments(): TrackSegment[] {
|
||||
@@ -509,7 +439,6 @@ export class TrackPoint {
|
||||
_data: { [key: string]: any } = {};
|
||||
|
||||
constructor(point: TrackPointType | TrackPoint) {
|
||||
this.attributes = point.attributes;
|
||||
this.attributes = point.attributes;
|
||||
this.ele = point.ele;
|
||||
this.time = point.time;
|
||||
@@ -602,9 +531,7 @@ export class Waypoint {
|
||||
constructor(waypoint: WaypointType | Waypoint) {
|
||||
this.attributes = waypoint.attributes;
|
||||
this.ele = waypoint.ele;
|
||||
if (waypoint.time) {
|
||||
this.time = new Date(waypoint.time.getTime());
|
||||
}
|
||||
this.time = waypoint.time;
|
||||
this.name = waypoint.name;
|
||||
this.cmt = waypoint.cmt;
|
||||
this.desc = waypoint.desc;
|
||||
@@ -637,88 +564,116 @@ export class Waypoint {
|
||||
}
|
||||
|
||||
export class GPXStatistics {
|
||||
distance: {
|
||||
moving: number;
|
||||
total: number;
|
||||
global: {
|
||||
distance: {
|
||||
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: {
|
||||
moving: number;
|
||||
total: number;
|
||||
local: {
|
||||
points: TrackPoint[],
|
||||
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() {
|
||||
this.distance = {
|
||||
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,
|
||||
this.global = {
|
||||
distance: {
|
||||
moving: 0,
|
||||
total: 0,
|
||||
},
|
||||
northEast: {
|
||||
lat: -90,
|
||||
lon: 180,
|
||||
time: {
|
||||
moving: 0,
|
||||
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 {
|
||||
this.distance.total += other.distance.total;
|
||||
this.distance.moving += other.distance.moving;
|
||||
|
||||
this.time.total += other.time.total;
|
||||
this.time.moving += other.time.moving;
|
||||
this.local.points = this.local.points.concat(other.local.points);
|
||||
|
||||
this.speed.moving = this.distance.moving / (this.time.moving / 3600);
|
||||
this.speed.total = this.distance.total / (this.time.total / 3600);
|
||||
this.local.distance = this.local.distance.concat(other.local.distance.map((distance) => distance + this.global.distance.total));
|
||||
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.elevation.loss += other.elevation.loss;
|
||||
this.local.speed = this.local.speed.concat(other.local.speed);
|
||||
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.bounds.southWest.lon = Math.max(this.bounds.southWest.lon, other.bounds.southWest.lon);
|
||||
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.distance.total += other.global.distance.total;
|
||||
this.global.distance.moving += other.global.distance.moving;
|
||||
|
||||
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;
|
||||
export function distance(coord1: Coordinates, coord2: Coordinates): number {
|
||||
const rad = Math.PI / 180;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
|
||||
import ElevationProfile from '$lib/components/ElevationProfile.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 Menu from '$lib/components/Menu.svelte';
|
||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
@@ -21,7 +21,7 @@
|
||||
<Toaster richColors />
|
||||
</div>
|
||||
<div class="h-48 flex flex-row gap-2 overflow-hidden">
|
||||
<GPXData />
|
||||
<GPXStatistics />
|
||||
<ElevationProfile />
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
import Chart from 'chart.js/auto';
|
||||
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 {
|
||||
@@ -233,16 +233,16 @@
|
||||
});
|
||||
|
||||
$: if (chart && $settings) {
|
||||
let data = $gpxData;
|
||||
let data = $gpxStatistics;
|
||||
|
||||
// update data
|
||||
chart.data.datasets[0] = {
|
||||
label: $_('quantities.elevation'),
|
||||
data: data.points.map((point, index) => {
|
||||
data: data.local.points.map((point, index) => {
|
||||
return {
|
||||
x: getConvertedDistance(data.point_statistics.distance[index]),
|
||||
x: getConvertedDistance(data.local.distance[index]),
|
||||
y: point.ele ? getConvertedElevation(point.ele) : 0,
|
||||
slope: data.point_statistics.slope[index],
|
||||
slope: data.local.slope[index],
|
||||
surface: point.getSurface(),
|
||||
coordinates: point.getCoordinates()
|
||||
};
|
||||
@@ -253,10 +253,10 @@
|
||||
};
|
||||
chart.data.datasets[1] = {
|
||||
label: datasets.speed.getLabel(),
|
||||
data: data.points.map((point, index) => {
|
||||
data: data.local.points.map((point, index) => {
|
||||
return {
|
||||
x: getConvertedDistance(data.point_statistics.distance[index]),
|
||||
y: getConvertedVelocity(data.point_statistics.speed[index])
|
||||
x: getConvertedDistance(data.local.distance[index]),
|
||||
y: getConvertedVelocity(data.local.speed[index])
|
||||
};
|
||||
}),
|
||||
normalized: true,
|
||||
@@ -265,9 +265,9 @@
|
||||
};
|
||||
chart.data.datasets[2] = {
|
||||
label: datasets.hr.getLabel(),
|
||||
data: data.points.map((point, index) => {
|
||||
data: data.local.points.map((point, index) => {
|
||||
return {
|
||||
x: getConvertedDistance(data.point_statistics.distance[index]),
|
||||
x: getConvertedDistance(data.local.distance[index]),
|
||||
y: point.getHeartRate()
|
||||
};
|
||||
}),
|
||||
@@ -277,9 +277,9 @@
|
||||
};
|
||||
chart.data.datasets[3] = {
|
||||
label: datasets.cad.getLabel(),
|
||||
data: data.points.map((point, index) => {
|
||||
data: data.local.points.map((point, index) => {
|
||||
return {
|
||||
x: getConvertedDistance(data.point_statistics.distance[index]),
|
||||
x: getConvertedDistance(data.local.distance[index]),
|
||||
y: point.getCadence()
|
||||
};
|
||||
}),
|
||||
@@ -289,9 +289,9 @@
|
||||
};
|
||||
chart.data.datasets[4] = {
|
||||
label: datasets.atemp.getLabel(),
|
||||
data: data.points.map((point, index) => {
|
||||
data: data.local.points.map((point, index) => {
|
||||
return {
|
||||
x: getConvertedDistance(data.point_statistics.distance[index]),
|
||||
x: getConvertedDistance(data.local.distance[index]),
|
||||
y: getConvertedTemperature(point.getTemperature())
|
||||
};
|
||||
}),
|
||||
@@ -301,9 +301,9 @@
|
||||
};
|
||||
chart.data.datasets[5] = {
|
||||
label: datasets.power.getLabel(),
|
||||
data: data.points.map((point, index) => {
|
||||
data: data.local.points.map((point, index) => {
|
||||
return {
|
||||
x: getConvertedDistance(data.point_statistics.distance[index]),
|
||||
x: getConvertedDistance(data.local.distance[index]),
|
||||
y: point.getPower()
|
||||
};
|
||||
}),
|
||||
@@ -312,7 +312,7 @@
|
||||
hidden: true
|
||||
};
|
||||
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
|
||||
for (let [id, dataset] of Object.entries(datasets)) {
|
||||
|
@@ -3,17 +3,11 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
|
||||
import { GPXStatistics } from 'gpx';
|
||||
|
||||
import { gpxData, settings } from '$lib/stores';
|
||||
import { gpxStatistics, settings } from '$lib/stores';
|
||||
|
||||
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from 'lucide-svelte';
|
||||
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
let data: GPXStatistics;
|
||||
|
||||
$: data = $gpxData.statistics;
|
||||
</script>
|
||||
|
||||
<Card.Root class="h-full overflow-hidden border-none shadow-none min-w-48 pl-4">
|
||||
@@ -21,25 +15,25 @@
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<Ruler size="18" class="mr-1" />
|
||||
<WithUnits value={data.distance.total} type="distance" />
|
||||
<WithUnits value={$gpxStatistics.global.distance.total} type="distance" />
|
||||
</span>
|
||||
<span slot="tooltip">{$_('quantities.distance')}</span>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<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" />
|
||||
<WithUnits value={data.elevation.loss} type="elevation" />
|
||||
<WithUnits value={$gpxStatistics.global.elevation.loss} type="elevation" />
|
||||
</span>
|
||||
<span slot="tooltip">{$_('quantities.elevation')}</span>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<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>
|
||||
<WithUnits value={data.speed.moving} type="speed" />
|
||||
<WithUnits value={$gpxStatistics.global.speed.moving} type="speed" />
|
||||
</span>
|
||||
<span slot="tooltip"
|
||||
>{$settings.velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_(
|
||||
@@ -50,9 +44,9 @@
|
||||
<Tooltip>
|
||||
<span slot="data" class="flex flex-row items-center">
|
||||
<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>
|
||||
<WithUnits value={data.time.moving} type="time" />
|
||||
<WithUnits value={$gpxStatistics.global.time.moving} type="time" />
|
||||
</span>
|
||||
<span slot="tooltip"
|
||||
>{$_('quantities.time')} ({$_('quantities.total')} / {$_('quantities.moving')})</span
|
@@ -1,8 +1,7 @@
|
||||
import type { GPXFile } from "gpx";
|
||||
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 type { FreezedObject } from "structurajs";
|
||||
|
||||
let defaultWeight = 6;
|
||||
let defaultOpacity = 1;
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { writable, get, type Writable } from 'svelte/store';
|
||||
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { GPXFile, buildGPX, parseGPX, GPXFiles } from 'gpx';
|
||||
import { GPXFile, buildGPX, parseGPX, GPXStatistics } from 'gpx';
|
||||
import { tick } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
|
||||
import { dbUtils, fileObservers } from './db';
|
||||
import type { FreezedObject } from 'structurajs';
|
||||
|
||||
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() {
|
||||
let fileIds: string[] = get(fileOrder).filter((f) => get(selectedFiles).has(f));
|
||||
let files: GPXFile[] = fileIds
|
||||
.map((id) => {
|
||||
let fileObserver = get(fileObservers).get(id);
|
||||
return fileObserver ? get(fileObserver) : null;
|
||||
})
|
||||
.filter((f) => f) as GPXFile[];
|
||||
let gpxFiles = new GPXFiles(files);
|
||||
gpxData.set(gpxFiles.getTrackPointsAndStatistics());
|
||||
let fileIds: string[] = get(fileOrder).filter((f) => fileStatistics.has(f) && get(selectedFiles).has(f));
|
||||
gpxStatistics.set(fileIds.reduce((stats: GPXStatistics, fileId: string) => {
|
||||
stats.mergeWith(fileStatistics.get(fileId) ?? new GPXStatistics());
|
||||
return stats;
|
||||
}, new GPXStatistics()));
|
||||
}
|
||||
|
||||
let selectedFilesUnsubscribe: Function[] = [];
|
||||
@@ -49,8 +45,11 @@ selectedFiles.subscribe((selectedFiles) => {
|
||||
selectedFiles.forEach((fileId) => {
|
||||
let fileObserver = get(fileObservers).get(fileId);
|
||||
if (fileObserver) {
|
||||
let unsubscribe = fileObserver.subscribe(() => {
|
||||
updateGPXData();
|
||||
let unsubscribe = fileObserver.subscribe((file) => {
|
||||
if (file) {
|
||||
fileStatistics.set(fileId, file.getStatistics());
|
||||
updateGPXData();
|
||||
}
|
||||
});
|
||||
selectedFilesUnsubscribe.push(unsubscribe);
|
||||
}
|
||||
@@ -116,23 +115,23 @@ export async function loadFiles(list: FileList) {
|
||||
if (file) {
|
||||
files.push(file);
|
||||
|
||||
let fileBounds = file.getStatistics().bounds;
|
||||
/*let fileBounds = file.getStatistics().bounds;
|
||||
bounds.extend(fileBounds.southWest);
|
||||
bounds.extend(fileBounds.northEast);
|
||||
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);
|
||||
|
||||
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, {
|
||||
padding: 80,
|
||||
linear: true,
|
||||
easing: () => 1
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
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 url = URL.createObjectURL(blob);
|
||||
let a = document.createElement('a');
|
||||
|
Reference in New Issue
Block a user