2024-01-28 00:04:24 +01:00
import { Map as MLMap , SourceSpecification } from "maplibre-gl"
import { Store , Stores , UIEventSource } from "../../Logic/UIEventSource"
import { RasterLayerPolygon } from "../../Models/RasterLayers"
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
import { Utils } from "../../Utils"
class SingleBackgroundHandler {
// Value between 0 and 1.0
public opacity = new UIEventSource < number > ( 0.0 )
private _map : Store < MLMap >
private _background : UIEventSource < RasterLayerPolygon | undefined >
private readonly _targetLayer : RasterLayerPolygon
private _deactivationTime : Date = undefined
/ * *
* Deactivate a layer after 60 seconds
* /
public static readonly DEACTIVATE_AFTER = 60
private fadeStep = 0.1
constructor (
map : Store < MLMap > ,
targetLayer : RasterLayerPolygon ,
background : UIEventSource < RasterLayerPolygon | undefined >
) {
this . _targetLayer = targetLayer
this . _map = map
this . _background = background
background . addCallback ( async ( ) = > {
await this . update ( )
} )
map . addCallbackAndRunD ( async ( map ) = > {
map . on ( "load" , async ( ) = > {
await this . update ( )
} )
await this . update ( )
map . on ( "moveend" , ( ) = > this . onMove ( map ) )
map . on ( "zoomend" , ( ) = > this . onMove ( map ) )
} )
}
private onMove ( map : MLMap ) {
if ( ! this . _deactivationTime ) {
return
}
// in seconds
const timeSinceDeactivation =
( new Date ( ) . getTime ( ) - this . _deactivationTime . getTime ( ) ) / 1000
if ( timeSinceDeactivation < SingleBackgroundHandler . DEACTIVATE_AFTER ) {
return
}
console . debug (
"Removing raster layer" ,
this . _targetLayer . properties . id ,
"map moved and not been used for" ,
SingleBackgroundHandler . DEACTIVATE_AFTER
)
if ( map . getLayer ( < string > this . _targetLayer . properties . id ) ) {
map . removeLayer ( < string > this . _targetLayer . properties . id )
}
}
private async update() {
const newTarget : RasterLayerPolygon | undefined = this . _background . data
const targetLayer = this . _targetLayer
if ( newTarget ? . properties ? . id !== targetLayer . properties . id ) {
this . _deactivationTime = new Date ( )
await this . awaitStyleIsLoaded ( )
this . fadeOut ( )
} else {
this . _deactivationTime = undefined
this . enable ( )
this . fadeIn ( )
}
}
private async awaitStyleIsLoaded ( ) : Promise < void > {
const map = this . _map . data
if ( ! map ) {
return
}
while ( ! map ? . isStyleLoaded ( ) ) {
await Utils . waitFor ( 250 )
}
}
private enable() {
const map : MLMap = this . _map . data
if ( ! map ) {
return
}
const background = this . _targetLayer . properties
console . debug ( "Enabling" , background . id )
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
if ( background . category === "osmbasedmap" || background . category === "map" ) {
// The background layer is already an OSM-based map or another map, so we don't want anything from the baselayer
addLayerBeforeId = undefined
}
if ( ! map . getSource ( background . id ) ) {
2024-01-29 16:43:23 +01:00
try {
map . addSource ( background . id , RasterLayerHandler . prepareWmsSource ( background ) )
} catch ( e ) {
console . error ( "Could not add source" , e )
return
}
2024-01-28 00:04:24 +01:00
}
if ( ! map . getLayer ( background . id ) ) {
addLayerBeforeId ? ? = map
. getStyle ( )
. layers . find ( ( l ) = > l . id . startsWith ( "mapcomplete_" ) ) ? . id
map . addLayer (
{
id : background.id ,
type : "raster" ,
source : background.id ,
paint : {
"raster-opacity" : 0 ,
} ,
} ,
addLayerBeforeId
)
this . opacity . addCallbackAndRun ( ( o ) = > {
map . setPaintProperty ( background . id , "raster-opacity" , o )
} )
}
}
private fadeOut() {
Stores . Chronic (
8 ,
( ) = > this . opacity . data > 0 && this . _deactivationTime !== undefined
) . addCallback ( ( _ ) = > this . opacity . setData ( Math . max ( 0 , this . opacity . data - this . fadeStep ) ) )
}
private fadeIn() {
Stores . Chronic (
8 ,
( ) = > this . opacity . data < 1.0 && this . _deactivationTime === undefined
) . addCallback ( ( _ ) = > this . opacity . setData ( Math . min ( 1.0 , this . opacity . data + this . fadeStep ) ) )
}
}
export default class RasterLayerHandler {
private _singleLayerHandlers : Record < string , SingleBackgroundHandler > = { }
constructor ( map : Store < MLMap > , background : UIEventSource < RasterLayerPolygon | undefined > ) {
background . addCallbackAndRunD ( ( l ) = > {
const key = l . properties . id
if ( ! this . _singleLayerHandlers [ key ] ) {
this . _singleLayerHandlers [ key ] = new SingleBackgroundHandler ( map , l , background )
}
} )
}
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 : [ RasterLayerHandler . prepareWmsURL ( layer . url , layer [ "tile-size" ] ? ? 256 ) ] ,
tileSize : layer [ "tile-size" ] ? ? 256 ,
minzoom : layer [ "min_zoom" ] ? ? 1 ,
maxzoom : layer [ "max_zoom" ] ? ? 25 ,
// Bit of a hack, but seems to work
scheme : layer.url.includes ( "{-y}" ) ? "tms" : "xyz" ,
}
}
private static prepareWmsURL ( url : string , size : number = 256 ) : string {
// 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}" ,
"{-y}" : "{y}" ,
}
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
}
}