| 
									
										
										
										
											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 LazyElement from "./Base/LazyElement"; | 
					
						
							|  |  |  | import Hash from "../Logic/Web/Hash"; | 
					
						
							|  |  |  | import {GeoOperations} from "../Logic/GeoOperations"; | 
					
						
							|  |  |  | 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 { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private readonly _layerDict; | 
					
						
							|  |  |  |     private readonly _leafletMap: UIEventSource<L.Map>; | 
					
						
							| 
									
										
										
										
											2021-01-04 23:36:02 +01:00
										 |  |  |     private readonly _onSelectedTrigger: any = {}; // osmId+geometry.type+matching_layer_id --> () => void
 | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, | 
					
						
							|  |  |  |                 leafletMap: UIEventSource<L.Map>, | 
					
						
							| 
									
										
										
										
											2021-01-04 18:55:10 +01:00
										 |  |  |                 layoutToUse: LayoutConfig) { | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |         this._leafletMap = leafletMap; | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         let oldGeoLayer: L.Layer = undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this._layerDict = {}; | 
					
						
							| 
									
										
										
										
											2021-01-04 18:55:10 +01:00
										 |  |  |         for (const layer of layoutToUse.layers) { | 
					
						
							|  |  |  |             this._layerDict[layer.id] = layer; | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function update() { | 
					
						
							|  |  |  |             if (features.data === undefined) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (leafletMap.data === undefined) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const mp = leafletMap.data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const feats = features.data.map(ff => ff.feature); | 
					
						
							| 
									
										
										
										
											2021-01-15 00:29:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-04 18:55:10 +01:00
										 |  |  |             let geoLayer = self.CreateGeojsonLayer(feats) | 
					
						
							| 
									
										
										
										
											2021-01-05 00:21:00 +01:00
										 |  |  |             if (layoutToUse.clustering.minNeededElements <= features.data.length) { | 
					
						
							| 
									
										
										
										
											2021-01-05 16:59:12 +01:00
										 |  |  |                     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
 | 
					
						
							| 
									
										
										
										
											2021-01-04 23:46:35 +01:00
										 |  |  |                     const cluster = cl.markerClusterGroup({ disableClusteringAtZoom: layoutToUse.clustering.maxZoom }); | 
					
						
							| 
									
										
										
										
											2021-01-04 20:09:07 +01:00
										 |  |  |                     cluster.addLayer(geoLayer); | 
					
						
							|  |  |  |                     geoLayer = cluster; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-04 18:55:10 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |             if (oldGeoLayer) { | 
					
						
							|  |  |  |                 mp.removeLayer(oldGeoLayer); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-04 04:36:21 +01:00
										 |  |  |             mp.addLayer(geoLayer); | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |             oldGeoLayer = geoLayer; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         features.addCallbackAndRun(() => update()); | 
					
						
							|  |  |  |         leafletMap.addCallback(() => update()); | 
					
						
							| 
									
										
										
										
											2021-01-04 23:36:02 +01:00
										 |  |  |         State.state.selectedElement.addCallback(feature => { | 
					
						
							|  |  |  |             if(feature === undefined){ | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const id = feature.properties.id+feature.geometry.type+feature._matching_layer_id; | 
					
						
							|  |  |  |             const action = self._onSelectedTrigger[id]; | 
					
						
							|  |  |  |             if(action){ | 
					
						
							|  |  |  |                 action(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2021-01-06 02:09:04 +01:00
										 |  |  |         Hash.hash.addCallback(id => { | 
					
						
							| 
									
										
										
										
											2021-01-06 13:18:33 +01:00
										 |  |  |             // This is a bit of an edge case: if the hash becomes an id to search, we have to show the corresponding popup
 | 
					
						
							|  |  |  |             if(State.state.selectedElement !== undefined){ | 
					
						
							|  |  |  |                 return; // Something is already selected, we don't have to apply this fix
 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-06 02:09:04 +01:00
										 |  |  |             const action = self._onSelectedTrigger[id]; | 
					
						
							|  |  |  |             if(action){ | 
					
						
							|  |  |  |                 action(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-15 00:29:07 +01:00
										 |  |  |         update(); | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private createStyleFor(feature) { | 
					
						
							|  |  |  |         const tagsSource = State.state.allElements.getEventSourceFor(feature); | 
					
						
							|  |  |  |         // Every object is tied to exactly one layer
 | 
					
						
							|  |  |  |         const layer = this._layerDict[feature._matching_layer_id]; | 
					
						
							|  |  |  |         return layer.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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-01-15 00:29:07 +01:00
										 |  |  |         const tagSource = State.state.allElements.addOrGetElement(feature) | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							|  |  |  |         const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); | 
					
						
							|  |  |  |         return L.marker(latLng, { | 
					
						
							|  |  |  |             icon: L.divIcon({ | 
					
						
							|  |  |  |                 html: style.icon.html.Render(), | 
					
						
							|  |  |  |                 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-01-04 04:06:21 +01:00
										 |  |  |         if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) { | 
					
						
							|  |  |  |             // No popup action defined -> Don't do anything
 | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-04 18:55:10 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |         const popup = L.popup({ | 
					
						
							|  |  |  |             autoPan: true, | 
					
						
							|  |  |  |             closeOnEscapeKey: true, | 
					
						
							|  |  |  |         }, leafletLayer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const tags = State.state.allElements.getEventSourceFor(feature); | 
					
						
							| 
									
										
										
										
											2021-01-07 20:11:07 +01:00
										 |  |  |         const uiElement: LazyElement = new LazyElement(() => new FeatureInfoBox(tags, layer), | 
					
						
							|  |  |  |             "<div style='height: 90vh'>Rendering</div>"); | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |         popup.setContent(uiElement.Render()); | 
					
						
							| 
									
										
										
										
											2021-01-05 16:59:12 +01:00
										 |  |  |         popup.on('remove', () => { | 
					
						
							| 
									
										
										
										
											2021-01-05 02:59:29 +01:00
										 |  |  |            State.state.selectedElement.setData(undefined);  | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |         leafletLayer.bindPopup(popup); | 
					
						
							|  |  |  |         // We first render the UIelement (which'll still need an update later on...)
 | 
					
						
							|  |  |  |         // But at least it'll be visible already
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         leafletLayer.on("click", (e) => { | 
					
						
							|  |  |  |             // We set the element as selected...
 | 
					
						
							| 
									
										
										
										
											2021-01-05 16:59:12 +01:00
										 |  |  |             uiElement.Activate(); | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |             State.state.selectedElement.setData(feature); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2021-01-04 23:36:02 +01:00
										 |  |  |          | 
					
						
							|  |  |  |         const id = feature.properties.id+feature.geometry.type+feature._matching_layer_id; | 
					
						
							|  |  |  |         this._onSelectedTrigger[id] | 
					
						
							|  |  |  |          = () => { | 
					
						
							| 
									
										
										
										
											2021-01-05 16:59:12 +01:00
										 |  |  |             if(popup.isOpen()){ | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-04 23:36:02 +01:00
										 |  |  |             leafletLayer.openPopup(); | 
					
						
							|  |  |  |             uiElement.Activate(); | 
					
						
							| 
									
										
										
										
											2021-01-06 13:18:33 +01:00
										 |  |  |             State.state.selectedElement.setData(feature); | 
					
						
							| 
									
										
										
										
											2021-01-04 23:36:02 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-06 02:09:04 +01:00
										 |  |  |         this._onSelectedTrigger[feature.properties.id.replace("/","_")] = this._onSelectedTrigger[id]; | 
					
						
							|  |  |  |         if (feature.properties.id.replace(/\//g, "_") === Hash.hash.data) { | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |             // This element is in the URL, so this is a share link
 | 
					
						
							| 
									
										
										
										
											2021-01-08 18:02:07 +01:00
										 |  |  |             // We open the relevant popup straight away
 | 
					
						
							| 
									
										
										
										
											2021-01-04 04:06:21 +01:00
										 |  |  |             uiElement.Activate(); | 
					
						
							|  |  |  |             popup.setContent(uiElement.Render()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const center = GeoOperations.centerpoint(feature).geometry.coordinates; | 
					
						
							|  |  |  |             popup.setLatLng({lat: center[1], lng: center[0]}); | 
					
						
							|  |  |  |             popup.openOn(State.state.leafletMap.data); | 
					
						
							|  |  |  |             State.state.selectedElement.setData(feature); | 
					
						
							|  |  |  |             uiElement.Update(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private CreateGeojsonLayer(features: any[]): L.Layer { | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         const data = { | 
					
						
							|  |  |  |             type: "FeatureCollection", | 
					
						
							|  |  |  |             features: features | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return L.geoJSON(data, { | 
					
						
							|  |  |  |             style: feature => self.createStyleFor(feature), | 
					
						
							|  |  |  |             pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), | 
					
						
							|  |  |  |             onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer) | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |