| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  | import UserRelatedState from "./UserRelatedState"; | 
					
						
							|  |  |  | import {UIEventSource} from "../UIEventSource"; | 
					
						
							|  |  |  | import BaseLayer from "../../Models/BaseLayer"; | 
					
						
							|  |  |  | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | 
					
						
							|  |  |  | import AvailableBaseLayers from "../Actors/AvailableBaseLayers"; | 
					
						
							|  |  |  | import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter"; | 
					
						
							|  |  |  | import Attribution from "../../UI/BigComponents/Attribution"; | 
					
						
							|  |  |  | import Minimap, {MinimapObj} from "../../UI/Base/Minimap"; | 
					
						
							|  |  |  | import {Tiles} from "../../Models/TileRange"; | 
					
						
							|  |  |  | import BaseUIElement from "../../UI/BaseUIElement"; | 
					
						
							|  |  |  | import FilteredLayer from "../../Models/FilteredLayer"; | 
					
						
							|  |  |  | import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"; | 
					
						
							|  |  |  | import {QueryParameters} from "../Web/QueryParameters"; | 
					
						
							|  |  |  | import * as personal from "../../assets/themes/personal/personal.json"; | 
					
						
							|  |  |  | import FilterConfig from "../../Models/ThemeConfig/FilterConfig"; | 
					
						
							|  |  |  | import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Contains all the leaflet-map related state | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export default class MapState extends UserRelatedState { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      The leaflet instance of the big basemap | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-15 14:52:11 +02:00
										 |  |  |     public leafletMap = new UIEventSource<any /*L.Map*/>(undefined, "leafletmap"); | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * A list of currently available background layers | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public availableBackgroundLayers: UIEventSource<BaseLayer[]>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The current background layer | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public backgroundLayer: UIEventSource<BaseLayer>; | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Last location where a click was registered | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public readonly LastClickLocation: UIEventSource<{ | 
					
						
							|  |  |  |         lat: number; | 
					
						
							|  |  |  |         lon: number; | 
					
						
							|  |  |  |     }> = new UIEventSource<{ lat: number; lon: number }>(undefined); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The location as delivered by the GPS | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public currentGPSLocation: UIEventSource<{ | 
					
						
							|  |  |  |         latlng: { lat: number; lng: number }; | 
					
						
							|  |  |  |         accuracy: number; | 
					
						
							|  |  |  |     }> = new UIEventSource<{ | 
					
						
							|  |  |  |         latlng: { lat: number; lng: number }; | 
					
						
							|  |  |  |         accuracy: number; | 
					
						
							|  |  |  |     }>(undefined); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public readonly mainMapObject: BaseUIElement & MinimapObj; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * WHich layers are enabled in the current theme | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([], "filteredLayers"); | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Which overlays are shown | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public overlayToggles: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor(layoutToUse: LayoutConfig) { | 
					
						
							|  |  |  |         super(layoutToUse); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.backgroundLayer = this.backgroundLayerId.map( | 
					
						
							|  |  |  |             (selectedId: string) => { | 
					
						
							|  |  |  |                 if (selectedId === undefined) { | 
					
						
							|  |  |  |                     return AvailableBaseLayers.osmCarto; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 const available = this.availableBackgroundLayers.data; | 
					
						
							|  |  |  |                 for (const layer of available) { | 
					
						
							|  |  |  |                     if (layer.id === selectedId) { | 
					
						
							|  |  |  |                         return layer; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return AvailableBaseLayers.osmCarto; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             [this.availableBackgroundLayers], | 
					
						
							|  |  |  |             (layer) => layer.id | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* | 
					
						
							|  |  |  |         * Selects a different background layer if the background layer has no coverage at the current location | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         new BackgroundLayerResetter( | 
					
						
							|  |  |  |             this.backgroundLayer, | 
					
						
							|  |  |  |             this.locationControl, | 
					
						
							|  |  |  |             this.availableBackgroundLayers, | 
					
						
							|  |  |  |             this.layoutToUse.defaultBackgroundId | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const attr = new Attribution( | 
					
						
							|  |  |  |             this.locationControl, | 
					
						
							|  |  |  |             this.osmConnection.userDetails, | 
					
						
							|  |  |  |             this.layoutToUse, | 
					
						
							|  |  |  |             this.currentBounds | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Will write into this.leafletMap
 | 
					
						
							|  |  |  |         this.mainMapObject = Minimap.createMiniMap({ | 
					
						
							|  |  |  |             background: this.backgroundLayer, | 
					
						
							|  |  |  |             location: this.locationControl, | 
					
						
							|  |  |  |             leafletMap: this.leafletMap, | 
					
						
							|  |  |  |             bounds: this.currentBounds, | 
					
						
							|  |  |  |             attribution: attr, | 
					
						
							|  |  |  |             lastClickLocation: this.LastClickLocation | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 14:52:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |         this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({ | 
					
						
							|  |  |  |             config: c, | 
					
						
							| 
									
										
										
										
											2021-10-23 02:46:37 +02:00
										 |  |  |             isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, "" + c.defaultState, "Wether or not the overlay " + c.id + " is shown") | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |         })) | 
					
						
							|  |  |  |         this.filteredLayers = this.InitializeFilteredLayers() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 14:52:11 +02:00
										 |  |  |         this.lockBounds() | 
					
						
							|  |  |  |         this.AddAllOverlaysToMap(this.leafletMap) | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private lockBounds() { | 
					
						
							|  |  |  |         const layout = this.layoutToUse; | 
					
						
							|  |  |  |         if (layout.lockLocation) { | 
					
						
							|  |  |  |             if (layout.lockLocation === true) { | 
					
						
							|  |  |  |                 const tile = Tiles.embedded_tile( | 
					
						
							|  |  |  |                     layout.startLat, | 
					
						
							|  |  |  |                     layout.startLon, | 
					
						
							|  |  |  |                     layout.startZoom - 1 | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 const bounds = Tiles.tile_bounds(tile.z, tile.x, tile.y); | 
					
						
							|  |  |  |                 // We use the bounds to get a sense of distance for this zoom level
 | 
					
						
							|  |  |  |                 const latDiff = bounds[0][0] - bounds[1][0]; | 
					
						
							|  |  |  |                 const lonDiff = bounds[0][1] - bounds[1][1]; | 
					
						
							|  |  |  |                 layout.lockLocation = [ | 
					
						
							|  |  |  |                     [layout.startLat - latDiff, layout.startLon - lonDiff], | 
					
						
							|  |  |  |                     [layout.startLat + latDiff, layout.startLon + lonDiff], | 
					
						
							|  |  |  |                 ]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             console.warn("Locking the bounds to ", layout.lockLocation); | 
					
						
							|  |  |  |             this.leafletMap.addCallbackAndRunD(map => { | 
					
						
							|  |  |  |                 // @ts-ignore
 | 
					
						
							|  |  |  |                 map.setMaxBounds(layout.lockLocation); | 
					
						
							|  |  |  |                 map.setMinZoom(layout.startZoom); | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-10-15 14:52:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |     private InitializeFilteredLayers() { | 
					
						
							|  |  |  |         // Initialize the filtered layers state
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const layoutToUse = this.layoutToUse; | 
					
						
							|  |  |  |         const empty = [] | 
					
						
							|  |  |  |         const flayers: FilteredLayer[] = []; | 
					
						
							|  |  |  |         for (const layer of layoutToUse.layers) { | 
					
						
							|  |  |  |             let isDisplayed: UIEventSource<boolean> | 
					
						
							|  |  |  |             if (layoutToUse.id === personal.id) { | 
					
						
							|  |  |  |                 isDisplayed = this.osmConnection.GetPreference("personal-theme-layer-" + layer.id + "-enabled") | 
					
						
							|  |  |  |                     .map(value => value === "yes", [], enabled => { | 
					
						
							|  |  |  |                         return enabled ? "yes" : ""; | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2021-10-23 02:46:37 +02:00
										 |  |  |                 isDisplayed = QueryParameters.GetBooleanQueryParameter( | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |                     "layer-" + layer.id, | 
					
						
							|  |  |  |                     "true", | 
					
						
							|  |  |  |                     "Wether or not layer " + layer.id + " is shown" | 
					
						
							| 
									
										
										
										
											2021-10-23 02:46:37 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             const flayer = { | 
					
						
							|  |  |  |                 isDisplayed: isDisplayed, | 
					
						
							|  |  |  |                 layerDef: layer, | 
					
						
							|  |  |  |                 appliedFilters: new UIEventSource<{ filter: FilterConfig, selected: number }[]>([]), | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (layer.filters.length > 0) { | 
					
						
							|  |  |  |                 const filtersPerName = new Map<string, FilterConfig>() | 
					
						
							|  |  |  |                 layer.filters.forEach(f => filtersPerName.set(f.id, f)) | 
					
						
							|  |  |  |                 const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer") | 
					
						
							|  |  |  |                 flayer.appliedFilters.map(filters => { | 
					
						
							|  |  |  |                     filters = filters ?? [] | 
					
						
							|  |  |  |                     return filters.map(f => f.filter.id + "." + f.selected).join(",") | 
					
						
							|  |  |  |                 }, [], textual => { | 
					
						
							|  |  |  |                     if (textual.length === 0) { | 
					
						
							|  |  |  |                         return empty | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return textual.split(",").map(part => { | 
					
						
							|  |  |  |                         const [filterId, selected] = part.split("."); | 
					
						
							|  |  |  |                         return {filter: filtersPerName.get(filterId), selected: Number(selected)} | 
					
						
							|  |  |  |                     }).filter(f => f.filter !== undefined && !isNaN(f.selected)) | 
					
						
							|  |  |  |                 }).syncWith(qp, true) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             flayers.push(flayer); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return new UIEventSource<FilteredLayer[]>(flayers); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 14:52:11 +02:00
										 |  |  |     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { | 
					
						
							|  |  |  |         const initialized = new Set() | 
					
						
							| 
									
										
										
										
											2021-10-15 05:20:02 +02:00
										 |  |  |         for (const overlayToggle of this.overlayToggles) { | 
					
						
							|  |  |  |             new ShowOverlayLayer(overlayToggle.config, leafletMap, overlayToggle.isDisplayed) | 
					
						
							|  |  |  |             initialized.add(overlayToggle.config) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const tileLayerSource of this.layoutToUse.tileLayerSources) { | 
					
						
							|  |  |  |             if (initialized.has(tileLayerSource)) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             new ShowOverlayLayer(tileLayerSource, leafletMap) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |