185 lines
6.0 KiB
TypeScript
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)
|
|
}
|
|
}
|
|
} |