2023-03-11 02:37:07 +01:00
|
|
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
|
|
|
import type { Map as MLMap } from "maplibre-gl"
|
2023-03-23 00:58:21 +01:00
|
|
|
import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers"
|
2023-03-11 02:37:07 +01:00
|
|
|
import { Utils } from "../../Utils"
|
2023-03-23 00:58:21 +01:00
|
|
|
import { BBox } from "../../Logic/BBox"
|
|
|
|
|
|
|
|
export interface MapState {
|
|
|
|
readonly location: UIEventSource<{ lon: number; lat: number }>
|
|
|
|
readonly zoom: UIEventSource<number>
|
|
|
|
readonly bounds: Store<BBox>
|
|
|
|
readonly rasterLayer: UIEventSource<RasterLayerPolygon | undefined>
|
|
|
|
}
|
|
|
|
export class MapLibreAdaptor implements MapState {
|
2023-03-11 02:37:07 +01:00
|
|
|
private readonly _maplibreMap: Store<MLMap>
|
2023-03-23 00:58:21 +01:00
|
|
|
|
|
|
|
readonly location: UIEventSource<{ lon: number; lat: number }>
|
|
|
|
readonly zoom: UIEventSource<number>
|
|
|
|
readonly bounds: Store<BBox>
|
|
|
|
readonly rasterLayer: UIEventSource<RasterLayerPolygon | undefined>
|
|
|
|
private readonly _bounds: UIEventSource<BBox>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used for internal bookkeeping (to remove a rasterLayer when done loading)
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private _currentRasterLayer: string
|
|
|
|
constructor(maplibreMap: Store<MLMap>, state?: Partial<Omit<MapState, "bounds">>) {
|
2023-03-11 02:37:07 +01:00
|
|
|
this._maplibreMap = maplibreMap
|
|
|
|
|
2023-03-23 00:58:21 +01:00
|
|
|
this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 })
|
|
|
|
this.zoom = state?.zoom ?? new UIEventSource(1)
|
|
|
|
this._bounds = new UIEventSource(BBox.global)
|
|
|
|
this.bounds = this._bounds
|
|
|
|
this.rasterLayer =
|
|
|
|
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
|
2023-03-11 02:37:07 +01:00
|
|
|
|
2023-03-23 00:58:21 +01:00
|
|
|
const self = this
|
2023-03-11 02:37:07 +01:00
|
|
|
maplibreMap.addCallbackAndRunD((map) => {
|
|
|
|
map.on("load", () => {
|
|
|
|
self.setBackground()
|
|
|
|
})
|
2023-03-23 00:58:21 +01:00
|
|
|
self.MoveMapToCurrentLoc(this.location.data)
|
|
|
|
self.SetZoom(this.zoom.data)
|
|
|
|
map.on("moveend", () => {
|
|
|
|
const dt = this.location.data
|
|
|
|
dt.lon = map.getCenter().lng
|
|
|
|
dt.lat = map.getCenter().lat
|
|
|
|
this.location.ping()
|
|
|
|
this.zoom.setData(map.getZoom())
|
|
|
|
})
|
2023-03-11 02:37:07 +01:00
|
|
|
})
|
|
|
|
|
2023-03-23 00:58:21 +01:00
|
|
|
this.rasterLayer.addCallback((_) =>
|
|
|
|
self.setBackground().catch((e) => {
|
|
|
|
console.error("Could not set background")
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.location.addCallbackAndRunD((loc) => {
|
2023-03-11 02:37:07 +01:00
|
|
|
self.MoveMapToCurrentLoc(loc)
|
|
|
|
})
|
2023-03-23 00:58:21 +01:00
|
|
|
this.zoom.addCallbackAndRunD((z) => self.SetZoom(z))
|
2023-03-11 02:37:07 +01:00
|
|
|
}
|
2023-03-23 00:58:21 +01:00
|
|
|
private SetZoom(z: number) {
|
2023-03-11 02:37:07 +01:00
|
|
|
const map = this._maplibreMap.data
|
2023-03-23 00:58:21 +01:00
|
|
|
if (map === undefined || z === undefined) {
|
2023-03-11 02:37:07 +01:00
|
|
|
return
|
|
|
|
}
|
2023-03-23 00:58:21 +01:00
|
|
|
if (map.getZoom() !== z) {
|
|
|
|
map.setZoom(z)
|
2023-03-11 02:37:07 +01:00
|
|
|
}
|
2023-03-23 00:58:21 +01:00
|
|
|
}
|
|
|
|
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) {
|
|
|
|
const map = this._maplibreMap.data
|
|
|
|
if (map === undefined || loc === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-11 02:37:07 +01:00
|
|
|
const center = map.getCenter()
|
|
|
|
if (center.lng !== loc.lon || center.lat !== loc.lat) {
|
|
|
|
map.setCenter({ lng: loc.lon, lat: loc.lat })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares an ELI-URL to be compatible with mapbox
|
|
|
|
*/
|
|
|
|
private static prepareWmsURL(url: string, size: number = 256) {
|
|
|
|
// ELI: LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap
|
|
|
|
// PROD: SERVICE=WMS&REQUEST=GetMap&LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&TRANSPARENT=false&VERSION=1.3.0&WIDTH=256&HEIGHT=256&CRS=EPSG:3857&BBOX=488585.4847988467,6590094.830634755,489196.9810251281,6590706.32686104
|
|
|
|
|
|
|
|
const toReplace = {
|
|
|
|
"{bbox}": "{bbox-epsg-3857}",
|
|
|
|
"{proj}": "EPSG:3857",
|
|
|
|
"{width}": "" + size,
|
|
|
|
"{height}": "" + size,
|
|
|
|
"{zoom}": "{z}",
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const key in toReplace) {
|
|
|
|
url = url.replace(new RegExp(key), toReplace[key])
|
|
|
|
}
|
|
|
|
|
|
|
|
const subdomains = url.match(/\{switch:([a-zA-Z0-9,]*)}/)
|
|
|
|
if (subdomains !== null) {
|
|
|
|
console.log("Found a switch:", subdomains)
|
|
|
|
const options = subdomains[1].split(",")
|
|
|
|
const option = options[Math.floor(Math.random() * options.length)]
|
|
|
|
url = url.replace(subdomains[0], option)
|
|
|
|
}
|
|
|
|
|
|
|
|
return url
|
|
|
|
}
|
|
|
|
|
|
|
|
private async awaitStyleIsLoaded(): Promise<void> {
|
|
|
|
const map = this._maplibreMap.data
|
|
|
|
if (map === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
while (!map.isStyleLoaded()) {
|
|
|
|
await Utils.waitFor(250)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private removeCurrentLayer(map: MLMap) {
|
|
|
|
if (this._currentRasterLayer) {
|
|
|
|
// hide the previous layer
|
|
|
|
console.log("Removing previous layer", this._currentRasterLayer)
|
|
|
|
map.removeLayer(this._currentRasterLayer)
|
|
|
|
map.removeSource(this._currentRasterLayer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async setBackground() {
|
|
|
|
const map = this._maplibreMap.data
|
|
|
|
if (map === undefined) {
|
|
|
|
return
|
|
|
|
}
|
2023-03-23 00:58:21 +01:00
|
|
|
const background: RasterLayerProperties = this.rasterLayer?.data?.properties
|
2023-03-11 02:37:07 +01:00
|
|
|
if (background !== undefined && this._currentRasterLayer === background.id) {
|
|
|
|
// already the correct background layer, nothing to do
|
|
|
|
return
|
|
|
|
}
|
|
|
|
await this.awaitStyleIsLoaded()
|
|
|
|
|
2023-03-23 00:58:21 +01:00
|
|
|
if (background !== this.rasterLayer?.data?.properties) {
|
2023-03-11 02:37:07 +01:00
|
|
|
// User selected another background in the meantime... abort
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (background !== undefined && this._currentRasterLayer === background.id) {
|
|
|
|
// already the correct background layer, nothing to do
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (background === undefined) {
|
|
|
|
// no background to set
|
|
|
|
this.removeCurrentLayer(map)
|
|
|
|
this._currentRasterLayer = undefined
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
map.addSource(background.id, {
|
|
|
|
type: "raster",
|
|
|
|
// use the tiles option to specify a 256WMS tile source URL
|
|
|
|
// https://maplibre.org/maplibre-gl-js-docs/style-spec/sources/
|
|
|
|
tiles: [MapLibreAdaptor.prepareWmsURL(background.url, background["tile-size"] ?? 256)],
|
|
|
|
tileSize: background["tile-size"] ?? 256,
|
|
|
|
minzoom: background["min_zoom"] ?? 1,
|
|
|
|
maxzoom: background["max_zoom"] ?? 25,
|
|
|
|
// scheme: background["type"] === "tms" ? "tms" : "xyz",
|
|
|
|
})
|
|
|
|
|
|
|
|
map.addLayer(
|
|
|
|
{
|
|
|
|
id: background.id,
|
|
|
|
type: "raster",
|
|
|
|
source: background.id,
|
|
|
|
paint: {},
|
|
|
|
},
|
|
|
|
background.category === "osmbasedmap" || background.category === "map"
|
|
|
|
? undefined
|
|
|
|
: "aeroway_fill"
|
|
|
|
)
|
|
|
|
await this.awaitStyleIsLoaded()
|
|
|
|
this.removeCurrentLayer(map)
|
|
|
|
this._currentRasterLayer = background?.id
|
|
|
|
}
|
|
|
|
}
|