mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 23:53:25 +00:00
elevation tool test
This commit is contained in:
@@ -21,6 +21,7 @@ export abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
|
|||||||
abstract getEndTimestamp(): Date | undefined;
|
abstract getEndTimestamp(): Date | undefined;
|
||||||
abstract getStatistics(): GPXStatistics;
|
abstract getStatistics(): GPXStatistics;
|
||||||
abstract getSegments(): TrackSegment[];
|
abstract getSegments(): TrackSegment[];
|
||||||
|
abstract getTrackPoints(): TrackPoint[];
|
||||||
|
|
||||||
abstract toGeoJSON(): GeoJSON.Feature | GeoJSON.Feature[] | GeoJSON.FeatureCollection | GeoJSON.FeatureCollection[];
|
abstract toGeoJSON(): GeoJSON.Feature | GeoJSON.Feature[] | GeoJSON.FeatureCollection | GeoJSON.FeatureCollection[];
|
||||||
|
|
||||||
@@ -66,6 +67,10 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
|
|||||||
return this.children.flatMap((child) => child.getSegments());
|
return this.children.flatMap((child) => child.getSegments());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTrackPoints(): TrackPoint[] {
|
||||||
|
return this.children.flatMap((child) => child.getTrackPoints());
|
||||||
|
}
|
||||||
|
|
||||||
// Producers
|
// Producers
|
||||||
_reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) {
|
_reverse(originalNextTimestamp?: Date, newPreviousTimestamp?: Date) {
|
||||||
let og = getOriginal(this);
|
let og = getOriginal(this);
|
||||||
@@ -315,6 +320,26 @@ export class GPXFile extends GPXTreeNode<Track>{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addElevation(callback: (Coordinates) => number, trackIndices?: number[], segmentIndices?: number[], waypointIndices?: number[]) {
|
||||||
|
let og = getOriginal(this); // Read as much as possible from the original object because it is faster
|
||||||
|
this.trk.forEach((track, trackIndex) => {
|
||||||
|
if (trackIndices === undefined || trackIndices.includes(trackIndex)) {
|
||||||
|
track.trkseg.forEach((segment, segmentIndex) => {
|
||||||
|
if (segmentIndices === undefined || segmentIndices.includes(segmentIndex)) {
|
||||||
|
segment.trkpt.forEach((point, pointIndex) => {
|
||||||
|
point.ele = callback(og.trk[trackIndex].trkseg[segmentIndex].trkpt[pointIndex].attributes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.wpt.forEach((waypoint, waypointIndex) => {
|
||||||
|
if (waypointIndices === undefined || waypointIndices.includes(waypointIndex)) {
|
||||||
|
waypoint.ele = callback(og.wpt[waypointIndex].attributes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setStyle(style: LineStyleExtension) {
|
setStyle(style: LineStyleExtension) {
|
||||||
this.trk.forEach((track) => {
|
this.trk.forEach((track) => {
|
||||||
track.setStyle(style);
|
track.setStyle(style);
|
||||||
@@ -753,6 +778,10 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
return [this];
|
return [this];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTrackPoints(): TrackPoint[] {
|
||||||
|
return this.trkpt;
|
||||||
|
}
|
||||||
|
|
||||||
toGeoJSON(): GeoJSON.Feature {
|
toGeoJSON(): GeoJSON.Feature {
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
|
@@ -9,7 +9,8 @@
|
|||||||
Ungroup,
|
Ungroup,
|
||||||
MapPin,
|
MapPin,
|
||||||
Filter,
|
Filter,
|
||||||
Scissors
|
Scissors,
|
||||||
|
MountainSnow
|
||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
|
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
@@ -45,6 +46,10 @@
|
|||||||
<Ungroup slot="icon" size="18" />
|
<Ungroup slot="icon" size="18" />
|
||||||
<span slot="tooltip">{$_('toolbar.extract.tooltip')}</span>
|
<span slot="tooltip">{$_('toolbar.extract.tooltip')}</span>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
|
<ToolbarItem tool={Tool.ELEVATION}>
|
||||||
|
<MountainSnow slot="icon" size="18" />
|
||||||
|
<span slot="tooltip">{$_('toolbar.elevation.tooltip')}</span>
|
||||||
|
</ToolbarItem>
|
||||||
<ToolbarItem tool={Tool.REDUCE}>
|
<ToolbarItem tool={Tool.REDUCE}>
|
||||||
<Filter slot="icon" size="18" />
|
<Filter slot="icon" size="18" />
|
||||||
<span slot="tooltip">{$_('toolbar.reduce.tooltip')}</span>
|
<span slot="tooltip">{$_('toolbar.reduce.tooltip')}</span>
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
import Time from '$lib/components/toolbar/tools/Time.svelte';
|
import Time from '$lib/components/toolbar/tools/Time.svelte';
|
||||||
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
|
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
|
||||||
import Extract from '$lib/components/toolbar/tools/Extract.svelte';
|
import Extract from '$lib/components/toolbar/tools/Extract.svelte';
|
||||||
|
import Elevation from '$lib/components/toolbar/tools/Elevation.svelte';
|
||||||
import Clean from '$lib/components/toolbar/tools/Clean.svelte';
|
import Clean from '$lib/components/toolbar/tools/Clean.svelte';
|
||||||
import Reduce from '$lib/components/toolbar/tools/Reduce.svelte';
|
import Reduce from '$lib/components/toolbar/tools/Reduce.svelte';
|
||||||
import RoutingControlPopup from '$lib/components/toolbar/tools/routing/RoutingControlPopup.svelte';
|
import RoutingControlPopup from '$lib/components/toolbar/tools/routing/RoutingControlPopup.svelte';
|
||||||
@@ -48,6 +49,8 @@
|
|||||||
<Time />
|
<Time />
|
||||||
{:else if $currentTool === Tool.MERGE}
|
{:else if $currentTool === Tool.MERGE}
|
||||||
<Merge />
|
<Merge />
|
||||||
|
{:else if $currentTool === Tool.ELEVATION}
|
||||||
|
<Elevation />
|
||||||
{:else if $currentTool === Tool.EXTRACT}
|
{:else if $currentTool === Tool.EXTRACT}
|
||||||
<Extract />
|
<Extract />
|
||||||
{:else if $currentTool === Tool.CLEAN}
|
{:else if $currentTool === Tool.CLEAN}
|
||||||
|
33
website/src/lib/components/toolbar/tools/Elevation.svelte
Normal file
33
website/src/lib/components/toolbar/tools/Elevation.svelte
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import { selection } from '$lib/components/file-list/Selection';
|
||||||
|
import Help from '$lib/components/Help.svelte';
|
||||||
|
import { MountainSnow } from 'lucide-svelte';
|
||||||
|
import { dbUtils } from '$lib/db';
|
||||||
|
import { map } from '$lib/stores';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
|
$: validSelection = $selection.size > 0;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
disabled={!validSelection}
|
||||||
|
on:click={async () => {
|
||||||
|
if ($map) {
|
||||||
|
dbUtils.addElevationToSelection($map);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MountainSnow size="16" class="mr-1" />
|
||||||
|
{$_('toolbar.elevation.button')}
|
||||||
|
</Button>
|
||||||
|
<Help>
|
||||||
|
{#if validSelection}
|
||||||
|
{$_('toolbar.elevation.help')}
|
||||||
|
{:else}
|
||||||
|
{$_('toolbar.elevation.help_no_selection')}
|
||||||
|
{/if}
|
||||||
|
</Help>
|
||||||
|
</div>
|
@@ -8,8 +8,8 @@ import { applyToOrderedItemsFromFile, applyToOrderedSelectedItemsFromFile, selec
|
|||||||
import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem, ListRootItem } from '$lib/components/file-list/FileList';
|
import { ListFileItem, ListItem, ListTrackItem, ListLevel, ListTrackSegmentItem, ListWaypointItem, ListRootItem } from '$lib/components/file-list/FileList';
|
||||||
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
|
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
|
||||||
import { SplitType } from '$lib/components/toolbar/tools/Scissors.svelte';
|
import { SplitType } from '$lib/components/toolbar/tools/Scissors.svelte';
|
||||||
import { getElevation } from '$lib/utils';
|
import { getElevation, getPreciseElevations } from '$lib/utils';
|
||||||
|
import type mapboxgl from 'mapbox-gl';
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
enablePatches();
|
enablePatches();
|
||||||
@@ -1008,6 +1008,64 @@ export const dbUtils = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addElevationToSelection: async (map: mapboxgl.Map) => {
|
||||||
|
if (get(selection).size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let points: (TrackPoint | Waypoint)[] = [];
|
||||||
|
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
||||||
|
let file = fileState.get(fileId);
|
||||||
|
if (file) {
|
||||||
|
if (level === ListLevel.FILE) {
|
||||||
|
points.push(...file.getTrackPoints());
|
||||||
|
points.push(...file.wpt);
|
||||||
|
} else if (level === ListLevel.TRACK) {
|
||||||
|
let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
|
||||||
|
trackIndices.forEach((trackIndex) => {
|
||||||
|
points.push(...file.trk[trackIndex].getTrackPoints());
|
||||||
|
});
|
||||||
|
} else if (level === ListLevel.SEGMENT) {
|
||||||
|
let trackIndex = (items[0] as ListTrackSegmentItem).getTrackIndex();
|
||||||
|
let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex());
|
||||||
|
segmentIndices.forEach((segmentIndex) => {
|
||||||
|
points.push(...file.trk[trackIndex].trkseg[segmentIndex].getTrackPoints());
|
||||||
|
});
|
||||||
|
} else if (level === ListLevel.WAYPOINTS) {
|
||||||
|
points.push(...file.wpt);
|
||||||
|
} else if (level === ListLevel.WAYPOINT) {
|
||||||
|
let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex());
|
||||||
|
points.push(...waypointIndices.map((waypointIndex) => file.wpt[waypointIndex]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getPreciseElevations(map, points).then((elevations) => {
|
||||||
|
|
||||||
|
let callback = (coordinates: Coordinates) => elevations.get(`${coordinates.lat},${coordinates.lon}`) ?? 0;
|
||||||
|
applyGlobal((draft) => {
|
||||||
|
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
||||||
|
let file = draft.get(fileId);
|
||||||
|
if (file) {
|
||||||
|
if (level === ListLevel.FILE) {
|
||||||
|
file.addElevation(callback);
|
||||||
|
} else if (level === ListLevel.TRACK) {
|
||||||
|
let trackIndices = items.map((item) => (item as ListTrackItem).getTrackIndex());
|
||||||
|
file.addElevation(callback, trackIndices, undefined, []);
|
||||||
|
} else if (level === ListLevel.SEGMENT) {
|
||||||
|
let trackIndices = [(items[0] as ListTrackSegmentItem).getTrackIndex()];
|
||||||
|
let segmentIndices = items.map((item) => (item as ListTrackSegmentItem).getSegmentIndex());
|
||||||
|
file.addElevation(callback, trackIndices, segmentIndices, []);
|
||||||
|
} else if (level === ListLevel.WAYPOINTS) {
|
||||||
|
file.addElevation(callback, [], [], undefined);
|
||||||
|
} else if (level === ListLevel.WAYPOINT) {
|
||||||
|
let waypointIndices = items.map((item) => (item as ListWaypointItem).getWaypointIndex());
|
||||||
|
file.addElevation(callback, [], [], waypointIndices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
deleteSelectedFiles: () => {
|
deleteSelectedFiles: () => {
|
||||||
if (get(selection).size === 0) {
|
if (get(selection).size === 0) {
|
||||||
return;
|
return;
|
||||||
|
@@ -135,6 +135,7 @@ export enum Tool {
|
|||||||
TIME,
|
TIME,
|
||||||
MERGE,
|
MERGE,
|
||||||
EXTRACT,
|
EXTRACT,
|
||||||
|
ELEVATION,
|
||||||
REDUCE,
|
REDUCE,
|
||||||
CLEAN
|
CLEAN
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ import { base } from "$app/paths";
|
|||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { languages } from "$lib/languages";
|
import { languages } from "$lib/languages";
|
||||||
import { locale } from "svelte-i18n";
|
import { locale } from "svelte-i18n";
|
||||||
import type Coordinates from "gpx";
|
import type { Coordinates, TrackPoint, Waypoint } from "gpx";
|
||||||
import type mapboxgl from "mapbox-gl";
|
import type mapboxgl from "mapbox-gl";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
@@ -74,6 +74,29 @@ export function getElevation(map: mapboxgl.Map, coordinates: Coordinates): numbe
|
|||||||
return elevation === null ? 0 : elevation;
|
return elevation === null ? 0 : elevation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPreciseElevation(map: mapboxgl.Map, coordinates: Coordinates | mapboxgl.LngLat): Promise<number> {
|
||||||
|
if (!map.getBounds().contains(coordinates) || map.getZoom() < 14) {
|
||||||
|
map.flyTo({ center: coordinates, zoom: 14 });
|
||||||
|
await map.once('idle');
|
||||||
|
}
|
||||||
|
let elevation = map.queryTerrainElevation(coordinates, { exaggerated: false });
|
||||||
|
return elevation === null ? 0 : elevation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPreciseElevations(map: mapboxgl.Map, points: (TrackPoint | Waypoint)[]): Promise<Map<string, number>> {
|
||||||
|
let elevations = new Map<string, number>();
|
||||||
|
|
||||||
|
for (let point of points) {
|
||||||
|
let key = `${point.getLatitude()},${point.getLongitude()}`;
|
||||||
|
if (elevations.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elevations.set(key, await getPreciseElevation(map, point.getCoordinates()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return elevations;
|
||||||
|
}
|
||||||
|
|
||||||
let previousCursors: string[] = [];
|
let previousCursors: string[] = [];
|
||||||
export function setCursor(cursor: string) {
|
export function setCursor(cursor: string) {
|
||||||
let m = get(map);
|
let m = get(map);
|
||||||
|
@@ -177,6 +177,12 @@
|
|||||||
"help": "Extracting the contents of the selected file items will create a separate file item for each of their contents.",
|
"help": "Extracting the contents of the selected file items will create a separate file item for each of their contents.",
|
||||||
"help_invalid_selection": "Your selection needs to contain file items with multiple traces to extract them."
|
"help_invalid_selection": "Your selection needs to contain file items with multiple traces to extract them."
|
||||||
},
|
},
|
||||||
|
"elevation": {
|
||||||
|
"tooltip": "Request elevation data",
|
||||||
|
"button": "Request elevation data",
|
||||||
|
"help": "Requesting elevation data will erase the existing elevation data, if any, and replace it with data from Mapbox.",
|
||||||
|
"help_no_selection": "Select a file item to request elevation data."
|
||||||
|
},
|
||||||
"waypoint": {
|
"waypoint": {
|
||||||
"tooltip": "Create and edit points of interest",
|
"tooltip": "Create and edit points of interest",
|
||||||
"longitude": "Longitude",
|
"longitude": "Longitude",
|
||||||
|
Reference in New Issue
Block a user