waypoint editing while drawing a route (#1060)

* waypoint editing while drawing a route

* fix waypoint click while drawing

---------

Co-authored-by: Christian Beutel <>
This commit is contained in:
slothful-vassal
2026-06-16 19:55:46 +02:00
committed by GitHub
parent c04a719ca6
commit 6baeafe755
6 changed files with 115 additions and 48 deletions

View File

@@ -48,7 +48,7 @@ export default defineConfig({
label: 'Authentication',
link: '/use/authentication/'
}, {
label: 'Create a trail',
label: 'Create/Edit a trail',
link: '/use/create-a-trail/'
},
{

View File

@@ -1,25 +1,21 @@
---
title: Create a trail
description: How to create a trail by uploading or drawing a trail using Valhalla
title: Create or edit a trail
description: How to create or edit a trail by uploading or drawing a trail using Valhalla
---
## What is a trail?
In <span class="-tracking-[0.075em]">wanderer</span>, a trail is a digital route that includes GPS data and descriptive metadata like name, difficulty, category, photos, and waypoints. Trails can be explored by others and searched in the app.
## Create a trail
To start, click the <button class="h-10 text-white rounded-lg px-4 py-2 mx-2 bg-primary font-semibold transition-all hover:bg-primary-hover focus:ring-4 ring-zinc-400 leading-none">+ New Trail</button> button in the top right corner.
## Step 1: Pick a route
### Step 1: Pick a route
Each trail must begin with a route. There are two ways to provide one:
### Upload a file
#### Upload a file
Click the **Upload file** button to select a GPS file. Accepted formats are **GPX**, **FIT**, **TCX**, or **KML**.
@@ -28,22 +24,20 @@ After uploading:
- The map centers on the route
- Elevation profile and speed (if available) are rendered
- Distance, elevation gain/loss, and other metadata are extracted
- The form fields on the left will be partially prefilled with data that extracted from the file
- The form fields on the left will be partially prefilled with data extracted from the file
### Draw a route
#### Draw a route
Click the **Draw a route** button to manually define a route on the map. While in drawing mode:
- Click on the map to place waypoints
- <span class="-tracking-[0.075em]">wanderer</span> will automatically route between points using the [Valhalla routing engine](https://github.com/valhalla/valhalla)
- You can drag points to reposition them
- The anchor list next to the map shows start, intermediate, and finish points with segment distance and elevation stats
- Hover an item in the anchor list to highlight its marker on the map
- Reorder intermediate anchors from the list to adjust the route sequence
- Click on the map to place route anchors
- <span class="-tracking-[0.075em]">wanderer</span> will automatically route between anchors using the [Valhalla routing engine](https://github.com/valhalla/valhalla)
- You can drag anchors to reposition them
- Use the top-left menu to change routing mode (e.g. walking, cycling)
- To remove a point, click on it and then click the red trash icon
- Use the waypoint button (location marker icon) in the route editing toolbar to show or hide existing waypoint markers on the map. Hiding them also disables right-clicking to create new waypoints.
- To remove an anchor, click on it and then click the red trash icon
If you disable Valhalla routing, straight lines will be used between points instead.
If you disable Valhalla routing, straight lines will be used between anchors instead.
To finish drawing, click **Stop drawing**.
@@ -51,11 +45,18 @@ To finish drawing, click **Stop drawing**.
<span class="-tracking-[0.075em]">wanderer</span> uses a public, donation-financed Valhalla server by default. Please consider supporting it at [https://www.fossgis.de/verein/spenden/](https://www.fossgis.de/verein/spenden/).
:::
#### Trail anchor list
While drawing or editing a route, the anchor list shows the route's start, intermediate, and finish anchors in order. It helps you inspect and adjust the structure of the route without relying only on the map:
## Step 2: Fill out trail details
- Each segment shows distance and elevation stats for the route section that follows the anchor.
- Hover an anchor in the list to highlight its marker on the map.
- Drag intermediate anchors in the list to reorder the route sequence.
- Delete an anchor from the list to remove it from the route.
### Basic Info
### Step 2: Fill out trail details
#### Basic Info
- **Name** Required. Every trail needs a name.
- **Location** Autofilled if available in the uploaded file.
@@ -66,7 +67,7 @@ To finish drawing, click **Stop drawing**.
- **Difficulty** Select the trail's difficulty (e.g. Easy, Moderate, Hard)
- **Category** Choose the activity type (e.g. Hiking, Cycling)
### Visibility
#### Visibility
Toggle the **Private** switch if you do not want the trail to be visible to others. When set to private, only you will be able to view and access this trail.
@@ -74,26 +75,23 @@ Toggle the **Private** switch if you do not want the trail to be visible to othe
Creating a public trail will automatically publish that trail to all your followers.
:::
## Step 3: Add Waypoints
### Step 3: Add Waypoints
Waypoints are points of interest along the trail.
- Click **+ Add Waypoint** to add one manually. It will appear centered on the map and can be dragged to another location.
- When you are not editing the route, click on the map to open a popup with a **Create waypoint** button at that location.
- While drawing or editing the route, right-click on the map to open the same popup without placing a route anchor. This works only while waypoint markers are visible (see the waypoint toggle in the route editing toolbar).
- Each waypoint can have a name, description, icon, and photos.
- Use Font Awesome icons for map markers. You can browse them at [fontawesome.com](https://fontawesome.com/search?q=share&o=r&m=free).
Alternatively, click **From Photos** to upload photos with GPS metadata. Waypoints will be created automatically based on the photo locations.
## Step 4: Add Photos & Videos
### Step 4: Add Photos & Videos
You can attach photos and videos to the trail itself. These will be shown in the trail's detail view. If you upload more than one, you can select one to be the trails thumbnail in the overview.
## Step 5: Add to Summit Book
### Step 5: Add to Summit Book
If you've completed this trail yourself, you can log a summit book entry.
@@ -103,8 +101,21 @@ If you've completed this trail yourself, you can log a summit book entry.
To learn more about summit logs visit the [dedicated section](/use/summit-logs) of the documentation.
## Step 6: Save the trail
### Step 6: Save the trail
When you're done, click <button class="h-10 text-white rounded-lg px-4 py-2 mx-2 bg-primary font-semibold transition-all hover:bg-primary-hover focus:ring-4 ring-zinc-400 leading-none">Save Trail</button> to persist your trail to the database. This will also re-index it for search and display it in your trail list.
## Edit an existing trail
Open a trail and choose **Edit** from its menu. If the trail already has route data, the editor starts in drawing mode so you can continue adjusting the existing route immediately.
Route editing works like drawing a new route, with a few differences:
- Click **Stop editing** to leave drawing mode. This protects the route from accidental changes when you click the map and restores left-clicking the map to create waypoints. Metadata, photos, waypoints, summit entries, and lists can be edited regardless of whether drawing mode is active.
- Click **Edit route** to return to route editing mode later.
- Existing waypoints remain visible while editing the route. Use the waypoint toggle in the route editing toolbar to hide them if they get in the way (this also disables right-click waypoint creation).
- Left-click the map to add or extend route anchors.
- Right-click the map to create a waypoint at that location without placing a route anchor.
- Use **Reset route** if you want to discard the current route and upload or draw a new one.
Saving an edited trail updates its route data, metadata, waypoints, photos, and search index.

View File

@@ -37,6 +37,7 @@
markers?: M.Marker[];
map?: M.Map | null;
drawing?: boolean;
displayWaypoints?: boolean;
showElevation?: boolean;
showInfoPopup?: boolean;
showGrid?: boolean;
@@ -65,6 +66,7 @@
onmoveend?: (map: M.Map) => void;
onzoom?: (map: M.Map) => void;
onclick?: (event: M.MapMouseEvent & Object) => void;
oncontextmenu?: (event: M.MapMouseEvent & Object) => void;
onUnclusteredClick?: (
event: M.MapMouseEvent & Object,
trail: Trail,
@@ -81,6 +83,7 @@
markers = $bindable([]),
map = $bindable(),
drawing = false,
displayWaypoints = true,
showElevation = true,
showInfoPopup = false,
showGrid = false,
@@ -101,6 +104,7 @@
onmoveend,
onzoom,
onclick,
oncontextmenu,
onUnclusteredClick,
oninit,
autoGeolocateOnDrawing = false,
@@ -190,8 +194,9 @@
});
$effect(() => {
waypoints;
displayWaypoints;
untrack(() => {
showWaypoints();
syncWaypointMarkers();
refreshElevationProfile();
});
});
@@ -668,7 +673,7 @@
if (showElevation) {
epc?.showProfile();
}
showWaypoints();
syncWaypointMarkers();
if (trail.id && gpxDataMap[trail.id]) {
addCaretLayer(gpxDataMap[trail.id]);
}
@@ -698,7 +703,6 @@
if (!map) {
return;
}
hideWaypoints();
activeTrail ??= 0;
map.getCanvas().style.cursor = "crosshair";
if (trails[activeTrail]) {
@@ -714,7 +718,7 @@
if (!map) {
return;
}
showWaypoints();
syncWaypointMarkers();
map.getCanvas().style.cursor = "inherit";
if (activeTrail !== null && trails[activeTrail] && !clusterTrails) {
@@ -792,7 +796,7 @@
}
function showWaypoints() {
if (!map || drawing) {
if (!map) {
return;
}
@@ -816,6 +820,14 @@
});
}
function syncWaypointMarkers() {
if (displayWaypoints) {
showWaypoints();
} else {
hideWaypoints();
}
}
function hideWaypoints() {
if (!map) {
return;
@@ -1015,6 +1027,10 @@
onclick?.(e);
});
map.on("contextmenu", (e) => {
oncontextmenu?.(e);
});
map.on("load", () => {
layerManager.init();
initMap(true);
@@ -1022,7 +1038,7 @@
mapLoaded = true;
});
showWaypoints();
syncWaypointMarkers();
});
function geolocate() {

View File

@@ -23,6 +23,7 @@
onRecalculateElevationData: () => void;
onUndo: () => void;
onRedo: () => void;
showWaypoints?: boolean;
resetLabel?: string;
resetAriaLabel?: string;
}
@@ -37,6 +38,7 @@
onRecalculateElevationData,
onUndo,
onRedo,
showWaypoints = $bindable(true),
resetLabel = "reset",
resetAriaLabel = "reset-route",
}: Props = $props();
@@ -149,6 +151,15 @@
aria-label="recalculate elevation data"
onclick={async () => await togglePanels(false, false, !recalculateElevationData)}><i class="fa fa-mountain text-sm"></i></button
>
<button
class="btn-icon tooltip"
class:bg-secondary-hover={showWaypoints}
type="button"
aria-label={$_("waypoints", { values: { n: 2 } })}
data-title={$_("waypoints", { values: { n: 2 } })}
onclick={() => (showWaypoints = !showWaypoints)}
><i class="fa fa-location-dot text-sm"></i></button
>
<button
class="btn-icon tooltip hover:text-red-500"
type="button"

View File

@@ -73,6 +73,11 @@ export function createMarkerFromWaypoint(waypoint: Waypoint, onDragEnd?: (marker
marker.on("dragend", () => onDragEnd(marker, waypoint.id,));
}
marker.getElement().addEventListener("click", (e) => {
e.stopPropagation();
marker.togglePopup();
});
return marker;
}

View File

@@ -136,6 +136,7 @@
let gpxFile: File | Blob | null = null;
let drawingActive = $state(false);
let showWaypointsWhileDrawing = $state(true);
let replacingRoute = $state(false);
let isNewTrail = $derived(page.params.id === "new");
@@ -856,6 +857,16 @@
}
}
function openWaypointActionPopup(lngLat: M.LngLat) {
mapPopup?.remove();
mapPopup = createEditTrailMapPopup(lngLat, () => {
mapPopup?.remove();
beforeWaypointModalOpen(lngLat.lat, lngLat.lng);
});
mapPopup.addTo(map!);
}
async function handleMapClick(e: M.MapMouseEvent) {
if (!drawingActive) {
if (
@@ -865,13 +876,7 @@
) {
return;
}
mapPopup?.remove();
mapPopup = createEditTrailMapPopup(e.lngLat, () => {
mapPopup?.remove();
beforeWaypointModalOpen(e.lngLat.lat, e.lngLat.lng);
});
mapPopup.addTo(map!);
openWaypointActionPopup(e.lngLat);
} else {
const anchorCount = valhallaStore.anchors.length;
if (anchorCount == 0) {
@@ -886,6 +891,20 @@
}
}
function handleMapContextMenu(e: M.MapMouseEvent) {
if (!drawingActive || !showWaypointsWhileDrawing) {
return;
}
if (
(e.originalEvent.target as HTMLElement).tagName.toLowerCase() !==
"canvas"
) {
return;
}
e.preventDefault();
openWaypointActionPopup(e.lngLat);
}
async function addAnchorAndRecalculate(lat: number, lon: number) {
const previousAnchor =
valhallaStore.anchors[valhallaStore.anchors.length - 1];
@@ -1799,8 +1818,10 @@
</div>
{/if}
<hr class="border-input-border" />
{#if isNewTrail || replacingRoute}
<h3 class="text-xl font-semibold">{$_("pick-a-trail")}</h3>
{#if isNewTrail || replacingRoute || drawingActive || $formData.expand?.gpx_data}
{#if isNewTrail || replacingRoute}
<h3 class="text-xl font-semibold">{$_("pick-a-trail")}</h3>
{/if}
<button
class="btn-primary"
type="button"
@@ -2125,6 +2146,7 @@
onRecalculateElevationData={recalculateElevationData}
onUndo={undoRouteEdit}
onRedo={redoRouteEdit}
bind:showWaypoints={showWaypointsWhileDrawing}
></RouteEditor>
</div>
{/if}
@@ -2133,6 +2155,7 @@
trails={mapTrail}
waypoints={$formData.expand?.waypoints_via_trail}
drawing={drawingActive}
displayWaypoints={!drawingActive || showWaypointsWhileDrawing}
showTerrain={true}
autoGeolocateOnDrawing={page.params.id === "new"}
onmarkerdragend={moveMarker}
@@ -2140,6 +2163,7 @@
bind:map
oninit={handleMapInit}
onclick={(target) => handleMapClick(target)}
oncontextmenu={(target) => handleMapContextMenu(target)}
onsegmentclick={(data) => handleSegmentClick(data)}
onsegmentdragend={(data) => handleSegmentDragEnd(data)}
mapOptions={{ preserveDrawingBuffer: true }}