2023-10-16 14:27:05 +02:00
import { Store , UIEventSource } from "../../Logic/UIEventSource"
import type { Map as MLMap } from "maplibre-gl"
import { Map as MlMap , SourceSpecification } from "maplibre-gl"
import { AvailableRasterLayers , RasterLayerPolygon } from "../../Models/RasterLayers"
import { Utils } from "../../Utils"
import { BBox } from "../../Logic/BBox"
2023-12-15 01:46:01 +01:00
import { ExportableMap , KeyNavigationEvent , MapProperties } from "../../Models/MapProperties"
2023-10-16 14:27:05 +02:00
import SvelteUIElement from "../Base/SvelteUIElement"
import MaplibreMap from "./MaplibreMap.svelte"
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
import * as htmltoimage from "html-to-image"
2023-03-23 00:58:21 +01:00
2023-03-24 19:21:15 +01:00
/ * *
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the ` MapProperties `
* /
2023-04-19 03:20:49 +02:00
export class MapLibreAdaptor implements MapProperties , ExportableMap {
2023-03-24 19:21:15 +01:00
private static maplibre_control_handlers = [
2023-03-25 02:48:24 +01:00
// "scrollZoom",
// "boxZoom",
// "doubleClickZoom",
2023-03-24 19:21:15 +01:00
"dragRotate" ,
"dragPan" ,
"keyboard" ,
"touchZoomRotate" ,
]
2023-03-28 05:13:48 +02:00
private static maplibre_zoom_handlers = [
"scrollZoom" ,
"boxZoom" ,
"doubleClickZoom" ,
"touchZoomRotate" ,
]
2023-03-23 00:58:21 +01:00
readonly location : UIEventSource < { lon : number ; lat : number } >
readonly zoom : UIEventSource < number >
2023-03-28 05:13:48 +02:00
readonly bounds : UIEventSource < BBox >
2023-03-23 00:58:21 +01:00
readonly rasterLayer : UIEventSource < RasterLayerPolygon | undefined >
2023-03-24 19:21:15 +01:00
readonly maxbounds : UIEventSource < BBox | undefined >
readonly allowMoving : UIEventSource < true | boolean | undefined >
2023-07-18 01:26:04 +02:00
readonly allowRotating : UIEventSource < true | boolean | undefined >
2023-03-28 05:13:48 +02:00
readonly allowZooming : UIEventSource < true | boolean | undefined >
readonly lastClickLocation : Store < undefined | { lon : number ; lat : number } >
2023-04-06 01:33:08 +02:00
readonly minzoom : UIEventSource < number >
2023-04-21 01:53:24 +02:00
readonly maxzoom : UIEventSource < number >
2023-12-19 22:21:34 +01:00
readonly rotation : UIEventSource < number >
readonly animationRunning = new UIEventSource ( false )
2023-12-15 01:46:01 +01:00
2023-11-16 03:32:04 +01:00
/ * *
2023-12-15 01:46:01 +01:00
* Functions that are called when one of those actions has happened
* @private
2023-11-16 03:32:04 +01:00
* /
2023-12-15 01:46:01 +01:00
private _onKeyNavigation : ( ( event : KeyNavigationEvent ) = > void | boolean ) [ ] = [ ]
2023-03-11 02:37:07 +01:00
private readonly _maplibreMap : Store < MLMap >
2023-03-23 00:58:21 +01:00
/ * *
* Used for internal bookkeeping ( to remove a rasterLayer when done loading )
* @private
* /
private _currentRasterLayer : string
2023-03-24 19:21:15 +01:00
2023-03-29 17:21:20 +02:00
constructor ( maplibreMap : Store < MLMap > , state? : Partial < MapProperties > ) {
2023-03-11 02:37:07 +01:00
this . _maplibreMap = maplibreMap
2023-06-07 22:46:41 +02:00
this . location = state ? . location ? ? new UIEventSource ( undefined )
2023-06-04 00:43:32 +02:00
if ( this . location . data ) {
2023-05-19 10:56:30 +02:00
// The MapLibre adaptor updates the element in the location and then pings them
// Often, code setting this up doesn't expect the object they pass in to be changed, so we create a copy
2023-06-14 20:39:36 +02:00
this . location . setData ( { . . . this . location . data } )
2023-05-19 10:56:30 +02:00
}
2023-03-23 00:58:21 +01:00
this . zoom = state ? . zoom ? ? new UIEventSource ( 1 )
2023-04-06 01:33:08 +02:00
this . minzoom = state ? . minzoom ? ? new UIEventSource ( 0 )
2023-04-21 01:53:24 +02:00
this . maxzoom = state ? . maxzoom ? ? new UIEventSource ( 24 )
2023-03-24 19:21:15 +01:00
this . zoom . addCallbackAndRunD ( ( z ) = > {
2023-04-06 01:33:08 +02:00
if ( z < this . minzoom . data ) {
this . zoom . setData ( this . minzoom . data )
2023-03-24 19:21:15 +01:00
}
2023-04-21 01:53:24 +02:00
const max = Math . min ( 24 , this . maxzoom . data ? ? 24 )
if ( z > max ) {
this . zoom . setData ( max )
2023-03-24 19:21:15 +01:00
}
} )
this . maxbounds = state ? . maxbounds ? ? new UIEventSource ( undefined )
this . allowMoving = state ? . allowMoving ? ? new UIEventSource ( true )
2023-07-18 01:26:04 +02:00
this . allowRotating = state ? . allowRotating ? ? new UIEventSource < boolean > ( true )
2023-03-28 05:13:48 +02:00
this . allowZooming = state ? . allowZooming ? ? new UIEventSource ( true )
2023-04-06 01:33:08 +02:00
this . bounds = state ? . bounds ? ? new UIEventSource ( undefined )
2023-12-19 22:21:34 +01:00
this . rotation = state ? . rotation ? ? new UIEventSource < number > ( 0 )
2023-03-23 00:58:21 +01:00
this . rasterLayer =
state ? . rasterLayer ? ? new UIEventSource < RasterLayerPolygon | undefined > ( undefined )
2023-03-11 02:37:07 +01:00
2023-03-28 05:13:48 +02:00
const lastClickLocation = new UIEventSource < { lon : number ; lat : number } > ( undefined )
this . lastClickLocation = lastClickLocation
2023-03-23 00:58:21 +01:00
const self = this
2023-04-16 03:42:26 +02:00
function handleClick ( e ) {
if ( e . originalEvent [ "consumed" ] ) {
// Workaround, 'ShowPointLayer' sets this flag
return
}
const lon = e . lngLat . lng
const lat = e . lngLat . lat
2023-06-14 20:39:36 +02:00
lastClickLocation . setData ( { lon , lat } )
2023-04-16 03:42:26 +02:00
}
2023-03-11 02:37:07 +01:00
maplibreMap . addCallbackAndRunD ( ( map ) = > {
map . on ( "load" , ( ) = > {
2023-03-24 19:21:15 +01:00
self . MoveMapToCurrentLoc ( self . location . data )
self . SetZoom ( self . zoom . data )
self . setMaxBounds ( self . maxbounds . data )
self . setAllowMoving ( self . allowMoving . data )
2023-07-18 01:26:04 +02:00
self . setAllowRotating ( self . allowRotating . data )
2023-03-28 05:13:48 +02:00
self . setAllowZooming ( self . allowZooming . data )
2023-04-06 01:33:08 +02:00
self . setMinzoom ( self . minzoom . data )
2023-04-21 01:53:24 +02:00
self . setMaxzoom ( self . maxzoom . data )
2023-04-20 01:52:23 +02:00
self . setBounds ( self . bounds . data )
2023-09-24 16:34:36 +02:00
self . setBackground ( )
2023-05-18 23:42:03 +02:00
this . updateStores ( true )
2023-03-11 02:37:07 +01:00
} )
2023-03-24 19:21:15 +01:00
self . MoveMapToCurrentLoc ( self . location . data )
self . SetZoom ( self . zoom . data )
self . setMaxBounds ( self . maxbounds . data )
self . setAllowMoving ( self . allowMoving . data )
2023-07-18 01:26:04 +02:00
self . setAllowRotating ( self . allowRotating . data )
2023-03-28 05:13:48 +02:00
self . setAllowZooming ( self . allowZooming . data )
2023-04-06 01:33:08 +02:00
self . setMinzoom ( self . minzoom . data )
2023-04-21 01:53:24 +02:00
self . setMaxzoom ( self . maxzoom . data )
2023-04-20 01:52:23 +02:00
self . setBounds ( self . bounds . data )
2023-12-19 22:21:34 +01:00
self . SetRotation ( self . rotation . data )
2023-09-24 16:34:36 +02:00
self . setBackground ( )
2023-05-18 23:42:03 +02:00
this . updateStores ( true )
2023-04-06 01:33:08 +02:00
map . on ( "moveend" , ( ) = > this . updateStores ( ) )
2023-03-28 05:13:48 +02:00
map . on ( "click" , ( e ) = > {
2023-04-16 03:42:26 +02:00
handleClick ( e )
} )
map . on ( "contextmenu" , ( e ) = > {
handleClick ( e )
} )
map . on ( "dblclick" , ( e ) = > {
handleClick ( e )
2023-03-28 05:13:48 +02:00
} )
2023-12-19 22:21:34 +01:00
map . on ( "rotateend" , ( e ) = > {
this . updateStores ( )
} )
2023-11-16 03:32:04 +01:00
map . getContainer ( ) . addEventListener ( "keydown" , ( event ) = > {
2023-12-15 01:46:01 +01:00
let locked : "islocked" = undefined
if ( ! this . allowMoving . data ) {
locked = "islocked"
}
switch ( event . key ) {
case "ArrowUp" :
this . pingKeycodeEvent ( locked ? ? "north" )
break
case "ArrowRight" :
this . pingKeycodeEvent ( locked ? ? "east" )
break
case "ArrowDown" :
this . pingKeycodeEvent ( locked ? ? "south" )
break
case "ArrowLeft" :
this . pingKeycodeEvent ( locked ? ? "west" )
break
case "+" :
this . pingKeycodeEvent ( "in" )
break
case "=" :
this . pingKeycodeEvent ( "in" )
break
case "-" :
this . pingKeycodeEvent ( "out" )
break
2023-11-16 03:32:04 +01:00
}
} )
2023-03-11 02:37:07 +01:00
} )
2023-09-24 16:34:36 +02:00
this . rasterLayer . addCallbackAndRun ( ( _ ) = >
2023-03-24 19:21:15 +01:00
self . setBackground ( ) . catch ( ( _ ) = > {
2023-03-23 00:58:21 +01:00
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-24 19:21:15 +01:00
this . maxbounds . addCallbackAndRun ( ( bbox ) = > self . setMaxBounds ( bbox ) )
2023-12-19 22:21:34 +01:00
this . rotation . addCallbackAndRunD ( ( bearing ) = > self . SetRotation ( bearing ) )
2023-12-15 01:46:01 +01:00
this . allowMoving . addCallbackAndRun ( ( allowMoving ) = > {
self . setAllowMoving ( allowMoving )
self . pingKeycodeEvent ( allowMoving ? "unlocked" : "locked" )
} )
2023-07-18 01:26:04 +02:00
this . allowRotating . addCallbackAndRunD ( ( allowRotating ) = >
self . setAllowRotating ( allowRotating )
)
2023-03-28 05:13:48 +02:00
this . allowZooming . addCallbackAndRun ( ( allowZooming ) = > self . setAllowZooming ( allowZooming ) )
this . bounds . addCallbackAndRunD ( ( bounds ) = > self . setBounds ( bounds ) )
2023-03-11 02:37:07 +01:00
}
2023-03-23 00:58:21 +01:00
2023-03-24 19:21:15 +01:00
/ * *
* Convenience constructor
* /
public static construct ( ) : {
map : Store < MLMap >
ui : SvelteUIElement
mapproperties : MapProperties
} {
const mlmap = new UIEventSource < MlMap > ( undefined )
return {
map : mlmap ,
ui : new SvelteUIElement ( MaplibreMap , {
map : mlmap ,
} ) ,
mapproperties : new MapLibreAdaptor ( mlmap ) ,
2023-03-11 02:37:07 +01:00
}
}
2023-04-21 01:53:24 +02:00
public static prepareWmsSource ( layer : RasterLayerProperties ) : SourceSpecification {
return {
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 ( layer . url , layer [ "tile-size" ] ? ? 256 ) ] ,
tileSize : layer [ "tile-size" ] ? ? 256 ,
minzoom : layer [ "min_zoom" ] ? ? 1 ,
maxzoom : layer [ "max_zoom" ] ? ? 25 ,
2023-09-02 23:23:46 +02:00
// Bit of a hack, but seems to work
scheme : layer.url.includes ( "{-y}" ) ? "tms" : "xyz" ,
2023-04-21 01:53:24 +02:00
}
}
2023-03-11 02:37:07 +01:00
/ * *
* Prepares an ELI - URL to be compatible with mapbox
* /
2023-04-21 01:53:24 +02:00
private static prepareWmsURL ( url : string , size : number = 256 ) : string {
2023-03-11 02:37:07 +01:00
// 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}" ,
2023-09-02 23:23:46 +02:00
"{-y}" : "{y}" ,
2023-03-11 02:37:07 +01:00
}
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 ) {
const options = subdomains [ 1 ] . split ( "," )
const option = options [ Math . floor ( Math . random ( ) * options . length ) ]
url = url . replace ( subdomains [ 0 ] , option )
}
return url
}
2023-11-14 16:14:27 +01:00
private static async toBlob ( canvas : HTMLCanvasElement ) : Promise < Blob > {
return await new Promise < Blob > ( ( resolve ) = > canvas . toBlob ( ( blob ) = > resolve ( blob ) ) )
}
private static async createImage ( url : string ) : Promise < HTMLImageElement > {
return new Promise ( ( resolve , reject ) = > {
const img = new Image ( )
img . decode = ( ) = > resolve ( img ) as any
img . onload = ( ) = > resolve ( img )
img . onerror = reject
img . crossOrigin = "anonymous"
img . decoding = "async"
img . src = url
} )
}
2023-12-15 01:46:01 +01:00
public onKeyNavigationEvent ( f : ( event : KeyNavigationEvent ) = > void | boolean ) {
this . _onKeyNavigation . push ( f )
return ( ) = > {
this . _onKeyNavigation . splice ( this . _onKeyNavigation . indexOf ( f ) , 1 )
}
}
2023-11-14 16:14:27 +01:00
public async exportAsPng (
rescaleIcons : number = 1 ,
progress : UIEventSource < { current : number ; total : number } > = undefined
) : Promise < Blob > {
2023-04-19 03:20:49 +02:00
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-04-19 03:20:49 +02:00
return undefined
}
2023-11-14 16:14:27 +01:00
const drawOn = document . createElement ( "canvas" , { } )
2023-06-04 00:43:32 +02:00
const ctx = drawOn . getContext ( "2d" )
2023-11-14 16:14:27 +01:00
// The width/height has been set in 'mm' on the parent element and converted to pixels by the browser
const w = map . getContainer ( ) . getBoundingClientRect ( ) . width
const h = map . getContainer ( ) . getBoundingClientRect ( ) . height
2023-04-19 03:20:49 +02:00
2023-11-14 16:14:27 +01:00
let dpi = map . getPixelRatio ( )
// The 'css'-size stays constant...
drawOn . style . width = w + "px"
drawOn . style . height = h + "px"
2023-04-19 03:20:49 +02:00
2023-11-14 16:14:27 +01:00
// ...but the number of pixels is increased
drawOn . width = Math . ceil ( w * dpi )
drawOn . height = Math . ceil ( h * dpi )
2023-11-13 15:14:41 +01:00
2023-11-14 16:14:27 +01:00
await this . exportBackgroundOnCanvas ( ctx )
await this . drawMarkers ( ctx , rescaleIcons , progress )
return await MapLibreAdaptor . toBlob ( drawOn )
2023-06-04 00:43:32 +02:00
}
2023-04-19 03:20:49 +02:00
2023-12-15 01:46:01 +01:00
private pingKeycodeEvent (
key : "north" | "east" | "south" | "west" | "in" | "out" | "islocked" | "locked" | "unlocked"
) {
const event = {
date : new Date ( ) ,
key : key ,
}
for ( let i = 0 ; i < this . _onKeyNavigation . length ; i ++ ) {
const f = this . _onKeyNavigation [ i ]
const unregister = f ( event )
if ( unregister === true ) {
this . _onKeyNavigation . splice ( i , 1 )
i --
}
}
}
2023-06-04 00:43:32 +02:00
/ * *
* Exports the background map and lines to PNG .
* Markers are _not_ rendered
* /
2023-06-04 22:52:13 +02:00
private async exportBackgroundOnCanvas ( ctx : CanvasRenderingContext2D ) : Promise < void > {
2023-06-04 00:43:32 +02:00
const map = this . _maplibreMap . data
// We draw the maplibre-map onto the canvas. This does not export markers
// Inspiration by https://github.com/mapbox/mapbox-gl-js/issues/2766
2023-04-19 03:20:49 +02:00
2023-06-04 00:43:32 +02:00
// Total hack - see https://stackoverflow.com/questions/42483449/mapbox-gl-js-export-map-to-png-or-pdf
const promise = new Promise < void > ( ( resolve ) = > {
map . once ( "render" , ( ) = > {
ctx . drawImage ( map . getCanvas ( ) , 0 , 0 )
resolve ( )
2023-04-19 03:20:49 +02:00
} )
2023-06-04 00:43:32 +02:00
} )
2023-04-19 03:20:49 +02:00
2023-06-04 00:43:32 +02:00
while ( ! map . isStyleLoaded ( ) ) {
console . log ( "Waiting to fully load the style..." )
await Utils . waitFor ( 100 )
2023-04-19 03:20:49 +02:00
}
2023-06-04 00:43:32 +02:00
map . triggerRepaint ( )
await promise
}
2023-04-19 03:20:49 +02:00
2023-12-06 04:01:42 +01:00
private async drawElement (
drawOn : CanvasRenderingContext2D ,
element : HTMLElement ,
rescaleIcons : number ,
pixelRatio : number
) {
2023-12-06 17:27:30 +01:00
const style = element . style . transform
let x = element . getBoundingClientRect ( ) . x
let y = element . getBoundingClientRect ( ) . y
element . style . transform = ""
2023-12-06 04:01:42 +01:00
const offset = style . match ( /translate\(([-0-9]+)%, ?([-0-9]+)%\)/ )
2023-12-06 17:27:30 +01:00
const w = element . style . width
const h = element . style . height
2023-12-06 04:01:42 +01:00
// Force a wider view for icon badges
2023-12-06 17:27:30 +01:00
element . style . width = element . getBoundingClientRect ( ) . width * 4 + "px"
element . style . height = element . getBoundingClientRect ( ) . height + "px"
const svgSource = await htmltoimage . toSvg ( element )
2023-12-06 04:01:42 +01:00
const img = await MapLibreAdaptor . createImage ( svgSource )
2023-12-06 17:27:30 +01:00
element . style . width = w
element . style . height = h
2023-12-06 04:01:42 +01:00
if ( offset && rescaleIcons !== 1 ) {
const [ _ , __ , relYStr ] = offset
const relY = Number ( relYStr )
y += img . height * ( relY / 100 )
}
x *= pixelRatio
y *= pixelRatio
try {
drawOn . drawImage ( img , x , y , img . width * rescaleIcons , img . height * rescaleIcons )
} catch ( e ) {
console . log ( "Could not draw image because of" , e )
}
}
2023-11-14 16:14:27 +01:00
/ * *
* Draws the markers of the current map on the specified canvas .
* The DPIfactor is used to calculate the correct position , whereas 'rescaleIcons' can be used to make the icons smaller
* /
private async drawMarkers (
drawOn : CanvasRenderingContext2D ,
rescaleIcons : number = 1 ,
progress : UIEventSource < { current : number ; total : number } >
) : Promise < void > {
2023-06-04 00:43:32 +02:00
const map = this . _maplibreMap . data
if ( ! map ) {
2023-11-14 16:14:27 +01:00
console . error ( "There is no map to export from" )
2023-06-04 00:43:32 +02:00
return undefined
2023-04-19 03:20:49 +02:00
}
2023-11-13 15:14:41 +01:00
const container = map . getContainer ( )
2023-12-06 04:01:42 +01:00
const pixelRatio = map . getPixelRatio ( )
2023-11-14 16:14:27 +01:00
2023-11-13 15:14:41 +01:00
function isDisplayed ( el : Element ) {
const r1 = el . getBoundingClientRect ( )
const r2 = container . getBoundingClientRect ( )
return ! (
r2 . left > r1 . right ||
r2 . right < r1 . left ||
r2 . top > r1 . bottom ||
r2 . bottom < r1 . top
)
}
2023-11-14 16:14:27 +01:00
2023-11-13 15:14:41 +01:00
const markers = Array . from ( container . getElementsByClassName ( "marker" ) )
for ( let i = 0 ; i < markers . length ; i ++ ) {
2023-11-14 16:14:27 +01:00
const marker = < HTMLElement > markers [ i ]
2023-12-06 04:01:42 +01:00
const labels = Array . from ( marker . getElementsByClassName ( "marker-label" ) )
2023-11-14 16:14:27 +01:00
const style = marker . style . transform
2023-12-06 04:01:42 +01:00
if ( isDisplayed ( marker ) ) {
await this . drawElement ( drawOn , marker , rescaleIcons , pixelRatio )
2023-11-14 16:14:27 +01:00
}
2023-12-06 04:01:42 +01:00
for ( const label of labels ) {
if ( isDisplayed ( label ) ) {
await this . drawElement ( drawOn , < HTMLElement > label , rescaleIcons , pixelRatio )
}
}
2023-11-14 16:14:27 +01:00
if ( progress ) {
progress . setData ( { current : i , total : markers.length } )
}
2023-12-06 04:01:42 +01:00
2023-11-14 16:14:27 +01:00
marker . style . transform = style
2023-11-13 15:14:41 +01:00
}
2023-04-19 03:20:49 +02:00
}
2023-05-18 23:42:03 +02:00
private updateStores ( isSetup : boolean = false ) : void {
2023-04-19 03:20:49 +02:00
const map = this . _maplibreMap . data
2023-04-20 17:42:07 +02:00
if ( ! map ) {
2023-04-19 03:20:49 +02:00
return
}
2023-06-14 20:39:36 +02:00
const { lng , lat } = map . getCenter ( )
2023-06-07 22:46:41 +02:00
if ( lng === 0 && lat === 0 ) {
return
}
if ( this . location . data === undefined ) {
2023-06-14 20:39:36 +02:00
this . location . setData ( { lon : lng , lat } )
2023-06-07 22:46:41 +02:00
} else if ( ! isSetup ) {
2023-06-14 23:21:19 +02:00
const lon = map . getCenter ( ) . lng
const lat = map . getCenter ( ) . lat
this . location . setData ( { lon , lat } )
2023-05-18 23:42:03 +02:00
}
2023-04-19 03:20:49 +02:00
this . zoom . setData ( Math . round ( map . getZoom ( ) * 10 ) / 10 )
const bounds = map . getBounds ( )
const bbox = new BBox ( [
[ bounds . getEast ( ) , bounds . getNorth ( ) ] ,
[ bounds . getWest ( ) , bounds . getSouth ( ) ] ,
] )
2023-05-18 23:42:03 +02:00
if ( this . bounds . data === undefined || ! isSetup ) {
this . bounds . setData ( bbox )
}
2023-12-19 22:21:34 +01:00
this . rotation . setData ( map . getBearing ( ) )
2023-04-19 03:20:49 +02:00
}
2023-05-18 15:44:54 +02:00
private SetZoom ( z : number ) : void {
2023-03-24 19:21:15 +01:00
const map = this . _maplibreMap . data
if ( ! map || z === undefined ) {
return
}
if ( Math . abs ( map . getZoom ( ) - z ) > 0.01 ) {
map . setZoom ( z )
}
}
2023-12-19 22:21:34 +01:00
private SetRotation ( bearing : number ) : void {
const map = this . _maplibreMap . data
if ( ! map || bearing === undefined ) {
return
}
map . rotateTo ( bearing , { duration : 0 } )
}
2023-05-18 15:44:54 +02:00
private MoveMapToCurrentLoc ( loc : { lat : number ; lon : number } ) : void {
2023-03-24 19:21:15 +01:00
const map = this . _maplibreMap . data
if ( ! map || loc === undefined ) {
return
}
const center = map . getCenter ( )
if ( center . lng !== loc . lon || center . lat !== loc . lat ) {
2023-07-08 01:21:11 +02:00
if ( isNaN ( loc . lon ) || isNaN ( loc . lat ) ) {
console . error ( "Got invalid lat or lon, not setting" )
} else {
map . setCenter ( { lng : loc.lon , lat : loc.lat } )
}
2023-03-24 19:21:15 +01:00
}
}
2023-03-11 02:37:07 +01:00
private async awaitStyleIsLoaded ( ) : Promise < void > {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-03-11 02:37:07 +01:00
return
}
2023-04-06 01:33:08 +02:00
while ( ! map ? . isStyleLoaded ( ) ) {
2023-03-11 02:37:07 +01:00
await Utils . waitFor ( 250 )
}
}
2024-01-01 03:29:57 +01:00
public installCustomKeyboardHandler ( viewportStore : UIEventSource < HTMLDivElement > ) {
viewportStore . mapD (
2023-12-22 18:50:22 +01:00
( viewport ) = > {
const map = this . _maplibreMap . data
if ( ! map ) {
return
}
const oldKeyboard = map . keyboard
2024-01-01 03:29:57 +01:00
const w = viewport . getBoundingClientRect ( ) . width
if ( w < 10 ) {
/// this is weird, but definitively wrong!
console . log ( "Got a very small bound" , w , viewport )
// We try again later on
window . requestAnimationFrame ( ( ) = > {
viewportStore . ping ( )
} )
return
}
oldKeyboard . _panStep = w
2023-12-22 18:50:22 +01:00
} ,
[ this . _maplibreMap ]
)
}
2023-05-18 15:44:54 +02:00
private removeCurrentLayer ( map : MLMap ) : void {
2023-03-11 02:37:07 +01:00
if ( this . _currentRasterLayer ) {
// hide the previous layer
2023-06-29 00:24:19 +02:00
try {
if ( map . getLayer ( this . _currentRasterLayer ) ) {
map . removeLayer ( this . _currentRasterLayer )
}
if ( map . getSource ( this . _currentRasterLayer ) ) {
map . removeSource ( this . _currentRasterLayer )
}
this . _currentRasterLayer = undefined
} catch ( e ) {
console . warn ( "Could not remove the previous layer" )
}
2023-03-11 02:37:07 +01:00
}
}
2023-05-18 15:44:54 +02:00
private async setBackground ( ) : Promise < void > {
2023-03-11 02:37:07 +01:00
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-03-11 02:37:07 +01:00
return
}
2023-03-23 00:58:21 +01:00
const background : RasterLayerProperties = this . rasterLayer ? . data ? . properties
2023-06-29 00:24:19 +02:00
if ( ! background ) {
2023-03-11 02:37:07 +01:00
return
}
2023-06-29 00:24:19 +02:00
if ( this . _currentRasterLayer === background . id ) {
2023-03-11 02:37:07 +01:00
// already the correct background layer, nothing to do
return
}
2023-06-29 00:24:19 +02:00
2023-03-28 05:13:48 +02:00
if ( ! background ? . url ) {
2023-03-11 02:37:07 +01:00
// no background to set
this . removeCurrentLayer ( map )
return
}
2023-06-14 20:39:36 +02:00
if ( background . type === "vector" ) {
2023-06-29 00:24:19 +02:00
this . removeCurrentLayer ( map )
2023-06-14 00:47:09 +02:00
map . setStyle ( background . url )
return
}
2023-06-14 20:39:36 +02:00
let addLayerBeforeId = "aeroway_fill" // this is the first non-landuse item in the stylesheet, we add the raster layer before the roads but above the landuse
2023-06-07 22:46:41 +02:00
if ( background . category === "osmbasedmap" || background . category === "map" ) {
2023-06-07 14:34:58 +02:00
// The background layer is already an OSM-based map or another map, so we don't want anything from the baselayer
2023-06-29 00:24:19 +02:00
addLayerBeforeId = undefined
this . removeCurrentLayer ( map )
} else {
// Make sure that the default maptiler style is loaded as it gives an overlay with roads
2023-09-24 18:24:10 +02:00
const maptiler = AvailableRasterLayers . maptilerDefaultLayer . properties
2023-06-29 00:24:19 +02:00
if ( ! map . getSource ( maptiler . id ) ) {
this . removeCurrentLayer ( map )
map . addSource ( maptiler . id , MapLibreAdaptor . prepareWmsSource ( maptiler ) )
map . setStyle ( maptiler . url )
await this . awaitStyleIsLoaded ( )
}
2023-06-07 14:34:58 +02:00
}
2023-06-29 00:24:19 +02:00
if ( ! map . getLayer ( addLayerBeforeId ) ) {
addLayerBeforeId = undefined
}
2023-10-06 02:23:24 +02:00
await this . awaitStyleIsLoaded ( )
2023-06-29 00:24:19 +02:00
if ( ! map . getSource ( background . id ) ) {
map . addSource ( background . id , MapLibreAdaptor . prepareWmsSource ( background ) )
}
if ( ! map . getLayer ( background . id ) ) {
2023-10-10 01:52:02 +02:00
addLayerBeforeId ? ? = map
. getStyle ( )
. layers . find ( ( l ) = > l . id . startsWith ( "mapcomplete_" ) ) ? . id
2023-10-06 03:34:26 +02:00
console . log (
"Adding background layer" ,
background . id ,
"beforeId" ,
addLayerBeforeId ,
"; all layers are" ,
map . getStyle ( ) . layers . map ( ( l ) = > l . id )
)
2023-06-29 00:24:19 +02:00
map . addLayer (
{
id : background.id ,
type : "raster" ,
source : background.id ,
paint : { } ,
} ,
addLayerBeforeId
)
}
2023-03-11 02:37:07 +01:00
await this . awaitStyleIsLoaded ( )
2023-09-28 23:50:27 +02:00
if ( this . _currentRasterLayer !== background ? . id ) {
2023-09-24 16:34:36 +02:00
this . removeCurrentLayer ( map )
}
2023-03-11 02:37:07 +01:00
this . _currentRasterLayer = background ? . id
}
2023-03-24 19:21:15 +01:00
private setMaxBounds ( bbox : undefined | BBox ) {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-03-24 19:21:15 +01:00
return
}
if ( bbox ) {
2023-04-06 01:33:08 +02:00
map ? . setMaxBounds ( bbox . toLngLat ( ) )
2023-03-24 19:21:15 +01:00
} else {
2023-04-06 01:33:08 +02:00
map ? . setMaxBounds ( null )
2023-03-24 19:21:15 +01:00
}
}
2023-07-18 01:26:04 +02:00
private setAllowRotating ( allow : true | boolean | undefined ) {
const map = this . _maplibreMap . data
if ( ! map ) {
return
}
if ( allow === false ) {
map . rotateTo ( 0 , { duration : 0 } )
map . setPitch ( 0 )
map . dragRotate . disable ( )
2023-12-15 01:46:01 +01:00
map . keyboard . disableRotation ( )
2023-09-28 23:50:27 +02:00
map . touchZoomRotate . disableRotation ( )
2023-07-18 01:26:04 +02:00
} else {
map . dragRotate . enable ( )
2023-12-15 01:46:01 +01:00
map . keyboard . enableRotation ( )
2023-09-28 23:50:27 +02:00
map . touchZoomRotate . enableRotation ( )
2023-07-18 01:26:04 +02:00
}
}
2023-03-24 19:21:15 +01:00
private setAllowMoving ( allow : true | boolean | undefined ) {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-03-24 19:21:15 +01:00
return
}
if ( allow === false ) {
for ( const id of MapLibreAdaptor . maplibre_control_handlers ) {
map [ id ] . disable ( )
}
} else {
for ( const id of MapLibreAdaptor . maplibre_control_handlers ) {
map [ id ] . enable ( )
}
}
2023-07-18 01:26:04 +02:00
this . setAllowRotating ( this . allowRotating . data )
2023-03-24 19:21:15 +01:00
}
2023-03-28 05:13:48 +02:00
2023-04-06 01:33:08 +02:00
private setMinzoom ( minzoom : number ) {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-04-06 01:33:08 +02:00
return
}
map . setMinZoom ( minzoom )
}
2023-04-21 01:53:24 +02:00
private setMaxzoom ( maxzoom : number ) {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-04-21 01:53:24 +02:00
return
}
map . setMaxZoom ( maxzoom )
}
2023-03-28 05:13:48 +02:00
private setAllowZooming ( allow : true | boolean | undefined ) {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map ) {
2023-03-28 05:13:48 +02:00
return
}
if ( allow === false ) {
for ( const id of MapLibreAdaptor . maplibre_zoom_handlers ) {
map [ id ] . disable ( )
}
} else {
for ( const id of MapLibreAdaptor . maplibre_zoom_handlers ) {
map [ id ] . enable ( )
}
}
}
private setBounds ( bounds : BBox ) {
const map = this . _maplibreMap . data
2023-05-05 02:03:41 +02:00
if ( ! map || bounds === undefined ) {
2023-03-28 05:13:48 +02:00
return
}
const oldBounds = map . getBounds ( )
const e = 0.0000001
const hasDiff =
Math . abs ( oldBounds . getWest ( ) - bounds . getWest ( ) ) > e &&
Math . abs ( oldBounds . getEast ( ) - bounds . getEast ( ) ) > e &&
Math . abs ( oldBounds . getNorth ( ) - bounds . getNorth ( ) ) > e &&
Math . abs ( oldBounds . getSouth ( ) - bounds . getSouth ( ) ) > e
if ( ! hasDiff ) {
return
}
map . fitBounds ( bounds . toLngLat ( ) )
}
2023-03-11 02:37:07 +01:00
}