map performance improvements
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import type { Waypoint } from "$lib/models/waypoint";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { fetchGPX } from "$lib/stores/trail_store";
|
||||
import { findStartAndEndPoints } from "$lib/util/geojson_util";
|
||||
import { toGeoJson } from "$lib/util/gpx_util";
|
||||
import {
|
||||
@@ -17,12 +18,8 @@
|
||||
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
|
||||
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
|
||||
import { StyleSwitcherControl } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
||||
import type {
|
||||
Feature,
|
||||
FeatureCollection,
|
||||
GeoJSON,
|
||||
Geometry,
|
||||
} from "geojson";
|
||||
import { T } from "@threlte/core";
|
||||
import type { Feature, FeatureCollection, GeoJSON } from "geojson";
|
||||
import * as M from "maplibre-gl";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import { onDestroy, onMount, untrack } from "svelte";
|
||||
@@ -46,7 +43,7 @@
|
||||
elevationProfileContainer?: string | HTMLDivElement | undefined;
|
||||
mapOptions?: Partial<M.MapOptions> | undefined;
|
||||
activeTrail?: number | null;
|
||||
minZoom?: number;
|
||||
clusterTrails?: boolean;
|
||||
onsegmentdragend?: (data: {
|
||||
segment: number;
|
||||
event: M.MapMouseEvent;
|
||||
@@ -57,6 +54,10 @@
|
||||
onmoveend?: (map: M.Map) => void;
|
||||
onzoom?: (map: M.Map) => void;
|
||||
onclick?: (event: M.MapMouseEvent & Object) => void;
|
||||
onUnclusteredClick?: (
|
||||
event: M.MapMouseEvent & Object,
|
||||
trail: Trail,
|
||||
) => void;
|
||||
oninit?: (map: M.Map) => void;
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@
|
||||
elevationProfileContainer = undefined,
|
||||
mapOptions = undefined,
|
||||
activeTrail = $bindable(0),
|
||||
minZoom = 0,
|
||||
clusterTrails = false,
|
||||
onmarkerdragend,
|
||||
onsegmentdragend,
|
||||
onselect,
|
||||
@@ -85,6 +86,7 @@
|
||||
onmoveend,
|
||||
onzoom,
|
||||
onclick,
|
||||
onUnclusteredClick,
|
||||
oninit,
|
||||
}: Props = $props();
|
||||
|
||||
@@ -99,6 +101,7 @@
|
||||
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;
|
||||
@@ -130,7 +133,9 @@
|
||||
"#DE7DC5",
|
||||
];
|
||||
|
||||
let data = $derived(getData(trails));
|
||||
let clusterPopup: M.Popup | null = null;
|
||||
|
||||
let [data, clusterData] = $derived(getData(trails));
|
||||
$effect(() => {
|
||||
if (data && map) {
|
||||
untrack(() => initMap(map?.loaded() ?? false));
|
||||
@@ -182,27 +187,21 @@
|
||||
});
|
||||
});
|
||||
|
||||
function getData(trails: Trail[]) {
|
||||
if (!trails.length) {
|
||||
return [];
|
||||
}
|
||||
function getData(trails: Trail[]): [GeoJSON[], FeatureCollection] {
|
||||
let cD: FeatureCollection = { type: "FeatureCollection", features: [] };
|
||||
let r: GeoJSON[] = [];
|
||||
|
||||
let r: GeoJSON[] =
|
||||
(map?.getZoom() ?? 0) > minZoom
|
||||
? []
|
||||
: [{ type: "FeatureCollection", features: [] }];
|
||||
trails.forEach((t) => {
|
||||
if ((map?.getZoom() ?? 0) > minZoom) {
|
||||
if (t.polyline) {
|
||||
r.push(polylineToGeoJSON(t.polyline, 5));
|
||||
} else if (t.expand?.gpx_data) {
|
||||
r.push(toGeoJson(t.expand.gpx_data) as GeoJSON);
|
||||
}
|
||||
} else if (t.lat !== null && t.lon !== null) {
|
||||
(r[0] as FeatureCollection).features.push({
|
||||
if (t.expand?.gpx_data) {
|
||||
r.push(toGeoJson(t.expand.gpx_data) as GeoJSON);
|
||||
}
|
||||
if (clusterTrails && t.lat !== null && t.lon !== null) {
|
||||
cD.features.push({
|
||||
id: t.id,
|
||||
type: "Feature",
|
||||
properties: {},
|
||||
properties: {
|
||||
trail: t.id,
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [t.lon ?? 0, t.lat ?? 0],
|
||||
@@ -211,7 +210,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
return r;
|
||||
return [r, cD];
|
||||
}
|
||||
|
||||
function initMap(mapLoaded: boolean) {
|
||||
@@ -226,13 +225,12 @@
|
||||
epc?.hideProfile();
|
||||
}
|
||||
|
||||
if ((map?.getZoom() ?? 0) > minZoom) {
|
||||
trails.forEach((t, i) => {
|
||||
const layerId = t.id!;
|
||||
addTrailLayer(t, layerId, i, data[i]);
|
||||
});
|
||||
} else {
|
||||
addClusterLayer(data[0] as FeatureCollection);
|
||||
trails.forEach((t, i) => {
|
||||
const layerId = t.id!;
|
||||
addTrailLayer(t, layerId, i, data[i]);
|
||||
});
|
||||
if (clusterTrails) {
|
||||
addClusterLayer(clusterData);
|
||||
}
|
||||
|
||||
Object.keys(layers).forEach((layerId) => {
|
||||
@@ -279,7 +277,12 @@
|
||||
maxY = Math.max(maxY, yMax);
|
||||
}
|
||||
|
||||
if(minX < Infinity && minY < Infinity && maxX > -Infinity && maxY > -Infinity) {
|
||||
if (
|
||||
minX < Infinity &&
|
||||
minY < Infinity &&
|
||||
maxX > -Infinity &&
|
||||
maxY > -Infinity
|
||||
) {
|
||||
return new M.LngLatBounds([minX, minY, maxX, maxY]);
|
||||
} else {
|
||||
return new M.LngLatBounds([0, 0, 0, 0]);
|
||||
@@ -342,6 +345,7 @@
|
||||
endMarker: null,
|
||||
source: null,
|
||||
layer: null,
|
||||
highlighted: false,
|
||||
listener: {
|
||||
onMouseUp: null,
|
||||
onMouseDown: null,
|
||||
@@ -384,7 +388,6 @@
|
||||
id: id,
|
||||
type: "line",
|
||||
source: id,
|
||||
minzoom: minZoom,
|
||||
paint: {
|
||||
"line-color": trailColors[index % trailColors.length],
|
||||
"line-width": 5,
|
||||
@@ -407,7 +410,7 @@
|
||||
map.on("mousedown", id, layers[id].listener.onMouseDown);
|
||||
}
|
||||
|
||||
if (!drawing) {
|
||||
if (!drawing && !clusterTrails) {
|
||||
addStartEndMarkers(trail, id, geojson);
|
||||
}
|
||||
}
|
||||
@@ -421,7 +424,6 @@
|
||||
type: "geojson",
|
||||
data: geojson,
|
||||
cluster: true,
|
||||
clusterMaxZoom: minZoom - 1,
|
||||
clusterRadius: 50,
|
||||
});
|
||||
} else {
|
||||
@@ -438,11 +440,17 @@
|
||||
"circle-radius": [
|
||||
"step",
|
||||
["get", "point_count"],
|
||||
10,
|
||||
10,
|
||||
15,
|
||||
20,
|
||||
20,
|
||||
50,
|
||||
25,
|
||||
100,
|
||||
30,
|
||||
750,
|
||||
40,
|
||||
200,
|
||||
35,
|
||||
],
|
||||
"circle-stroke-width": 3,
|
||||
"circle-stroke-color": "#fff",
|
||||
@@ -461,7 +469,6 @@
|
||||
"text-size": 12,
|
||||
},
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: "unclustered-point",
|
||||
type: "circle",
|
||||
@@ -477,6 +484,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addClusterHighlightLayer(geojson: GeoJSON) {
|
||||
if (!geojson || !map || !map.style) {
|
||||
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")) {
|
||||
map.addLayer({
|
||||
id: "cluster-highlight",
|
||||
type: "line",
|
||||
source: "cluster-highlight",
|
||||
paint: {
|
||||
"line-color": trailColors[0],
|
||||
"line-width": 5,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function moveCrosshairToCursorPosition(e: M.MapMouseEvent) {
|
||||
epc?.moveCrosshair(e.lngLat.lat, e.lngLat.lng);
|
||||
moveElevationMarkerToCursorPosition(e);
|
||||
@@ -534,7 +569,7 @@
|
||||
"interpolate",
|
||||
["exponential", 1.5],
|
||||
["zoom"],
|
||||
minZoom,
|
||||
0,
|
||||
80,
|
||||
18,
|
||||
200,
|
||||
@@ -544,7 +579,7 @@
|
||||
"interpolate",
|
||||
["exponential", 1.5],
|
||||
["zoom"],
|
||||
minZoom,
|
||||
0,
|
||||
0.5,
|
||||
18,
|
||||
0.8,
|
||||
@@ -590,6 +625,34 @@
|
||||
// map?.setPaintProperty(id, "line-color", "#648ad5");
|
||||
}
|
||||
|
||||
export async function highlightCluster(trail: Trail) {
|
||||
if (!map || !map.style) {
|
||||
return;
|
||||
}
|
||||
clusterPopup = createPopupFromTrail(trail);
|
||||
clusterPopup.setLngLat([trail.lon!, trail.lat!]).addTo(map);
|
||||
|
||||
const geojson = await fetchGPX(trail);
|
||||
|
||||
addClusterHighlightLayer(toGeoJson(geojson));
|
||||
|
||||
clusterPopup.on("close", () => {
|
||||
unHighlightCluster(false);
|
||||
});
|
||||
}
|
||||
|
||||
export async function unHighlightCluster(closePopup: boolean = true) {
|
||||
if (!map || !map.style) {
|
||||
return;
|
||||
}
|
||||
if (map?.getLayer("cluster-highlight")) {
|
||||
map?.removeLayer("cluster-highlight");
|
||||
}
|
||||
if (closePopup) {
|
||||
clusterPopup?.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function adjustTrailFocus(activeTrail: number | null) {
|
||||
if (activeTrail !== null && trails[activeTrail] !== undefined) {
|
||||
if (
|
||||
@@ -658,7 +721,7 @@
|
||||
}
|
||||
map.getCanvas().style.cursor = "inherit";
|
||||
|
||||
if (activeTrail !== null && trails[activeTrail]) {
|
||||
if (activeTrail !== null && trails[activeTrail] && !clusterTrails) {
|
||||
addStartEndMarkers(
|
||||
trails[activeTrail],
|
||||
trails[activeTrail].id,
|
||||
@@ -710,7 +773,7 @@
|
||||
layers[id].endMarker.setLngLat(
|
||||
startEndPoint[startEndPoint.length - 1] as M.LngLatLike,
|
||||
);
|
||||
if (map.getZoom() > minZoom) {
|
||||
if (!clusterTrails) {
|
||||
layers[id].endMarker.addTo(map);
|
||||
}
|
||||
|
||||
@@ -727,16 +790,6 @@
|
||||
layers[id].endMarker?.remove();
|
||||
}
|
||||
|
||||
export function togglePopup(id: string, currentState?: boolean) {
|
||||
if (
|
||||
currentState !== undefined &&
|
||||
layers[id]?.startMarker?.getPopup().isOpen() != currentState
|
||||
) {
|
||||
return;
|
||||
}
|
||||
layers[id]?.startMarker?.togglePopup();
|
||||
}
|
||||
|
||||
function showWaypoints() {
|
||||
if (!map) {
|
||||
return;
|
||||
@@ -1014,17 +1067,6 @@
|
||||
});
|
||||
|
||||
map.on("zoom", (e) => {
|
||||
const zoom = e.target.getZoom();
|
||||
Object.values(layers).forEach((l) => {
|
||||
if (zoom > minZoom && map && !drawing) {
|
||||
l.startMarker?.addTo(map);
|
||||
l.endMarker?.addTo(map);
|
||||
} else {
|
||||
l.startMarker?.remove();
|
||||
l.endMarker?.remove();
|
||||
}
|
||||
});
|
||||
|
||||
onzoom?.(e.target);
|
||||
});
|
||||
|
||||
@@ -1035,6 +1077,51 @@
|
||||
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", () => {
|
||||
initMap(true);
|
||||
oninit?.(map!);
|
||||
|
||||
@@ -32,6 +32,7 @@ export type TrailSearchResult = {
|
||||
location: string;
|
||||
name: string;
|
||||
public: boolean;
|
||||
polyline?: string;
|
||||
}
|
||||
|
||||
export type ListSearchResult = {
|
||||
|
||||
@@ -66,7 +66,7 @@ export function splitMultiLineStringToLineStrings(geojson: GeoJsonObject): Featu
|
||||
...feature.properties,
|
||||
coordinateProperties: {
|
||||
...feature.properties.coordinateProperties,
|
||||
times: feature.properties.coordinateProperties.times[lineIndex]
|
||||
times: feature.properties.coordinateProperties?.times?.[lineIndex]
|
||||
},
|
||||
featureId: features.length,
|
||||
segmentId: lineIndex,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
||||
import type { Settings } from "$lib/models/settings";
|
||||
import {
|
||||
defaultTrailSearchAttributes,
|
||||
defaultTrailSearchAttributes,
|
||||
type Trail,
|
||||
type TrailBoundingBox,
|
||||
type TrailFilter,
|
||||
@@ -24,11 +24,12 @@
|
||||
type LocationSearchResult,
|
||||
type TrailSearchResult,
|
||||
} from "$lib/stores/search_store";
|
||||
import { trails_search_bounding_box } from "$lib/stores/trail_store";
|
||||
import {
|
||||
trails_search_bounding_box
|
||||
} from "$lib/stores/trail_store";
|
||||
import { getIconForLocation } from "$lib/util/icon_util";
|
||||
import type { Snapshot } from "@sveltejs/kit";
|
||||
import * as M from "maplibre-gl";
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { slide } from "svelte/transition";
|
||||
|
||||
@@ -45,7 +46,7 @@
|
||||
const maxBoundingBox: TrailBoundingBox = page.data.boundingBox;
|
||||
const settings: Settings = page.data.settings;
|
||||
|
||||
const MIN_ZOOM = 6;
|
||||
const MIN_ZOOM = 100;
|
||||
|
||||
let loading: boolean = $state(true);
|
||||
let loadingNextPage: boolean = false;
|
||||
@@ -57,7 +58,7 @@
|
||||
|
||||
export const snapshot: Snapshot<TrailFilter> = {
|
||||
capture: () => filter,
|
||||
restore: (value) => {
|
||||
restore: (value) => {
|
||||
filter = value;
|
||||
handleFilterUpdate();
|
||||
},
|
||||
@@ -140,11 +141,11 @@
|
||||
}
|
||||
|
||||
function handleTrailCardMouseEnter(trail: Trail) {
|
||||
mapWithElevation?.togglePopup(trail.id!, false);
|
||||
mapWithElevation?.highlightCluster(trail);
|
||||
}
|
||||
|
||||
function handleTrailCardMouseLeave(trail: Trail) {
|
||||
mapWithElevation?.togglePopup(trail.id!, true);
|
||||
mapWithElevation?.unHighlightCluster();
|
||||
}
|
||||
|
||||
async function handleFilterUpdate() {
|
||||
@@ -356,7 +357,7 @@
|
||||
showInfoPopup={true}
|
||||
activeTrail={-1}
|
||||
fitBounds="off"
|
||||
minZoom={MIN_ZOOM}
|
||||
clusterTrails={true}
|
||||
bind:map
|
||||
bind:this={mapWithElevation}
|
||||
></MapWithElevationMaplibre>
|
||||
|
||||
Reference in New Issue
Block a user