UNPKG

svelte-maplibre-gl

Version:

Build interactive web maps effortlessly with MapLibre GL JS and Svelte

192 lines (191 loc) 6.1 kB
import { setContext, getContext } from 'svelte'; const MAP_CONTEXT_KEY = Symbol('MapLibre map context'); const SOURCE_CONTEXT_KEY = Symbol('MapLibre source context'); const LAYER_CONTEXT_KEY = Symbol('MapLibre layer context'); const MARKER_CONTEXT_KEY = Symbol('MapLibre marker context'); // https://svelte.dev/docs/svelte/$state#Classes class MapContext { /** Map instance */ _map = $state.raw(null); /** Callbacks to be called when the map style is loaded */ _listener = undefined; _pending = []; /** Names of layers dynamically added */ userLayers = new Set(); /** Names of sources dynamically added */ userSources = new Set(); /** Terrain specification of the current base style */ baseTerrain; /** Sky specification set by user */ userTerrain; /** Sky specification of the current base style */ baseSky; /** Sky specification set by user */ userSky; /** Light specification of the current base style */ baseLight; /** Light specification set by user */ userLight; /** Projection specification of the current base style */ baseProjection; /** Projection specification set by user */ userProjection; get map() { return this._map; } set map(value) { if (this._listener) { this._map?.off('styledata', this._listener); this._listener = undefined; } this._map = value; if (this._map) { this._listener = this._onstyledata.bind(this); this._map.on('styledata', this._listener); } } addLayer(addLayerObject, beforeId) { if (!this.map) throw new Error('Map is not initialized'); this.userLayers.add(addLayerObject.id); this.waitForStyleLoaded((map) => { map.addLayer(addLayerObject, beforeId); }); } removeLayer(id) { if (!this.map) throw new Error('Map is not initialized'); this.userLayers.delete(id); this.waitForStyleLoaded((map) => { map.removeLayer(id); }); } addSource(id, source) { this.userSources.add(id); this.waitForStyleLoaded((map) => { map.addSource(id, source); }); } removeSource(id) { this.userSources.delete(id); this.waitForStyleLoaded((map) => { map.removeSource(id); }); } /** Wait for the style to be loaded before calling the function */ waitForStyleLoaded(func) { if (!this.map) { return; } if (this.map.style._loaded) { // style is already loaded func(this.map); } else { // we need to wait the style to be loaded this._pending.push(func); } } _onstyledata(ev) { const map = ev.target; if (map?.style._loaded) { for (const func of this._pending) { // call pending tasks func(map); } this._pending = []; } } setStyle(style) { const { userSources: addedSources, userLayers: addedLayers } = this; if (!style) { this.map?.setStyle(null); return; } this.map?.setStyle($state.snapshot(style), { // Preserves user styles when the base style changes transformStyle: (previous, next) => { this.baseLight = next.light; this.baseProjection = next.projection; this.baseSky = next.sky; this.baseTerrain = next.terrain; if (!previous) { return next; } const sources = next.sources; for (const [key, value] of Object.entries(previous.sources)) { if (addedSources.has(key)) { sources[key] = value; } } const userLayers = previous.layers.filter((layer) => addedLayers.has(layer.id)); const layers = [...next.layers, ...userLayers]; return { ...next, light: this.userLight || this.baseLight, projection: this.userProjection || this.baseProjection, sky: this.userSky || this.baseSky, terrain: this.userTerrain || this.baseTerrain, sources, layers }; } }); } } export function prepareMapContext() { const mapCtx = new MapContext(); setContext(MAP_CONTEXT_KEY, mapCtx); return mapCtx; } export function getMapContext() { const mapCtx = getContext(MAP_CONTEXT_KEY); if (!mapCtx) throw new Error('Map context not found'); return mapCtx; } // https://svelte.dev/docs/svelte/$state#Classes class SourceContext { /** sourceId */ id = $state(''); } export function prepareSourceContext() { const sourceCtx = new SourceContext(); setContext(SOURCE_CONTEXT_KEY, sourceCtx); return sourceCtx; } export function getSourceContext() { const sourceCtx = getContext(SOURCE_CONTEXT_KEY); if (!sourceCtx || !sourceCtx.id) throw new Error('Source context not found'); return sourceCtx; } class LayerContext { id = $state(''); } export function prepareLayerContext() { const layerCtx = new LayerContext(); setContext(LAYER_CONTEXT_KEY, layerCtx); return layerCtx; } export function getLayerContext() { const layerCtx = getContext(LAYER_CONTEXT_KEY); if (!layerCtx || !layerCtx.id) throw new Error('Layer context not found'); return layerCtx; } class MarkerContext { marker = $state.raw(null); } export function prepareMarkerContext() { const markerCtx = new MarkerContext(); setContext(MARKER_CONTEXT_KEY, markerCtx); return markerCtx; } export function getMarkerContext() { const markerCtx = getContext(MARKER_CONTEXT_KEY); if (!markerCtx) { return null; } return markerCtx; }