adds maplibre for new routes
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 ?? "";
|
||||
|
||||
@@ -42,3 +42,71 @@ 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
|
||||
// }
|
||||
@@ -348,14 +348,3 @@
|
||||
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;
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -299,7 +296,9 @@
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user