2021-10-22 02:34:06 +02:00
2021-09-21 02:10:42 +02:00
import { UIEventSource } from "../../Logic/UIEventSource" ;
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" ;
import FeatureInfoBox from "../Popup/FeatureInfoBox" ;
import { ShowDataLayerOptions } from "./ShowDataLayerOptions" ;
2021-10-15 05:20:02 +02:00
import { ElementStorage } from "../../Logic/ElementStorage" ;
2021-10-22 18:53:07 +02:00
import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource" ;
2021-10-22 02:34:06 +02:00
/ *
// import 'leaflet-polylineoffset';
We don 't actually import it here. It is imported in the ' MinimapImplementation '-class, which' ll result in a patched 'L' object .
Even though actually importing this here would seem cleaner , we don ' t do this as this breaks some scripts :
- Scripts are ran in ts - node
- ts - node doesn 't define the ' window ' - object
- Importing this will execute some code which needs the window object
2021-01-04 04:06:21 +01:00
2021-10-22 02:34:06 +02:00
* /
/ * *
* The data layer shows all the given geojson elements with the appropriate icon etc
* /
2021-01-04 04:06:21 +01:00
export default class ShowDataLayer {
private readonly _leafletMap : UIEventSource < L.Map > ;
2021-06-23 02:15:28 +02:00
private readonly _enablePopups : boolean ;
2021-10-22 01:07:32 +02:00
private readonly _features : RenderingMultiPlexerFeatureSource
2021-09-21 01:47:58 +02:00
private readonly _layerToShow : LayerConfig ;
2021-10-15 05:20:02 +02:00
private readonly _selectedElement : UIEventSource < any >
2021-10-22 02:16:07 +02:00
private readonly allElements : ElementStorage
2021-09-21 01:47:58 +02:00
// Used to generate a fresh ID when needed
private _cleanCount = 0 ;
2021-09-21 03:10:15 +02:00
private geoLayer = undefined ;
2021-09-27 14:45:48 +02:00
private isDirty = false ;
2021-09-21 03:10:15 +02:00
/ * *
* If the selected element triggers , this is used to lookup the correct layer and to open the popup
* Used to avoid a lot of callbacks on the selected element
2021-10-01 05:24:10 +02:00
*
2021-10-01 04:49:40 +02:00
* Note : the key of this dictionary is 'feature.properties.id+features.geometry.type' as one feature might have multiple presentations
2021-09-21 03:10:15 +02:00
* @private
* /
private readonly leafletLayersPerId = new Map < string , { feature : any , leafletlayer : any } > ( )
2021-10-22 02:16:07 +02:00
private readonly showDataLayerid : number ;
2021-10-13 17:18:14 +02:00
private static dataLayerIds = 0
2021-09-21 03:10:15 +02:00
constructor ( options : ShowDataLayerOptions & { layerToShow : LayerConfig } ) {
2021-09-21 01:47:58 +02:00
this . _leafletMap = options . leafletMap ;
2021-10-13 17:18:14 +02:00
this . showDataLayerid = ShowDataLayer . dataLayerIds ;
ShowDataLayer . dataLayerIds ++
2021-09-21 01:47:58 +02:00
this . _enablePopups = options . enablePopups ? ? true ;
2021-09-21 03:10:15 +02:00
if ( options . features === undefined ) {
2021-10-15 18:48:33 +02:00
console . error ( "Invalid ShowDataLayer invocation: options.features is undefed" )
throw "Invalid ShowDataLayer invocation: options.features is undefed"
2021-09-21 01:47:58 +02:00
}
2021-10-22 01:07:32 +02:00
this . _features = new RenderingMultiPlexerFeatureSource ( options . features , options . layerToShow ) ;
2021-09-21 01:47:58 +02:00
this . _layerToShow = options . layerToShow ;
2021-10-15 05:20:02 +02:00
this . _selectedElement = options . selectedElement
this . allElements = options . allElements ;
2021-01-04 04:06:21 +01:00
const self = this ;
2021-02-06 00:05:38 +01:00
2021-10-15 13:43:11 +02:00
options . leafletMap . addCallback ( _ = > {
2021-09-27 14:45:48 +02:00
self . update ( options )
}
) ;
2021-10-22 01:07:32 +02:00
this . _features . features . addCallback ( _ = > self . update ( options ) ) ;
2021-10-15 13:43:11 +02:00
options . doShowLayer ? . addCallback ( doShow = > {
2021-09-27 14:45:48 +02:00
const mp = options . leafletMap . data ;
if ( mp == undefined ) {
return ;
}
if ( doShow ) {
if ( self . isDirty ) {
self . update ( options )
} else {
mp . addLayer ( this . geoLayer )
}
} else {
2021-10-01 05:24:10 +02:00
if ( this . geoLayer !== undefined ) {
2021-09-27 14:45:48 +02:00
mp . removeLayer ( this . geoLayer )
}
}
} )
2021-02-06 00:05:38 +01:00
2021-10-01 05:24:10 +02:00
2021-10-15 05:20:02 +02:00
this . _selectedElement ? . addCallbackAndRunD ( selected = > {
2021-09-21 03:10:15 +02:00
if ( self . _leafletMap . data === undefined ) {
2021-01-04 04:06:21 +01:00
return ;
}
2021-10-01 05:24:10 +02:00
const v = self . leafletLayersPerId . get ( selected . properties . id + selected . geometry . type )
2021-09-26 17:36:39 +02:00
if ( v === undefined ) {
return ;
}
2021-09-21 03:10:15 +02:00
const leafletLayer = v . leafletlayer
const feature = v . feature
if ( leafletLayer . getPopup ( ) . isOpen ( ) ) {
return ;
2021-02-14 19:45:02 +01:00
}
2021-09-29 01:12:29 +02:00
if ( selected . properties . id !== feature . properties . id ) {
return ;
}
2021-10-01 05:24:10 +02:00
2021-09-29 01:12:29 +02:00
if ( feature . id !== feature . properties . id ) {
// Probably a feature which has renamed
2021-10-01 05:24:10 +02:00
// the feature might have as id 'node/-1' and as 'feature.properties.id' = 'the newly assigned id'. That is no good too
2021-10-01 04:49:40 +02:00
console . log ( "Not opening the popup for" , feature , "as probably renamed" )
2021-09-29 01:12:29 +02:00
return ;
}
if ( selected . geometry . type === feature . geometry . type // If a feature is rendered both as way and as point, opening one popup might trigger the other to open, which might trigger the one to open again
) {
console . log ( "Opening popup of feature" , feature )
leafletLayer . openPopup ( )
2021-06-24 01:17:29 +02:00
}
2021-09-21 03:10:15 +02:00
} )
2021-10-22 02:16:07 +02:00
2021-10-15 13:43:11 +02:00
this . update ( options )
2021-09-26 17:36:39 +02:00
2021-09-21 03:10:15 +02:00
}
2021-09-27 14:45:48 +02:00
private update ( options : ShowDataLayerOptions ) {
2021-10-22 01:07:32 +02:00
if ( this . _features . features . data === undefined ) {
2021-09-21 03:10:15 +02:00
return ;
}
2021-09-27 14:45:48 +02:00
this . isDirty = true ;
if ( options ? . doShowLayer ? . data === false ) {
return ;
}
2021-09-21 03:10:15 +02:00
const mp = options . leafletMap . data ;
if ( mp === undefined ) {
return ;
}
this . _cleanCount ++
// clean all the old stuff away, if any
if ( this . geoLayer !== undefined ) {
mp . removeLayer ( this . geoLayer ) ;
}
2021-09-27 14:45:48 +02:00
const self = this ;
const data = {
type : "FeatureCollection" ,
features : [ ]
}
// @ts-ignore
this . geoLayer = L . geoJSON ( data , {
style : feature = > self . createStyleFor ( feature ) ,
pointToLayer : ( feature , latLng ) = > self . pointToLayer ( feature , latLng ) ,
onEachFeature : ( feature , leafletLayer ) = > self . postProcessFeature ( feature , leafletLayer )
} ) ;
2021-10-22 01:07:32 +02:00
const allFeats = this . _features . features . data ;
2021-09-21 03:10:15 +02:00
for ( const feat of allFeats ) {
if ( feat === undefined ) {
continue
2021-08-07 21:19:01 +02:00
}
2021-09-26 17:36:39 +02:00
try {
2021-10-22 02:16:07 +02:00
if ( ( feat . geometry . type === "LineString" || feat . geometry . type === "MultiLineString" ) ) {
const self = this ;
2021-10-22 01:07:32 +02:00
const coords = L . GeoJSON . coordsToLatLngs ( feat . geometry . coordinates )
const tagsSource = this . allElements ? . addOrGetElement ( feat ) ? ? new UIEventSource < any > ( feat . properties ) ;
2021-10-22 02:16:07 +02:00
let offsettedLine ;
2021-10-22 18:53:07 +02:00
tagsSource
. map ( tags = > this . _layerToShow . lineRendering [ feat . lineRenderingIndex ] . GenerateLeafletStyle ( tags ) )
. withEqualityStabilized ( ( a , b ) = > {
if ( a === b ) {
return true
}
if ( a === undefined || b === undefined ) {
return false
}
return a . offset === b . offset && a . color === b . color && a . weight === b . weight && a . dashArray === b . dashArray
} )
. addCallbackAndRunD ( lineStyle = > {
2021-10-22 02:16:07 +02:00
if ( offsettedLine !== undefined ) {
self . geoLayer . removeLayer ( offsettedLine )
}
offsettedLine = L . polyline ( coords , lineStyle ) ;
this . postProcessFeature ( feat , offsettedLine )
offsettedLine . addTo ( this . geoLayer )
} )
} else {
2021-10-22 01:07:32 +02:00
this . geoLayer . addData ( feat ) ;
}
2021-09-26 17:36:39 +02:00
} catch ( e ) {
2021-10-15 15:20:08 +02:00
console . error ( "Could not add " , feat , "to the geojson layer in leaflet due to" , e , e . stack )
2021-09-22 05:02:09 +02:00
}
2021-01-04 04:06:21 +01:00
}
2021-09-21 03:10:15 +02:00
if ( options . zoomToFeatures ? ? false ) {
try {
mp . fitBounds ( this . geoLayer . getBounds ( ) , { animate : false } )
} catch ( e ) {
console . error ( e )
}
}
2021-09-26 17:36:39 +02:00
if ( options . doShowLayer ? . data ? ? true ) {
mp . addLayer ( this . geoLayer )
}
2021-09-27 14:45:48 +02:00
this . isDirty = false ;
2021-01-04 04:06:21 +01:00
}
private createStyleFor ( feature ) {
2021-10-15 18:48:33 +02:00
const tagsSource = this . allElements ? . addOrGetElement ( feature ) ? ? new UIEventSource < any > ( feature . properties ) ;
2021-01-04 04:06:21 +01:00
// Every object is tied to exactly one layer
2021-09-21 03:10:15 +02:00
const layer = this . _layerToShow
2021-10-22 02:16:07 +02:00
2021-10-22 01:07:32 +02:00
const pointRenderingIndex = feature . pointRenderingIndex
const lineRenderingIndex = feature . lineRenderingIndex
2021-10-22 02:16:07 +02:00
if ( pointRenderingIndex !== undefined ) {
2021-10-28 03:15:36 +02:00
const style = layer . mapRendering [ pointRenderingIndex ] . GenerateLeafletStyle ( tagsSource , this . _enablePopups )
2021-10-22 01:07:32 +02:00
return {
2021-10-28 03:15:36 +02:00
icon : style
2021-10-22 01:07:32 +02:00
}
}
2021-10-22 02:16:07 +02:00
if ( lineRenderingIndex !== undefined ) {
2021-10-28 03:15:36 +02:00
return layer . lineRendering [ lineRenderingIndex ] . GenerateLeafletStyle ( tagsSource . data )
2021-10-22 01:07:32 +02:00
}
2021-10-22 02:16:07 +02:00
throw "Neither lineRendering nor mapRendering defined for " + feature
2021-01-04 04:06:21 +01:00
}
2021-02-15 19:06:06 +01:00
2021-01-04 04:06:21 +01:00
private pointToLayer ( feature , latLng ) : L . Layer {
// Leaflet cannot handle geojson points natively
// We have to convert them to the appropriate icon
// Click handling is done in the next step
2021-09-21 03:10:15 +02:00
const layer : LayerConfig = this . _layerToShow
2021-02-14 19:45:02 +01:00
if ( layer === undefined ) {
return ;
}
2021-10-15 05:20:02 +02:00
let tagSource = this . allElements ? . getEventSourceById ( feature . properties . id ) ? ? new UIEventSource < any > ( feature . properties )
2021-10-28 03:15:36 +02:00
const clickable = ! ( layer . title === undefined && ( layer . tagRenderings ? ? [ ] ) . length === 0 ) && this . _enablePopups
2021-10-22 02:16:07 +02:00
let style : any = layer . mapRendering [ feature . pointRenderingIndex ] . GenerateLeafletStyle ( tagSource , clickable ) ;
2021-10-22 01:07:32 +02:00
const baseElement = style . html ;
2021-06-24 02:34:13 +02:00
if ( ! this . _enablePopups ) {
2021-06-23 02:41:30 +02:00
baseElement . SetStyle ( "cursor: initial !important" )
}
2021-10-22 01:07:32 +02:00
style . html = style . html . ConstructElement ( )
2021-01-04 04:06:21 +01:00
return L . marker ( latLng , {
2021-10-22 01:07:32 +02:00
icon : L.divIcon ( style )
2021-01-04 04:06:21 +01:00
} ) ;
}
2021-07-18 21:37:14 +02:00
2021-09-21 01:47:58 +02:00
/ * *
* POst processing - basically adding the popup
* @param feature
* @param leafletLayer
* @private
* /
2021-01-04 18:55:10 +01:00
private postProcessFeature ( feature , leafletLayer : L.Layer ) {
2021-09-21 01:47:58 +02:00
const layer : LayerConfig = this . _layerToShow
2021-06-23 02:41:30 +02:00
if ( layer . title === undefined || ! this . _enablePopups ) {
2021-01-04 04:06:21 +01:00
// No popup action defined -> Don't do anything
2021-06-23 02:41:30 +02:00
// or probably a map in the popup - no popups needed!
2021-06-23 02:15:28 +02:00
return ;
}
2021-06-24 02:34:13 +02:00
2021-01-04 04:06:21 +01:00
const popup = L . popup ( {
autoPan : true ,
closeOnEscapeKey : true ,
2021-07-18 21:48:11 +02:00
closeButton : false ,
2021-08-07 21:19:01 +02:00
autoPanPaddingTopLeft : [ 15 , 15 ] ,
2021-01-04 04:06:21 +01:00
} , leafletLayer ) ;
2021-06-15 16:18:58 +02:00
leafletLayer . bindPopup ( popup ) ;
let infobox : FeatureInfoBox = undefined ;
2021-10-22 18:53:07 +02:00
const id = ` popup- ${ feature . properties . id } - ${ feature . geometry . type } - ${ this . showDataLayerid } - ${ this . _cleanCount } - ${ feature . pointRenderingIndex ? ? feature . lineRenderingIndex } `
2021-10-13 17:18:14 +02:00
popup . setContent ( ` <div style='height: 65vh' id=' ${ id } '>Popup for ${ feature . properties . id } ${ feature . geometry . type } ${ id } is loading</div> ` )
2021-02-05 18:58:06 +01:00
leafletLayer . on ( "popupopen" , ( ) = > {
2021-06-15 16:18:58 +02:00
if ( infobox === undefined ) {
2021-10-15 05:20:02 +02:00
const tags = this . allElements ? . getEventSourceById ( feature . properties . id ) ? ? new UIEventSource < any > ( feature . properties ) ;
2021-06-14 17:28:11 +02:00
infobox = new FeatureInfoBox ( tags , layer ) ;
infobox . isShown . addCallback ( isShown = > {
if ( ! isShown ) {
2021-06-15 16:18:58 +02:00
leafletLayer . closePopup ( )
2021-06-14 17:28:11 +02:00
}
} ) ;
}
infobox . AttachTo ( id )
2021-07-18 21:37:14 +02:00
infobox . Activate ( ) ;
2021-10-15 05:20:02 +02:00
if ( this . _selectedElement ? . data ? . properties ? . id !== feature . properties . id ) {
this . _selectedElement ? . setData ( feature )
2021-10-02 22:31:16 +02:00
}
2021-10-01 05:24:10 +02:00
2021-03-22 00:28:21 +01:00
} ) ;
2021-10-27 20:19:45 +02:00
2021-09-26 17:36:39 +02:00
2021-09-21 03:10:15 +02:00
// Add the feature to the index to open the popup when needed
2021-10-01 05:24:10 +02:00
this . leafletLayersPerId . set ( feature . properties . id + feature . geometry . type , {
feature : feature ,
leafletlayer : leafletLayer
} )
2021-10-22 02:16:07 +02:00
2021-09-26 17:36:39 +02:00
2021-01-04 04:06:21 +01:00
}
}