adds route editing capabilities

This commit is contained in:
Christian Beutel
2025-01-11 22:43:09 +01:00
parent a19a96f524
commit 35f12f1816
12 changed files with 469 additions and 181 deletions

View File

@@ -53,8 +53,6 @@
: null;
let selectedTrail: Trail | null = null;
let activeTrailIndex: number = -1;
let loading: boolean = false;
let loadingNextPage: boolean = false;
let filterExpanded: boolean = false;
@@ -63,6 +61,12 @@
let userQuery = "";
$: selectedTrailIndex = selectedTrail
? (selectedList?.expand?.trails?.indexOf(selectedTrail) ?? null)
: null;
$: selectedTrailWaypoints = selectedTrail?.expand?.waypoints;
onMount(() => {
if ($page.url.searchParams.get("list") && selectedList) {
setCurrentList(selectedList);
@@ -121,7 +125,6 @@
loadAllListsOnNextBack = false;
}
if (selectedTrail) {
mapWithElevation.unFocusTrail(selectedTrail);
selectedTrail = null;
} else if (selectedList) {
selectedList = null;
@@ -135,7 +138,6 @@
function selectTrail(trail: Trail) {
selectedTrail = trail;
mapWithElevation.focusTrail(trail);
window.scrollTo({ top: 0 });
}
@@ -353,14 +355,15 @@
<div id="trail-map" class:hidden={!showMap}>
<MapWithElevationMaplibre
trails={selectedList?.expand?.trails ?? []}
waypoints={selectedTrailWaypoints}
bind:map
bind:this={mapWithElevation}
bind:markers
activeTrail={selectedTrailIndex}
fitBounds="animate"
on:select={(e) => {
selectedTrail = e.detail;
}}
bind:activeTrail={activeTrailIndex}
showInfoPopup={true}
showTerrain={true}
></MapWithElevationMaplibre>

View File

@@ -19,6 +19,8 @@
<div id="trail-details" class="sticky top-[62px]">
<MapWithElevationMaplibre
trails={[$trail]}
waypoints={$trail.expand?.waypoints}
activeTrail={0}
bind:markers
showTerrain={true}
></MapWithElevationMaplibre>

View File

@@ -516,6 +516,8 @@
<div class="basis-full">
<MapWithElevationMaplibre
trails={[$trail]}
waypoints={$trail.expand?.waypoints}
activeTrail={0}
on:zoom={(e) => updateScale(e.detail)}
bind:map
{showGrid}

View File

@@ -80,7 +80,7 @@
const file = files[i];
try {
uploadProgress.set((progress += 100 / (files.length * 2)));
await trails_upload(file);
// await trails_upload(file);
} catch (e) {
errorsThrown += 1;
show_toast({
@@ -98,14 +98,7 @@
uploading = false;
jsConfetti.addConfetti({
confettiRadius: 4,
confettiColors: [
"#F4C842",
"#3C9D9B",
"#D1C4E9",
"#FF6F61",
"#A9D9C1",
"#F2F2F2",
],
emojis: ['🍃', '🍁'],
});
}, 500);

View File

@@ -37,11 +37,11 @@
} from "$lib/stores/trail_store.js";
import {
anchors,
appendToRoute,
calculateRouteBetween,
clearRoute,
deleteFromRoute,
editRoute,
insertIntoRoute,
route,
setRoute,
} from "$lib/stores/valhalla_store";
@@ -54,6 +54,9 @@
} from "$lib/util/format_util";
import { fromFile, gpx2trail } from "$lib/util/gpx_util";
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import { theme } from "$lib/stores/theme_store.js";
import {
createAnchorMarker,
createMarkerFromWaypoint,
@@ -67,9 +70,6 @@
import { backInOut } from "svelte/easing";
import { scale } from "svelte/transition";
import { z } from "zod";
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import { theme } from "$lib/stores/theme_store.js";
export let data;
@@ -94,6 +94,7 @@
let drawingActive = false;
let overwriteGPX = false;
let draggingMarker = false;
const ClientTrailCreateSchema = TrailCreateSchema.extend({
expand: z
@@ -171,7 +172,7 @@
?.trkpt?.at(0)?.$.lon;
}
if (!form.id) {
if ($page.params.id === "new") {
const createdTrail = await trails_create(
form as Trail,
photoFiles,
@@ -213,6 +214,19 @@
if ($formData.expand!.gpx_data) {
const gpx = await GPX.parse($formData.expand!.gpx_data);
if (!(gpx instanceof Error)) {
if (gpx.rte && !gpx.trk) {
gpx.trk = [
{
trkseg: [
{
trkpt: gpx.rte?.at(0)?.rtept,
},
],
},
];
gpx.rte = undefined;
}
setRoute(gpx);
initRouteAnchors(gpx);
}
@@ -245,7 +259,7 @@
const prevId = $formData.id;
const parseResult = await gpx2trail(gpxData, selectedFile.name);
setFields(parseResult.trail);
$formData.id = prevId;
$formData.id = prevId ?? cryptoRandomString({ length: 15 });
$formData.expand!.gpx_data = gpxData;
setFields(
"category",
@@ -273,12 +287,20 @@
$formData.expand!.summit_logs.push(log);
if (parseResult.gpx.rte?.length && !parseResult.gpx.trk) {
parseResult.gpx.trk = [
{
trkseg: [
{
trkpt: parseResult.gpx.rte?.at(0)?.rtept,
},
],
},
];
parseResult.gpx.rte = undefined;
}
setRoute(parseResult.gpx);
initRouteAnchors(parseResult.gpx);
for (const waypoint of $formData.expand!.waypoints) {
saveWaypoint(waypoint);
}
} catch (e) {
console.error(e);
@@ -329,12 +351,18 @@
const points = segment.trkpt ?? [];
if (points.length > 0) {
addAnchor(points[0].$.lat!, points[0].$.lon!, false);
addAnchor(
points[0].$.lat!,
points[0].$.lon!,
anchors.length,
false,
);
}
if (i == segments.length - 1) {
addAnchor(
points[points.length - 1].$.lat!,
points[points.length - 1].$.lon!,
anchors.length,
false,
);
}
@@ -369,6 +397,7 @@
$formData.expand!.waypoints.splice(index, 1);
$formData.waypoints.splice(index, 1);
$formData.expand!.waypoints = $formData.expand!.waypoints;
// updateTrailOnMap();
}
function saveWaypoint(savedWaypoint: Waypoint) {
@@ -377,7 +406,6 @@
);
if (editedWaypointIndex >= 0) {
$formData.expand!.waypoints[editedWaypointIndex].marker?.remove();
$formData.expand!.waypoints[editedWaypointIndex] = savedWaypoint;
} else {
savedWaypoint.id = cryptoRandomString({ length: 15 });
@@ -385,11 +413,9 @@
...$formData.expand!.waypoints,
savedWaypoint,
];
}
const marker = createMarkerFromWaypoint(savedWaypoint, moveMarker);
marker.addTo(map);
savedWaypoint.marker = marker;
// updateTrailOnMap();
}
}
function moveMarker(marker: M.Marker, wpId?: string) {
@@ -405,6 +431,7 @@
editableWaypoint.lat = position.lat;
editableWaypoint.lon = position.lng;
$formData.expand!.waypoints = [...$formData.expand!.waypoints];
// updateTrailOnMap();
}
function beforeSummitLogModalOpen() {
@@ -493,7 +520,7 @@
}
const anchorCount = anchors.length;
if (anchorCount == 0) {
addAnchor(e.lngLat.lat, e.lngLat.lng);
addAnchor(e.lngLat.lat, e.lngLat.lng, anchors.length);
} else {
const previousAnchor = anchors[anchorCount - 1];
try {
@@ -505,8 +532,8 @@
selectedModeOfTransport,
autoRouting,
);
appendToRoute(routeWaypoints);
addAnchor(e.lngLat.lat, e.lngLat.lng);
insertIntoRoute(routeWaypoints);
addAnchor(e.lngLat.lat, e.lngLat.lng, anchors.length);
updateTrailWithRouteData();
} catch (e) {
console.error(e);
@@ -519,7 +546,12 @@
}
}
function addAnchor(lat: number, lon: number, addtoMap: boolean = true) {
function addAnchor(
lat: number,
lon: number,
index: number,
addtoMap: boolean = true,
) {
const anchor: ValhallaAnchor = {
id: cryptoRandomString({ length: 15 }),
lat: lat,
@@ -528,10 +560,13 @@
const marker = createAnchorMarker(
lat,
lon,
anchors.length + 1,
index + 1,
() => {
removeAnchor(anchors.findIndex((a) => a.id == anchor.id));
},
(e) => {
draggingMarker = true;
},
(_) => {
if (!drawingActive) {
return;
@@ -540,13 +575,16 @@
anchor.lat = position.lat;
anchor.lon = position.lng;
recalculateRoute(anchors.findIndex((a) => a.id == anchor.id));
draggingMarker = false;
},
);
if (addtoMap) {
marker.addTo(map);
}
anchor.marker = marker;
anchors.push(anchor);
anchors.splice(index, 0, anchor);
return anchor;
}
function removeAnchor(anchorIndex: number) {
@@ -583,21 +621,75 @@
}
let nextRouteSegment;
let previousRouteSegment;
if (anchorIndex < anchors.length - 1) {
const nextAnchor = anchors[anchorIndex + 1];
try {
if (anchorIndex < anchors.length - 1) {
const nextAnchor = anchors[anchorIndex + 1];
nextRouteSegment = await calculateRouteBetween(
anchor.lat,
anchor.lon,
nextAnchor.lat,
nextAnchor.lon,
selectedModeOfTransport,
autoRouting,
);
nextRouteSegment = await calculateRouteBetween(
anchor.lat,
anchor.lon,
nextAnchor.lat,
nextAnchor.lon,
selectedModeOfTransport,
autoRouting,
);
}
if (anchorIndex > 0) {
const previousAnchor = anchors[anchorIndex - 1];
previousRouteSegment = await calculateRouteBetween(
previousAnchor.lat,
previousAnchor.lon,
anchor.lat,
anchor.lon,
selectedModeOfTransport,
autoRouting,
);
}
if (nextRouteSegment) {
editRoute(anchorIndex, nextRouteSegment);
}
if (previousRouteSegment) {
editRoute(anchorIndex - 1, previousRouteSegment);
}
updateTrailWithRouteData();
} catch (e) {
console.error(e);
show_toast({
text: "Error calculating route",
icon: "close",
type: "error",
});
}
if (anchorIndex > 0) {
const previousAnchor = anchors[anchorIndex - 1];
previousRouteSegment = await calculateRouteBetween(
}
async function handleSegmentDragEnd(data: {
segment: number;
event: M.MapMouseEvent;
}) {
if (draggingMarker) {
return;
}
const anchor = addAnchor(
data.event.lngLat.lat,
data.event.lngLat.lng,
data.segment + 1,
);
for (let i = data.segment + 2; i < anchors.length; i++) {
const anchor = anchors[i];
const markerIcon = anchor.marker?.getElement();
if (markerIcon) {
const markerText = markerIcon.textContent ?? "0";
const markerIndex = parseInt(markerText);
markerIcon.textContent = markerIndex + 1 + "";
}
}
const previousAnchor = anchors[data.segment];
const nextAnchor = anchors[data.segment + 2];
try {
const previousRouteSegment = await calculateRouteBetween(
previousAnchor.lat,
previousAnchor.lon,
anchor.lat,
@@ -605,14 +697,26 @@
selectedModeOfTransport,
autoRouting,
);
const nextRouteSegment = await calculateRouteBetween(
anchor.lat,
anchor.lon,
nextAnchor.lat,
nextAnchor.lon,
selectedModeOfTransport,
autoRouting,
);
editRoute(data.segment, previousRouteSegment);
insertIntoRoute(nextRouteSegment, data.segment + 1);
updateTrailWithRouteData();
} catch (e) {
console.error(e);
show_toast({
text: "Error calculating route",
icon: "close",
type: "error",
});
}
if (nextRouteSegment) {
editRoute(anchorIndex, nextRouteSegment);
}
if (previousRouteSegment) {
editRoute(anchorIndex - 1, previousRouteSegment);
}
updateTrailWithRouteData();
}
function updateTrailWithRouteData() {
@@ -623,10 +727,13 @@
$formData.elevation_gain = totals.elevationGain;
$formData.elevation_loss = totals.elevationLoss;
$formData.expand!.gpx_data = route.toString();
if (!$formData.id) {
$formData.id = cryptoRandomString({ length: 15 });
}
}
function updateTrailOnMap() {
mapTrail = [{ ...($formData as Trail) }];
mapTrail = [$formData as Trail];
}
</script>
@@ -902,11 +1009,14 @@
<div id="trail-map">
<MapWithElevationMaplibre
trails={mapTrail}
waypoints={$formData.expand?.waypoints}
drawing={drawingActive}
showTerrain={true}
onMarkerDragEnd={moveMarker}
activeTrail={0}
bind:map
on:click={(e) => handleMapClick(e.detail)}
on:segmentDragEnd={(e) => handleSegmentDragEnd(e.detail)}
></MapWithElevationMaplibre>
</div>
</div>