adds split gpx function
This commit is contained in:
@@ -55,6 +55,10 @@
|
|||||||
segment: number;
|
segment: number;
|
||||||
event: M.MapMouseEvent;
|
event: M.MapMouseEvent;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
onsegmentclick?: (data: {
|
||||||
|
segment: number;
|
||||||
|
event: M.MapMouseEvent;
|
||||||
|
}) => void;
|
||||||
onselect?: (trail: Trail) => void;
|
onselect?: (trail: Trail) => void;
|
||||||
onunselect?: (trail: Trail) => void;
|
onunselect?: (trail: Trail) => void;
|
||||||
onfullscreen?: () => void;
|
onfullscreen?: () => void;
|
||||||
@@ -87,6 +91,7 @@
|
|||||||
clusterTrails = false,
|
clusterTrails = false,
|
||||||
onmarkerdragend,
|
onmarkerdragend,
|
||||||
onsegmentdragend,
|
onsegmentdragend,
|
||||||
|
onsegmentclick,
|
||||||
onselect,
|
onselect,
|
||||||
onunselect,
|
onunselect,
|
||||||
onfullscreen,
|
onfullscreen,
|
||||||
@@ -408,7 +413,7 @@
|
|||||||
activeTrail = trails.findIndex((t) => t.id == trail.id);
|
activeTrail = trails.findIndex((t) => t.id == trail.id);
|
||||||
};
|
};
|
||||||
layers[id].listener.onMouseMove = moveCrosshairToCursorPosition;
|
layers[id].listener.onMouseMove = moveCrosshairToCursorPosition;
|
||||||
layers[id].listener.onMouseDown = (e) => handDragStart(e, id);
|
layers[id].listener.onMouseDown = (e) => handleDragStart(e, id);
|
||||||
|
|
||||||
map.on("mouseenter", id, layers[id].listener.onEnter);
|
map.on("mouseenter", id, layers[id].listener.onEnter);
|
||||||
map.on("mouseleave", id, layers[id].listener.onLeave);
|
map.on("mouseleave", id, layers[id].listener.onLeave);
|
||||||
@@ -529,7 +534,7 @@
|
|||||||
elevationMarker.setLngLat(e.lngLat);
|
elevationMarker.setLngLat(e.lngLat);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handDragStart(e: M.MapMouseEvent, id: string) {
|
function handleDragStart(e: M.MapMouseEvent, id: string) {
|
||||||
if (
|
if (
|
||||||
!drawing ||
|
!drawing ||
|
||||||
(e.originalEvent.target as HTMLElement | null)?.classList.contains(
|
(e.originalEvent.target as HTMLElement | null)?.classList.contains(
|
||||||
@@ -549,13 +554,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
map?.on("mousemove", moveElevationMarkerToCursorPosition);
|
map?.on("mousemove", moveElevationMarkerToCursorPosition);
|
||||||
map?.once("mouseup", handleDragEnd);
|
map?.once("mouseup", (e2) => handleDragEnd(e2, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragEnd(e: M.MapMouseEvent) {
|
function handleDragEnd(end: M.MapMouseEvent, start: M.MapMouseEvent) {
|
||||||
map?.off("mousemove", moveElevationMarkerToCursorPosition);
|
map?.off("mousemove", moveElevationMarkerToCursorPosition);
|
||||||
epc?.hideCrosshair();
|
epc?.hideCrosshair();
|
||||||
onsegmentdragend?.({ segment: draggingSegment!, event: e });
|
const distanceDragged = Math.sqrt(
|
||||||
|
Math.pow(end.originalEvent.x - start.originalEvent.x, 2) +
|
||||||
|
Math.pow(end.originalEvent.y - start.originalEvent.y, 2),
|
||||||
|
);
|
||||||
|
if (distanceDragged < 0.5) {
|
||||||
|
onsegmentclick?.({ segment: draggingSegment!, event: end });
|
||||||
|
} else {
|
||||||
|
onsegmentdragend?.({ segment: draggingSegment!, event: end });
|
||||||
|
}
|
||||||
draggingSegment = null;
|
draggingSegment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,7 +1109,7 @@
|
|||||||
map!.getCanvas().style.cursor = "";
|
map!.getCanvas().style.cursor = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on("load", () => {
|
map.on("load", () => {
|
||||||
initMap(true);
|
initMap(true);
|
||||||
layerManager.init();
|
layerManager.init();
|
||||||
oninit?.(map!);
|
oninit?.(map!);
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
recalculateElevationData = false;
|
recalculateElevationData = false;
|
||||||
crop = false;
|
crop = false;
|
||||||
editRoute = !editRoute;
|
editRoute = !editRoute;
|
||||||
}}><i class="fa fa-pen text-sm"></i></button
|
}}><i class="fa fa-route text-sm"></i></button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import GPX from "$lib/models/gpx/gpx";
|
import GPX from "$lib/models/gpx/gpx";
|
||||||
import type Track from "$lib/models/gpx/track";
|
import type Track from "$lib/models/gpx/track";
|
||||||
import TrackSegment from "$lib/models/gpx/track-segment";
|
import TrackSegment from "$lib/models/gpx/track-segment";
|
||||||
|
import { haversineDistance } from "$lib/models/gpx/utils";
|
||||||
import Waypoint from "$lib/models/gpx/waypoint";
|
import Waypoint from "$lib/models/gpx/waypoint";
|
||||||
import { type RoutingOptions, type ValhallaAnchor, type ValhallaHeightResponse, type ValhallaRouteResponse } from "$lib/models/valhalla";
|
import { type RoutingOptions, type ValhallaAnchor, type ValhallaHeightResponse, type ValhallaRouteResponse } from "$lib/models/valhalla";
|
||||||
import { APIError } from "$lib/util/api_util";
|
import { APIError } from "$lib/util/api_util";
|
||||||
import { decodePolyline, encodePolyline } from "$lib/util/polyline_util";
|
import { decodePolyline, encodePolyline } from "$lib/util/polyline_util";
|
||||||
import { get } from "svelte/store";
|
import type { LngLat } from "maplibre-gl";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
|
||||||
const emtpyTrack: Track = { trkseg: [] }
|
const emtpyTrack: Track = { trkseg: [] }
|
||||||
@@ -138,7 +140,7 @@ export function reverseRoute() {
|
|||||||
._content.getElementsByTagName("h5")[0];
|
._content.getElementsByTagName("h5")[0];
|
||||||
if (anchorPopupHeading) {
|
if (anchorPopupHeading) {
|
||||||
anchorPopupHeading.textContent =
|
anchorPopupHeading.textContent =
|
||||||
get(_)("valhallaStore.route-point") + " #" + (i + 1);
|
get(_)("route-point") + " #" + (i + 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -161,6 +163,31 @@ export async function recalculateHeight() {
|
|||||||
await valhallaStore.route.correctElevation();
|
await valhallaStore.route.correctElevation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function splitSegment(index: number, pos: LngLat) {
|
||||||
|
let seg = valhallaStore.route.trk?.at(0)?.trkseg?.at(index);
|
||||||
|
if (!seg || !seg.trkpt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const points = seg.trkpt;
|
||||||
|
|
||||||
|
let bestSplitIndex: number = 0
|
||||||
|
let minDistance = Infinity
|
||||||
|
for (let i = 1; i < points.length; i++) {
|
||||||
|
const pt = points[i]
|
||||||
|
const dist = haversineDistance(pt.$.lat!, pt.$.lon!, pos.lat, pos.lng);
|
||||||
|
|
||||||
|
if (dist < minDistance) {
|
||||||
|
bestSplitIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstSegmentPoints = [...points.slice(0, index)];
|
||||||
|
const secondSegmentPoints = [...points.slice(index)];
|
||||||
|
|
||||||
|
seg.trkpt = firstSegmentPoints;
|
||||||
|
insertIntoRoute(secondSegmentPoints, index)
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeRouteTime() {
|
export function normalizeRouteTime() {
|
||||||
let currentTime = new Date();
|
let currentTime = new Date();
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
resetRoute,
|
resetRoute,
|
||||||
reverseRoute,
|
reverseRoute,
|
||||||
setRoute,
|
setRoute,
|
||||||
|
splitSegment,
|
||||||
} from "$lib/stores/valhalla_store.svelte.js";
|
} from "$lib/stores/valhalla_store.svelte.js";
|
||||||
import { waypoint } from "$lib/stores/waypoint_store";
|
import { waypoint } from "$lib/stores/waypoint_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
import { onMount, untrack } from "svelte";
|
import { onMount, untrack } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { backInOut } from "svelte/easing";
|
import { backInOut } from "svelte/easing";
|
||||||
import { slide } from "svelte/transition";
|
import { fly, slide } from "svelte/transition";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -124,7 +125,7 @@
|
|||||||
let cropStartMarker: FontawesomeMarker;
|
let cropStartMarker: FontawesomeMarker;
|
||||||
let cropEndMarker: FontawesomeMarker;
|
let cropEndMarker: FontawesomeMarker;
|
||||||
|
|
||||||
let flatRoute: GPXWaypoint[] = $derived(valhallaStore.route.flatten())
|
let flatRoute: GPXWaypoint[] = $derived(valhallaStore.route.flatten());
|
||||||
|
|
||||||
let croppedGPX: GPX | null = null;
|
let croppedGPX: GPX | null = null;
|
||||||
|
|
||||||
@@ -411,12 +412,16 @@
|
|||||||
|
|
||||||
function initCropMarkers() {
|
function initCropMarkers() {
|
||||||
const routeStartPoint: M.LngLatLike = [
|
const routeStartPoint: M.LngLatLike = [
|
||||||
valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)?.$.lon ?? 0,
|
valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)?.$
|
||||||
valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)?.$.lat ?? 0,
|
.lon ?? 0,
|
||||||
|
valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)?.$
|
||||||
|
.lat ?? 0,
|
||||||
];
|
];
|
||||||
const routeEndPoint: M.LngLatLike = [
|
const routeEndPoint: M.LngLatLike = [
|
||||||
valhallaStore.route.trk?.at(-1)?.trkseg?.at(-1)?.trkpt?.at(-1)?.$.lon ?? 0,
|
valhallaStore.route.trk?.at(-1)?.trkseg?.at(-1)?.trkpt?.at(-1)?.$
|
||||||
valhallaStore.route.trk?.at(-1)?.trkseg?.at(-1)?.trkpt?.at(-1)?.$.lat ?? 0,
|
.lon ?? 0,
|
||||||
|
valhallaStore.route.trk?.at(-1)?.trkseg?.at(-1)?.trkpt?.at(-1)?.$
|
||||||
|
.lat ?? 0,
|
||||||
];
|
];
|
||||||
if (!cropStartMarker || !cropEndMarker) {
|
if (!cropStartMarker || !cropEndMarker) {
|
||||||
cropStartMarker = new FontawesomeMarker(
|
cropStartMarker = new FontawesomeMarker(
|
||||||
@@ -611,6 +616,7 @@
|
|||||||
for (const anchor of valhallaStore.anchors) {
|
for (const anchor of valhallaStore.anchors) {
|
||||||
anchor.marker?.remove();
|
anchor.marker?.remove();
|
||||||
}
|
}
|
||||||
|
toggleCropMarkers(false);
|
||||||
|
|
||||||
if (valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)) {
|
if (valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)) {
|
||||||
$formData.lat = valhallaStore.route.trk
|
$formData.lat = valhallaStore.route.trk
|
||||||
@@ -649,7 +655,11 @@
|
|||||||
} else {
|
} else {
|
||||||
const anchorCount = valhallaStore.anchors.length;
|
const anchorCount = valhallaStore.anchors.length;
|
||||||
if (anchorCount == 0) {
|
if (anchorCount == 0) {
|
||||||
addAnchor(e.lngLat.lat, e.lngLat.lng, valhallaStore.anchors.length);
|
addAnchor(
|
||||||
|
e.lngLat.lat,
|
||||||
|
e.lngLat.lng,
|
||||||
|
valhallaStore.anchors.length,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await addAnchorAndRecalculate(e.lngLat.lat, e.lngLat.lng);
|
await addAnchorAndRecalculate(e.lngLat.lat, e.lngLat.lng);
|
||||||
}
|
}
|
||||||
@@ -657,7 +667,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addAnchorAndRecalculate(lat: number, lon: number) {
|
async function addAnchorAndRecalculate(lat: number, lon: number) {
|
||||||
const previousAnchor = valhallaStore.anchors[valhallaStore.anchors.length - 1];
|
const previousAnchor =
|
||||||
|
valhallaStore.anchors[valhallaStore.anchors.length - 1];
|
||||||
const anchor = addAnchor(lat, lon, valhallaStore.anchors.length);
|
const anchor = addAnchor(lat, lon, valhallaStore.anchors.length);
|
||||||
const markerText = startAnchorLoading(anchor);
|
const markerText = startAnchorLoading(anchor);
|
||||||
try {
|
try {
|
||||||
@@ -699,10 +710,14 @@
|
|||||||
lon,
|
lon,
|
||||||
index + 1,
|
index + 1,
|
||||||
() => {
|
() => {
|
||||||
removeAnchor(valhallaStore.anchors.findIndex((a) => a.id == anchor.id));
|
removeAnchor(
|
||||||
|
valhallaStore.anchors.findIndex((a) => a.id == anchor.id),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
const thisAnchor = valhallaStore.anchors.find((a) => a.id == anchor.id);
|
const thisAnchor = valhallaStore.anchors.find(
|
||||||
|
(a) => a.id == anchor.id,
|
||||||
|
);
|
||||||
addAnchorAndRecalculate(
|
addAnchorAndRecalculate(
|
||||||
thisAnchor?.lat ?? lat,
|
thisAnchor?.lat ?? lat,
|
||||||
thisAnchor?.lon ?? lon,
|
thisAnchor?.lon ?? lon,
|
||||||
@@ -794,7 +809,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function recalculateRoute(anchorIndex: number) {
|
async function recalculateRoute(anchorIndex: number) {
|
||||||
const markerText = startAnchorLoading(valhallaStore.anchors[anchorIndex]);
|
const markerText = startAnchorLoading(
|
||||||
|
valhallaStore.anchors[anchorIndex],
|
||||||
|
);
|
||||||
|
|
||||||
const anchor = valhallaStore.anchors[anchorIndex];
|
const anchor = valhallaStore.anchors[anchorIndex];
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
@@ -860,21 +877,8 @@
|
|||||||
data.segment + 1,
|
data.segment + 1,
|
||||||
);
|
);
|
||||||
const markerText = startAnchorLoading(anchor);
|
const markerText = startAnchorLoading(anchor);
|
||||||
|
updateFollowingAnchors(data.segment);
|
||||||
|
|
||||||
for (let i = data.segment + 2; i < valhallaStore.anchors.length; i++) {
|
|
||||||
const anchor = valhallaStore.anchors[i];
|
|
||||||
const markerIcon = anchor.marker?.getElement();
|
|
||||||
if (markerIcon) {
|
|
||||||
const markerText = markerIcon.textContent ?? "0";
|
|
||||||
const markerIndex = parseInt(markerText);
|
|
||||||
const newIndex = markerIndex + 1;
|
|
||||||
markerIcon.textContent = newIndex + "";
|
|
||||||
anchor
|
|
||||||
.marker!.getPopup()
|
|
||||||
._content.getElementsByTagName("h5")[0].textContent =
|
|
||||||
$_("route-point") + " #" + newIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const previousAnchor = valhallaStore.anchors[data.segment];
|
const previousAnchor = valhallaStore.anchors[data.segment];
|
||||||
const nextAnchor = valhallaStore.anchors[data.segment + 2];
|
const nextAnchor = valhallaStore.anchors[data.segment + 2];
|
||||||
|
|
||||||
@@ -910,6 +914,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateFollowingAnchors(segment: number) {
|
||||||
|
for (let i = segment + 2; i < valhallaStore.anchors.length; i++) {
|
||||||
|
const anchor = valhallaStore.anchors[i];
|
||||||
|
const markerIcon = anchor.marker?.getElement();
|
||||||
|
if (markerIcon) {
|
||||||
|
const markerText = markerIcon.textContent ?? "0";
|
||||||
|
const markerIndex = parseInt(markerText);
|
||||||
|
const newIndex = markerIndex + 1;
|
||||||
|
markerIcon.textContent = newIndex + "";
|
||||||
|
anchor
|
||||||
|
.marker!.getPopup()
|
||||||
|
._content.getElementsByTagName("h5")[0].textContent =
|
||||||
|
$_("route-point") + " #" + newIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSegmentClick(data: {
|
||||||
|
segment: number;
|
||||||
|
event: M.MapMouseEvent;
|
||||||
|
}) {
|
||||||
|
addAnchor(
|
||||||
|
data.event.lngLat.lat,
|
||||||
|
data.event.lngLat.lng,
|
||||||
|
data.segment + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
splitSegment(data.segment, data.event.lngLat);
|
||||||
|
updateFollowingAnchors(data.segment);
|
||||||
|
}
|
||||||
|
|
||||||
function reverseTrail() {
|
function reverseTrail() {
|
||||||
reverseRoute();
|
reverseRoute();
|
||||||
|
|
||||||
@@ -935,20 +970,27 @@
|
|||||||
} else {
|
} else {
|
||||||
cropStartMarker?.setOpacity("0");
|
cropStartMarker?.setOpacity("0");
|
||||||
cropEndMarker?.setOpacity("0");
|
cropEndMarker?.setOpacity("0");
|
||||||
|
const totals = valhallaStore.route.features;
|
||||||
|
$formData.distance = totals.distance;
|
||||||
|
$formData.duration = totals.duration / 1000;
|
||||||
|
$formData.elevation_gain = totals.elevationGain;
|
||||||
|
$formData.elevation_loss = totals.elevationLoss;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCropMarkers(range: [start: number, end: number]) {
|
function updateCropMarkers(range: [start: number, end: number]) {
|
||||||
const [start, end] = range;
|
const [start, end] = range;
|
||||||
|
|
||||||
const targetStartDistance = valhallaStore.route.features.distance * (start / 100);
|
const targetStartDistance =
|
||||||
|
valhallaStore.route.features.distance * (start / 100);
|
||||||
const [startLon, startLat, startIndex] = getCoordinateAtDistance(
|
const [startLon, startLat, startIndex] = getCoordinateAtDistance(
|
||||||
flatRoute,
|
flatRoute,
|
||||||
valhallaStore.route.features.cumulativeDistance,
|
valhallaStore.route.features.cumulativeDistance,
|
||||||
targetStartDistance,
|
targetStartDistance,
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetEndDistance = valhallaStore.route.features.distance * (end / 100);
|
const targetEndDistance =
|
||||||
|
valhallaStore.route.features.distance * (end / 100);
|
||||||
const [endLon, endLat, endIndex] = getCoordinateAtDistance(
|
const [endLon, endLat, endIndex] = getCoordinateAtDistance(
|
||||||
flatRoute,
|
flatRoute,
|
||||||
valhallaStore.route.features.cumulativeDistance,
|
valhallaStore.route.features.cumulativeDistance,
|
||||||
@@ -958,7 +1000,11 @@
|
|||||||
cropStartMarker.setLngLat([startLon, startLat]);
|
cropStartMarker.setLngLat([startLon, startLat]);
|
||||||
cropEndMarker.setLngLat([endLon, endLat]);
|
cropEndMarker.setLngLat([endLon, endLat]);
|
||||||
|
|
||||||
croppedGPX = cropGPX(flatRoute[startIndex], flatRoute[endIndex], valhallaStore.route);
|
croppedGPX = cropGPX(
|
||||||
|
flatRoute[startIndex],
|
||||||
|
flatRoute[endIndex],
|
||||||
|
valhallaStore.route,
|
||||||
|
);
|
||||||
const totals = croppedGPX.features;
|
const totals = croppedGPX.features;
|
||||||
$formData.distance = totals.distance;
|
$formData.distance = totals.distance;
|
||||||
$formData.duration = totals.duration / 1000;
|
$formData.duration = totals.duration / 1000;
|
||||||
@@ -1425,8 +1471,8 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#if drawingActive}
|
{#if drawingActive}
|
||||||
<div
|
<div
|
||||||
in:slide={{ easing: backInOut, axis: "x" }}
|
in:fly={{ easing: backInOut, x: -30 }}
|
||||||
out:slide={{ easing: backInOut, axis: "x" }}
|
out:fly={{ easing: backInOut, x: -30 }}
|
||||||
class="absolute top-8 left-2 z-50"
|
class="absolute top-8 left-2 z-50"
|
||||||
>
|
>
|
||||||
<RouteEditor
|
<RouteEditor
|
||||||
@@ -1450,6 +1496,7 @@
|
|||||||
activeTrail={0}
|
activeTrail={0}
|
||||||
bind:map
|
bind:map
|
||||||
onclick={(target) => handleMapClick(target)}
|
onclick={(target) => handleMapClick(target)}
|
||||||
|
onsegmentclick={(data) => handleSegmentClick(data)}
|
||||||
onsegmentdragend={(data) => handleSegmentDragEnd(data)}
|
onsegmentdragend={(data) => handleSegmentDragEnd(data)}
|
||||||
mapOptions={{ preserveDrawingBuffer: true }}
|
mapOptions={{ preserveDrawingBuffer: true }}
|
||||||
></MapWithElevationMaplibre>
|
></MapWithElevationMaplibre>
|
||||||
|
|||||||
Reference in New Issue
Block a user