switches to layer manager
This commit is contained in:
@@ -15,8 +15,12 @@
|
|||||||
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
|
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
|
||||||
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
|
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
|
||||||
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
|
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
|
||||||
|
import { CaretLayer } from "$lib/vendor/maplibre-layer-manager/caret-layer";
|
||||||
|
import { ClusterLayer } from "$lib/vendor/maplibre-layer-manager/cluster-layer";
|
||||||
import { baseMapStyles } from "$lib/vendor/maplibre-layer-manager/layers";
|
import { baseMapStyles } from "$lib/vendor/maplibre-layer-manager/layers";
|
||||||
import { LayerManager } from "$lib/vendor/maplibre-layer-manager/maplibre-layer-manager";
|
import { LayerManager } from "$lib/vendor/maplibre-layer-manager/maplibre-layer-manager";
|
||||||
|
import { TerrainLayer } from "$lib/vendor/maplibre-layer-manager/terrain-layer";
|
||||||
|
import { TrailLayer } from "$lib/vendor/maplibre-layer-manager/trail-layer";
|
||||||
import { StyleSwitcherControl } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
import { StyleSwitcherControl } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
||||||
import type { Feature, FeatureCollection, GeoJSON } from "geojson";
|
import type { Feature, FeatureCollection, GeoJSON } from "geojson";
|
||||||
import * as M from "maplibre-gl";
|
import * as M from "maplibre-gl";
|
||||||
@@ -101,30 +105,14 @@
|
|||||||
|
|
||||||
let layerManager: LayerManager;
|
let layerManager: LayerManager;
|
||||||
|
|
||||||
let layers: Record<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
startMarker: M.Marker | null;
|
|
||||||
endMarker: M.Marker | null;
|
|
||||||
source: M.GeoJSONSource | null;
|
|
||||||
layer: M.LineLayerSpecification | null;
|
|
||||||
highlighted: boolean;
|
|
||||||
listener: {
|
|
||||||
onEnter: ((e: M.MapMouseEvent) => void) | null;
|
|
||||||
onLeave: ((e: M.MapMouseEvent) => void) | null;
|
|
||||||
onMouseUp: ((e: M.MapMouseEvent) => void) | null;
|
|
||||||
onMouseDown: ((e: M.MapMouseEvent) => void) | null;
|
|
||||||
onMouseMove: ((e: M.MapMouseEvent) => void) | null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
> = {};
|
|
||||||
|
|
||||||
let elevationMarker: FontawesomeMarker;
|
let elevationMarker: FontawesomeMarker;
|
||||||
|
|
||||||
let draggingSegment: number | null = null;
|
let draggingSegment: number | null = null;
|
||||||
|
|
||||||
let hoveringTrail: boolean = false;
|
let hoveringTrail: boolean = false;
|
||||||
|
|
||||||
|
let mapLoaded: boolean = false;
|
||||||
|
|
||||||
const trailColors = [
|
const trailColors = [
|
||||||
"#3549BB",
|
"#3549BB",
|
||||||
"#592E9E",
|
"#592E9E",
|
||||||
@@ -142,7 +130,7 @@
|
|||||||
|
|
||||||
let [data, clusterData] = $derived(getData(trails));
|
let [data, clusterData] = $derived(getData(trails));
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (data && map) {
|
if (data && map && mapLoaded) {
|
||||||
untrack(() => initMap(map?.loaded() ?? false));
|
untrack(() => initMap(map?.loaded() ?? false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -153,9 +141,9 @@
|
|||||||
toggleEpcTheme();
|
toggleEpcTheme();
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (drawing) {
|
if (drawing && map && layerManager) {
|
||||||
untrack(() => startDrawing());
|
untrack(() => startDrawing());
|
||||||
} else {
|
} else if (map && layerManager) {
|
||||||
untrack(() => stopDrawing());
|
untrack(() => stopDrawing());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -192,9 +180,14 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getData(trails: Trail[]): [GeoJSON[], FeatureCollection] {
|
function getData(
|
||||||
let cD: FeatureCollection = { type: "FeatureCollection", features: [] };
|
trails: Trail[],
|
||||||
let r: GeoJSON[] = [];
|
): [FeatureCollection[], FeatureCollection] {
|
||||||
|
let clusterData: FeatureCollection = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features: [],
|
||||||
|
};
|
||||||
|
let r: FeatureCollection[] = [];
|
||||||
|
|
||||||
for (const t of trails) {
|
for (const t of trails) {
|
||||||
if (t.expand?.gpx) {
|
if (t.expand?.gpx) {
|
||||||
@@ -203,7 +196,7 @@
|
|||||||
r.push(GPX.parse(t.expand.gpx_data).toGeoJSON());
|
r.push(GPX.parse(t.expand.gpx_data).toGeoJSON());
|
||||||
}
|
}
|
||||||
if (clusterTrails && t.lat !== null && t.lon !== null) {
|
if (clusterTrails && t.lat !== null && t.lon !== null) {
|
||||||
cD.features.push({
|
clusterData.features.push({
|
||||||
id: t.id,
|
id: t.id,
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
properties: {
|
properties: {
|
||||||
@@ -217,11 +210,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [r, cD];
|
return [r, clusterData];
|
||||||
}
|
}
|
||||||
|
|
||||||
function initMap(mapLoaded: boolean) {
|
function initMap(mapLoaded: boolean) {
|
||||||
if (!map) {
|
if (!map || !layerManager) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,11 +233,14 @@
|
|||||||
addClusterLayer(clusterData);
|
addClusterLayer(clusterData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(layers).forEach((layerId) => {
|
Object.entries(layerManager.layers).forEach(([id, layer]) => {
|
||||||
const isStillVisible = trails.some((t) => t.id === layerId);
|
if (!(layer instanceof TrailLayer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isStillVisible = trails.some((t) => t.id === id);
|
||||||
if (!isStillVisible) {
|
if (!isStillVisible) {
|
||||||
removeCaretLayer();
|
removeCaretLayer();
|
||||||
removeTrailLayer(layerId);
|
removeTrailLayer(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -259,7 +255,7 @@
|
|||||||
flyToBounds();
|
flyToBounds();
|
||||||
}
|
}
|
||||||
} else if (drawing && activeTrail !== null && mapLoaded) {
|
} else if (drawing && activeTrail !== null && mapLoaded) {
|
||||||
addCaretLayer(trails[activeTrail]?.id);
|
addCaretLayer(data[activeTrail]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,100 +318,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeTrailLayer(id: string) {
|
function removeTrailLayer(id: string) {
|
||||||
if (!layers[id]) {
|
layerManager.removeLayer(id);
|
||||||
return;
|
|
||||||
}
|
|
||||||
const layer = layers[id];
|
|
||||||
|
|
||||||
if (layer.layer) {
|
|
||||||
map?.removeLayer(id);
|
|
||||||
}
|
|
||||||
if (layer.source) {
|
|
||||||
map?.removeSource(id);
|
|
||||||
}
|
|
||||||
layer.startMarker?.remove();
|
|
||||||
layer.endMarker?.remove();
|
|
||||||
|
|
||||||
map?.off("mouseenter", id, layers[id].listener.onEnter!);
|
|
||||||
map?.off("mouseleave", id, layers[id].listener.onLeave!);
|
|
||||||
map?.off("mouseup", id, layers[id].listener.onMouseUp!);
|
|
||||||
map?.off("mousemove", id, layers[id].listener.onMouseMove!);
|
|
||||||
map?.off("mousedown", id, layers[id].listener.onMouseDown!);
|
|
||||||
|
|
||||||
delete layers[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEmptyLayer(id: string) {
|
|
||||||
if (!layers[id]) {
|
|
||||||
layers[id] = {
|
|
||||||
startMarker: null,
|
|
||||||
endMarker: null,
|
|
||||||
source: null,
|
|
||||||
layer: null,
|
|
||||||
highlighted: false,
|
|
||||||
listener: {
|
|
||||||
onMouseUp: null,
|
|
||||||
onMouseDown: null,
|
|
||||||
onEnter: null,
|
|
||||||
onLeave: null,
|
|
||||||
onMouseMove: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTrailLayer(
|
function addTrailLayer(
|
||||||
trail: Trail,
|
trail: Trail,
|
||||||
id: string,
|
id: string,
|
||||||
index: number,
|
index: number,
|
||||||
geojson: GeoJSON | null | undefined,
|
geojson: GeoJSON.FeatureCollection | null | undefined,
|
||||||
) {
|
) {
|
||||||
if (!geojson || !map) {
|
if (!geojson || !map) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmptyLayer(id);
|
const trailLayer = new TrailLayer(
|
||||||
|
id,
|
||||||
|
geojson,
|
||||||
|
trailColors[index % trailColors.length],
|
||||||
|
{
|
||||||
|
onEnter: (e) =>
|
||||||
|
highlightTrail(id, trails[activeTrail ?? -1]?.id == id),
|
||||||
|
|
||||||
if (!layers[id].source || !map.getSource(id)) {
|
onLeave: (e) => unHighlightTrail(id),
|
||||||
try {
|
onMouseUp: (e) => {
|
||||||
map.addSource(id, {
|
activeTrail = trails.findIndex((t) => t.id == trail.id);
|
||||||
type: "geojson",
|
|
||||||
data: geojson,
|
|
||||||
});
|
|
||||||
layers[id].source = map.getSource(id) as M.GeoJSONSource;
|
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
layers[id].source.setData(geojson);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!layers[id].layer || !map.getLayer(id)) {
|
|
||||||
map.addLayer({
|
|
||||||
id: id,
|
|
||||||
type: "line",
|
|
||||||
source: id,
|
|
||||||
paint: {
|
|
||||||
"line-color": trailColors[index % trailColors.length],
|
|
||||||
"line-width": 5,
|
|
||||||
},
|
},
|
||||||
});
|
onMouseMove: moveCrosshairToCursorPosition,
|
||||||
layers[id].layer = map.getLayer(id) as M.LineLayerSpecification;
|
onMouseDown: (e) => handleDragStart(e, id),
|
||||||
layers[id].listener.onEnter = (e) =>
|
},
|
||||||
highlightTrail(id, trails[activeTrail ?? -1]?.id == id);
|
);
|
||||||
layers[id].listener.onLeave = (e) => unHighlightTrail(id);
|
|
||||||
layers[id].listener.onMouseUp = (e) => {
|
|
||||||
activeTrail = trails.findIndex((t) => t.id == trail.id);
|
|
||||||
};
|
|
||||||
layers[id].listener.onMouseMove = moveCrosshairToCursorPosition;
|
|
||||||
layers[id].listener.onMouseDown = (e) => handleDragStart(e, id);
|
|
||||||
|
|
||||||
map.on("mouseenter", id, layers[id].listener.onEnter);
|
layerManager.addLayer(id, trailLayer);
|
||||||
map.on("mouseleave", id, layers[id].listener.onLeave);
|
|
||||||
map.on("mouseup", id, layers[id].listener.onMouseUp);
|
|
||||||
map.on("mousemove", id, layers[id].listener.onMouseMove);
|
|
||||||
map.on("mousedown", id, layers[id].listener.onMouseDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!drawing && !clusterTrails) {
|
if (!drawing && !clusterTrails) {
|
||||||
addStartEndMarkers(trail, id, geojson);
|
addStartEndMarkers(trail, id, geojson);
|
||||||
@@ -426,98 +359,34 @@
|
|||||||
if (!geojson || !map || !map.style) {
|
if (!geojson || !map || !map.style) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!map.getSource("trails")) {
|
layerManager.addLayer(
|
||||||
map.addSource("trails", {
|
"clusters",
|
||||||
type: "geojson",
|
new ClusterLayer(map, geojson, {
|
||||||
data: geojson,
|
"unclustered-point": {
|
||||||
cluster: true,
|
onMouseDown: (e) => {
|
||||||
clusterRadius: 50,
|
const trail = trails.find(
|
||||||
});
|
(t) =>
|
||||||
} else {
|
t.id == (e as any).features[0].properties.trail,
|
||||||
(map.getSource("trails") as M.GeoJSONSource).setData(geojson);
|
);
|
||||||
}
|
if (!trail || !map) {
|
||||||
if (!map.getLayer("clusters")) {
|
return;
|
||||||
map.addLayer({
|
}
|
||||||
id: "clusters",
|
highlightCluster(trail);
|
||||||
type: "circle",
|
},
|
||||||
source: "trails",
|
|
||||||
filter: ["has", "point_count"],
|
|
||||||
paint: {
|
|
||||||
"circle-color": "#242734",
|
|
||||||
"circle-radius": [
|
|
||||||
"step",
|
|
||||||
["get", "point_count"],
|
|
||||||
10,
|
|
||||||
10,
|
|
||||||
15,
|
|
||||||
20,
|
|
||||||
20,
|
|
||||||
50,
|
|
||||||
25,
|
|
||||||
100,
|
|
||||||
30,
|
|
||||||
200,
|
|
||||||
35,
|
|
||||||
],
|
|
||||||
"circle-stroke-width": 3,
|
|
||||||
"circle-stroke-color": "#fff",
|
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
map.addLayer({
|
);
|
||||||
id: "cluster-count",
|
|
||||||
type: "symbol",
|
|
||||||
source: "trails",
|
|
||||||
filter: ["has", "point_count"],
|
|
||||||
paint: {
|
|
||||||
"text-color": "#fff",
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
"text-field": "{point_count_abbreviated}",
|
|
||||||
"text-font": ["Noto Sans Regular"],
|
|
||||||
"text-size": 12,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
map.addLayer({
|
|
||||||
id: "unclustered-point",
|
|
||||||
type: "circle",
|
|
||||||
source: "trails",
|
|
||||||
filter: ["!", ["has", "point_count"]],
|
|
||||||
paint: {
|
|
||||||
"circle-color": "#242734",
|
|
||||||
"circle-radius": 7,
|
|
||||||
"circle-stroke-width": 2,
|
|
||||||
"circle-stroke-color": "#fff",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addClusterHighlightLayer(geojson: GeoJSON) {
|
function addClusterHighlightLayer(geojson: FeatureCollection) {
|
||||||
if (!geojson || !map || !map.style) {
|
if (!geojson || !map || !map.style) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!map.getSource("cluster-highlight")) {
|
|
||||||
map.addSource("cluster-highlight", {
|
|
||||||
type: "geojson",
|
|
||||||
data: geojson,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
(map.getSource("cluster-highlight") as M.GeoJSONSource).setData(
|
|
||||||
geojson,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!map.getLayer("cluster-highlight")) {
|
layerManager.addLayer(
|
||||||
map.addLayer({
|
"cluster-highlight",
|
||||||
id: "cluster-highlight",
|
new TrailLayer("cluster-highlight", geojson, trailColors[0]),
|
||||||
type: "line",
|
);
|
||||||
source: "cluster-highlight",
|
|
||||||
paint: {
|
|
||||||
"line-color": trailColors[0],
|
|
||||||
"line-width": 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveCrosshairToCursorPosition(e: M.MapMouseEvent) {
|
function moveCrosshairToCursorPosition(e: M.MapMouseEvent) {
|
||||||
@@ -567,48 +436,21 @@
|
|||||||
draggingSegment = null;
|
draggingSegment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCaretLayer(id?: string) {
|
function addCaretLayer(geojson: GeoJSON) {
|
||||||
if (!map || !id || !map.getSource(id)) {
|
if (!map) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (map.getLayer("direction-carets")) {
|
if (map.getLayer("direction-carets")) {
|
||||||
removeCaretLayer();
|
removeCaretLayer();
|
||||||
}
|
}
|
||||||
|
layerManager.addLayer(
|
||||||
map.addLayer({
|
"direction-carets",
|
||||||
id: "direction-carets",
|
new CaretLayer({ type: "geojson", data: geojson }),
|
||||||
type: "symbol",
|
);
|
||||||
source: id,
|
|
||||||
layout: {
|
|
||||||
"symbol-placement": "line",
|
|
||||||
"symbol-spacing": [
|
|
||||||
"interpolate",
|
|
||||||
["exponential", 1.5],
|
|
||||||
["zoom"],
|
|
||||||
0,
|
|
||||||
80,
|
|
||||||
18,
|
|
||||||
200,
|
|
||||||
],
|
|
||||||
"icon-image": "direction-caret",
|
|
||||||
"icon-size": [
|
|
||||||
"interpolate",
|
|
||||||
["exponential", 1.5],
|
|
||||||
["zoom"],
|
|
||||||
0,
|
|
||||||
0.5,
|
|
||||||
18,
|
|
||||||
0.8,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCaretLayer() {
|
function removeCaretLayer() {
|
||||||
if (!map || !map.getLayer("direction-carets")) {
|
layerManager?.removeLayer("direction-carets");
|
||||||
return;
|
|
||||||
}
|
|
||||||
map.removeLayer("direction-carets");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function highlightTrail(
|
export function highlightTrail(
|
||||||
@@ -661,9 +503,7 @@
|
|||||||
if (!map || !map.style) {
|
if (!map || !map.style) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (map?.getLayer("cluster-highlight")) {
|
layerManager.removeLayer("cluster-highlight");
|
||||||
map?.removeLayer("cluster-highlight");
|
|
||||||
}
|
|
||||||
if (closePopup) {
|
if (closePopup) {
|
||||||
clusterPopup?.remove();
|
clusterPopup?.remove();
|
||||||
}
|
}
|
||||||
@@ -697,7 +537,7 @@
|
|||||||
epc?.showProfile();
|
epc?.showProfile();
|
||||||
}
|
}
|
||||||
showWaypoints();
|
showWaypoints();
|
||||||
addCaretLayer(trail.id!);
|
addCaretLayer(data[activeTrail]);
|
||||||
flyToBounds();
|
flyToBounds();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
@@ -740,10 +580,12 @@
|
|||||||
map.getCanvas().style.cursor = "inherit";
|
map.getCanvas().style.cursor = "inherit";
|
||||||
|
|
||||||
if (activeTrail !== null && trails[activeTrail] && !clusterTrails) {
|
if (activeTrail !== null && trails[activeTrail] && !clusterTrails) {
|
||||||
|
console.log(data[activeTrail].features[0]);
|
||||||
|
|
||||||
addStartEndMarkers(
|
addStartEndMarkers(
|
||||||
trails[activeTrail],
|
trails[activeTrail],
|
||||||
trails[activeTrail].id,
|
trails[activeTrail].id,
|
||||||
data?.at(activeTrail),
|
data[activeTrail],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -753,21 +595,24 @@
|
|||||||
id: string | undefined,
|
id: string | undefined,
|
||||||
geojson: GeoJSON | null | undefined,
|
geojson: GeoJSON | null | undefined,
|
||||||
) {
|
) {
|
||||||
if (!map || !trail || !id) {
|
if (
|
||||||
|
!map ||
|
||||||
|
!trail ||
|
||||||
|
!id ||
|
||||||
|
!(layerManager.layers[id] instanceof TrailLayer)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
createEmptyLayer(id);
|
|
||||||
|
|
||||||
layers[id].startMarker ??= new FontawesomeMarker(
|
const startMarker = new FontawesomeMarker(
|
||||||
{ icon: "fa fa-bullseye" },
|
{ icon: "fa fa-bullseye" },
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
layerManager.layers[id].markers.start = startMarker;
|
||||||
|
|
||||||
if (!geojson) {
|
if (!geojson) {
|
||||||
if (trail.lon && trail.lat) {
|
if (trail.lon && trail.lat) {
|
||||||
layers[id].startMarker
|
startMarker.setLngLat([trail.lon, trail.lat]).addTo(map);
|
||||||
.setLngLat([trail.lon, trail.lat])
|
|
||||||
.addTo(map);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -778,34 +623,35 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showInfoPopup) {
|
const endMarker = new FontawesomeMarker(
|
||||||
const popup = createPopupFromTrail(trail);
|
|
||||||
layers[id].startMarker.setPopup(popup);
|
|
||||||
layers[id].endMarker?.setPopup(popup);
|
|
||||||
}
|
|
||||||
|
|
||||||
layers[id].endMarker ??= new FontawesomeMarker(
|
|
||||||
{ icon: "fa fa-flag-checkered" },
|
{ icon: "fa fa-flag-checkered" },
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
layers[id].endMarker.setLngLat(
|
layerManager.layers[id].markers.end = endMarker;
|
||||||
|
|
||||||
|
startMarker.setLngLat(startEndPoint[0] as M.LngLatLike);
|
||||||
|
endMarker.setLngLat(
|
||||||
startEndPoint[startEndPoint.length - 1] as M.LngLatLike,
|
startEndPoint[startEndPoint.length - 1] as M.LngLatLike,
|
||||||
);
|
);
|
||||||
if (!clusterTrails) {
|
|
||||||
layers[id].endMarker.addTo(map);
|
if (showInfoPopup) {
|
||||||
|
const popup = createPopupFromTrail(trail);
|
||||||
|
startMarker.setPopup(popup);
|
||||||
|
endMarker.setPopup(popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
layers[id].startMarker
|
startMarker.addTo(map);
|
||||||
.setLngLat(startEndPoint[0] as M.LngLatLike)
|
if (!clusterTrails) {
|
||||||
.addTo(map);
|
endMarker.addTo(map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeStartEndMarkers(id: string | undefined) {
|
function removeStartEndMarkers(id: string | undefined) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
layers[id].startMarker?.remove();
|
layerManager.layers[id].markers?.start?.remove();
|
||||||
layers[id].endMarker?.remove();
|
layerManager.layers[id].markers?.end?.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWaypoints() {
|
function showWaypoints() {
|
||||||
@@ -999,48 +845,15 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.on("styledata", () => {
|
map.on("styledata", (e) => {
|
||||||
if (showTerrain) {
|
if (showTerrain && page.data.settings?.terrain?.terrain) {
|
||||||
try {
|
layerManager.addLayer(
|
||||||
if (
|
"terrain",
|
||||||
page.data.settings?.terrain?.terrain &&
|
new TerrainLayer(
|
||||||
!map?.getSource("terrain")
|
page.data.settings.terrain.terrain,
|
||||||
) {
|
page.data.settings?.terrain?.hillshading,
|
||||||
map!.addSource("terrain", {
|
),
|
||||||
type: "raster-dem",
|
);
|
||||||
url: page.data.settings?.terrain?.terrain,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
page.data.settings?.terrain?.hillshading &&
|
|
||||||
!map?.getSource("hillshading")
|
|
||||||
) {
|
|
||||||
map!.addSource("hillshading", {
|
|
||||||
type: "raster-dem",
|
|
||||||
url: page.data.settings?.terrain?.hillshading,
|
|
||||||
});
|
|
||||||
map!.addLayer({
|
|
||||||
id: "hillshading",
|
|
||||||
source: "terrain",
|
|
||||||
type: "hillshade",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
trails.forEach((t, i) => {
|
|
||||||
const layerId = t.id!;
|
|
||||||
|
|
||||||
if (map?.getLayer(layerId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addTrailLayer(t, layerId, i, data[i]);
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
activeTrail !== null &&
|
|
||||||
trails[activeTrail] &&
|
|
||||||
!map?.getLayer("direction-carets")
|
|
||||||
) {
|
|
||||||
addCaretLayer(trails[activeTrail].id!);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1059,55 +872,11 @@
|
|||||||
onclick?.(e);
|
onclick?.(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
map.on("click", "clusters", async (e) => {
|
|
||||||
if (!map) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const features = map.queryRenderedFeatures(e.point, {
|
|
||||||
layers: ["clusters"],
|
|
||||||
});
|
|
||||||
const clusterId = features[0].properties.cluster_id;
|
|
||||||
const zoom = await (
|
|
||||||
map.getSource("trails") as M.GeoJSONSource
|
|
||||||
).getClusterExpansionZoom(clusterId);
|
|
||||||
map.flyTo({
|
|
||||||
center: (features[0].geometry as any).coordinates,
|
|
||||||
zoom,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on(
|
|
||||||
"click",
|
|
||||||
"unclustered-point",
|
|
||||||
async (e: M.MapMouseEvent & Object) => {
|
|
||||||
const trail = trails.find(
|
|
||||||
(t) => t.id == (e as any).features[0].properties.trail,
|
|
||||||
);
|
|
||||||
if (!trail || !map) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
highlightCluster(trail);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
map.on("mouseenter", "clusters", () => {
|
|
||||||
map!.getCanvas().style.cursor = "pointer";
|
|
||||||
});
|
|
||||||
map.on("mouseleave", "clusters", () => {
|
|
||||||
map!.getCanvas().style.cursor = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on("mouseenter", "unclustered-point", () => {
|
|
||||||
map!.getCanvas().style.cursor = "pointer";
|
|
||||||
});
|
|
||||||
map.on("mouseleave", "unclustered-point", () => {
|
|
||||||
map!.getCanvas().style.cursor = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on("load", () => {
|
map.on("load", () => {
|
||||||
initMap(true);
|
|
||||||
layerManager.init();
|
layerManager.init();
|
||||||
|
initMap(true);
|
||||||
oninit?.(map!);
|
oninit?.(map!);
|
||||||
|
mapLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
showWaypoints();
|
showWaypoints();
|
||||||
@@ -1152,7 +921,7 @@
|
|||||||
if (e.key == "m") {
|
if (e.key == "m") {
|
||||||
if (trails.length === 1) {
|
if (trails.length === 1) {
|
||||||
addTrailLayer(trails[0], trails[0].id!, 0, data[0]);
|
addTrailLayer(trails[0], trails[0].id!, 0, data[0]);
|
||||||
addCaretLayer(trails[0].id);
|
addCaretLayer(data[0]);
|
||||||
}
|
}
|
||||||
} else if (e.key == "p") {
|
} else if (e.key == "p") {
|
||||||
if (showElevation) {
|
if (showElevation) {
|
||||||
|
|||||||
@@ -244,16 +244,16 @@ export default class GPX {
|
|||||||
}()) as unknown as GPX;
|
}()) as unknown as GPX;
|
||||||
}
|
}
|
||||||
|
|
||||||
toGeoJSON(): GeoJSON.FeatureCollection {
|
toGeoJSON(includeRoute: boolean = false, includeWaypoints: boolean = false): GeoJSON.FeatureCollection {
|
||||||
const features: GeoJSON.Feature[] = [];
|
const features: GeoJSON.Feature[] = [];
|
||||||
|
|
||||||
if (this.wpt) {
|
if (this.wpt && includeWaypoints) {
|
||||||
for (const wpt of this.wpt) {
|
for (const wpt of this.wpt) {
|
||||||
features.push(wpt.toGeoJSON());
|
features.push(wpt.toGeoJSON());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rte) {
|
if (this.rte && includeRoute) {
|
||||||
for (const rte of this.rte) {
|
for (const rte of this.rte) {
|
||||||
features.push(rte.toGeoJSON());
|
features.push(rte.toGeoJSON());
|
||||||
}
|
}
|
||||||
@@ -269,6 +269,7 @@ export default class GPX {
|
|||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features
|
features
|
||||||
};
|
};
|
||||||
|
|
||||||
geojson.bbox = bbox(geojson)
|
geojson.bbox = bbox(geojson)
|
||||||
|
|
||||||
return geojson
|
return geojson
|
||||||
|
|||||||
45
web/src/lib/vendor/maplibre-layer-manager/caret-layer.ts
vendored
Normal file
45
web/src/lib/vendor/maplibre-layer-manager/caret-layer.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import type { SourceSpecification, StyleSpecification } from "maplibre-gl";
|
||||||
|
import type { BaseLayer } from "./layers";
|
||||||
|
|
||||||
|
export class CaretLayer implements BaseLayer {
|
||||||
|
|
||||||
|
spec: StyleSpecification;
|
||||||
|
|
||||||
|
constructor(source: SourceSpecification) {
|
||||||
|
this.spec = {
|
||||||
|
version: 8,
|
||||||
|
name: "direction-carets",
|
||||||
|
sources: {
|
||||||
|
caret: source
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: "direction-carets",
|
||||||
|
type: "symbol",
|
||||||
|
source: "caret",
|
||||||
|
layout: {
|
||||||
|
"symbol-placement": "line",
|
||||||
|
"symbol-spacing": [
|
||||||
|
"interpolate",
|
||||||
|
["exponential", 1.5],
|
||||||
|
["zoom"],
|
||||||
|
0,
|
||||||
|
80,
|
||||||
|
18,
|
||||||
|
200,
|
||||||
|
],
|
||||||
|
"icon-image": "direction-caret",
|
||||||
|
"icon-size": [
|
||||||
|
"interpolate",
|
||||||
|
["exponential", 1.5],
|
||||||
|
["zoom"],
|
||||||
|
0,
|
||||||
|
0.5,
|
||||||
|
18,
|
||||||
|
0.8,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
113
web/src/lib/vendor/maplibre-layer-manager/cluster-layer.ts
vendored
Normal file
113
web/src/lib/vendor/maplibre-layer-manager/cluster-layer.ts
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import type { MapMouseEvent, SourceSpecification, StyleSpecification } from "maplibre-gl";
|
||||||
|
import type { BaseLayer } from "./layers";
|
||||||
|
import * as M from "maplibre-gl";
|
||||||
|
|
||||||
|
export class ClusterLayer implements BaseLayer {
|
||||||
|
|
||||||
|
spec: StyleSpecification;
|
||||||
|
|
||||||
|
private map: M.Map;
|
||||||
|
|
||||||
|
|
||||||
|
listeners: Record<string, { onMouseUp?: (e: MapMouseEvent) => void; onMouseDown?: (e: MapMouseEvent) => void; onEnter?: (e: MapMouseEvent) => void; onLeave?: (e: MapMouseEvent) => void; onMouseMove?: (e: MapMouseEvent) => void; }> = {
|
||||||
|
"clusters": {
|
||||||
|
onMouseDown: this.zoomOnCluster.bind(this),
|
||||||
|
onEnter: () => this.map!.getCanvas().style.cursor = "pointer",
|
||||||
|
onLeave: () => this.map!.getCanvas().style.cursor = ""
|
||||||
|
},
|
||||||
|
"unclustered-point": {
|
||||||
|
onEnter: () => this.map!.getCanvas().style.cursor = "pointer",
|
||||||
|
onLeave: () => this.map!.getCanvas().style.cursor = ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(map: M.Map, geojson: GeoJSON.FeatureCollection, listeners?: Record<string, { onMouseUp?: (e: MapMouseEvent) => void; onMouseDown?: (e: MapMouseEvent) => void; onEnter?: (e: MapMouseEvent) => void; onLeave?: (e: MapMouseEvent) => void; onMouseMove?: (e: MapMouseEvent) => void; }>) {
|
||||||
|
this.map = map;
|
||||||
|
this.listeners = {
|
||||||
|
"clusters": { ...this.listeners["clusters"], ...listeners?.["clusters"] },
|
||||||
|
"unclustered-point": { ...this.listeners["unclustered-point"], ...listeners?.["unclustered-point"] }
|
||||||
|
}
|
||||||
|
this.spec = {
|
||||||
|
version: 8,
|
||||||
|
name: "clusters",
|
||||||
|
glyphs: "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
|
||||||
|
sources: {
|
||||||
|
"cluster-trails": {
|
||||||
|
type: "geojson",
|
||||||
|
data: geojson,
|
||||||
|
cluster: true,
|
||||||
|
clusterRadius: 50,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: "clusters",
|
||||||
|
type: "circle",
|
||||||
|
source: "cluster-trails",
|
||||||
|
filter: ["has", "point_count"],
|
||||||
|
paint: {
|
||||||
|
"circle-color": "#242734",
|
||||||
|
"circle-radius": [
|
||||||
|
"step",
|
||||||
|
["get", "point_count"],
|
||||||
|
10,
|
||||||
|
10,
|
||||||
|
15,
|
||||||
|
20,
|
||||||
|
20,
|
||||||
|
50,
|
||||||
|
25,
|
||||||
|
100,
|
||||||
|
30,
|
||||||
|
200,
|
||||||
|
35,
|
||||||
|
],
|
||||||
|
"circle-stroke-width": 3,
|
||||||
|
"circle-stroke-color": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cluster-count",
|
||||||
|
type: "symbol",
|
||||||
|
source: "cluster-trails",
|
||||||
|
filter: ["has", "point_count"],
|
||||||
|
paint: {
|
||||||
|
"text-color": "#fff",
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
"text-field": "{point_count_abbreviated}",
|
||||||
|
"text-font": ["Noto Sans Regular"],
|
||||||
|
"text-size": 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "unclustered-point",
|
||||||
|
type: "circle",
|
||||||
|
source: "cluster-trails",
|
||||||
|
filter: ["!", ["has", "point_count"]],
|
||||||
|
paint: {
|
||||||
|
"circle-color": "#242734",
|
||||||
|
"circle-radius": 7,
|
||||||
|
"circle-stroke-width": 2,
|
||||||
|
"circle-stroke-color": "#fff",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async zoomOnCluster(e: MapMouseEvent) {
|
||||||
|
const features = this.map.queryRenderedFeatures(e.point, {
|
||||||
|
layers: ["clusters"],
|
||||||
|
});
|
||||||
|
const clusterId = features[0].properties.cluster_id;
|
||||||
|
const zoom = await (
|
||||||
|
this.map.getSource("cluster-trails") as M.GeoJSONSource
|
||||||
|
).getClusterExpansionZoom(clusterId);
|
||||||
|
this.map.flyTo({
|
||||||
|
center: (features[0].geometry as any).coordinates,
|
||||||
|
zoom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Marker, StyleSpecification, MapMouseEvent } from "maplibre-gl";
|
import type { StyleSpecification } from "maplibre-gl";
|
||||||
import type { BaseLayer } from "./layers";
|
import type { BaseLayer } from "./layers";
|
||||||
import * as M from "maplibre-gl";
|
|
||||||
|
|
||||||
export class DebugLayer implements BaseLayer {
|
export class DebugLayer implements BaseLayer {
|
||||||
spec: StyleSpecification = {
|
spec: StyleSpecification = {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { OverpassLayer } from "./overpass-layer";
|
|||||||
export class LayerManager {
|
export class LayerManager {
|
||||||
private map: M.Map;
|
private map: M.Map;
|
||||||
state!: MapState;
|
state!: MapState;
|
||||||
private layers: Record<string, BaseLayer> = {};
|
layers: Record<string, BaseLayer> = {};
|
||||||
private addedListeners: Set<string> = new Set();
|
private addedListeners: Set<string> = new Set();
|
||||||
|
|
||||||
constructor(map: M.Map) {
|
constructor(map: M.Map) {
|
||||||
@@ -87,8 +87,18 @@ export class LayerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private addLayer(id: string, layer: BaseLayer) {
|
addLayer(id: string, layer: BaseLayer) {
|
||||||
if (this.layers[id] && this.map.getLayer(id)) {
|
if (this.layers[id] && this.map.getLayer(id)) {
|
||||||
|
// update sources and return
|
||||||
|
for (const [sourceId, s] of Object.entries(layer.spec.sources)) {
|
||||||
|
if (s.type != "geojson" || !this.map.getSource(sourceId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = this.map.getSource(sourceId) as M.GeoJSONSource
|
||||||
|
source.setData(s.data)
|
||||||
|
this.layers[id] = layer
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const [id, s] of Object.entries(layer.spec.sources)) {
|
for (const [id, s] of Object.entries(layer.spec.sources)) {
|
||||||
@@ -134,7 +144,7 @@ export class LayerManager {
|
|||||||
this.layers[id] = layer
|
this.layers[id] = layer
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeLayer(id: string) {
|
removeLayer(id: string) {
|
||||||
const layer = this.layers[id];
|
const layer = this.layers[id];
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
33
web/src/lib/vendor/maplibre-layer-manager/terrain-layer.ts
vendored
Normal file
33
web/src/lib/vendor/maplibre-layer-manager/terrain-layer.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { StyleSpecification } from "maplibre-gl";
|
||||||
|
import type { BaseLayer } from "./layers";
|
||||||
|
|
||||||
|
export class TerrainLayer implements BaseLayer {
|
||||||
|
spec: StyleSpecification;
|
||||||
|
|
||||||
|
constructor(terrainURL: string, hillshadingURL?: string) {
|
||||||
|
this.spec = {
|
||||||
|
version: 8,
|
||||||
|
name: "terrain",
|
||||||
|
sources: {
|
||||||
|
terrain: {
|
||||||
|
type: "raster-dem",
|
||||||
|
url: terrainURL,
|
||||||
|
},
|
||||||
|
...(hillshadingURL ? {
|
||||||
|
hillshading: {
|
||||||
|
type: "raster-dem",
|
||||||
|
url: hillshadingURL
|
||||||
|
}
|
||||||
|
} : {})
|
||||||
|
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: "hillshading",
|
||||||
|
source: "terrain",
|
||||||
|
type: "hillshade",
|
||||||
|
}]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
34
web/src/lib/vendor/maplibre-layer-manager/trail-layer.ts
vendored
Normal file
34
web/src/lib/vendor/maplibre-layer-manager/trail-layer.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { MapMouseEvent, Marker, StyleSpecification } from "maplibre-gl";
|
||||||
|
import type { BaseLayer } from "./layers";
|
||||||
|
|
||||||
|
export class TrailLayer implements BaseLayer {
|
||||||
|
|
||||||
|
spec: StyleSpecification;
|
||||||
|
listeners: Record<string, { onMouseUp?: (e: MapMouseEvent) => void; onMouseDown?: (e: MapMouseEvent) => void; onEnter?: (e: MapMouseEvent) => void; onLeave?: (e: MapMouseEvent) => void; onMouseMove?: (e: MapMouseEvent) => void; }>
|
||||||
|
markers: Record<string, Marker> = {};
|
||||||
|
|
||||||
|
constructor(id: string, geojson: GeoJSON.FeatureCollection, color: string, listerners?: { onMouseUp?: (e: MapMouseEvent) => void; onMouseDown?: (e: MapMouseEvent) => void; onEnter?: (e: MapMouseEvent) => void; onLeave?: (e: MapMouseEvent) => void; onMouseMove?: (e: MapMouseEvent) => void; }) {
|
||||||
|
this.spec = {
|
||||||
|
version: 8,
|
||||||
|
name: id,
|
||||||
|
sources: {
|
||||||
|
[id]: {
|
||||||
|
type: "geojson",
|
||||||
|
data: geojson,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: id,
|
||||||
|
type: "line",
|
||||||
|
source: id,
|
||||||
|
paint: {
|
||||||
|
"line-color": color,
|
||||||
|
"line-width": 5,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.listeners = { id: listerners ?? {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user