adds maplibre for new routes

This commit is contained in:
Christian Beutel
2024-11-28 19:35:59 +01:00
parent b6b409e855
commit aea4e26084
7 changed files with 201 additions and 112 deletions

View File

@@ -9,21 +9,30 @@
FontawesomeMarker,
} from "$lib/util/maplibre_util";
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
import type { GeoJsonObject } from "geojson";
import type { GeoJSON } from "geojson";
import * as M from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { onDestroy, onMount } from "svelte";
import { createEventDispatcher, onDestroy, onMount } from "svelte";
export let trail: Trail;
export let trail: Trail | null;
export let markers: M.Marker[] = [];
export let map: M.Map | null = null;
export let crosshairCursor: boolean = false;
export let drawing: boolean = false;
let mapContainer: HTMLDivElement;
let epc: ElevationProfileControl;
let startMarker: M.Marker;
let endMarker: M.Marker;
$: data = toGeoJson(trail.expand.gpx_data!) as GeoJsonObject;
const dispatch = createEventDispatcher();
$: data = trail?.expand.gpx_data
? (toGeoJson(trail.expand.gpx_data!) as GeoJSON)
: null;
$: if (data && map) {
initMap();
}
$: if ($theme == "dark") {
epc?.toggleTheme({
@@ -41,11 +50,86 @@
});
}
$: if (drawing && map) {
map.getCanvas().style.cursor = "crosshair";
} else if (!drawing && map) {
map.getCanvas().style.cursor = "inherit";
addStartEndMarkers();
}
function initMap() {
if (!map || !data) {
return;
}
for (const waypoint of trail?.expand.waypoints ?? []) {
const marker = createMarkerFromWaypoint(waypoint);
marker.addTo(map);
markers.push(marker);
}
epc.setData(data, trail!.expand.waypoints);
epc.showProfile();
const trailSource = map.getSource("trail-source");
if (!trailSource) {
map.on("load", () => {
map!.addSource("trail-source", {
type: "geojson",
data: data,
});
map!.addLayer({
id: "uploaded-polygons",
type: "line",
source: "trail-source",
paint: {
"line-color": "#648ad5",
"line-width": 5,
},
});
});
} else {
(trailSource as M.GeoJSONSource).setData(data);
}
if (!drawing) {
addStartEndMarkers();
}
}
function addStartEndMarkers() {
if (!map || !data) {
return;
}
const startEndPoint = findStartAndEndPoints(data);
startMarker ??= new FontawesomeMarker({ icon: "fa fa-bullseye" }, {});
startMarker.setLngLat(startEndPoint[0] as M.LngLatLike).addTo(map);
endMarker ??= new FontawesomeMarker(
{ icon: "fa fa-flag-checkered" },
{},
);
endMarker.setLngLat(startEndPoint[1] as M.LngLatLike).addTo(map);
map!.fitBounds(data.bbox as any, {
animate: false,
padding: {
top: 16,
left: 16,
right: 16,
bottom: map!.getContainer().clientHeight * 0.3 + 16,
},
});
}
onMount(async () => {
const initialState = {
lng: 0,
lat: 0,
zoom: 14,
zoom: 1,
};
const ElevationProfileControl = (
await import(
@@ -60,28 +144,6 @@
zoom: initialState.zoom,
});
for (const waypoint of trail?.expand.waypoints ?? []) {
const marker = createMarkerFromWaypoint(waypoint);
marker.addTo(map);
markers.push(marker);
}
const startEndPoint = findStartAndEndPoints(data);
const startMarker = new FontawesomeMarker(
{ icon: "fa fa-bullseye" },
{},
)
.setLngLat(startEndPoint[0] as M.LngLatLike)
.addTo(map);
const endMarker = new FontawesomeMarker(
{ icon: "fa fa-flag-checkered" },
{},
)
.setLngLat(startEndPoint[1] as M.LngLatLike)
.addTo(map);
const elevationMarker = new FontawesomeMarker(
{
icon: "fa-regular fa-circle",
@@ -96,7 +158,7 @@
elevationMarker.setOpacity("0");
epc = new ElevationProfileControl({
visible: true,
visible: false,
profileBackgroundColor: $theme == "light" ? "#242734" : "#191b24",
backgroundColor: "bg-menu-background/90",
unit: $page.data.settings?.unit ?? "metric",
@@ -122,32 +184,9 @@
"top-left",
);
map.addControl(epc);
epc.setData(data, trail.expand.waypoints);
map.on("load", () => {
map!.addSource("trail-source", {
type: "geojson",
data: data as any,
});
map!.addLayer({
id: "uploaded-polygons",
type: "line",
source: "trail-source",
paint: {
"line-color": "#648ad5",
"line-width": 5,
},
});
map!.fitBounds(data.bbox as any, {
animate: false,
padding: {
top: 16,
left: 16,
right: 16,
bottom: map!.getContainer().clientHeight * 0.3 + 16,
},
});
map.on("click", (e) => {
dispatch("click", e);
});
});
@@ -156,7 +195,7 @@
});
</script>
<div id="map" class:cursor-pointer={crosshairCursor} bind:this={mapContainer}></div>
<div id="map" bind:this={mapContainer}></div>
<style>
#map {

View File

@@ -1,4 +1,4 @@
import type { LatLng, Marker } from "leaflet";
import * as M from "maplibre-gl";
interface ValhallaRouteResponse {
trip: {
@@ -16,7 +16,7 @@ interface ValhallaAnchor {
id: string,
lat: number,
lon: number,
marker?: Marker
marker?: M.Marker
}
export { type ValhallaRouteResponse, type ValhallaHeightResponse, type ValhallaAnchor }
export { type ValhallaAnchor, type ValhallaHeightResponse, type ValhallaRouteResponse };

View File

@@ -1,4 +1,5 @@
import type { Marker } from "leaflet";
import type { Marker } from "maplibre-gl";
import * as M from "maplibre-gl";
import { number, object, string } from "yup";
class Waypoint {
@@ -8,13 +9,13 @@ class Waypoint {
lat: number;
lon: number;
icon?: string;
marker?: Marker;
marker?: M.Marker;
photos: string[];
_photos: File[];
author?: string;
constructor(lat: number, lon: number, params?: {
id?: string, name?: string, description?: string, icon?: string, marker?: Marker, photos?: string[];
id?: string, name?: string, description?: string, icon?: string, marker?: M.Marker, photos?: string[];
}) {
this.id = params?.id;
this.name = params?.name ?? "";

View File

@@ -41,4 +41,72 @@ export function createMarkerFromWaypoint(waypoint: Waypoint, onDragEnd?: (marker
}
return marker;
}
}
export function createAnchorMarker(lat: number, lon: number, index: number, onDeleteClick: () => void, onDragEnd: (event: M.Marker) => void): FontawesomeMarker {
const anchorElement = document.createElement("span")
anchorElement.className = "cursor-pointer rounded-full w-6 h-6 border border-black text-center bg-background-inverse text-content-inverse"
anchorElement.textContent = "" + index
const marker = new M.Marker(
{
draggable: true,
element: anchorElement
}
);
marker.setLngLat([lon, lat]);
const deleteButton = document.createElement("button");
deleteButton.className = "fa fa-trash text-red-500 rounded-full aspect-square h-8 text-lg";
deleteButton.addEventListener("click", onDeleteClick)
const popup = new M.Popup({})
popup.setDOMContent(deleteButton)
marker.setPopup(popup);
marker.on("dragend", onDragEnd);
marker.getElement().addEventListener("click", (e) => {
e.stopPropagation();
marker.togglePopup();
})
return marker
}
// export function calculatePixelPerMeter(map: Map, meters: number) {
// const y = map.getSize().y;
// const x = map.getSize().x;
// const maxMeters = map.containerPointToLatLng([0, y]).distanceTo(map.containerPointToLatLng([x, y]));
// const pixelPerMeter = x / maxMeters;
// return pixelPerMeter * meters
// }
// export function calculateScaleFactor(map: Map) {
// function _pxTOmm() {
// let heightRef = document.createElement('div');
// heightRef.style.height = '1mm';
// heightRef.style.position = "absolute";
// heightRef.id = 'heightRef';
// document.body.appendChild(heightRef);
// const pxPermm = heightRef.getBoundingClientRect().height;
// document.body.removeChild(heightRef);
// return function pxTOmm(px: number) {
// return px / pxPermm;
// }
// }
// var centerOfMap = map.getSize().y / 2;
// var realWorlMetersPer100Pixels = map.distance(
// map.containerPointToLatLng([0, centerOfMap]),
// map.containerPointToLatLng([100, centerOfMap])
// );
// const screenMetersPer100Pixels = _pxTOmm()(100) / 1000;
// const scaleFactor = realWorlMetersPer100Pixels / screenMetersPer100Pixels
// return scaleFactor
// }

View File

@@ -347,15 +347,4 @@
.leaflet-popup-content {
width: max-content !important;
max-width: 100%;
}
.leaflet-anchor {
background-color: #fff;
border: 2px solid black;
width: 24px;
height: 24px;
border-radius: 50%;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
text-align: center;
}

View File

@@ -25,7 +25,10 @@ export function haversineCumulatedDistanceWgs84(path: Position[]): number[] {
export function smoothElevations(positions: Position[], windowSize: number): Position[] {
// Ensure windowSize is valid (at least 1)
if (windowSize < 1) throw new Error("Window size must be at least 1.");
if (windowSize < 1) {
console.warn("Window size must be at least 1.");
return positions
};
// Create a new array with smoothed elevations
return positions.map((pos, i, arr) => {

View File

@@ -10,7 +10,7 @@
import ListSelectModal from "$lib/components/list/list_select_modal.svelte";
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
import MapWithElevationMaplibre from "$lib/components/trail/map_with_elevation_maplibre.svelte";
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";
@@ -58,13 +58,14 @@
gpx2trail,
isFITFile,
} from "$lib/util/gpx_util";
import {
createAnchorMarker,
createMarkerFromWaypoint,
} from "$lib/util/leaflet_util";
} from "$lib/util/maplibre_util";
import { createForm } from "$lib/vendor/svelte-form-lib";
import cryptoRandomString from "crypto-random-string";
import type { DivIcon, LeafletMouseEvent, Map } from "leaflet";
import * as M from "maplibre-gl";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import { backInOut } from "svelte/easing";
@@ -73,8 +74,7 @@
export let data: { trail: Trail };
let L: any;
let map: Map;
let map: M.Map;
let openWaypointModal: () => void;
let openSummitLogModal: () => void;
@@ -215,9 +215,6 @@
});
onMount(async () => {
L = (await import("leaflet")).default;
await import("leaflet.awesome-markers");
clearAnchorMarker();
clearRoute();
@@ -296,10 +293,12 @@
elevation_loss: $form.elevation_loss,
duration: $form.duration ? $form.duration * 60 : undefined,
});
log.expand.gpx_data = gpxData;
const blob = new Blob([gpxData], { type: selectedFile.type });
log._gpx = new File([blob], selectedFile.name, { type: selectedFile.type });
log._gpx = new File([blob], selectedFile.name, {
type: selectedFile.type,
});
$form.expand.summit_logs.push(log);
@@ -370,7 +369,7 @@
}
function openMarkerPopup(waypoint: Waypoint) {
waypoint.marker?.openPopup();
waypoint.marker?.togglePopup();
}
function handleWaypointMenuClick(
@@ -411,9 +410,9 @@
savedWaypoint.id = cryptoRandomString({ length: 15 });
$form.expand.waypoints = [...$form.expand.waypoints, savedWaypoint];
}
const marker = createMarkerFromWaypoint(L, savedWaypoint, (event) => {
var marker = event.target;
var position = marker.getLatLng();
const marker = createMarkerFromWaypoint(savedWaypoint, (event) => {
var marker = event;
var position = marker.getLngLat();
const editableWaypointIndex = $form.expand.waypoints.findIndex(
(w) => w.id == savedWaypoint.id,
);
@@ -503,26 +502,26 @@
}
}
async function handleMapClick(e: LeafletMouseEvent) {
async function handleMapClick(e: M.MapMouseEvent) {
if (!drawingActive) {
return;
}
const anchorCount = anchors.length;
if (anchorCount == 0) {
addAnchor(e.latlng.lat, e.latlng.lng);
addAnchor(e.lngLat.lat, e.lngLat.lng);
} else {
const previousAnchor = anchors[anchorCount - 1];
try {
const routeWaypoints = await calculateRouteBetween(
previousAnchor.lat,
previousAnchor.lon,
e.latlng.lat,
e.latlng.lng,
e.lngLat.lat,
e.lngLat.lng,
selectedModeOfTransport,
autoRouting,
);
appendToRoute(routeWaypoints);
addAnchor(e.latlng.lat, e.latlng.lng);
addAnchor(e.lngLat.lat, e.lngLat.lng);
updateTrailWithRouteData();
} catch (e) {
console.error(e);
@@ -542,7 +541,6 @@
lon: lon,
};
const marker = createAnchorMarker(
L,
lat,
lon,
anchors.length + 1,
@@ -553,7 +551,7 @@
if (!drawingActive) {
return;
}
const position = marker.getLatLng();
const position = marker.getLngLat();
anchor.lat = position.lat;
anchor.lon = position.lng;
recalculateRoute(anchors.findIndex((a) => a.id == anchor.id));
@@ -574,14 +572,11 @@
anchors.splice(anchorIndex, 1);
for (let i = anchorIndex; i < anchors.length; i++) {
const anchor = anchors[i];
const markerIcon = anchor.marker?.getIcon() as DivIcon | undefined;
const markerIcon = anchor.marker?.getElement();
if (markerIcon) {
const markerText =
(markerIcon.options.html as HTMLSpanElement).textContent ??
"0";
const markerText = markerIcon.textContent ?? "0";
const markerIndex = parseInt(markerText);
(markerIcon.options.html as HTMLSpanElement).textContent =
markerIndex - 1 + "";
markerIcon.textContent = markerIndex - 1 + "";
}
}
if (anchorIndex == 0) {
@@ -924,19 +919,13 @@
></Select>
</div>
{/if}
<MapWithElevation
<MapWithElevationMaplibre
trail={$form}
crosshair={drawingActive}
options={{
autofitBounds: !drawingActive,
mapTooltip: !drawingActive,
speed: !drawingActive,
speedFactor: drawingActive ? 0 : 1,
showStartEnd: !drawingActive,
}}
drawing={drawingActive}
bind:map
on:click={(e) => handleMapClick(e.detail)}
></MapWithElevation>
></MapWithElevationMaplibre>
</div>
</main>
<WaypointModal