This commit is contained in:
vcoppe
2024-04-24 19:32:55 +02:00
parent e12a704c2e
commit 88c6681a78
8 changed files with 103 additions and 43 deletions

View File

@@ -16,6 +16,7 @@ abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
abstract computeStatistics(): GPXStatistics;
abstract append(points: TrackPoint[]): void;
abstract reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void;
abstract getStartTimestamp(): Date;
@@ -44,6 +45,16 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
return statistics;
}
append(points: TrackPoint[]): void {
let children = this.getChildren();
if (children.length === 0) {
return;
}
children[children.length - 1].append(points);
}
reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void {
const children = this.getChildren();
@@ -130,7 +141,10 @@ export class GPXFiles extends GPXTreeNode<GPXFile> {
super();
this.files = files;
this.computeStatistics();
this.statistics = new GPXStatistics();
for (let file of files) {
this.statistics.mergeWith(file.statistics);
}
}
getChildren(): GPXFile[] {
@@ -357,6 +371,11 @@ export class TrackSegment extends GPXTreeLeaf {
return distanceWindowSmoothingWithDistanceAccumulator(points, 50, (accumulated, start, end) => 100 * (points[end].ele - points[start].ele) / accumulated);
}
append(points: TrackPoint[]): void {
this.trkpt = this.trkpt.concat(points);
//this.computeStatistics();
}
reverse(originalNextTimestamp: Date | undefined, newPreviousTimestamp: Date | undefined): void {
if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined) {
let originalEndTimestamp = this.getEndTimestamp();

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import Data from '$lib/components/Data.svelte';
import GPXMapLayers from '$lib/components/GPXMapLayers.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import FileList from '$lib/components/FileList.svelte';
import GPXData from '$lib/components/GPXData.svelte';
@@ -15,7 +15,7 @@
<Toolbar />
<Map class="h-full" />
<LayerControl />
<Data />
<GPXMapLayers />
<FileList />
</div>
<div class="h-60 flex flex-row gap-2 overflow-hidden border">

View File

@@ -6,7 +6,8 @@
import Chart from 'chart.js/auto';
import mapboxgl from 'mapbox-gl';
import { map, fileCollection, fileOrder, selectedFiles, settings } from '$lib/stores';
import { map, fileOrder, selectedFiles, settings } from '$lib/stores';
import { get } from 'svelte/store';
import { onDestroy, onMount } from 'svelte';
import {
@@ -230,9 +231,8 @@
$: if (chart && $settings) {
let gpxFiles = new GPXFiles(Array.from($selectedFiles));
let order = $fileOrder.length == 0 ? $fileCollection.files : $fileOrder;
gpxFiles.files.sort(function (a, b) {
return order.indexOf(a) - order.indexOf(b);
return get(fileOrder).indexOf(a) - get(fileOrder).indexOf(b);
});
// update data

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import GPX from './GPX.svelte';
import GPXMapLayer from './GPXMapLayer.svelte';
import { fileCollection } from '$lib/stores';
</script>
{#each $fileCollection.files as file}
<GPX {file} />
<GPXMapLayer {file} />
{/each}

View File

@@ -7,8 +7,8 @@
import * as Alert from '$lib/components/ui/alert';
import { CircleHelp } from 'lucide-svelte';
import { map, selectedFiles } from '$lib/stores';
import { AnchorPointHierarchy, getMarker } from './routing';
import { map, selectedFiles, fileCollection } from '$lib/stores';
import { AnchorPointHierarchy, getMarker, route } from './routing';
import { onDestroy } from 'svelte';
import mapboxgl from 'mapbox-gl';
import KDBush from 'kdbush';
@@ -17,22 +17,23 @@
import { _ } from 'svelte-i18n';
let routingProfile = {
value: 'bike',
label: 'bike'
};
let brouterProfiles = {
let brouterProfiles: { [key: string]: string } = {
bike: 'Trekking-dry',
racingBike: 'fastbike',
mountainBike: 'MTB',
racing_bike: 'fastbike',
mountain_bike: 'MTB',
foot: 'Hiking-Alpine-SAC6',
motorcycle: 'Car-FastEco',
water: 'river',
railway: 'rail'
};
let routingProfile = {
value: 'bike',
label: $_('toolbar.routing.activities.bike')
};
let routing = true;
let privateRoads = false;
let anchorPointHierarchy: AnchorPointHierarchy | null = null;
let markers: mapboxgl.Marker[] = [];
let file: GPXFile | null = null;
let kdbush: KDBush | null = null;
@@ -50,8 +51,21 @@
}
}
function extendFile(e: mapboxgl.MapMouseEvent) {
console.log(e.lngLat);
async function extendFile(e: mapboxgl.MapMouseEvent) {
if (file && anchorPointHierarchy && anchorPointHierarchy.points.length > 0) {
let lastPoint = anchorPointHierarchy.points[anchorPointHierarchy.points.length - 1];
let newPoint = {
lon: e.lngLat.lng,
lat: e.lngLat.lat
};
let response = await route(
[lastPoint.point.getCoordinates(), newPoint],
brouterProfiles[routingProfile.value],
privateRoads,
routing
);
console.log(response);
}
}
let insertableMarker: mapboxgl.Marker | null = null;
@@ -107,12 +121,12 @@
file = $selectedFiles.values().next().value;
// record time
let start = performance.now();
let anchorPoints = AnchorPointHierarchy.create(file);
anchorPointHierarchy = AnchorPointHierarchy.create(file);
// record time
let end = performance.now();
console.log('Time to create anchor points: ' + (end - start) + 'ms');
markers = anchorPoints.getMarkers();
markers = anchorPointHierarchy.getMarkers();
toggleMarkersForZoomLevelAndBounds();
$map.on('zoom', toggleMarkersForZoomLevelAndBounds);
@@ -150,7 +164,9 @@
</Select.Trigger>
<Select.Content>
{#each Object.keys(brouterProfiles) as profile}
<Select.Item value={profile}>{profile}</Select.Item>
<Select.Item value={profile}
>{$_(`toolbar.routing.activities.${profile}`)}</Select.Item
>
{/each}
</Select.Content>
</Select.Root>

View File

@@ -1,4 +1,5 @@
import type { Coordinates, GPXFile, TrackPoint } from "gpx";
import type { Coordinates, GPXFile } from "gpx";
import { TrackPoint } from "gpx";
import mapboxgl from "mapbox-gl";
export function getMarker(coordinates: Coordinates, draggable: boolean = false): mapboxgl.Marker {
@@ -13,24 +14,19 @@ export function getMarker(coordinates: Coordinates, draggable: boolean = false):
export type SimplifiedTrackPoint = { point: TrackPoint, index: number, distance?: number, segment?: number, zoom?: number };
export class AnchorPointHierarchy {
points: SimplifiedTrackPoint[][];
points: SimplifiedTrackPoint[];
constructor() {
this.points = [];
for (let i = 0; i <= 20; i++) {
this.points.push([]);
}
}
getMarkers(): mapboxgl.Marker[] {
let markers = [];
for (let points of this.points) {
for (let point of points) {
for (let point of this.points) {
let marker = getMarker(point.point.getCoordinates(), true);
Object.defineProperty(marker, '_simplified', { value: point });
markers.push(marker);
}
}
return markers;
}
@@ -46,7 +42,7 @@ export class AnchorPointHierarchy {
simplified.forEach((point) => {
point.segment = s;
point.zoom = getZoomLevelForDistance(point.point.getLatitude(), point.distance);
hierarchy.points[point.zoom].push(point);
hierarchy.points.push(point);
});
s++;
}
@@ -158,19 +154,39 @@ function bearing(latA: number, lonA: number, latB: number, lonB: number): number
Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(lonB - lonA));
}
export function route(points: TrackPoint[], brouterProfile: string, privateRoads: boolean, routing: boolean) {
export function route(points: Coordinates[], brouterProfile: string, privateRoads: boolean, routing: boolean): Promise<TrackPoint[]> {
if (routing) {
getRoute(points, brouterProfile, privateRoads).then(response => {
return response.json();
});
return getRoute(points, brouterProfile, privateRoads);
} else {
return new Promise((resolve) => {
resolve(points);
resolve(points.map(point => new TrackPoint({
attributes: {
lat: point.lat,
lon: point.lon
}
})));
});
}
}
function getRoute(points: TrackPoint[], brouterProfile: string, privateRoads: boolean): Promise<Response> {
let url = `https://routing.gpx.studio?profile=${brouterProfile + privateRoads ? '-private' : ''}&lonlats=${points.map(point => `${point.getLongitude()},${point.getLatitude()}`).join('|')}&format=geojson&alternativeidx=0`;
return fetch(url);
async function getRoute(points: Coordinates[], brouterProfile: string, privateRoads: boolean): Promise<TrackPoint[]> {
let url = `https://routing.gpx.studio?lonlats=${points.map(point => `${point.lon},${point.lat}`).join('|')}&profile=${brouterProfile + (privateRoads ? '-private' : '')}&format=geojson&alternativeidx=0`;
let response = await fetch(url);
let geojson = await response.json();
let route: TrackPoint[] = [];
let coordinates = geojson.features[0].geometry.coordinates;
for (let i = 0; i < coordinates.length; i++) {
let coord = coordinates[i];
route.push(new TrackPoint({
attributes: {
lat: coord[1],
lon: coord[0]
},
ele: coord[2] ?? undefined
}));
}
return route;
}

View File

@@ -33,7 +33,16 @@
"allow_private": "Allow private roads",
"help_no_file": "Select a file to use the routing tool, or create a new file from the menu",
"help_multiple_files": "Select a single file to use the routing tool",
"help": "Click on the map to add a new point, or drag existing points to change the route"
"help": "Click on the map to add a new point, or drag existing points to change the route",
"activities": {
"bike": "Bike",
"racing_bike": "Racing bike",
"mountain_bike": "Mountain bike",
"foot": "Run/hike",
"motorcycle": "Motorcycle",
"water": "Water",
"railway": "Railway"
}
},
"time_tooltip": "Change the time and speed data",
"reverse_tooltip": "Reverse the direction",