| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | import {Basemap} from "./Basemap"; | 
					
						
							|  |  |  | import {TagsFilter, TagUtils} from "./TagsFilter"; | 
					
						
							|  |  |  | import {UIEventSource} from "../UI/UIEventSource"; | 
					
						
							|  |  |  | import {ElementStorage} from "./ElementStorage"; | 
					
						
							|  |  |  | import {Changes} from "./Changes"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | import L from "leaflet" | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  | import {GeoOperations} from "./GeoOperations"; | 
					
						
							|  |  |  | import {UIElement} from "../UI/UIElement"; | 
					
						
							|  |  |  | import {LayerDefinition} from "../Customizations/LayerDefinition"; | 
					
						
							|  |  |  | import {UserDetails} from "./OsmConnection"; | 
					
						
							| 
									
										
										
										
											2020-07-26 19:13:52 +02:00
										 |  |  | import codegrid from "codegrid-js"; | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 00:50:30 +02:00
										 |  |  |     public readonly name: string | UIElement; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     public readonly filters: TagsFilter; | 
					
						
							| 
									
										
										
										
											2020-07-15 15:55:08 +02:00
										 |  |  |     public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true); | 
					
						
							| 
									
										
										
										
											2020-07-22 11:01:25 +02:00
										 |  |  |     public readonly layerDef: LayerDefinition; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     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-26 23:28:31 +02:00
										 |  |  |     private readonly _style: (properties) => { color: string, icon: { iconUrl: string, iconSize? : number[], popupAnchor?: number[], iconAnchor?:number[] } }; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private readonly _storage: ElementStorage; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** The featurecollection from overpass | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private _dataFromOverpass; | 
					
						
							| 
									
										
										
										
											2020-07-22 00:50:30 +02:00
										 |  |  |     private _wayHandling: number; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     /** List of new elements, geojson features | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private _newElements = []; | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The leaflet layer object which should be removed on rerendering | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private _geolayer; | 
					
						
							| 
									
										
										
										
											2020-07-22 11:05:04 +02:00
										 |  |  |     private _selectedElement: UIEventSource<{ feature: any }>; | 
					
						
							| 
									
										
										
										
											2020-07-22 01:07:32 +02:00
										 |  |  |     private _showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-26 19:13:52 +02:00
										 |  |  |     private static readonly grid = codegrid.CodeGrid(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     constructor( | 
					
						
							| 
									
										
										
										
											2020-07-22 11:01:25 +02:00
										 |  |  |         layerDef: LayerDefinition, | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         map: Basemap, storage: ElementStorage, | 
					
						
							|  |  |  |         changes: Changes, | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |         selectedElement: UIEventSource<any>, | 
					
						
							| 
									
										
										
										
											2020-07-24 15:52:21 +02:00
										 |  |  |         showOnPopup: ((tags: UIEventSource<any>, feature: any) => UIElement) | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2020-07-22 11:01:25 +02:00
										 |  |  |         this.layerDef = layerDef; | 
					
						
							| 
									
										
										
										
											2020-07-22 11:05:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this._wayHandling = layerDef.wayHandling; | 
					
						
							| 
									
										
										
										
											2020-06-27 03:06:51 +02:00
										 |  |  |         this._selectedElement = selectedElement; | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  |         this._showOnPopup = showOnPopup; | 
					
						
							| 
									
										
										
										
											2020-07-22 11:01:25 +02:00
										 |  |  |         this._style = layerDef.style; | 
					
						
							|  |  |  |         if (this._style === undefined) { | 
					
						
							|  |  |  |             this._style = function () { | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |                 return {icon: {iconUrl: "./assets/bug.svg"}, color: "#000000"}; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.name = name; | 
					
						
							|  |  |  |         this._map = map; | 
					
						
							| 
									
										
										
										
											2020-07-22 11:01:25 +02:00
										 |  |  |         this.filters = layerDef.overpassFilter; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         this._storage = storage; | 
					
						
							| 
									
										
										
										
											2020-07-22 11:01:25 +02:00
										 |  |  |         this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage; | 
					
						
							| 
									
										
										
										
											2020-07-15 15:55:08 +02:00
										 |  |  |         const self = this; | 
					
						
							|  |  |  |         this.isDisplayed.addCallback(function (isDisplayed) { | 
					
						
							|  |  |  |             if (self._geolayer !== undefined && self._geolayer !== null) { | 
					
						
							|  |  |  |                 if (isDisplayed) { | 
					
						
							|  |  |  |                     self._geolayer.addTo(self._map.map); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     self._map.map.removeLayer(self._geolayer); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |      | 
					
						
							|  |  |  |     static fromDefinition( | 
					
						
							|  |  |  |         definition, | 
					
						
							|  |  |  |         basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, | 
					
						
							|  |  |  |                  selectedElement: UIEventSource<{feature: any}>, | 
					
						
							|  |  |  |                  showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement): | 
					
						
							|  |  |  |         FilteredLayer { | 
					
						
							|  |  |  |         return new FilteredLayer( | 
					
						
							|  |  |  |             definition, | 
					
						
							|  |  |  |             basemap, allElements, changes, | 
					
						
							|  |  |  |             selectedElement, | 
					
						
							|  |  |  |             showOnPopup); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											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 = []; | 
					
						
							| 
									
										
										
										
											2020-07-22 00:50:30 +02:00
										 |  |  |         for (let feature of geojson.features) { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             // feature.properties contains all the properties
 | 
					
						
							|  |  |  |             var tags = TagUtils.proprtiesToKV(feature.properties); | 
					
						
							|  |  |  |             if (this.filters.matches(tags)) { | 
					
						
							| 
									
										
										
										
											2020-07-26 19:13:52 +02:00
										 |  |  |                 const centerPoint = GeoOperations.centerpoint(feature); | 
					
						
							| 
									
										
										
										
											2020-07-22 01:07:32 +02:00
										 |  |  |                 feature.properties["_surface"] = GeoOperations.surfaceAreaInSqMeters(feature); | 
					
						
							| 
									
										
										
										
											2020-07-26 19:13:52 +02:00
										 |  |  |                 const lat = centerPoint.geometry.coordinates[1]; | 
					
						
							|  |  |  |                 const lon = centerPoint.geometry.coordinates[0] | 
					
						
							|  |  |  |                 feature.properties["_lon"] = lat; | 
					
						
							|  |  |  |                 feature.properties["_lat"] = lon; | 
					
						
							|  |  |  |                 FilteredLayer.grid.getCode(lat, lon, (error, code) => { | 
					
						
							|  |  |  |                     if (error === null) { | 
					
						
							|  |  |  |                         feature.properties["_country"] = code; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 11:05:04 +02:00
										 |  |  |                 if (feature.geometry.type !== "Point") { | 
					
						
							|  |  |  |                     if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY) { | 
					
						
							| 
									
										
										
										
											2020-07-26 19:13:52 +02:00
										 |  |  |                         selfFeatures.push(centerPoint); | 
					
						
							| 
									
										
										
										
											2020-07-22 11:05:04 +02:00
										 |  |  |                     } else if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY) { | 
					
						
							| 
									
										
										
										
											2020-07-26 19:13:52 +02:00
										 |  |  |                         feature = centerPoint; | 
					
						
							| 
									
										
										
										
											2020-07-22 00:50:30 +02:00
										 |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 selfFeatures.push(feature); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 leftoverFeatures.push(feature); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.RenderLayer({ | 
					
						
							|  |  |  |             type: "FeatureCollection", | 
					
						
							|  |  |  |             features: selfFeatures | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const notShadowed = []; | 
					
						
							|  |  |  |         for (const feature of leftoverFeatures) { | 
					
						
							| 
									
										
										
										
											2020-07-18 20:40:51 +02:00
										 |  |  |             if (this._maxAllowedOverlap !== undefined && this._maxAllowedOverlap > 0) { | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |                 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 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-07-15 15:55:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 } else { | 
					
						
							| 
									
										
										
										
											2020-07-26 23:28:31 +02:00
										 |  |  |                     if(style.icon.iconSize === undefined){ | 
					
						
							|  |  |  |                         style.icon.iconSize = [50,50] | 
					
						
							|  |  |  |                     }if(style.icon.iconAnchor === undefined){ | 
					
						
							|  |  |  |                         style.icon.iconAnchor = [style.icon.iconSize[0] / 2,style.icon.iconSize[1]] | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     if (style.icon.popupAnchor === undefined) { | 
					
						
							|  |  |  |                         style.icon.popupAnchor = [0, 8 - (style.icon.iconSize[1])] | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                     marker = L.marker(latLng, { | 
					
						
							| 
									
										
										
										
											2020-07-26 23:28:31 +02:00
										 |  |  |                         icon: new L.icon(style.icon), | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                     }); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-07-26 23:28:31 +02:00
										 |  |  |                 let eventSource = self._storage.addOrGetElement(feature); | 
					
						
							|  |  |  |                 const uiElement = self._showOnPopup(eventSource, feature); | 
					
						
							| 
									
										
										
										
											2020-07-27 00:14:34 +02:00
										 |  |  |                 const popup = L.popup({}, marker).setContent(uiElement.Render()); | 
					
						
							|  |  |  |                 marker.bindPopup(popup) | 
					
						
							|  |  |  |                     .on("popupopen", (popup) => { | 
					
						
							|  |  |  |                         uiElement.Activate();    | 
					
						
							|  |  |  |                         uiElement.Update(); | 
					
						
							|  |  |  |                     }); | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 return marker; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             onEachFeature: function (feature, layer) { | 
					
						
							|  |  |  |                 let eventSource = self._storage.addOrGetElement(feature); | 
					
						
							|  |  |  |                 eventSource.addCallback(function () { | 
					
						
							| 
									
										
										
										
											2020-07-16 17:24:18 +02:00
										 |  |  |                     if (layer.setIcon) { | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |                         layer.setIcon(L.icon(self._style(feature.properties).icon)) | 
					
						
							| 
									
										
										
										
											2020-07-16 17:24:18 +02:00
										 |  |  |                     } else { | 
					
						
							|  |  |  |                         self._geolayer.setStyle(function (feature) { | 
					
						
							|  |  |  |                             return self._style(feature.properties); | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2020-06-29 16:21:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-01 16:32:17 +02:00
										 |  |  |                 layer.on("click", function (e) { | 
					
						
							| 
									
										
										
										
											2020-07-22 01:07:32 +02:00
										 |  |  |                     self._selectedElement.setData({feature: feature}); | 
					
						
							| 
									
										
										
										
											2020-07-26 23:28:31 +02:00
										 |  |  |                     if (feature.geometry.type === "Point") { | 
					
						
							|  |  |  |                         return; // Points bind there own popups
 | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-22 01:07:32 +02:00
										 |  |  |                     const uiElement = self._showOnPopup(eventSource, feature); | 
					
						
							| 
									
										
										
										
											2020-07-26 23:28:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-26 02:01:34 +02:00
										 |  |  |                     const popup = L.popup({ | 
					
						
							|  |  |  |                         autoPan: true, | 
					
						
							|  |  |  |                     }) | 
					
						
							| 
									
										
										
										
											2020-07-07 15:08:52 +02:00
										 |  |  |                         .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
										 |  |  |                 }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 15:55:08 +02:00
										 |  |  |         if (this.isDisplayed.data) { | 
					
						
							|  |  |  |             this._geolayer.addTo(this._map.map); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |