adds split gpx function
This commit is contained in:
@@ -55,6 +55,10 @@
|
||||
segment: number;
|
||||
event: M.MapMouseEvent;
|
||||
}) => void;
|
||||
onsegmentclick?: (data: {
|
||||
segment: number;
|
||||
event: M.MapMouseEvent;
|
||||
}) => void;
|
||||
onselect?: (trail: Trail) => void;
|
||||
onunselect?: (trail: Trail) => void;
|
||||
onfullscreen?: () => void;
|
||||
@@ -87,6 +91,7 @@
|
||||
clusterTrails = false,
|
||||
onmarkerdragend,
|
||||
onsegmentdragend,
|
||||
onsegmentclick,
|
||||
onselect,
|
||||
onunselect,
|
||||
onfullscreen,
|
||||
@@ -408,7 +413,7 @@
|
||||
activeTrail = trails.findIndex((t) => t.id == trail.id);
|
||||
};
|
||||
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("mouseleave", id, layers[id].listener.onLeave);
|
||||
@@ -529,7 +534,7 @@
|
||||
elevationMarker.setLngLat(e.lngLat);
|
||||
}
|
||||
|
||||
function handDragStart(e: M.MapMouseEvent, id: string) {
|
||||
function handleDragStart(e: M.MapMouseEvent, id: string) {
|
||||
if (
|
||||
!drawing ||
|
||||
(e.originalEvent.target as HTMLElement | null)?.classList.contains(
|
||||
@@ -549,13 +554,21 @@
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
recalculateElevationData = false;
|
||||
crop = false;
|
||||
editRoute = !editRoute;
|
||||
}}><i class="fa fa-pen text-sm"></i></button
|
||||
}}><i class="fa fa-route text-sm"></i></button
|
||||
>
|
||||
<button
|
||||
class="btn-icon"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import GPX from "$lib/models/gpx/gpx";
|
||||
import type Track from "$lib/models/gpx/track";
|
||||
import TrackSegment from "$lib/models/gpx/track-segment";
|
||||
import { haversineDistance } from "$lib/models/gpx/utils";
|
||||
import Waypoint from "$lib/models/gpx/waypoint";
|
||||
import { type RoutingOptions, type ValhallaAnchor, type ValhallaHeightResponse, type ValhallaRouteResponse } from "$lib/models/valhalla";
|
||||
import { APIError } from "$lib/util/api_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 { get } from "svelte/store";
|
||||
|
||||
|
||||
const emtpyTrack: Track = { trkseg: [] }
|
||||
@@ -138,7 +140,7 @@ export function reverseRoute() {
|
||||
._content.getElementsByTagName("h5")[0];
|
||||
if (anchorPopupHeading) {
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
let currentTime = new Date();
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
resetRoute,
|
||||
reverseRoute,
|
||||
setRoute,
|
||||
splitSegment,
|
||||
} from "$lib/stores/valhalla_store.svelte.js";
|
||||
import { waypoint } from "$lib/stores/waypoint_store";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
@@ -93,7 +94,7 @@
|
||||
import { onMount, untrack } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { backInOut } from "svelte/easing";
|
||||
import { slide } from "svelte/transition";
|
||||
import { fly, slide } from "svelte/transition";
|
||||
import { z } from "zod";
|
||||
|
||||
let { data } = $props();
|
||||
@@ -124,7 +125,7 @@
|
||||
let cropStartMarker: FontawesomeMarker;
|
||||
let cropEndMarker: FontawesomeMarker;
|
||||
|
||||
let flatRoute: GPXWaypoint[] = $derived(valhallaStore.route.flatten())
|
||||
let flatRoute: GPXWaypoint[] = $derived(valhallaStore.route.flatten());
|
||||
|
||||
let croppedGPX: GPX | null = null;
|
||||
|
||||
@@ -411,12 +412,16 @@
|
||||
|
||||
function initCropMarkers() {
|
||||
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)?.$.lat ?? 0,
|
||||
valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)?.$
|
||||
.lon ?? 0,
|
||||
valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)?.$
|
||||
.lat ?? 0,
|
||||
];
|
||||
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)?.$.lat ?? 0,
|
||||
valhallaStore.route.trk?.at(-1)?.trkseg?.at(-1)?.trkpt?.at(-1)?.$
|
||||
.lon ?? 0,
|
||||
valhallaStore.route.trk?.at(-1)?.trkseg?.at(-1)?.trkpt?.at(-1)?.$
|
||||
.lat ?? 0,
|
||||
];
|
||||
if (!cropStartMarker || !cropEndMarker) {
|
||||
cropStartMarker = new FontawesomeMarker(
|
||||
@@ -611,6 +616,7 @@
|
||||
for (const anchor of valhallaStore.anchors) {
|
||||
anchor.marker?.remove();
|
||||
}
|
||||
toggleCropMarkers(false);
|
||||
|
||||
if (valhallaStore.route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0)) {
|
||||
$formData.lat = valhallaStore.route.trk
|
||||
@@ -649,7 +655,11 @@
|
||||
} else {
|
||||
const anchorCount = valhallaStore.anchors.length;
|
||||
if (anchorCount == 0) {
|
||||
addAnchor(e.lngLat.lat, e.lngLat.lng, valhallaStore.anchors.length);
|
||||
addAnchor(
|
||||
e.lngLat.lat,
|
||||
e.lngLat.lng,
|
||||
valhallaStore.anchors.length,
|
||||
);
|
||||
} else {
|
||||
await addAnchorAndRecalculate(e.lngLat.lat, e.lngLat.lng);
|
||||
}
|
||||
@@ -657,7 +667,8 @@
|
||||
}
|
||||
|
||||
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 markerText = startAnchorLoading(anchor);
|
||||
try {
|
||||
@@ -699,10 +710,14 @@
|
||||
lon,
|
||||
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(
|
||||
thisAnchor?.lat ?? lat,
|
||||
thisAnchor?.lon ?? lon,
|
||||
@@ -794,7 +809,9 @@
|
||||
}
|
||||
|
||||
async function recalculateRoute(anchorIndex: number) {
|
||||
const markerText = startAnchorLoading(valhallaStore.anchors[anchorIndex]);
|
||||
const markerText = startAnchorLoading(
|
||||
valhallaStore.anchors[anchorIndex],
|
||||
);
|
||||
|
||||
const anchor = valhallaStore.anchors[anchorIndex];
|
||||
if (!anchor) {
|
||||
@@ -860,21 +877,8 @@
|
||||
data.segment + 1,
|
||||
);
|
||||
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 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() {
|
||||
reverseRoute();
|
||||
|
||||
@@ -935,20 +970,27 @@
|
||||
} else {
|
||||
cropStartMarker?.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]) {
|
||||
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(
|
||||
flatRoute,
|
||||
valhallaStore.route.features.cumulativeDistance,
|
||||
targetStartDistance,
|
||||
);
|
||||
|
||||
const targetEndDistance = valhallaStore.route.features.distance * (end / 100);
|
||||
const targetEndDistance =
|
||||
valhallaStore.route.features.distance * (end / 100);
|
||||
const [endLon, endLat, endIndex] = getCoordinateAtDistance(
|
||||
flatRoute,
|
||||
valhallaStore.route.features.cumulativeDistance,
|
||||
@@ -958,7 +1000,11 @@
|
||||
cropStartMarker.setLngLat([startLon, startLat]);
|
||||
cropEndMarker.setLngLat([endLon, endLat]);
|
||||
|
||||
croppedGPX = cropGPX(flatRoute[startIndex], flatRoute[endIndex], valhallaStore.route);
|
||||
croppedGPX = cropGPX(
|
||||
flatRoute[startIndex],
|
||||
flatRoute[endIndex],
|
||||
valhallaStore.route,
|
||||
);
|
||||
const totals = croppedGPX.features;
|
||||
$formData.distance = totals.distance;
|
||||
$formData.duration = totals.duration / 1000;
|
||||
@@ -1425,8 +1471,8 @@
|
||||
<div class="relative">
|
||||
{#if drawingActive}
|
||||
<div
|
||||
in:slide={{ easing: backInOut, axis: "x" }}
|
||||
out:slide={{ easing: backInOut, axis: "x" }}
|
||||
in:fly={{ easing: backInOut, x: -30 }}
|
||||
out:fly={{ easing: backInOut, x: -30 }}
|
||||
class="absolute top-8 left-2 z-50"
|
||||
>
|
||||
<RouteEditor
|
||||
@@ -1450,6 +1496,7 @@
|
||||
activeTrail={0}
|
||||
bind:map
|
||||
onclick={(target) => handleMapClick(target)}
|
||||
onsegmentclick={(data) => handleSegmentClick(data)}
|
||||
onsegmentdragend={(data) => handleSegmentDragEnd(data)}
|
||||
mapOptions={{ preserveDrawingBuffer: true }}
|
||||
></MapWithElevationMaplibre>
|
||||
|
||||
Reference in New Issue
Block a user