mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-01 08:12:32 +00:00
progress
This commit is contained in:
@@ -16,6 +16,7 @@ abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
|
|||||||
|
|
||||||
abstract computeStatistics(): GPXStatistics;
|
abstract computeStatistics(): GPXStatistics;
|
||||||
|
|
||||||
|
abstract append(points: TrackPoint[]): void;
|
||||||
abstract reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void;
|
abstract reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void;
|
||||||
|
|
||||||
abstract getStartTimestamp(): Date;
|
abstract getStartTimestamp(): Date;
|
||||||
@@ -44,6 +45,16 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
|
|||||||
return statistics;
|
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 {
|
reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date): void {
|
||||||
const children = this.getChildren();
|
const children = this.getChildren();
|
||||||
|
|
||||||
@@ -130,7 +141,10 @@ export class GPXFiles extends GPXTreeNode<GPXFile> {
|
|||||||
super();
|
super();
|
||||||
this.files = files;
|
this.files = files;
|
||||||
|
|
||||||
this.computeStatistics();
|
this.statistics = new GPXStatistics();
|
||||||
|
for (let file of files) {
|
||||||
|
this.statistics.mergeWith(file.statistics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildren(): GPXFile[] {
|
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);
|
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 {
|
reverse(originalNextTimestamp: Date | undefined, newPreviousTimestamp: Date | undefined): void {
|
||||||
if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined) {
|
if (originalNextTimestamp !== undefined && newPreviousTimestamp !== undefined) {
|
||||||
let originalEndTimestamp = this.getEndTimestamp();
|
let originalEndTimestamp = this.getEndTimestamp();
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<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 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 GPXData from '$lib/components/GPXData.svelte';
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<Toolbar />
|
<Toolbar />
|
||||||
<Map class="h-full" />
|
<Map class="h-full" />
|
||||||
<LayerControl />
|
<LayerControl />
|
||||||
<Data />
|
<GPXMapLayers />
|
||||||
<FileList />
|
<FileList />
|
||||||
</div>
|
</div>
|
||||||
<div class="h-60 flex flex-row gap-2 overflow-hidden border">
|
<div class="h-60 flex flex-row gap-2 overflow-hidden border">
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
import Chart from 'chart.js/auto';
|
import Chart from 'chart.js/auto';
|
||||||
import mapboxgl from 'mapbox-gl';
|
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 { onDestroy, onMount } from 'svelte';
|
||||||
import {
|
import {
|
||||||
@@ -230,9 +231,8 @@
|
|||||||
|
|
||||||
$: if (chart && $settings) {
|
$: if (chart && $settings) {
|
||||||
let gpxFiles = new GPXFiles(Array.from($selectedFiles));
|
let gpxFiles = new GPXFiles(Array.from($selectedFiles));
|
||||||
let order = $fileOrder.length == 0 ? $fileCollection.files : $fileOrder;
|
|
||||||
gpxFiles.files.sort(function (a, b) {
|
gpxFiles.files.sort(function (a, b) {
|
||||||
return order.indexOf(a) - order.indexOf(b);
|
return get(fileOrder).indexOf(a) - get(fileOrder).indexOf(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
// update data
|
// update data
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import GPX from './GPX.svelte';
|
import GPXMapLayer from './GPXMapLayer.svelte';
|
||||||
|
|
||||||
import { fileCollection } from '$lib/stores';
|
import { fileCollection } from '$lib/stores';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $fileCollection.files as file}
|
{#each $fileCollection.files as file}
|
||||||
<GPX {file} />
|
<GPXMapLayer {file} />
|
||||||
{/each}
|
{/each}
|
@@ -7,8 +7,8 @@
|
|||||||
import * as Alert from '$lib/components/ui/alert';
|
import * as Alert from '$lib/components/ui/alert';
|
||||||
import { CircleHelp } from 'lucide-svelte';
|
import { CircleHelp } from 'lucide-svelte';
|
||||||
|
|
||||||
import { map, selectedFiles } from '$lib/stores';
|
import { map, selectedFiles, fileCollection } from '$lib/stores';
|
||||||
import { AnchorPointHierarchy, getMarker } from './routing';
|
import { AnchorPointHierarchy, getMarker, route } from './routing';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import mapboxgl from 'mapbox-gl';
|
import mapboxgl from 'mapbox-gl';
|
||||||
import KDBush from 'kdbush';
|
import KDBush from 'kdbush';
|
||||||
@@ -17,22 +17,23 @@
|
|||||||
|
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
let routingProfile = {
|
let brouterProfiles: { [key: string]: string } = {
|
||||||
value: 'bike',
|
|
||||||
label: 'bike'
|
|
||||||
};
|
|
||||||
let brouterProfiles = {
|
|
||||||
bike: 'Trekking-dry',
|
bike: 'Trekking-dry',
|
||||||
racingBike: 'fastbike',
|
racing_bike: 'fastbike',
|
||||||
mountainBike: 'MTB',
|
mountain_bike: 'MTB',
|
||||||
foot: 'Hiking-Alpine-SAC6',
|
foot: 'Hiking-Alpine-SAC6',
|
||||||
motorcycle: 'Car-FastEco',
|
motorcycle: 'Car-FastEco',
|
||||||
water: 'river',
|
water: 'river',
|
||||||
railway: 'rail'
|
railway: 'rail'
|
||||||
};
|
};
|
||||||
|
let routingProfile = {
|
||||||
|
value: 'bike',
|
||||||
|
label: $_('toolbar.routing.activities.bike')
|
||||||
|
};
|
||||||
let routing = true;
|
let routing = true;
|
||||||
let privateRoads = false;
|
let privateRoads = false;
|
||||||
|
|
||||||
|
let anchorPointHierarchy: AnchorPointHierarchy | null = null;
|
||||||
let markers: mapboxgl.Marker[] = [];
|
let markers: mapboxgl.Marker[] = [];
|
||||||
let file: GPXFile | null = null;
|
let file: GPXFile | null = null;
|
||||||
let kdbush: KDBush | null = null;
|
let kdbush: KDBush | null = null;
|
||||||
@@ -50,8 +51,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function extendFile(e: mapboxgl.MapMouseEvent) {
|
async function extendFile(e: mapboxgl.MapMouseEvent) {
|
||||||
console.log(e.lngLat);
|
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;
|
let insertableMarker: mapboxgl.Marker | null = null;
|
||||||
@@ -107,12 +121,12 @@
|
|||||||
file = $selectedFiles.values().next().value;
|
file = $selectedFiles.values().next().value;
|
||||||
// record time
|
// record time
|
||||||
let start = performance.now();
|
let start = performance.now();
|
||||||
let anchorPoints = AnchorPointHierarchy.create(file);
|
anchorPointHierarchy = AnchorPointHierarchy.create(file);
|
||||||
// record time
|
// record time
|
||||||
let end = performance.now();
|
let end = performance.now();
|
||||||
console.log('Time to create anchor points: ' + (end - start) + 'ms');
|
console.log('Time to create anchor points: ' + (end - start) + 'ms');
|
||||||
|
|
||||||
markers = anchorPoints.getMarkers();
|
markers = anchorPointHierarchy.getMarkers();
|
||||||
|
|
||||||
toggleMarkersForZoomLevelAndBounds();
|
toggleMarkersForZoomLevelAndBounds();
|
||||||
$map.on('zoom', toggleMarkersForZoomLevelAndBounds);
|
$map.on('zoom', toggleMarkersForZoomLevelAndBounds);
|
||||||
@@ -150,7 +164,9 @@
|
|||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
{#each Object.keys(brouterProfiles) as profile}
|
{#each Object.keys(brouterProfiles) as profile}
|
||||||
<Select.Item value={profile}>{profile}</Select.Item>
|
<Select.Item value={profile}
|
||||||
|
>{$_(`toolbar.routing.activities.${profile}`)}</Select.Item
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</Select.Root>
|
||||||
|
@@ -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";
|
import mapboxgl from "mapbox-gl";
|
||||||
|
|
||||||
export function getMarker(coordinates: Coordinates, draggable: boolean = false): mapboxgl.Marker {
|
export function getMarker(coordinates: Coordinates, draggable: boolean = false): mapboxgl.Marker {
|
||||||
@@ -13,23 +14,18 @@ export function getMarker(coordinates: Coordinates, draggable: boolean = false):
|
|||||||
export type SimplifiedTrackPoint = { point: TrackPoint, index: number, distance?: number, segment?: number, zoom?: number };
|
export type SimplifiedTrackPoint = { point: TrackPoint, index: number, distance?: number, segment?: number, zoom?: number };
|
||||||
|
|
||||||
export class AnchorPointHierarchy {
|
export class AnchorPointHierarchy {
|
||||||
points: SimplifiedTrackPoint[][];
|
points: SimplifiedTrackPoint[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.points = [];
|
this.points = [];
|
||||||
for (let i = 0; i <= 20; i++) {
|
|
||||||
this.points.push([]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarkers(): mapboxgl.Marker[] {
|
getMarkers(): mapboxgl.Marker[] {
|
||||||
let markers = [];
|
let markers = [];
|
||||||
for (let points of this.points) {
|
for (let point of this.points) {
|
||||||
for (let point of points) {
|
let marker = getMarker(point.point.getCoordinates(), true);
|
||||||
let marker = getMarker(point.point.getCoordinates(), true);
|
Object.defineProperty(marker, '_simplified', { value: point });
|
||||||
Object.defineProperty(marker, '_simplified', { value: point });
|
markers.push(marker);
|
||||||
markers.push(marker);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return markers;
|
return markers;
|
||||||
}
|
}
|
||||||
@@ -46,7 +42,7 @@ export class AnchorPointHierarchy {
|
|||||||
simplified.forEach((point) => {
|
simplified.forEach((point) => {
|
||||||
point.segment = s;
|
point.segment = s;
|
||||||
point.zoom = getZoomLevelForDistance(point.point.getLatitude(), point.distance);
|
point.zoom = getZoomLevelForDistance(point.point.getLatitude(), point.distance);
|
||||||
hierarchy.points[point.zoom].push(point);
|
hierarchy.points.push(point);
|
||||||
});
|
});
|
||||||
s++;
|
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));
|
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) {
|
if (routing) {
|
||||||
getRoute(points, brouterProfile, privateRoads).then(response => {
|
return getRoute(points, brouterProfile, privateRoads);
|
||||||
return response.json();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return new Promise((resolve) => {
|
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> {
|
async function getRoute(points: Coordinates[], brouterProfile: string, privateRoads: boolean): Promise<TrackPoint[]> {
|
||||||
let url = `https://routing.gpx.studio?profile=${brouterProfile + privateRoads ? '-private' : ''}&lonlats=${points.map(point => `${point.getLongitude()},${point.getLatitude()}`).join('|')}&format=geojson&alternativeidx=0`;
|
let url = `https://routing.gpx.studio?lonlats=${points.map(point => `${point.lon},${point.lat}`).join('|')}&profile=${brouterProfile + (privateRoads ? '-private' : '')}&format=geojson&alternativeidx=0`;
|
||||||
return fetch(url);
|
|
||||||
|
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;
|
||||||
}
|
}
|
@@ -33,7 +33,16 @@
|
|||||||
"allow_private": "Allow private roads",
|
"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_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_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",
|
"time_tooltip": "Change the time and speed data",
|
||||||
"reverse_tooltip": "Reverse the direction",
|
"reverse_tooltip": "Reverse the direction",
|
||||||
|
Reference in New Issue
Block a user