more route drawing

This commit is contained in:
Christian Beutel
2024-04-23 16:11:49 +02:00
parent 8a3894106f
commit b3ed2b1098
10 changed files with 261 additions and 98 deletions

View File

@@ -13,9 +13,11 @@
import PhotoPicker from "$lib/components/trail/photo_picker.svelte";
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
import GPX from "$lib/models/gpx/gpx";
import type { List } from "$lib/models/list";
import { SummitLog } from "$lib/models/summit_log";
import { Trail } from "$lib/models/trail";
import type { ValhallaAnchor } from "$lib/models/valhalla";
import { Waypoint } from "$lib/models/waypoint";
import { categories } from "$lib/stores/category_store";
import {
@@ -32,12 +34,14 @@
trails_update,
} from "$lib/stores/trail_store";
import {
anchors,
appendToRoute,
calculateRouteBetween,
clearRoute,
deleteFromRoute,
editRoute,
route,
setRoute,
} from "$lib/stores/valhalla_store";
import { waypoint } from "$lib/stores/waypoint_store";
import { getFileURL } from "$lib/util/file_util";
@@ -47,10 +51,13 @@
formatTimeHHMM,
} from "$lib/util/format_util";
import { fromKML, fromTCX, gpx2trail } from "$lib/util/gpx_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import {
createAnchorMarker,
createMarkerFromWaypoint,
} from "$lib/util/leaflet_util";
import { createForm } from "$lib/vendor/svelte-form-lib";
import cryptoRandomString from "crypto-random-string";
import type { LeafletMouseEvent, Map } from "leaflet";
import type { DivIcon, LatLng, LeafletMouseEvent, Map } from "leaflet";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import { array, number, object, string } from "yup";
@@ -73,6 +80,7 @@
let gpxFile: File | Blob | null = null;
let drawingActive = false;
let overwriteGPX = false;
const trailSchema = object<Trail>({
id: string().optional(),
@@ -126,6 +134,13 @@
submittedTrail.photos = submittedTrail.photos.filter(
(p) => !p.startsWith("data:image/svg+xml;base64"),
);
if ($form.expand.gpx_data && overwriteGPX) {
gpxFile = new Blob([$form.expand.gpx_data], {
type: "text/xml",
});
}
if (!submittedTrail.id) {
const createdTrail = await trails_create(
submittedTrail,
@@ -165,8 +180,18 @@
L = (await import("leaflet")).default;
await import("leaflet.awesome-markers");
$form.lat = 47.74604748696788;
$form.lon = 11.588988304138185;
// $form.lat = 47.74604748696788;
// $form.lon = 11.588988304138185;
clearAnchorMarker();
clearRoute();
if ($form.expand.gpx_data) {
const gpx = await GPX.parse($form.expand.gpx_data);
if (!(gpx instanceof Error)) {
setRoute(gpx);
initRouteAnchors(gpx);
}
}
});
function openFileBrowser() {
@@ -183,7 +208,10 @@
}
clearWaypoints();
clearAnchorMarker();
clearRoute();
drawingActive = false;
overwriteGPX = false;
var reader = new FileReader();
reader.readAsText(selectedFile);
@@ -202,9 +230,13 @@
gpxFile = selectedFile;
}
try {
$form = await gpx2trail(gpxData);
const parseResult = await gpx2trail(gpxData);
$form = parseResult.trail;
$form.expand.gpx_data = gpxData;
setRoute(parseResult.gpx);
initRouteAnchors(parseResult.gpx);
for (const waypoint of $form.expand.waypoints) {
saveWaypoint(waypoint);
}
@@ -245,6 +277,32 @@
$form.waypoints = [];
}
function clearAnchorMarker() {
for (const anchor of anchors) {
anchor.marker?.remove();
}
}
function initRouteAnchors(gpx: GPX) {
const segments = gpx.trk?.at(0)?.trkseg ?? [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
const points = segment.trkpt ?? [];
if (points.length > 0) {
addAnchor(points[0].$.lat!, points[0].$.lon!, false);
}
if (i == segments.length - 1) {
addAnchor(
points[points.length - 1].$.lat!,
points[points.length - 1].$.lon!,
false,
);
}
}
}
function openMarkerPopup(waypoint: Waypoint) {
waypoint.marker?.openPopup();
}
@@ -273,28 +331,6 @@
$form.expand.waypoints.splice(index, 1);
$form.waypoints.splice(index, 1);
$form.expand.waypoints = $form.expand.waypoints;
if (drawingActive) {
if (index == 0) {
deleteFromRoute(index);
updateTrailWithRouteData();
const nextWaypoint = $form.expand.waypoints[index];
nextWaypoint.icon = "circle-half-stroke";
saveWaypoint(nextWaypoint);
} else if (index == $form.expand.waypoints.length) {
deleteFromRoute(index - 1);
updateTrailWithRouteData();
if ($form.expand.waypoints.length > 1) {
const previousWaypoint = $form.expand.waypoints[index - 1];
previousWaypoint.icon = "flag-checkered";
saveWaypoint(previousWaypoint);
}
} else {
deleteFromRoute(index - 1);
recalculateRoute(index);
}
}
}
function saveWaypoint(savedWaypoint: Waypoint) {
@@ -320,10 +356,6 @@
editableWaypoint!.lat = position.lat;
editableWaypoint!.lon = position.lng;
$form.expand.waypoints = [...$form.expand.waypoints];
if (drawingActive) {
recalculateRoute(editableWaypointIndex);
}
});
marker.addTo(map);
@@ -386,42 +418,37 @@
function startDrawing() {
drawingActive = true;
if (!route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.length) {
$form.expand.gpx_data = "";
clearWaypoints();
}
for (const anchor of anchors) {
anchor.marker?.addTo(map);
}
}
function stopDrawing() {
drawingActive = false;
for (const anchor of anchors) {
anchor.marker?.remove();
}
}
async function handleMapClick(e: LeafletMouseEvent) {
if (!drawingActive) {
return;
}
const waypointCount = $form.expand.waypoints.length;
const wp = new Waypoint(e.latlng.lat, e.latlng.lng, {
name: `${$_("waypoints", { values: { n: 1 } })} ${waypointCount + 1}`,
icon: waypointCount ? "flag-checkered" : "circle-half-stroke",
});
if (waypointCount == 0) {
saveWaypoint(wp);
const anchorCount = anchors.length;
if (anchorCount == 0) {
addAnchor(e.latlng.lat, e.latlng.lng);
} else {
const previousWaypoint = $form.expand.waypoints[waypointCount - 1];
const previousAnchor = anchors[anchorCount - 1];
try {
const routeWaypoints = await calculateRouteBetween(
previousWaypoint.lat,
previousWaypoint.lon,
previousAnchor.lat,
previousAnchor.lon,
e.latlng.lat,
e.latlng.lng,
);
appendToRoute(routeWaypoints);
saveWaypoint(wp);
if (waypointCount > 1) {
previousWaypoint.icon = "circle";
saveWaypoint(previousWaypoint);
}
addAnchor(e.latlng.lat, e.latlng.lng);
updateTrailWithRouteData();
} catch (e) {
console.error(e);
@@ -434,39 +461,104 @@
}
}
async function recalculateRoute(waypointIndex: number) {
const waypoint = $form.expand.waypoints[waypointIndex];
let nextWaypoints;
let previousWaypoints;
if (waypointIndex < $form.expand.waypoints.length - 1) {
const nextWaypoint = $form.expand.waypoints[waypointIndex + 1];
function addAnchor(lat: number, lon: number, addtoMap: boolean = true) {
const anchor: ValhallaAnchor = {
id: cryptoRandomString({ length: 15 }),
lat: lat,
lon: lon,
};
const marker = createAnchorMarker(
L,
lat,
lon,
anchors.length + 1,
() => {
removeAnchor(anchors.findIndex((a) => a.id == anchor.id));
},
(_) => {
if (!drawingActive) {
return;
}
const position = marker.getLatLng();
anchor.lat = position.lat;
anchor.lon = position.lng;
recalculateRoute(anchors.findIndex((a) => a.id == anchor.id));
},
);
if (addtoMap) {
marker.addTo(map);
}
anchor.marker = marker;
anchors.push(anchor);
}
nextWaypoints = await calculateRouteBetween(
waypoint.lat,
waypoint.lon,
nextWaypoint.lat,
nextWaypoint.lon,
function removeAnchor(anchorIndex: number) {
if (!drawingActive) {
return;
}
anchors[anchorIndex]?.marker?.remove();
anchors.splice(anchorIndex, 1);
for (let i = anchorIndex; i < anchors.length; i++) {
const anchor = anchors[i];
const markerIcon = anchor.marker?.getIcon() as DivIcon | undefined;
if (markerIcon) {
const markerText =
(markerIcon.options.html as HTMLSpanElement).textContent ??
"0";
const markerIndex = parseInt(markerText);
(markerIcon.options.html as HTMLSpanElement).textContent =
markerIndex - 1 + "";
}
}
if (anchorIndex == 0) {
deleteFromRoute(anchorIndex);
updateTrailWithRouteData();
} else if (anchorIndex == anchors.length) {
deleteFromRoute(anchorIndex - 1);
updateTrailWithRouteData();
} else {
deleteFromRoute(anchorIndex - 1);
recalculateRoute(anchorIndex);
}
}
async function recalculateRoute(anchorIndex: number) {
const anchor = anchors[anchorIndex];
if (!anchor) {
return;
}
let nextRouteSegment;
let previousRouteSegment;
if (anchorIndex < anchors.length - 1) {
const nextAnchor = anchors[anchorIndex + 1];
nextRouteSegment = await calculateRouteBetween(
anchor.lat,
anchor.lon,
nextAnchor.lat,
nextAnchor.lon,
);
}
if (waypointIndex > 0) {
const previousWaypoint = $form.expand.waypoints[waypointIndex - 1];
previousWaypoints = await calculateRouteBetween(
previousWaypoint.lat,
previousWaypoint.lon,
waypoint.lat,
waypoint.lon,
if (anchorIndex > 0) {
const previousAnchor = anchors[anchorIndex - 1];
previousRouteSegment = await calculateRouteBetween(
previousAnchor.lat,
previousAnchor.lon,
anchor.lat,
anchor.lon,
);
}
if (nextWaypoints) {
editRoute(waypointIndex, nextWaypoints);
if (nextRouteSegment) {
editRoute(anchorIndex, nextRouteSegment);
}
if (previousWaypoints) {
editRoute(waypointIndex - 1, previousWaypoints);
if (previousRouteSegment) {
editRoute(anchorIndex - 1, previousRouteSegment);
}
updateTrailWithRouteData();
}
function updateTrailWithRouteData() {
overwriteGPX = true;
const totals = route.getTotals();
$form.distance = totals.distance;
$form.duration = totals.duration;
@@ -720,11 +812,11 @@
trail={$form}
crosshair={drawingActive}
options={{
hotline: true,
autofitBounds: false,
trkStart: false,
trkEnd: false,
speed: false,
autofitBounds: !drawingActive,
mapTooltip: !drawingActive,
speed: !drawingActive,
speedFactor: drawingActive ? 0 : 1,
showStartEnd: !drawingActive,
}}
bind:map
on:click={(e) => handleMapClick(e.detail)}
@@ -742,11 +834,11 @@
></ListSelectModal>
<style>
:global(#map) {
#map {
height: calc(400px);
}
@media only screen and (min-width: 768px) {
:global(#map),
#map,
form {
height: calc(100vh - 124px);
}