2021-01-04 04:06:21 +01:00
/ * *
* The data layer shows all the given geojson elements with the appropriate icon etc
* /
import { UIEventSource } from "../Logic/UIEventSource" ;
import * as L from "leaflet"
2021-01-04 18:55:10 +01:00
import "leaflet.markercluster"
2021-01-04 04:06:21 +01:00
import LayerConfig from "../Customizations/JSON/LayerConfig" ;
import State from "../State" ;
import FeatureInfoBox from "./Popup/FeatureInfoBox" ;
2021-01-04 18:55:10 +01:00
import LayoutConfig from "../Customizations/JSON/LayoutConfig" ;
2021-01-04 04:06:21 +01:00
export default class ShowDataLayer {
2021-02-20 01:45:51 +01:00
private _layerDict ;
2021-01-04 04:06:21 +01:00
private readonly _leafletMap : UIEventSource < L.Map > ;
2021-06-14 17:28:11 +02:00
private _cleanCount = 0 ;
2021-06-23 02:15:28 +02:00
private readonly _enablePopups : boolean ;
2021-06-24 02:34:13 +02:00
private readonly _features : UIEventSource < { feature : any , freshness : Date } [ ] >
2021-03-22 00:28:21 +01:00
2021-01-04 04:06:21 +01:00
constructor ( features : UIEventSource < { feature : any , freshness : Date } [ ] > ,
leafletMap : UIEventSource < L.Map > ,
2021-06-23 02:15:28 +02:00
layoutToUse : UIEventSource < LayoutConfig > ,
2021-06-24 02:34:13 +02:00
enablePopups = true ,
2021-06-24 01:17:29 +02:00
zoomToFeatures = false ) {
2021-01-04 04:06:21 +01:00
this . _leafletMap = leafletMap ;
2021-06-23 02:15:28 +02:00
this . _enablePopups = enablePopups ;
2021-06-24 01:17:29 +02:00
this . _features = features ;
2021-01-04 04:06:21 +01:00
const self = this ;
2021-02-20 01:45:51 +01:00
self . _layerDict = { } ;
2021-02-05 19:11:19 +01:00
2021-02-14 19:45:02 +01:00
layoutToUse . addCallbackAndRun ( layoutToUse = > {
for ( const layer of layoutToUse . layers ) {
2021-03-29 02:53:06 +02:00
if ( self . _layerDict [ layer . id ] === undefined ) {
self . _layerDict [ layer . id ] = layer ;
}
2021-02-05 18:58:06 +01:00
}
2021-02-14 19:45:02 +01:00
} ) ;
2021-02-06 00:05:38 +01:00
2021-02-14 19:45:02 +01:00
let geoLayer = undefined ;
2021-02-06 00:05:38 +01:00
let cluster = undefined ;
2021-01-04 04:06:21 +01:00
function update() {
if ( features . data === undefined ) {
return ;
}
2021-06-23 02:15:28 +02:00
const mp = leafletMap . data ;
2021-06-24 02:34:13 +02:00
if ( mp === undefined ) {
2021-01-04 04:06:21 +01:00
return ;
}
2021-01-15 00:29:07 +01:00
2021-06-14 17:28:11 +02:00
self . _cleanCount ++
2021-02-14 19:45:02 +01:00
// clean all the old stuff away, if any
if ( geoLayer !== undefined ) {
mp . removeLayer ( geoLayer ) ;
}
if ( cluster !== undefined ) {
mp . removeLayer ( cluster ) ;
}
const allFeats = features . data . map ( ff = > ff . feature ) ;
geoLayer = self . CreateGeojsonLayer ( ) ;
for ( const feat of allFeats ) {
2021-07-03 14:35:44 +02:00
if ( feat === undefined ) {
2021-06-30 18:48:23 +02:00
continue
}
2021-02-06 00:05:38 +01:00
// @ts-ignore
geoLayer . addData ( feat ) ;
2021-01-04 20:09:07 +01:00
}
2021-02-14 19:45:02 +01:00
if ( layoutToUse . data . clustering . minNeededElements <= allFeats . length ) {
// Activate clustering if it wasn't already activated
const cl = window [ "L" ] ; // This is a dirty workaround, the clustering plugin binds to the L of the window, not of the namespace or something
cluster = cl . markerClusterGroup ( { disableClusteringAtZoom : layoutToUse.data.clustering.maxZoom } ) ;
cluster . addLayer ( geoLayer ) ;
mp . addLayer ( cluster ) ;
} else {
mp . addLayer ( geoLayer )
2021-01-04 04:06:21 +01:00
}
2021-02-15 19:06:06 +01:00
2021-06-24 02:34:13 +02:00
if ( zoomToFeatures ) {
2021-07-03 14:35:44 +02:00
try {
2021-07-10 21:07:44 +02:00
mp . fitBounds ( geoLayer . getBounds ( ) , { animate : false } )
2021-07-03 14:35:44 +02:00
} catch ( e ) {
2021-06-30 18:48:23 +02:00
console . error ( e )
}
2021-06-24 01:17:29 +02:00
}
2021-06-24 02:34:13 +02:00
2021-02-14 20:36:20 +01:00
State . state . selectedElement . ping ( ) ;
2021-01-04 04:06:21 +01:00
}
2021-02-05 18:58:06 +01:00
features . addCallback ( ( ) = > update ( ) ) ;
2021-01-04 04:06:21 +01:00
leafletMap . addCallback ( ( ) = > update ( ) ) ;
2021-02-06 00:05:38 +01:00
update ( ) ;
2021-01-04 04:06:21 +01:00
}
private createStyleFor ( feature ) {
2021-03-25 15:19:44 +01:00
const tagsSource = State . state . allElements . addOrGetElement ( feature ) ;
2021-01-04 04:06:21 +01:00
// Every object is tied to exactly one layer
const layer = this . _layerDict [ feature . _matching_layer_id ] ;
2021-02-20 01:45:51 +01:00
return layer ? . GenerateLeafletStyle ( tagsSource , layer . _showOnPopup !== undefined ) ;
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-04-17 23:36:46 +02:00
const tagSource = State . state . allElements . getEventSourceById ( feature . properties . id )
2021-01-04 18:55:10 +01:00
const layer : LayerConfig = this . _layerDict [ feature . _matching_layer_id ] ;
2021-01-04 04:06:21 +01:00
2021-02-14 19:45:02 +01:00
if ( layer === undefined ) {
return ;
}
2021-01-04 04:06:21 +01:00
const style = layer . GenerateLeafletStyle ( tagSource , ! ( layer . title === undefined && ( layer . tagRenderings ? ? [ ] ) . length === 0 ) ) ;
2021-06-23 02:41:30 +02:00
const baseElement = style . icon . 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-01-04 04:06:21 +01:00
return L . marker ( latLng , {
icon : L.divIcon ( {
2021-06-23 02:41:30 +02:00
html : baseElement.ConstructElement ( ) ,
2021-01-04 04:06:21 +01:00
className : style.icon.className ,
iconAnchor : style.icon.iconAnchor ,
iconUrl : style.icon.iconUrl ,
popupAnchor : style.icon.popupAnchor ,
iconSize : style.icon.iconSize
} )
} ) ;
}
2021-01-04 18:55:10 +01:00
private postProcessFeature ( feature , leafletLayer : L.Layer ) {
const layer : LayerConfig = this . _layerDict [ feature . _matching_layer_id ] ;
2021-03-22 00:28:21 +01:00
if ( layer === undefined ) {
2021-03-21 01:32:21 +01:00
console . warn ( "No layer found for object (probably a now disabled layer)" , feature , this . _layerDict )
2021-02-20 01:45:51 +01:00
return ;
}
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 ,
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-06-14 17:28:11 +02:00
const id = ` popup- ${ feature . properties . id } - ${ this . _cleanCount } `
2021-06-15 16:18:58 +02:00
popup . setContent ( ` <div style='height: 65vh' id=' ${ id } '>Rendering</div> ` )
2021-06-14 17:28:11 +02:00
2021-02-05 18:58:06 +01:00
leafletLayer . on ( "popupopen" , ( ) = > {
2021-03-22 00:28:21 +01:00
State . state . selectedElement . setData ( feature )
2021-07-03 14:35:44 +02:00
2021-06-15 16:18:58 +02:00
if ( infobox === undefined ) {
2021-06-14 17:28:11 +02:00
const tags = State . state . allElements . getEventSourceById ( feature . properties . id ) ;
infobox = new FeatureInfoBox ( tags , layer ) ;
infobox . isShown . addCallback ( isShown = > {
if ( ! isShown ) {
State . state . selectedElement . setData ( undefined ) ;
2021-06-15 16:18:58 +02:00
leafletLayer . closePopup ( )
2021-06-14 17:28:11 +02:00
}
} ) ;
}
infobox . AttachTo ( id )
2021-07-03 14:35:44 +02:00
infobox . Activate ( ) ;
2021-03-22 00:28:21 +01:00
} ) ;
2021-06-14 17:28:11 +02:00
const self = this ;
2021-06-30 18:48:23 +02:00
State . state . selectedElement . addCallbackAndRunD ( selected = > {
2021-07-03 14:35:44 +02:00
if ( self . _leafletMap . data === undefined ) {
2021-06-14 17:28:11 +02:00
return ;
}
2021-06-16 14:23:53 +02:00
if ( leafletLayer . getPopup ( ) . isOpen ( ) ) {
2021-06-14 17:28:11 +02:00
return ;
}
if ( selected . properties . id === feature . properties . id ) {
2021-06-18 01:50:03 +02:00
// A small sanity check to prevent infinite loops:
2021-07-03 14:35:44 +02:00
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
&& feature . id === feature . properties . id // the feature might have as id 'node/-1' and as 'feature.properties.id' = 'the newly assigned id'. That is no good too
) {
2021-06-24 02:34:13 +02:00
leafletLayer . openPopup ( )
2021-06-18 01:50:03 +02:00
}
2021-06-24 02:34:13 +02:00
2021-06-14 17:28:11 +02:00
}
} )
2021-06-15 16:18:58 +02:00
2021-01-04 04:06:21 +01:00
}
2021-02-06 00:05:38 +01:00
private CreateGeojsonLayer ( ) : L . Layer {
2021-06-24 02:34:13 +02:00
const self = this ;
const data = {
type : "FeatureCollection" ,
features : [ ]
}
// @ts-ignore
return L . geoJSON ( data , {
style : feature = > self . createStyleFor ( feature ) ,
pointToLayer : ( feature , latLng ) = > self . pointToLayer ( feature , latLng ) ,
2021-01-04 04:06:21 +01:00
onEachFeature : ( feature , leafletLayer ) = > self . postProcessFeature ( feature , leafletLayer )
} ) ;
}
}