| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | import {Basemap} from "./Basemap"; | 
					
						
							|  |  |  | import {TagsFilter, TagUtils} from "./TagsFilter"; | 
					
						
							|  |  |  | import {UIEventSource} from "../UI/UIEventSource"; | 
					
						
							|  |  |  | import {ElementStorage} from "./ElementStorage"; | 
					
						
							|  |  |  | import {Changes} from "./Changes"; | 
					
						
							|  |  |  | import L from "leaflet" | 
					
						
							|  |  |  | import {GeoOperations} from "./GeoOperations"; | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  | import {UIElement} from "../UI/UIElement"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*** | 
					
						
							|  |  |  |  * A filtered layer is a layer which offers a 'set-data' function | 
					
						
							|  |  |  |  * It is initialized with a tagfilter. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * When geojson-data is given to 'setData', all the geojson matching the filter, is rendered on this layer. | 
					
						
							|  |  |  |  * If it is not rendered, it is returned in a 'leftOver'-geojson; which can be consumed by the next layer. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This also makes sure that no objects are rendered twice if they are applicable on two layers | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class FilteredLayer { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public readonly name: string; | 
					
						
							|  |  |  |     public readonly filters: TagsFilter; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private readonly _map: Basemap; | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |     private readonly _maxAllowedOverlap: number; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-01 17:38:48 +02:00
										 |  |  |     private readonly _style: (properties) => { color: string, icon: any }; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private readonly _storage: ElementStorage; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** The featurecollection from overpass | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private _dataFromOverpass; | 
					
						
							|  |  |  |     /** List of new elements, geojson features | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private _newElements = []; | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The leaflet layer object which should be removed on rerendering | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private _geolayer; | 
					
						
							| 
									
										
										
										
											2020-06-27 03:06:51 +02:00
										 |  |  |     private _selectedElement: UIEventSource<any>; | 
					
						
							| 
									
										
										
										
											2020-07-01 16:32:17 +02:00
										 |  |  |     private _showOnPopup: (tags: UIEventSource<any>) => UIElement; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor( | 
					
						
							|  |  |  |         name: string, | 
					
						
							|  |  |  |         map: Basemap, storage: ElementStorage, | 
					
						
							|  |  |  |         changes: Changes, | 
					
						
							|  |  |  |         filters: TagsFilter, | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |         maxAllowedOverlap: number, | 
					
						
							| 
									
										
										
										
											2020-06-27 03:06:51 +02:00
										 |  |  |         style: ((properties) => any), | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |         selectedElement: UIEventSource<any>, | 
					
						
							| 
									
										
										
										
											2020-07-01 16:32:17 +02:00
										 |  |  |         showOnPopup: ((tags: UIEventSource<any>) => UIElement) | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2020-06-27 03:06:51 +02:00
										 |  |  |         this._selectedElement = selectedElement; | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |         this._showOnPopup = showOnPopup; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (style === undefined) { | 
					
						
							|  |  |  |             style = function () { | 
					
						
							|  |  |  |                 return {}; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.name = name; | 
					
						
							|  |  |  |         this._map = map; | 
					
						
							|  |  |  |         this.filters = filters; | 
					
						
							|  |  |  |         this._style = style; | 
					
						
							|  |  |  |         this._storage = storage; | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |         this._maxAllowedOverlap = maxAllowedOverlap; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The main function to load data into this layer. | 
					
						
							|  |  |  |      * The data that is NOT used by this layer, is returned as a geojson object; the other data is rendered | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public SetApplicableData(geojson: any): any { | 
					
						
							|  |  |  |         const leftoverFeatures = []; | 
					
						
							|  |  |  |         const selfFeatures = []; | 
					
						
							|  |  |  |         for (const feature of geojson.features) { | 
					
						
							|  |  |  |             // feature.properties contains all the properties
 | 
					
						
							|  |  |  |             var tags = TagUtils.proprtiesToKV(feature.properties); | 
					
						
							|  |  |  |             if (this.filters.matches(tags)) { | 
					
						
							|  |  |  |                 selfFeatures.push(feature); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 leftoverFeatures.push(feature); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.RenderLayer({ | 
					
						
							|  |  |  |             type: "FeatureCollection", | 
					
						
							|  |  |  |             features: selfFeatures | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const notShadowed = []; | 
					
						
							|  |  |  |         for (const feature of leftoverFeatures) { | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |             if (this._maxAllowedOverlap !== undefined && this._maxAllowedOverlap >= 0) { | 
					
						
							|  |  |  |                 if (GeoOperations.featureIsContainedInAny(feature, selfFeatures, this._maxAllowedOverlap)) { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                     // This feature is filtered away
 | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             notShadowed.push(feature); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             type: "FeatureCollection", | 
					
						
							|  |  |  |             features: notShadowed | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public updateStyle() { | 
					
						
							|  |  |  |         if (this._geolayer === undefined) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         this._geolayer.setStyle(function (feature) { | 
					
						
							|  |  |  |             return self._style(feature.properties); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public AddNewElement(element) { | 
					
						
							|  |  |  |         this._newElements.push(element); | 
					
						
							|  |  |  |         console.log("Element added"); | 
					
						
							|  |  |  |         this.RenderLayer(this._dataFromOverpass); // Update the layer
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private RenderLayer(data) { | 
					
						
							|  |  |  |         let self = this; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this._geolayer !== undefined && this._geolayer !== null) { | 
					
						
							|  |  |  |             this._map.map.removeLayer(this._geolayer); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this._dataFromOverpass = data; | 
					
						
							|  |  |  |         const fusedFeatures = []; | 
					
						
							|  |  |  |         const idsFromOverpass = []; | 
					
						
							|  |  |  |         for (const feature of data.features) { | 
					
						
							|  |  |  |             idsFromOverpass.push(feature.properties.id); | 
					
						
							|  |  |  |             fusedFeatures.push(feature); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const feature of this._newElements) { | 
					
						
							|  |  |  |             if (idsFromOverpass.indexOf(feature.properties.id) < 0) { | 
					
						
							|  |  |  |                 // This element is not yet uploaded or not yet visible in overpass
 | 
					
						
							|  |  |  |                 // We include it in the layer
 | 
					
						
							|  |  |  |                 fusedFeatures.push(feature); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // We use a new, fused dataset
 | 
					
						
							|  |  |  |         data = { | 
					
						
							|  |  |  |             type: "FeatureCollection", | 
					
						
							|  |  |  |             features: fusedFeatures | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // The data is split in two parts: the poinst and the rest
 | 
					
						
							|  |  |  |         // The points get a special treatment in order to render them properly
 | 
					
						
							|  |  |  |         // Note that some features might get a point representation as well
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this._geolayer = L.geoJSON(data, { | 
					
						
							|  |  |  |             style: function (feature) { | 
					
						
							|  |  |  |                 return self._style(feature.properties); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             pointToLayer: function (feature, latLng) { | 
					
						
							|  |  |  |                 const style = self._style(feature.properties); | 
					
						
							|  |  |  |                 let marker; | 
					
						
							|  |  |  |                 if (style.icon === undefined) { | 
					
						
							| 
									
										
										
										
											2020-07-01 17:38:48 +02:00
										 |  |  |                     marker = L.circle(latLng, { | 
					
						
							| 
									
										
										
										
											2020-07-01 21:21:29 +02:00
										 |  |  |                         radius: 25, | 
					
						
							| 
									
										
										
										
											2020-07-01 17:38:48 +02:00
										 |  |  |                         color: style.color | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                      | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 } else { | 
					
						
							|  |  |  |                     marker = L.marker(latLng, { | 
					
						
							|  |  |  |                         icon: style.icon | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return marker; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             onEachFeature: function (feature, layer) { | 
					
						
							|  |  |  |                 let eventSource = self._storage.addOrGetElement(feature); | 
					
						
							|  |  |  |                 eventSource.addCallback(function () { | 
					
						
							|  |  |  |                     self.updateStyle(); | 
					
						
							|  |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-01 16:32:17 +02:00
										 |  |  |                 layer.on("click", function (e) { | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |                     console.log("Selected ", feature) | 
					
						
							| 
									
										
										
										
											2020-06-27 03:06:51 +02:00
										 |  |  |                     self._selectedElement.setData(feature.properties); | 
					
						
							| 
									
										
										
										
											2020-07-07 15:08:52 +02:00
										 |  |  |                     const uiElement = self._showOnPopup(eventSource); | 
					
						
							|  |  |  |                     const popup = L.popup() | 
					
						
							|  |  |  |                         .setContent(uiElement.Render()) | 
					
						
							|  |  |  |                         .setLatLng(e.latlng) | 
					
						
							|  |  |  |                         .openOn(self._map.map); | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |                     uiElement.Update(); | 
					
						
							|  |  |  |                     uiElement.Activate(); | 
					
						
							| 
									
										
										
										
											2020-07-01 16:32:17 +02:00
										 |  |  |                     L.DomEvent.stop(e); // Marks the event as consumed
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this._geolayer.addTo(this._map.map); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |