Files
gpx.studio/website/src/lib/components/map/map.ts

181 lines
6.2 KiB
TypeScript
Raw Normal View History

2026-01-30 21:01:24 +01:00
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import MaplibreGeocoder, {
type MaplibreGeocoderFeatureResults,
} from '@maplibre/maplibre-gl-geocoder';
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
2025-10-17 23:54:45 +02:00
import { get, writable, type Writable } from 'svelte/store';
import { settings } from '$lib/logic/settings';
2025-10-19 16:45:12 +02:00
import { tick } from 'svelte';
2026-01-30 21:01:24 +01:00
import { ANCHOR_LAYER_KEY, StyleManager } from '$lib/components/map/style';
import { MapLayerEventManager } from '$lib/components/map/map-layer-event-manager';
2025-10-17 23:54:45 +02:00
2026-01-30 21:01:24 +01:00
const { treeFileView, elevationProfile, bottomPanelSize, rightPanelSize, distanceUnits } = settings;
2025-06-21 21:07:36 +02:00
2026-01-30 21:01:24 +01:00
let fitBoundsOptions: maplibregl.MapOptions['fitBoundsOptions'] = {
2025-06-21 21:07:36 +02:00
maxZoom: 15,
linear: true,
easing: () => 1,
};
2026-01-30 21:01:24 +01:00
export class MapLibreGLMap {
private _maptilerKey: string = '';
private _map: maplibregl.Map | null = null;
private _mapStore: Writable<maplibregl.Map | null> = writable(null);
private _styleManager: StyleManager | null = null;
private _onLoadCallbacks: ((map: maplibregl.Map) => void)[] = [];
2025-10-17 23:54:45 +02:00
private _unsubscribes: (() => void)[] = [];
2026-01-30 21:01:24 +01:00
private callOnLoadBinded: () => void = this.callOnLoad.bind(this);
public layerEventManager: MapLayerEventManager | null = null;
2025-10-17 23:54:45 +02:00
2026-01-30 21:01:24 +01:00
subscribe(run: (value: maplibregl.Map | null) => void, invalidate?: () => void) {
return this._mapStore.subscribe(run, invalidate);
2025-10-17 23:54:45 +02:00
}
2025-06-21 21:07:36 +02:00
init(
2026-01-30 21:01:24 +01:00
maptilerKey: string,
2025-06-21 21:07:36 +02:00
language: string,
hash: boolean,
geocoder: boolean,
geolocate: boolean
) {
2026-01-30 21:01:24 +01:00
this._maptilerKey = maptilerKey;
this._styleManager = new StyleManager(this._mapStore, this._maptilerKey);
const map = new maplibregl.Map({
2025-06-21 21:07:36 +02:00
container: 'map',
style: {
version: 8,
2026-01-30 21:01:24 +01:00
projection: {
type: 'globe',
},
2025-06-21 21:07:36 +02:00
sources: {},
layers: [],
},
zoom: 0,
hash: hash,
boxZoom: false,
2026-01-30 21:01:24 +01:00
maxPitch: 85,
2025-06-21 21:07:36 +02:00
});
this.layerEventManager = new MapLayerEventManager(map);
2025-06-21 21:07:36 +02:00
map.addControl(
2026-01-30 21:01:24 +01:00
new maplibregl.NavigationControl({
2025-06-21 21:07:36 +02:00
visualizePitch: true,
})
);
if (geocoder) {
2026-01-30 21:01:24 +01:00
let geocoder = new MaplibreGeocoder(
{
forwardGeocode: async (config) => {
const results: MaplibreGeocoderFeatureResults = {
features: [],
type: 'FeatureCollection',
};
try {
const request = `https://nominatim.openstreetmap.org/search?format=json&q=${config.query}&limit=5&accept-language=${language}`;
const response = await fetch(request);
const geojson = await response.json();
results.features = geojson.map((result: any) => {
2025-06-21 21:07:36 +02:00
return {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [result.lon, result.lat],
},
place_name: result.display_name,
};
});
2026-01-30 21:01:24 +01:00
} catch (e) {}
return results;
},
},
{
maplibregl: maplibregl,
enableEventLogging: false,
collapsed: true,
flyTo: fitBoundsOptions,
language,
2025-06-21 21:07:36 +02:00
}
2026-01-30 21:01:24 +01:00
);
2025-06-21 21:07:36 +02:00
map.addControl(geocoder);
}
if (geolocate) {
map.addControl(
2026-01-30 21:01:24 +01:00
new maplibregl.GeolocateControl({
2025-06-21 21:07:36 +02:00
positionOptions: {
enableHighAccuracy: true,
},
fitBoundsOptions,
trackUserLocation: true,
})
);
}
2026-01-30 21:01:24 +01:00
const scaleControl = new maplibregl.ScaleControl({
2025-10-17 23:54:45 +02:00
unit: get(distanceUnits),
2025-06-21 21:07:36 +02:00
});
map.addControl(scaleControl);
map.on('load', () => {
2026-01-30 21:01:24 +01:00
this._map = map;
this._mapStore.set(map); // only set the store after the map has loaded
2025-06-21 21:07:36 +02:00
window._map = map; // entry point for extensions
2025-11-09 19:20:10 +01:00
this.resize();
2025-10-17 23:54:45 +02:00
scaleControl.setUnit(get(distanceUnits));
2025-06-21 21:07:36 +02:00
});
2026-01-30 21:01:24 +01:00
map.on('style.load', this.callOnLoadBinded);
2025-10-17 23:54:45 +02:00
this._unsubscribes.push(treeFileView.subscribe(() => this.resize()));
this._unsubscribes.push(elevationProfile.subscribe(() => this.resize()));
this._unsubscribes.push(bottomPanelSize.subscribe(() => this.resize()));
this._unsubscribes.push(rightPanelSize.subscribe(() => this.resize()));
this._unsubscribes.push(
distanceUnits.subscribe((units) => {
scaleControl.setUnit(units);
})
);
2025-06-21 21:07:36 +02:00
}
destroy() {
2026-01-30 21:01:24 +01:00
if (this._map) {
this._map.remove();
this._mapStore.set(null);
2025-06-21 21:07:36 +02:00
}
2025-10-17 23:54:45 +02:00
this._unsubscribes.forEach((unsubscribe) => unsubscribe());
this._unsubscribes = [];
2025-06-21 21:07:36 +02:00
}
resize() {
2026-01-30 21:01:24 +01:00
if (this._map) {
2025-10-19 16:45:12 +02:00
tick().then(() => {
2026-01-30 21:01:24 +01:00
this._map?.resize();
2025-10-19 16:45:12 +02:00
});
2025-06-21 21:07:36 +02:00
}
}
toggle3D() {
2026-01-30 21:01:24 +01:00
if (this._map) {
if (this._map.getPitch() === 0) {
this._map.easeTo({ pitch: 70 });
2025-06-21 21:07:36 +02:00
} else {
2026-01-30 21:01:24 +01:00
this._map.easeTo({ pitch: 0 });
2025-06-21 21:07:36 +02:00
}
}
}
2026-01-30 21:01:24 +01:00
onLoad(callback: (map: maplibregl.Map) => void) {
if (this._map) {
callback(this._map);
} else {
this._onLoadCallbacks.push(callback);
}
}
callOnLoad() {
if (this._map && this._map.getLayer(ANCHOR_LAYER_KEY.overlays)) {
this._onLoadCallbacks.forEach((callback) => callback(this._map!));
this._onLoadCallbacks = [];
this._map.off('style.load', this.callOnLoadBinded);
}
}
2025-06-21 21:07:36 +02:00
}
2026-01-30 21:01:24 +01:00
export const map = new MapLibreGLMap();