| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | import LayoutConfig from "./ThemeConfig/LayoutConfig" | 
					
						
							|  |  |  | import { SpecialVisualizationState } from "../UI/SpecialVisualization" | 
					
						
							|  |  |  | import { Changes } from "../Logic/Osm/Changes" | 
					
						
							|  |  |  | import { Store, UIEventSource } from "../Logic/UIEventSource" | 
					
						
							| 
									
										
										
										
											2023-07-18 01:26:04 +02:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     FeatureSource, | 
					
						
							|  |  |  |     IndexedFeatureSource, | 
					
						
							|  |  |  |     WritableFeatureSource, | 
					
						
							|  |  |  | } from "../Logic/FeatureSource/FeatureSource" | 
					
						
							|  |  |  | import { OsmConnection } from "../Logic/Osm/OsmConnection" | 
					
						
							|  |  |  | import { ExportableMap, MapProperties } from "./MapProperties" | 
					
						
							|  |  |  | import LayerState from "../Logic/State/LayerState" | 
					
						
							|  |  |  | import { Feature, Point, Polygon } from "geojson" | 
					
						
							|  |  |  | import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" | 
					
						
							|  |  |  | import { Map as MlMap } from "maplibre-gl" | 
					
						
							|  |  |  | import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" | 
					
						
							|  |  |  | import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor" | 
					
						
							|  |  |  | import { GeoLocationState } from "../Logic/State/GeoLocationState" | 
					
						
							|  |  |  | import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | 
					
						
							|  |  |  | import { QueryParameters } from "../Logic/Web/QueryParameters" | 
					
						
							|  |  |  | import UserRelatedState from "../Logic/State/UserRelatedState" | 
					
						
							|  |  |  | import LayerConfig from "./ThemeConfig/LayerConfig" | 
					
						
							|  |  |  | import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" | 
					
						
							|  |  |  | import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers" | 
					
						
							|  |  |  | import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" | 
					
						
							|  |  |  | import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" | 
					
						
							|  |  |  | import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" | 
					
						
							|  |  |  | import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" | 
					
						
							|  |  |  | import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" | 
					
						
							|  |  |  | import ShowDataLayer from "../UI/Map/ShowDataLayer" | 
					
						
							|  |  |  | import TitleHandler from "../Logic/Actors/TitleHandler" | 
					
						
							|  |  |  | import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor" | 
					
						
							|  |  |  | import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader" | 
					
						
							|  |  |  | import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater" | 
					
						
							|  |  |  | import { BBox } from "../Logic/BBox" | 
					
						
							|  |  |  | import Constants from "./Constants" | 
					
						
							|  |  |  | import Hotkeys from "../UI/Base/Hotkeys" | 
					
						
							|  |  |  | import Translations from "../UI/i18n/Translations" | 
					
						
							|  |  |  | import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" | 
					
						
							|  |  |  | import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" | 
					
						
							|  |  |  | import { MenuState } from "./MenuState" | 
					
						
							|  |  |  | import MetaTagging from "../Logic/MetaTagging" | 
					
						
							|  |  |  | import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" | 
					
						
							|  |  |  | import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" | 
					
						
							|  |  |  | import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" | 
					
						
							|  |  |  | import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" | 
					
						
							|  |  |  | import { Utils } from "../Utils" | 
					
						
							|  |  |  | import { EliCategory } from "./RasterLayerProperties" | 
					
						
							|  |  |  | import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" | 
					
						
							|  |  |  | import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" | 
					
						
							|  |  |  | import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | 
					
						
							|  |  |  | import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" | 
					
						
							|  |  |  | import NoElementsInViewDetector, { | 
					
						
							|  |  |  |     FeatureViewState, | 
					
						
							|  |  |  | } from "../Logic/Actors/NoElementsInViewDetector" | 
					
						
							|  |  |  | import FilteredLayer from "./FilteredLayer" | 
					
						
							|  |  |  | import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" | 
					
						
							|  |  |  | import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" | 
					
						
							|  |  |  | import { Imgur } from "../Logic/ImageProviders/Imgur" | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  | import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource" | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  | import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" | 
					
						
							| 
									
										
										
										
											2023-12-05 18:35:18 +01:00
										 |  |  | import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider" | 
					
						
							| 
									
										
										
										
											2023-12-18 01:30:02 +01:00
										 |  |  | import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl" | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * The themeviewState contains all the state needed for the themeViewGUI. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This is pretty much the 'brain' or the HQ of MapComplete | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * It ties up all the needed elements and starts some actors. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export default class ThemeViewState implements SpecialVisualizationState { | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     readonly layout: LayoutConfig | 
					
						
							|  |  |  |     readonly map: UIEventSource<MlMap> | 
					
						
							|  |  |  |     readonly changes: Changes | 
					
						
							|  |  |  |     readonly featureSwitches: FeatureSwitchState | 
					
						
							|  |  |  |     readonly featureSwitchIsTesting: Store<boolean> | 
					
						
							|  |  |  |     readonly featureSwitchUserbadge: Store<boolean> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     readonly featureProperties: FeaturePropertiesStore | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     readonly osmConnection: OsmConnection | 
					
						
							|  |  |  |     readonly selectedElement: UIEventSource<Feature> | 
					
						
							|  |  |  |     readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> | 
					
						
							| 
									
										
										
										
											2023-12-22 18:50:22 +01:00
										 |  |  |     readonly mapProperties: MapLibreAdaptor & MapProperties & ExportableMap | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     readonly osmObjectDownloader: OsmObjectDownloader | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     readonly dataIsLoading: Store<boolean> | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Indicates if there is _some_ data in view, even if it is not shown due to the filters | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     readonly hasDataInView: Store<FeatureViewState> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     readonly guistate: MenuState | 
					
						
							|  |  |  |     readonly fullNodeDatabase?: FullNodeDatabaseSource | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     readonly historicalUserLocations: WritableFeatureSource<Feature<Point>> | 
					
						
							|  |  |  |     readonly indexedFeatures: IndexedFeatureSource & LayoutSource | 
					
						
							|  |  |  |     readonly currentView: FeatureSource<Feature<Polygon>> | 
					
						
							|  |  |  |     readonly featuresInView: FeatureSource | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |     readonly favourites: FavouritesFeatureSource | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Contains a few (<10) >features that are near the center of the map. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |     readonly closestFeatures: NearbyFeatureSource | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     readonly newFeatures: WritableFeatureSource | 
					
						
							|  |  |  |     readonly layerState: LayerState | 
					
						
							|  |  |  |     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | 
					
						
							|  |  |  |     readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     readonly availableLayers: Store<RasterLayerPolygon[]> | 
					
						
							|  |  |  |     readonly selectedLayer: UIEventSource<LayerConfig> | 
					
						
							|  |  |  |     readonly userRelatedState: UserRelatedState | 
					
						
							|  |  |  |     readonly geolocation: GeoLocationHandler | 
					
						
							| 
									
										
										
										
											2023-12-18 01:30:02 +01:00
										 |  |  |     readonly geolocationControl: GeolocationControlState | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     readonly imageUploadManager: ImageUploadManager | 
					
						
							| 
									
										
										
										
											2023-12-05 18:35:18 +01:00
										 |  |  |     readonly previewedImage = new UIEventSource<ProvidedImage>(undefined) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     readonly addNewPoint: UIEventSource<boolean> = new UIEventSource<boolean>(false) | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * When using arrow keys to move, the accessibility mode is activated, which has a small rectangle set. | 
					
						
							|  |  |  |      * This is the 'viewport' which 'closestFeatures' uses to filter wilt | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     readonly visualFeedbackViewportBounds: UIEventSource<BBox> = new UIEventSource<BBox>(undefined) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     readonly lastClickObject: LastClickFeatureSource | 
					
						
							|  |  |  |     readonly overlayLayerStates: ReadonlyMap< | 
					
						
							|  |  |  |         string, | 
					
						
							|  |  |  |         { readonly isDisplayed: UIEventSource<boolean> } | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * All 'level'-tags that are available with the current features | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     readonly floors: Store<string[]> | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * If true, the user interface will toggle some extra aids for people using screenreaders and keyboard navigation | 
					
						
							|  |  |  |      * Triggered by navigating the map with arrows or by pressing 'space' or 'enter' | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public readonly visualFeedback: UIEventSource<boolean> = new UIEventSource<boolean>(false) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     private readonly newPointDialog: FilteredLayer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor(layout: LayoutConfig) { | 
					
						
							|  |  |  |         Utils.initDomPurify() | 
					
						
							|  |  |  |         this.layout = layout | 
					
						
							|  |  |  |         this.featureSwitches = new FeatureSwitchState(layout) | 
					
						
							|  |  |  |         this.guistate = new MenuState( | 
					
						
							|  |  |  |             this.featureSwitches.featureSwitchWelcomeMessage.data, | 
					
						
							|  |  |  |             layout.id | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         this.map = new UIEventSource<MlMap>(undefined) | 
					
						
							|  |  |  |         const initial = new InitialMapPositioning(layout) | 
					
						
							|  |  |  |         this.mapProperties = new MapLibreAdaptor(this.map, initial) | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         const geolocationState = new GeoLocationState() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting | 
					
						
							|  |  |  |         this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.osmConnection = new OsmConnection({ | 
					
						
							|  |  |  |             dryRun: this.featureSwitches.featureSwitchIsTesting, | 
					
						
							|  |  |  |             fakeUser: this.featureSwitches.featureSwitchFakeUser.data, | 
					
						
							|  |  |  |             oauth_token: QueryParameters.GetQueryParameter( | 
					
						
							|  |  |  |                 "oauth_token", | 
					
						
							|  |  |  |                 undefined, | 
					
						
							|  |  |  |                 "Used to complete the login" | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         this.userRelatedState = new UserRelatedState( | 
					
						
							|  |  |  |             this.osmConnection, | 
					
						
							|  |  |  |             layout?.language, | 
					
						
							|  |  |  |             layout, | 
					
						
							|  |  |  |             this.featureSwitches, | 
					
						
							|  |  |  |             this.mapProperties | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { | 
					
						
							|  |  |  |             this.mapProperties.allowRotating.setData(fixated !== "yes") | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element") | 
					
						
							|  |  |  |         this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.selectedElementAndLayer = this.selectedElement.mapD( | 
					
						
							|  |  |  |             (feature) => { | 
					
						
							|  |  |  |                 const layer = this.selectedLayer.data | 
					
						
							|  |  |  |                 if (!layer) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return { layer, feature } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             [this.selectedLayer] | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.geolocation = new GeoLocationHandler( | 
					
						
							|  |  |  |             geolocationState, | 
					
						
							|  |  |  |             this.selectedElement, | 
					
						
							|  |  |  |             this.mapProperties, | 
					
						
							|  |  |  |             this.userRelatedState.gpsLocationHistoryRetentionTime | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-12-18 01:30:02 +01:00
										 |  |  |         this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  |         this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) | 
					
						
							| 
									
										
										
										
											2023-04-21 16:02:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 01:26:12 +02:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>() | 
					
						
							|  |  |  |             for (const rasterInfo of this.layout.tileLayerSources) { | 
					
						
							|  |  |  |                 const isDisplayed = QueryParameters.GetBooleanQueryParameter( | 
					
						
							|  |  |  |                     "overlay-" + rasterInfo.id, | 
					
						
							|  |  |  |                     rasterInfo.defaultState ?? true, | 
					
						
							|  |  |  |                     "Wether or not overlayer layer " + rasterInfo.id + " is shown" | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 const state = { isDisplayed } | 
					
						
							|  |  |  |                 overlayLayerStates.set(rasterInfo.id, state) | 
					
						
							|  |  |  |                 new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this.overlayLayerStates = overlayLayerStates | 
					
						
							| 
									
										
										
										
											2023-04-21 16:02:36 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             /* Setup the layout source | 
					
						
							|  |  |  |              * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too | 
					
						
							|  |  |  |              */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) { | 
					
						
							|  |  |  |                 this.fullNodeDatabase = new FullNodeDatabaseSource() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const layoutSource = new LayoutSource( | 
					
						
							|  |  |  |                 layout.layers, | 
					
						
							|  |  |  |                 this.featureSwitches, | 
					
						
							|  |  |  |                 this.mapProperties, | 
					
						
							|  |  |  |                 this.osmConnection.Backend(), | 
					
						
							|  |  |  |                 (id) => self.layerState.filteredLayers.get(id).isDisplayed, | 
					
						
							|  |  |  |                 this.fullNodeDatabase | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let currentViewIndex = 0 | 
					
						
							|  |  |  |             const empty = [] | 
					
						
							|  |  |  |             this.currentView = new StaticFeatureSource( | 
					
						
							|  |  |  |                 this.mapProperties.bounds.map((bbox) => { | 
					
						
							|  |  |  |                     if (!bbox) { | 
					
						
							|  |  |  |                         return empty | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     currentViewIndex++ | 
					
						
							|  |  |  |                     return <Feature[]>[ | 
					
						
							|  |  |  |                         bbox.asGeoJson({ | 
					
						
							|  |  |  |                             zoom: this.mapProperties.zoom.data, | 
					
						
							|  |  |  |                             ...this.mapProperties.location.data, | 
					
						
							|  |  |  |                             id: "current_view_" + currentViewIndex, | 
					
						
							|  |  |  |                         }), | 
					
						
							|  |  |  |                     ] | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             this.dataIsLoading = layoutSource.isLoading | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |             this.indexedFeatures = layoutSource | 
					
						
							|  |  |  |             this.featureProperties = new FeaturePropertiesStore(layoutSource) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             this.changes = new Changes( | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     dryRun: this.featureSwitches.featureSwitchIsTesting, | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |                     allElements: layoutSource, | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |                     featurePropertiesStore: this.featureProperties, | 
					
						
							|  |  |  |                     osmConnection: this.osmConnection, | 
					
						
							|  |  |  |                     historicalUserLocations: this.geolocation.historicalUserLocations, | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 layout?.isLeftRightSensitive() ?? false | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             this.historicalUserLocations = this.geolocation.historicalUserLocations | 
					
						
							|  |  |  |             this.newFeatures = new NewGeometryFromChangesFeatureSource( | 
					
						
							|  |  |  |                 this.changes, | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |                 layoutSource, | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |                 this.featureProperties | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             layoutSource.addSource(this.newFeatures) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const perLayer = new PerLayerFeatureSourceSplitter( | 
					
						
							|  |  |  |                 Array.from(this.layerState.filteredLayers.values()).filter( | 
					
						
							|  |  |  |                     (l) => l.layerDef?.source !== null | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |                 new ChangeGeometryApplicator(this.indexedFeatures, this.changes), | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     constructStore: (features, layer) => | 
					
						
							|  |  |  |                         new GeoIndexedStoreForLayer(features, layer), | 
					
						
							|  |  |  |                     handleLeftovers: (features) => { | 
					
						
							|  |  |  |                         console.warn( | 
					
						
							|  |  |  |                             "Got ", | 
					
						
							|  |  |  |                             features.length, | 
					
						
							|  |  |  |                             "leftover features, such as", | 
					
						
							|  |  |  |                             features[0].properties | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             this.perLayer = perLayer.perLayer | 
					
						
							| 
									
										
										
										
											2023-10-06 03:34:26 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         this.perLayer.forEach((fs) => { | 
					
						
							|  |  |  |             new SaveFeatureSourceToLocalStorage( | 
					
						
							|  |  |  |                 this.osmConnection.Backend(), | 
					
						
							|  |  |  |                 fs.layer.layerDef.id, | 
					
						
							|  |  |  |                 15, | 
					
						
							|  |  |  |                 fs, | 
					
						
							|  |  |  |                 this.featureProperties, | 
					
						
							|  |  |  |                 fs.layer.layerDef.maxAgeOfCache | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         this.newPointDialog = this.layerState.filteredLayers.get("last_click") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.floors = this.featuresInView.features.stabilized(500).map((features) => { | 
					
						
							|  |  |  |             if (!features) { | 
					
						
							|  |  |  |                 return [] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const floors = new Set<string>() | 
					
						
							|  |  |  |             for (const feature of features) { | 
					
						
							|  |  |  |                 let level = feature.properties["_level"] | 
					
						
							|  |  |  |                 if (level) { | 
					
						
							|  |  |  |                     const levels = level.split(";") | 
					
						
							|  |  |  |                     for (const l of levels) { | 
					
						
							|  |  |  |                         floors.add(l) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     floors.add("0") // '0' is the default and is thus _always_ present
 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const sorted = Array.from(floors) | 
					
						
							|  |  |  |             // Sort alphabetically first, to deal with floor "A", "B" and "C"
 | 
					
						
							|  |  |  |             sorted.sort() | 
					
						
							|  |  |  |             sorted.sort((a, b) => { | 
					
						
							|  |  |  |                 // We use the laxer 'parseInt' to deal with floor '1A'
 | 
					
						
							|  |  |  |                 const na = parseInt(a) | 
					
						
							|  |  |  |                 const nb = parseInt(b) | 
					
						
							|  |  |  |                 if (isNaN(na) || isNaN(nb)) { | 
					
						
							|  |  |  |                     return 0 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return na - nb | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             sorted.reverse(/* new list, no side-effects */) | 
					
						
							|  |  |  |             return sorted | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |         this.lastClickObject = new LastClickFeatureSource( | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             this.mapProperties.lastClickLocation, | 
					
						
							|  |  |  |             this.layout | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.osmObjectDownloader = new OsmObjectDownloader( | 
					
						
							|  |  |  |             this.osmConnection.Backend(), | 
					
						
							|  |  |  |             this.changes | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.perLayerFiltered = this.showNormalDataOn(this.map) | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |         this.closestFeatures = new NearbyFeatureSource( | 
					
						
							|  |  |  |             this.mapProperties.location, | 
					
						
							|  |  |  |             this.perLayerFiltered, | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |             { | 
					
						
							|  |  |  |                 currentZoom: this.mapProperties.zoom, | 
					
						
							|  |  |  |                 layerState: this.layerState, | 
					
						
							|  |  |  |                 bounds: this.visualFeedbackViewportBounds, | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView | 
					
						
							|  |  |  |         this.imageUploadManager = new ImageUploadManager( | 
					
						
							|  |  |  |             layout, | 
					
						
							|  |  |  |             Imgur.singleton, | 
					
						
							|  |  |  |             this.featureProperties, | 
					
						
							|  |  |  |             this.osmConnection, | 
					
						
							|  |  |  |             this.changes | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |         this.favourites = new FavouritesFeatureSource(this) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.initActors() | 
					
						
							|  |  |  |         this.drawSpecialLayers() | 
					
						
							|  |  |  |         this.initHotkeys() | 
					
						
							|  |  |  |         this.miscSetup() | 
					
						
							| 
									
										
										
										
											2023-11-03 02:24:33 +01:00
										 |  |  |         this.focusOnMap() | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         if (!Utils.runningFromConsole) { | 
					
						
							|  |  |  |             console.log("State setup completed", this) | 
					
						
							| 
									
										
										
										
											2023-10-09 01:26:12 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-03 02:24:33 +01:00
										 |  |  |     /* By focussing on the map, the keyboard panning and zoom with '+' and '+' works */ | 
					
						
							|  |  |  |     public focusOnMap() { | 
					
						
							|  |  |  |         if (this.map.data) { | 
					
						
							|  |  |  |             this.map.data.getCanvas().focus() | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |             console.log("Focused on map") | 
					
						
							| 
									
										
										
										
											2023-11-03 02:24:33 +01:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.map.addCallbackAndRunD((map) => { | 
					
						
							|  |  |  |             map.on("load", () => { | 
					
						
							|  |  |  |                 map.getCanvas().focus() | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             return true | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> { | 
					
						
							|  |  |  |         const filteringFeatureSource = new Map<string, FilteringFeatureSource>() | 
					
						
							|  |  |  |         this.perLayer.forEach((fs, layerName) => { | 
					
						
							|  |  |  |             const doShowLayer = this.mapProperties.zoom.map( | 
					
						
							|  |  |  |                 (z) => | 
					
						
							|  |  |  |                     (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), | 
					
						
							|  |  |  |                 [fs.layer.isDisplayed] | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { | 
					
						
							|  |  |  |                 /* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined) | 
					
						
							|  |  |  |                  * | 
					
						
							|  |  |  |                  * This means that we don't have to filter it, nor do we have to display it | 
					
						
							|  |  |  |                  * | 
					
						
							|  |  |  |                  * Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden. | 
					
						
							|  |  |  |                  * However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer! | 
					
						
							|  |  |  |                  * */ | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const filtered = new FilteringFeatureSource( | 
					
						
							|  |  |  |                 fs.layer, | 
					
						
							|  |  |  |                 fs, | 
					
						
							|  |  |  |                 (id) => this.featureProperties.getStore(id), | 
					
						
							|  |  |  |                 this.layerState.globalFilters | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             filteringFeatureSource.set(layerName, filtered) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             new ShowDataLayer(map, { | 
					
						
							|  |  |  |                 layer: fs.layer.layerDef, | 
					
						
							|  |  |  |                 features: filtered, | 
					
						
							|  |  |  |                 doShowLayer, | 
					
						
							| 
									
										
										
										
											2023-11-14 17:35:12 +01:00
										 |  |  |                 metaTags: this.userRelatedState.preferencesAsTags, | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |                 selectedElement: this.selectedElement, | 
					
						
							|  |  |  |                 selectedLayer: this.selectedLayer, | 
					
						
							|  |  |  |                 fetchStore: (id) => this.featureProperties.getStore(id), | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         return filteringFeatureSource | 
					
						
							| 
									
										
										
										
											2023-05-01 01:12:04 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |     public openNewDialog() { | 
					
						
							|  |  |  |         this.selectedLayer.setData(undefined) | 
					
						
							|  |  |  |         this.selectedElement.setData(undefined) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const { lon, lat } = this.mapProperties.location.data | 
					
						
							|  |  |  |         const feature = this.lastClickObject.createFeature(lon, lat) | 
					
						
							|  |  |  |         this.featureProperties.trackFeature(feature) | 
					
						
							|  |  |  |         this.selectedElement.setData(feature) | 
					
						
							|  |  |  |         this.selectedLayer.setData(this.newPointDialog.layerDef) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Various small methods that need to be called | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private miscSetup() { | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |         this.mapProperties.onKeyNavigationEvent((keyEvent) => { | 
					
						
							|  |  |  |             if (["north", "east", "south", "west"].indexOf(keyEvent.key) >= 0) { | 
					
						
							|  |  |  |                 this.visualFeedback.setData(true) | 
					
						
							|  |  |  |                 return true // Our job is done, unregister
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         this.userRelatedState.markLayoutAsVisited(this.layout) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.selectedElement.addCallbackAndRunD((feature) => { | 
					
						
							|  |  |  |             // As soon as we have a selected element, we clear the selected element
 | 
					
						
							|  |  |  |             // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
 | 
					
						
							|  |  |  |             // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
 | 
					
						
							|  |  |  |             if (feature.properties.id === "last_click") { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this.lastClickObject.features.setData([]) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { | 
					
						
							|  |  |  |             Utils.LoadCustomCss(this.layout.customCss) | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Selects the feature that is 'i' closest to the map center | 
					
						
							|  |  |  |      * @param i | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private selectClosestAtCenter(i: number = 0) { | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |         this.visualFeedback.setData(true) | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |         const toSelect = this.closestFeatures.features?.data?.[i] | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |         if (!toSelect) { | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |             window.requestAnimationFrame(() => { | 
					
						
							|  |  |  |                 const toSelect = this.closestFeatures.features?.data?.[i] | 
					
						
							|  |  |  |                 if (!toSelect) { | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const layer = this.layout.getMatchingLayer(toSelect.properties) | 
					
						
							|  |  |  |                 this.selectedElement.setData(undefined) | 
					
						
							|  |  |  |                 this.selectedLayer.setData(layer) | 
					
						
							|  |  |  |                 this.selectedElement.setData(toSelect) | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const layer = this.layout.getMatchingLayer(toSelect.properties) | 
					
						
							|  |  |  |         this.selectedElement.setData(undefined) | 
					
						
							|  |  |  |         this.selectedLayer.setData(layer) | 
					
						
							|  |  |  |         this.selectedElement.setData(toSelect) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     private initHotkeys() { | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |         const docs = Translations.t.hotkeyDocumentation | 
					
						
							|  |  |  |         Hotkeys.RegisterHotkey({ nomod: "Escape", onUp: true }, docs.closeSidebar, () => { | 
					
						
							|  |  |  |             if (this.previewedImage.data !== undefined) { | 
					
						
							|  |  |  |                 this.previewedImage.setData(undefined) | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |             this.selectedElement.setData(undefined) | 
					
						
							|  |  |  |             this.guistate.closeAll() | 
					
						
							|  |  |  |             this.focusOnMap() | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |         Hotkeys.RegisterHotkey({ nomod: "f" }, docs.selectFavourites, () => { | 
					
						
							|  |  |  |             this.guistate.menuViewTab.setData("favourites") | 
					
						
							|  |  |  |             this.guistate.menuIsOpened.setData(true) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |         Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 nomod: " ", | 
					
						
							|  |  |  |                 onUp: true, | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |             docs.selectItem, | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |             () => { | 
					
						
							|  |  |  |                 if (this.selectedElement.data !== undefined) { | 
					
						
							|  |  |  |                     return false | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-12-16 01:29:42 +01:00
										 |  |  |                 if ( | 
					
						
							|  |  |  |                     this.guistate.menuIsOpened.data || | 
					
						
							|  |  |  |                     this.guistate.themeIsOpened.data || | 
					
						
							|  |  |  |                     this.previewedImage.data !== undefined | 
					
						
							|  |  |  |                 ) { | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |                 this.selectClosestAtCenter(0) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (let i = 1; i < 9; i++) { | 
					
						
							| 
									
										
										
										
											2023-12-20 21:56:16 +01:00
										 |  |  |             let doc = docs.selectItemI.Subs({ i }) | 
					
						
							|  |  |  |             if (i === 1) { | 
					
						
							|  |  |  |                 doc = docs.selectItem | 
					
						
							|  |  |  |             } else if (i === 2) { | 
					
						
							|  |  |  |                 doc = docs.selectItem2 | 
					
						
							|  |  |  |             } else if (i === 3) { | 
					
						
							|  |  |  |                 doc = docs.selectItem3 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |                     nomod: "" + i, | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |                     onUp: true, | 
					
						
							|  |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2023-12-20 21:56:16 +01:00
										 |  |  |                 doc, | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |                 () => this.selectClosestAtCenter(i - 1) | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-12-15 01:46:01 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-11-16 03:32:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         this.featureSwitches.featureSwitchBackgroundSelection.addCallbackAndRun((enable) => { | 
					
						
							|  |  |  |             if (!enable) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     nomod: "b", | 
					
						
							|  |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |                 docs.openLayersPanel, | 
					
						
							| 
									
										
										
										
											2023-12-09 15:28:11 +01:00
										 |  |  |                 () => { | 
					
						
							|  |  |  |                     if (this.featureSwitches.featureSwitchBackgroundSelection.data) { | 
					
						
							|  |  |  |                         this.guistate.backgroundLayerSelectionIsOpened.setData(true) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     nomod: "s", | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 Translations.t.hotkeyDocumentation.openFilterPanel, | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |                 () => { | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |                     console.log("S pressed") | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |                     if (this.featureSwitches.featureSwitchFilter.data) { | 
					
						
							|  |  |  |                         this.guistate.openFilterView() | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { shift: "O" }, | 
					
						
							|  |  |  |                 Translations.t.hotkeyDocumentation.selectMapnik, | 
					
						
							|  |  |  |                 () => { | 
					
						
							|  |  |  |                     this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) | 
					
						
							| 
									
										
										
										
											2023-10-11 01:41:42 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             ) | 
					
						
							|  |  |  |             const setLayerCategory = (category: EliCategory) => { | 
					
						
							|  |  |  |                 const available = this.availableLayers.data | 
					
						
							|  |  |  |                 const current = this.mapProperties.rasterLayer | 
					
						
							|  |  |  |                 const best = RasterLayerUtils.SelectBestLayerAccordingTo( | 
					
						
							|  |  |  |                     available, | 
					
						
							|  |  |  |                     category, | 
					
						
							|  |  |  |                     current.data | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 console.log("Best layer for category", category, "is", best.properties.id) | 
					
						
							|  |  |  |                 current.setData(best) | 
					
						
							| 
									
										
										
										
											2023-10-11 01:41:42 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { nomod: "O" }, | 
					
						
							|  |  |  |                 Translations.t.hotkeyDocumentation.selectOsmbasedmap, | 
					
						
							|  |  |  |                 () => setLayerCategory("osmbasedmap") | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { nomod: "M" }, | 
					
						
							|  |  |  |                 Translations.t.hotkeyDocumentation.selectMap, | 
					
						
							|  |  |  |                 () => setLayerCategory("map") | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { nomod: "P" }, | 
					
						
							|  |  |  |                 Translations.t.hotkeyDocumentation.selectAerial, | 
					
						
							|  |  |  |                 () => setLayerCategory("photo") | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-12-18 01:30:02 +01:00
										 |  |  |             Hotkeys.RegisterHotkey( | 
					
						
							|  |  |  |                 { nomod: "L" }, | 
					
						
							|  |  |  |                 Translations.t.hotkeyDocumentation.geolocate, | 
					
						
							|  |  |  |                 () => { | 
					
						
							|  |  |  |                     this.geolocationControl.handleClick() | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             return true | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 01:26:12 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |      * Add the special layers to the map | 
					
						
							| 
									
										
										
										
											2023-10-09 01:26:12 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     private drawSpecialLayers() { | 
					
						
							|  |  |  |         type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] | 
					
						
							|  |  |  |         const empty = [] | 
					
						
							|  |  |  |         /** | 
					
						
							|  |  |  |          * A listing which maps the layerId onto the featureSource | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         const specialLayers: Record< | 
					
						
							|  |  |  |             Exclude<AddedByDefaultTypes, "last_click"> | "current_view", | 
					
						
							|  |  |  |             FeatureSource | 
					
						
							|  |  |  |         > = { | 
					
						
							|  |  |  |             home_location: this.userRelatedState.homeLocation, | 
					
						
							|  |  |  |             gps_location: this.geolocation.currentUserLocation, | 
					
						
							|  |  |  |             gps_location_history: this.geolocation.historicalUserLocations, | 
					
						
							|  |  |  |             gps_track: this.geolocation.historicalUserLocationsTrack, | 
					
						
							|  |  |  |             selected_element: new StaticFeatureSource( | 
					
						
							|  |  |  |                 this.selectedElement.map((f) => (f === undefined ? empty : [f])) | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             range: new StaticFeatureSource( | 
					
						
							|  |  |  |                 this.mapProperties.maxbounds.map((bbox) => | 
					
						
							|  |  |  |                     bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })] | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             current_view: this.currentView, | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |             favourite: this.favourites, | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.closestFeatures.registerSource(specialLayers.favourite, "favourite") | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         if (this.layout?.lockLocation) { | 
					
						
							| 
									
										
										
										
											2023-12-20 02:50:08 +01:00
										 |  |  |             const bbox = new BBox(<any>this.layout.lockLocation) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             this.mapProperties.maxbounds.setData(bbox) | 
					
						
							|  |  |  |             ShowDataLayer.showRange( | 
					
						
							|  |  |  |                 this.map, | 
					
						
							|  |  |  |                 new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]), | 
					
						
							|  |  |  |                 this.featureSwitches.featureSwitchIsTesting | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") | 
					
						
							|  |  |  |         if (currentViewLayer?.tagRenderings?.length > 0) { | 
					
						
							|  |  |  |             const params = MetaTagging.createExtraFuncParams(this) | 
					
						
							|  |  |  |             this.featureProperties.trackFeatureSource(specialLayers.current_view) | 
					
						
							|  |  |  |             specialLayers.current_view.features.addCallbackAndRunD((features) => { | 
					
						
							|  |  |  |                 MetaTagging.addMetatags( | 
					
						
							|  |  |  |                     features, | 
					
						
							|  |  |  |                     params, | 
					
						
							|  |  |  |                     currentViewLayer, | 
					
						
							|  |  |  |                     this.layout, | 
					
						
							|  |  |  |                     this.osmObjectDownloader, | 
					
						
							|  |  |  |                     this.featureProperties | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range") | 
					
						
							|  |  |  |         const rangeIsDisplayed = rangeFLayer?.isDisplayed | 
					
						
							|  |  |  |         if ( | 
					
						
							| 
									
										
										
										
											2023-12-06 12:25:47 +01:00
										 |  |  |             rangeFLayer && | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |             rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |         // enumarate all 'normal' layers and match them with the appropriate 'special' layer - if applicable
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         this.layerState.filteredLayers.forEach((flayer) => { | 
					
						
							|  |  |  |             const id = flayer.layerDef.id | 
					
						
							|  |  |  |             const features: FeatureSource = specialLayers[id] | 
					
						
							|  |  |  |             if (features === undefined) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-11-22 19:39:19 +01:00
										 |  |  |             if (id === "favourite") { | 
					
						
							|  |  |  |                 console.log("Matching special layer", id, flayer) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             this.featureProperties.trackFeatureSource(features) | 
					
						
							|  |  |  |             new ShowDataLayer(this.map, { | 
					
						
							|  |  |  |                 features, | 
					
						
							|  |  |  |                 doShowLayer: flayer.isDisplayed, | 
					
						
							|  |  |  |                 layer: flayer.layerDef, | 
					
						
							| 
									
										
										
										
											2023-11-14 17:35:12 +01:00
										 |  |  |                 metaTags: this.userRelatedState.preferencesAsTags, | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |                 selectedElement: this.selectedElement, | 
					
						
							|  |  |  |                 selectedLayer: this.selectedLayer, | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Setup various services for which no reference are needed | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private initActors() { | 
					
						
							|  |  |  |         // Unselect the selected element if it is panned out of view
 | 
					
						
							|  |  |  |         this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { | 
					
						
							|  |  |  |             const selected = this.selectedElement.data | 
					
						
							|  |  |  |             if (selected === undefined) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const bbox = BBox.get(selected) | 
					
						
							|  |  |  |             if (!bbox.overlapsWith(bounds)) { | 
					
						
							|  |  |  |                 this.selectedElement.setData(undefined) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.selectedElement.addCallback((selected) => { | 
					
						
							|  |  |  |             if (selected === undefined) { | 
					
						
							|  |  |  |                 // We did _unselect_ an item - we always remove the lastclick-object
 | 
					
						
							|  |  |  |                 this.lastClickObject.features.setData([]) | 
					
						
							|  |  |  |                 this.selectedLayer.setData(undefined) | 
					
						
							| 
									
										
										
										
											2023-11-03 02:24:33 +01:00
										 |  |  |                 this.focusOnMap() | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-11-03 02:24:33 +01:00
										 |  |  |         this.guistate.allToggles.forEach((toggle) => { | 
					
						
							|  |  |  |             toggle.toggle.addCallbackD((isOpened) => { | 
					
						
							|  |  |  |                 if (!isOpened) { | 
					
						
							|  |  |  |                     this.focusOnMap() | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-10-16 14:27:05 +02:00
										 |  |  |         new ThemeViewStateHashActor(this) | 
					
						
							|  |  |  |         new MetaTagging(this) | 
					
						
							|  |  |  |         new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) | 
					
						
							|  |  |  |         new ChangeToElementsActor(this.changes, this.featureProperties) | 
					
						
							|  |  |  |         new PendingChangesUploader(this.changes, this.selectedElement) | 
					
						
							|  |  |  |         new SelectedElementTagsUpdater(this) | 
					
						
							|  |  |  |         new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers) | 
					
						
							|  |  |  |         new PreferredRasterLayerSelector( | 
					
						
							|  |  |  |             this.mapProperties.rasterLayer, | 
					
						
							|  |  |  |             this.availableLayers, | 
					
						
							|  |  |  |             this.featureSwitches.backgroundLayerId, | 
					
						
							|  |  |  |             this.userRelatedState.preferredBackgroundLayer | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | } |