Files
wanderer/web/src/lib/vendor/maplibre-layer-manager/maplibre-layer-manager.ts
2025-07-20 13:40:18 +02:00

185 lines
6.0 KiB
TypeScript

import * as M from "maplibre-gl";
import { DebugLayer } from "./debug-layer";
import { baseMapStyles, defaultMapState, type BaseLayer, type MapState } from "./layers";
import { OverlayLayer } from "./overlay-layer";
import { OverpassLayer } from "./overpass-layer";
export class LayerManager {
private map: M.Map;
state!: MapState;
private layers: Record<string, BaseLayer> = {};
private addedListeners: Set<string> = new Set();
constructor(map: M.Map) {
this.map = map;
const storedMapState = localStorage.getItem("map-state")
if (storedMapState) {
this.state = JSON.parse(storedMapState)
} else {
this.state = defaultMapState
}
}
init() {
this.map.on('moveend', () => { });
this.map.on('styledata', () => {
this.restoreLayers();
})
try {
this.update(this.state, true);
const overpassLayer = new OverpassLayer(this.map)
const debugLayer = new DebugLayer()
this.addLayer("overpass", overpassLayer)
this.addLayer("debug", debugLayer)
this.map.on('moveend', this.updateOverpassLayerAfterMapMoveBinded);
} catch (e) {
console.error(e)
// map is probably not initialized yet
}
}
update(newState: MapState, initialize: boolean = false) {
const oldState = this.state;
if (oldState.base != newState.base || initialize) {
this.updateBaseLayer(baseMapStyles[newState.base])
}
for (const [name, active] of Object.entries(newState.overlays)) {
const oldOverlayActive = oldState.overlays[name]
if (active && (!oldOverlayActive || initialize)) {
this.addLayer(name, new OverlayLayer(name))
} else if (oldOverlayActive && !active) {
this.removeLayer(name)
}
}
this.updateOverpassLayer(newState);
this.state = newState
localStorage.setItem("map-state", JSON.stringify(this.state));
}
updateOverpassLayerAfterMapMoveBinded = this.updateOverpassLayerAfterMapMove.bind(this);
updateOverpassLayerAfterMapMove() {
this.updateOverpassLayer(this.state)
}
private async updateOverpassLayer(newState: MapState) {
const overpassLayer = this.layers.overpass;
if (overpassLayer && this.map.getSource('overpass')) {
const castedOverpassLayer = overpassLayer as OverpassLayer;
overpassLayer.filter = await castedOverpassLayer.updateLayerIfNeeded(newState, this.map.getBounds());
(this.map.getSource('overpass') as M.GeoJSONSource).setData(castedOverpassLayer.data);
}
}
private updateBaseLayer(layer: string | M.StyleSpecification) {
this.map.setStyle(layer);
}
private addLayer(id: string, layer: BaseLayer) {
if (this.layers[id] && this.map.getLayer(id)) {
return;
}
for (const [id, s] of Object.entries(layer.spec.sources)) {
if (!this.map.getSource(id)) {
this.map.addSource(id, s)
}
}
for (const l of layer.spec.layers) {
if (!this.map.getLayer(l.id)) {
this.map.addLayer(l)
}
}
if (layer.listeners) {
for (const [id, listener] of Object.entries(layer.listeners)) {
if (listener.onEnter && !this.addedListeners.has("mouseenter-" + id)) {
this.addedListeners.add("mouseenter-" + id)
this.map.on('mouseenter', id, listener.onEnter);
}
if (listener.onLeave && !this.addedListeners.has("onleave-" + id)) {
this.addedListeners.add("mouseleave-" + id)
this.map.on('mouseleave', id, listener.onLeave);
}
if (listener.onMouseDown && !this.addedListeners.has("click-" + id)) {
this.addedListeners.add("click-" + id)
this.map.on('click', id, listener.onMouseDown);
}
if (listener.onMouseMove && !this.addedListeners.has("mousemove-" + id)) {
this.addedListeners.add("mousemove-" + id)
this.map.on('mousemove', id, listener.onMouseMove);
}
}
}
if (layer.filter && !this.map.getFilter(id)) {
this.map.setFilter(id, layer.filter)
}
this.layers[id] = layer
}
private removeLayer(id: string) {
const layer = this.layers[id];
if (!layer) {
return;
}
for (const l of layer.spec.layers) {
if (this.map.getLayer(l.id)) {
this.map.removeLayer(l.id)
}
}
for (const [id, _] of Object.entries(layer.spec.sources)) {
if (this.map.getSource(id)) {
this.map.removeSource(id)
}
}
if (layer.listeners) {
for (const [id, listener] of Object.entries(layer.listeners)) {
if (listener.onEnter) {
this.addedListeners.delete("mouseenter-" + id)
this.map.off('mouseenter', id, listener.onEnter);
}
if (listener.onLeave) {
this.addedListeners.delete("mouseleave-" + id)
this.map.off('mouseleave', id, listener.onLeave);
}
if (listener.onMouseDown) {
this.addedListeners.delete("click-" + id)
this.map.off('click', id, listener.onMouseDown);
}
if (listener.onMouseMove) {
this.addedListeners.delete("mousemove-" + id)
this.map.off('mousemove', id, listener.onMouseMove);
}
}
}
delete this.layers[id]
}
private restoreLayers() {
for (const [id, layer] of Object.entries(this.layers)) {
this.addLayer(id, layer)
}
}
}