forked from MapComplete/MapComplete
		
	refactoring: split all the states
This commit is contained in:
		
							parent
							
								
									4d48b1cf2b
								
							
						
					
					
						commit
						8e2f04c0d0
					
				
					 32 changed files with 411 additions and 395 deletions
				
			
		|  | @ -148,7 +148,7 @@ export default class OverpassFeatureSource implements FeatureSource { | |||
|             if (typeof layer === "string") { | ||||
|                 throw "A layer was not expanded!" | ||||
|             } | ||||
|             if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { | ||||
|             if (layer.source === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             if (this.state.locationControl.data.zoom < layer.minzoom) { | ||||
|  |  | |||
|  | @ -7,14 +7,9 @@ import { ElementStorage } from "../ElementStorage" | |||
| import { Utils } from "../../Utils" | ||||
| 
 | ||||
| export default class TitleHandler { | ||||
|     constructor(state: { | ||||
|         selectedElement: Store<any> | ||||
|         layoutToUse: LayoutConfig | ||||
|         allElements: ElementStorage | ||||
|     }) { | ||||
|         const currentTitle: Store<string> = state.selectedElement.map( | ||||
|     constructor(selectedElement: Store<any>, layout: LayoutConfig, allElements: ElementStorage) { | ||||
|         const currentTitle: Store<string> = selectedElement.map( | ||||
|             (selected) => { | ||||
|                 const layout = state.layoutToUse | ||||
|                 const defaultTitle = layout?.title?.txt ?? "MapComplete" | ||||
| 
 | ||||
|                 if (selected === undefined) { | ||||
|  | @ -28,8 +23,7 @@ export default class TitleHandler { | |||
|                     } | ||||
|                     if (layer.source.osmTags.matchesProperties(tags)) { | ||||
|                         const tagsSource = | ||||
|                             state.allElements.getEventSourceById(tags.id) ?? | ||||
|                             new UIEventSource<any>(tags) | ||||
|                             allElements.getEventSourceById(tags.id) ?? new UIEventSource<any>(tags) | ||||
|                         const title = new TagRenderingAnswer(tagsSource, layer.title, {}) | ||||
|                         return ( | ||||
|                             new Combine([defaultTitle, " | ", title]).ConstructElement() | ||||
|  |  | |||
|  | @ -189,7 +189,7 @@ export class BBox { | |||
|     public asGeoJson<T>(properties: T): Feature<Polygon, T> { | ||||
|         return { | ||||
|             type: "Feature", | ||||
|             properties: properties, | ||||
|             properties, | ||||
|             geometry: this.asGeometry(), | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ export default class FeaturePipeline { | |||
|     public readonly relationTracker: RelationsTracker | ||||
|     /** | ||||
|      * Keeps track of all raw OSM-nodes. | ||||
|      * Only initialized if 'type_node' is defined as layer | ||||
|      * Only initialized if `ReplaceGeometryAction` is needed somewhere | ||||
|      */ | ||||
|     public readonly fullNodeDatabase?: FullNodeDatabaseSource | ||||
|     private readonly overpassUpdater: OverpassFeatureSource | ||||
|  | @ -132,14 +132,6 @@ export default class FeaturePipeline { | |||
|             // We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader)
 | ||||
|         } | ||||
| 
 | ||||
|         function handlePriviligedFeatureSource(src: FeatureSourceForLayer & Tiled) { | ||||
|             // Passthrough to passed function, except that it registers as well
 | ||||
|             handleFeatureSource(src) | ||||
|             src.features.addCallbackAndRunD((fs) => { | ||||
|                 fs.forEach((ff) => state.allElements.addOrGetElement(<any>ff)) | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         for (const filteredLayer of state.filteredLayers.data) { | ||||
|             const id = filteredLayer.layerDef.id | ||||
|             const source = filteredLayer.layerDef.source | ||||
|  | @ -160,36 +152,6 @@ export default class FeaturePipeline { | |||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             if (id === "selected_element") { | ||||
|                 handlePriviligedFeatureSource(state.selectedElementsLayer) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             if (id === "gps_location") { | ||||
|                 handlePriviligedFeatureSource(state.currentUserLocation) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             if (id === "gps_location_history") { | ||||
|                 handlePriviligedFeatureSource(state.historicalUserLocations) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             if (id === "gps_track") { | ||||
|                 handlePriviligedFeatureSource(state.historicalUserLocationsTrack) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             if (id === "home_location") { | ||||
|                 handlePriviligedFeatureSource(state.homeLocation) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             if (id === "current_view") { | ||||
|                 handlePriviligedFeatureSource(state.currentView) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             const localTileSaver = new SaveTileToLocalStorageActor(filteredLayer) | ||||
|             this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver) | ||||
| 
 | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" | |||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import ShowDataLayer from "../../UI/Map/ShowDataLayer" | ||||
| 
 | ||||
| export default class FeaturePipelineState extends MapState { | ||||
| export default class FeaturePipelineState { | ||||
|     /** | ||||
|      * The piece of code which fetches data from various sources and shows it on the background map | ||||
|      */ | ||||
|  | @ -27,8 +27,6 @@ export default class FeaturePipelineState extends MapState { | |||
|     >() | ||||
| 
 | ||||
|     constructor(layoutToUse: LayoutConfig) { | ||||
|         super(layoutToUse) | ||||
| 
 | ||||
|         const clustering = layoutToUse?.clustering | ||||
|         this.featureAggregator = TileHierarchyAggregator.createHierarchy(this) | ||||
|         const clusterCounter = this.featureAggregator | ||||
|  |  | |||
							
								
								
									
										145
									
								
								Logic/State/LayerState.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								Logic/State/LayerState.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,145 @@ | |||
| import { UIEventSource } from "../UIEventSource" | ||||
| import { GlobalFilter } from "../../Models/GlobalFilter" | ||||
| import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import { OsmConnection } from "../Osm/OsmConnection" | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import { QueryParameters } from "../Web/QueryParameters" | ||||
| 
 | ||||
| /** | ||||
|  * The layer state keeps track of: | ||||
|  * - Which layers are enabled | ||||
|  * - Which filters are used, including 'global' filters | ||||
|  */ | ||||
| export default class LayerState { | ||||
|     /** | ||||
|      * Filters which apply onto all layers | ||||
|      */ | ||||
|     public readonly globalFilters: UIEventSource<GlobalFilter[]> = new UIEventSource( | ||||
|         [], | ||||
|         "globalFilters" | ||||
|     ) | ||||
| 
 | ||||
|     /** | ||||
|      * Which layers are enabled in the current theme and what filters are applied onto them | ||||
|      */ | ||||
|     public readonly filteredLayers: Map<string, FilteredLayer> | ||||
|     private readonly osmConnection: OsmConnection | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param osmConnection | ||||
|      * @param layers | ||||
|      * @param context: the context, probably the name of the theme. Used to disambiguate the upstream user preference | ||||
|      */ | ||||
|     constructor(osmConnection: OsmConnection, layers: LayerConfig[], context: string) { | ||||
|         this.osmConnection = osmConnection | ||||
|         this.filteredLayers = new Map() | ||||
|         for (const layer of layers) { | ||||
|             this.filteredLayers.set(layer.id, this.initFilteredLayer(layer, context)) | ||||
|         } | ||||
|         layers.forEach((l) => this.linkFilterStates(l)) | ||||
|     } | ||||
| 
 | ||||
|     private static getPref( | ||||
|         osmConnection: OsmConnection, | ||||
|         key: string, | ||||
|         layer: LayerConfig | ||||
|     ): UIEventSource<boolean> { | ||||
|         return osmConnection.GetPreference(key, layer.shownByDefault + "").sync( | ||||
|             (v) => { | ||||
|                 if (v === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return v === "true" | ||||
|             }, | ||||
|             [], | ||||
|             (b) => { | ||||
|                 if (b === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return "" + b | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|     /** | ||||
|      * INitializes a filtered layer for the given layer. | ||||
|      * @param layer | ||||
|      * @param context: probably the theme-name. This is used to disambiguate the user settings; e.g. when using the same layer in different contexts | ||||
|      * @private | ||||
|      */ | ||||
|     private initFilteredLayer(layer: LayerConfig, context: string): FilteredLayer | undefined { | ||||
|         let isDisplayed: UIEventSource<boolean> | ||||
|         const osmConnection = this.osmConnection | ||||
|         if (layer.syncSelection === "local") { | ||||
|             isDisplayed = LocalStorageSource.GetParsed( | ||||
|                 context + "-layer-" + layer.id + "-enabled", | ||||
|                 layer.shownByDefault | ||||
|             ) | ||||
|         } else if (layer.syncSelection === "theme-only") { | ||||
|             isDisplayed = LayerState.getPref( | ||||
|                 osmConnection, | ||||
|                 context + "-layer-" + layer.id + "-enabled", | ||||
|                 layer | ||||
|             ) | ||||
|         } else if (layer.syncSelection === "global") { | ||||
|             isDisplayed = LayerState.getPref(osmConnection, "layer-" + layer.id + "-enabled", layer) | ||||
|         } else { | ||||
|             isDisplayed = QueryParameters.GetBooleanQueryParameter( | ||||
|                 "layer-" + layer.id, | ||||
|                 layer.shownByDefault, | ||||
|                 "Wether or not layer " + layer.id + " is shown" | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         const flayer: FilteredLayer = { | ||||
|             isDisplayed, | ||||
|             layerDef: layer, | ||||
|             appliedFilters: new UIEventSource<Map<string, FilterState>>( | ||||
|                 new Map<string, FilterState>() | ||||
|             ), | ||||
|         } | ||||
|         layer.filters?.forEach((filterConfig) => { | ||||
|             const stateSrc = filterConfig.initState() | ||||
| 
 | ||||
|             stateSrc.addCallbackAndRun((state) => | ||||
|                 flayer.appliedFilters.data.set(filterConfig.id, state) | ||||
|             ) | ||||
|             flayer.appliedFilters | ||||
|                 .map((dict) => dict.get(filterConfig.id)) | ||||
|                 .addCallback((state) => stateSrc.setData(state)) | ||||
|         }) | ||||
| 
 | ||||
|         return flayer | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Some layers copy the filter state of another layer - this is quite often the case for 'sibling'-layers, | ||||
|      * (where two variations of the same layer are used, e.g. a specific type of shop on all zoom levels and all shops on high zoom). | ||||
|      * | ||||
|      * This methods links those states for the given layer | ||||
|      */ | ||||
|     private linkFilterStates(layer: LayerConfig) { | ||||
|         if (layer.filterIsSameAs === undefined) { | ||||
|             return | ||||
|         } | ||||
|         const toReuse = this.filteredLayers.get(layer.filterIsSameAs) | ||||
|         if (toReuse === undefined) { | ||||
|             throw ( | ||||
|                 "Error in layer " + | ||||
|                 layer.id + | ||||
|                 ": it defines that it should be use the filters of " + | ||||
|                 layer.filterIsSameAs + | ||||
|                 ", but this layer was not loaded" | ||||
|             ) | ||||
|         } | ||||
|         console.warn( | ||||
|             "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs | ||||
|         ) | ||||
|         this.filteredLayers.set(layer.id, { | ||||
|             isDisplayed: toReuse.isDisplayed, | ||||
|             layerDef: layer, | ||||
|             appliedFilters: toReuse.appliedFilters, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | @ -1,31 +1,19 @@ | |||
| import { Store, UIEventSource } from "../UIEventSource" | ||||
| import Attribution from "../../UI/BigComponents/Attribution" | ||||
| import BaseUIElement from "../../UI/BaseUIElement" | ||||
| import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" | ||||
| import FilteredLayer from "../../Models/FilteredLayer" | ||||
| import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" | ||||
| import { QueryParameters } from "../Web/QueryParameters" | ||||
| import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" | ||||
| import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import TitleHandler from "../Actors/TitleHandler" | ||||
| import { BBox } from "../BBox" | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" | ||||
| import StaticFeatureSource, { | ||||
|     TiledStaticFeatureSource, | ||||
| } from "../FeatureSource/Sources/StaticFeatureSource" | ||||
| import { OsmConnection } from "../Osm/OsmConnection" | ||||
| import { Feature } from "geojson" | ||||
| import { Map as MlMap } from "maplibre-gl" | ||||
| import { GlobalFilter } from "../../Models/GlobalFilter" | ||||
| import { MapProperties } from "../../Models/MapProperties" | ||||
| import ShowDataLayer from "../../UI/Map/ShowDataLayer" | ||||
| 
 | ||||
| /** | ||||
|  * Contains all the leaflet-map related state | ||||
|  */ | ||||
| export default class MapState { | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Last location where a click was registered | ||||
|      */ | ||||
|  | @ -46,21 +34,6 @@ export default class MapState { | |||
|      */ | ||||
|     public selectedElementsLayer: FeatureSourceForLayer & Tiled | ||||
| 
 | ||||
|     public readonly mainMapObject: BaseUIElement | ||||
| 
 | ||||
|     /** | ||||
|      * Which layers are enabled in the current theme and what filters are applied onto them | ||||
|      */ | ||||
|     public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>( | ||||
|         [], | ||||
|         "filteredLayers" | ||||
|     ) | ||||
| 
 | ||||
|     /** | ||||
|      * Filters which apply onto all layers | ||||
|      */ | ||||
|     public globalFilters: UIEventSource<GlobalFilter[]> = new UIEventSource([], "globalFilters") | ||||
| 
 | ||||
|     /** | ||||
|      * Which overlays are shown | ||||
|      */ | ||||
|  | @ -80,16 +53,6 @@ export default class MapState { | |||
|         this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer) | ||||
|         this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id)) | ||||
| 
 | ||||
|         // 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, | ||||
|         }) | ||||
| 
 | ||||
|         this.overlayToggles = | ||||
|             this.layoutToUse?.tileLayerSources | ||||
|                 ?.filter((c) => c.name !== undefined) | ||||
|  | @ -101,16 +64,11 @@ export default class MapState { | |||
|                         "Wether or not the overlay " + c.id + " is shown" | ||||
|                     ), | ||||
|                 })) ?? [] | ||||
|         this.filteredLayers = new UIEventSource<FilteredLayer[]>( | ||||
|             MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection) | ||||
|         ) | ||||
| 
 | ||||
|         this.AddAllOverlaysToMap(this.leafletMap) | ||||
| 
 | ||||
|         this.initCurrentView() | ||||
|         this.initSelectedElement() | ||||
| 
 | ||||
|         new TitleHandler(this) | ||||
|     } | ||||
| 
 | ||||
|     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { | ||||
|  | @ -128,48 +86,23 @@ export default class MapState { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private initCurrentView() { | ||||
|         let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter( | ||||
|             (l) => l.layerDef.id === "current_view" | ||||
|         )[0] | ||||
| 
 | ||||
|         if (currentViewLayer === undefined) { | ||||
|             // This layer is not needed by the theme and thus unloaded
 | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|     private static initCurrentView(mapproperties: MapProperties): FeatureSource { | ||||
|         let i = 0 | ||||
|         const self = this | ||||
|         const features: Store<Feature[]> = this.currentBounds.map((bounds) => { | ||||
|         const features: Store<Feature[]> = mapproperties.bounds.map((bounds) => { | ||||
|             if (bounds === undefined) { | ||||
|                 return [] | ||||
|             } | ||||
|             i++ | ||||
|             const feature = { | ||||
|                 type: "Feature", | ||||
|                 properties: { | ||||
|             return [ | ||||
|                 bounds.asGeoJson({ | ||||
|                     id: "current_view-" + i, | ||||
|                     current_view: "yes", | ||||
|                     zoom: "" + self.locationControl.data.zoom, | ||||
|                 }, | ||||
|                 geometry: { | ||||
|                     type: "Polygon", | ||||
|                     coordinates: [ | ||||
|                         [ | ||||
|                             [bounds.maxLon, bounds.maxLat], | ||||
|                             [bounds.minLon, bounds.maxLat], | ||||
|                             [bounds.minLon, bounds.minLat], | ||||
|                             [bounds.maxLon, bounds.minLat], | ||||
|                             [bounds.maxLon, bounds.maxLat], | ||||
|                         ], | ||||
|                     ], | ||||
|                 }, | ||||
|             } | ||||
|             return [feature] | ||||
|                     zoom: "" + mapproperties.zoom.data, | ||||
|                 }), | ||||
|             ] | ||||
|         }) | ||||
| 
 | ||||
|         this.currentView = new TiledStaticFeatureSource(features, currentViewLayer) | ||||
|         return new StaticFeatureSource(features) | ||||
|     } | ||||
| 
 | ||||
|     private initSelectedElement() { | ||||
|  | @ -197,113 +130,4 @@ export default class MapState { | |||
|         }) | ||||
|         this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef) | ||||
|     } | ||||
| 
 | ||||
|     private static getPref( | ||||
|         osmConnection: OsmConnection, | ||||
|         key: string, | ||||
|         layer: LayerConfig | ||||
|     ): UIEventSource<boolean> { | ||||
|         return osmConnection.GetPreference(key, layer.shownByDefault + "").sync( | ||||
|             (v) => { | ||||
|                 if (v === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return v === "true" | ||||
|             }, | ||||
|             [], | ||||
|             (b) => { | ||||
|                 if (b === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return "" + b | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     public static InitializeFilteredLayers( | ||||
|         layoutToUse: { layers: LayerConfig[]; id: string }, | ||||
|         osmConnection: OsmConnection | ||||
|     ): FilteredLayer[] { | ||||
|         if (layoutToUse === undefined) { | ||||
|             return [] | ||||
|         } | ||||
|         const flayers: FilteredLayer[] = [] | ||||
|         for (const layer of layoutToUse.layers) { | ||||
|             let isDisplayed: UIEventSource<boolean> | ||||
|             if (layer.syncSelection === "local") { | ||||
|                 isDisplayed = LocalStorageSource.GetParsed( | ||||
|                     layoutToUse.id + "-layer-" + layer.id + "-enabled", | ||||
|                     layer.shownByDefault | ||||
|                 ) | ||||
|             } else if (layer.syncSelection === "theme-only") { | ||||
|                 isDisplayed = MapState.getPref( | ||||
|                     osmConnection, | ||||
|                     layoutToUse.id + "-layer-" + layer.id + "-enabled", | ||||
|                     layer | ||||
|                 ) | ||||
|             } else if (layer.syncSelection === "global") { | ||||
|                 isDisplayed = MapState.getPref( | ||||
|                     osmConnection, | ||||
|                     "layer-" + layer.id + "-enabled", | ||||
|                     layer | ||||
|                 ) | ||||
|             } else { | ||||
|                 isDisplayed = QueryParameters.GetBooleanQueryParameter( | ||||
|                     "layer-" + layer.id, | ||||
|                     layer.shownByDefault, | ||||
|                     "Wether or not layer " + layer.id + " is shown" | ||||
|                 ) | ||||
|             } | ||||
| 
 | ||||
|             const flayer: FilteredLayer = { | ||||
|                 isDisplayed, | ||||
|                 layerDef: layer, | ||||
|                 appliedFilters: new UIEventSource<Map<string, FilterState>>( | ||||
|                     new Map<string, FilterState>() | ||||
|                 ), | ||||
|             } | ||||
|             layer.filters.forEach((filterConfig) => { | ||||
|                 const stateSrc = filterConfig.initState() | ||||
| 
 | ||||
|                 stateSrc.addCallbackAndRun((state) => | ||||
|                     flayer.appliedFilters.data.set(filterConfig.id, state) | ||||
|                 ) | ||||
|                 flayer.appliedFilters | ||||
|                     .map((dict) => dict.get(filterConfig.id)) | ||||
|                     .addCallback((state) => stateSrc.setData(state)) | ||||
|             }) | ||||
| 
 | ||||
|             flayers.push(flayer) | ||||
|         } | ||||
| 
 | ||||
|         for (const layer of layoutToUse.layers) { | ||||
|             if (layer.filterIsSameAs === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             const toReuse = flayers.find((l) => l.layerDef.id === layer.filterIsSameAs) | ||||
|             if (toReuse === undefined) { | ||||
|                 throw ( | ||||
|                     "Error in layer " + | ||||
|                     layer.id + | ||||
|                     ": it defines that it should be use the filters of " + | ||||
|                     layer.filterIsSameAs + | ||||
|                     ", but this layer was not loaded" | ||||
|                 ) | ||||
|             } | ||||
|             console.warn( | ||||
|                 "Linking filter and isDisplayed-states of " + | ||||
|                     layer.id + | ||||
|                     " and " + | ||||
|                     layer.filterIsSameAs | ||||
|             ) | ||||
|             const selfLayer = flayers.findIndex((l) => l.layerDef.id === layer.id) | ||||
|             flayers[selfLayer] = { | ||||
|                 isDisplayed: toReuse.isDisplayed, | ||||
|                 layerDef: layer, | ||||
|                 appliedFilters: toReuse.appliedFilters, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return flayers | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import Locale from "../../UI/i18n/Locale" | |||
| import { Changes } from "../Osm/Changes" | ||||
| import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" | ||||
| import FeatureSource from "../FeatureSource/FeatureSource" | ||||
| import { Feature } from "geojson" | ||||
| 
 | ||||
| /** | ||||
|  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, | ||||
|  | @ -182,7 +183,7 @@ export default class UserRelatedState { | |||
| 
 | ||||
|     private initHomeLocation(): FeatureSource { | ||||
|         const empty = [] | ||||
|         const feature = Stores.ListStabilized( | ||||
|         const feature: Store<Feature[]> = Stores.ListStabilized( | ||||
|             this.osmConnection.userDetails.map((userDetails) => { | ||||
|                 if (userDetails === undefined) { | ||||
|                     return undefined | ||||
|  | @ -198,8 +199,7 @@ export default class UserRelatedState { | |||
|                 return empty | ||||
|             } | ||||
|             return [ | ||||
|                 { | ||||
|                     feature: { | ||||
|                 <Feature>{ | ||||
|                     type: "Feature", | ||||
|                     properties: { | ||||
|                         id: "home", | ||||
|  | @ -212,8 +212,6 @@ export default class UserRelatedState { | |||
|                         coordinates: homeLonLat, | ||||
|                     }, | ||||
|                 }, | ||||
|                     freshness: new Date(), | ||||
|                 }, | ||||
|             ] | ||||
|         }) | ||||
|         return new StaticFeatureSource(feature) | ||||
|  |  | |||
|  | @ -26,31 +26,30 @@ export default class Constants { | |||
|         // Doesn't support nwr: "https://overpass.openstreetmap.fr/api/interpreter"
 | ||||
|     ] | ||||
| 
 | ||||
|     public static readonly added_by_default: string[] = [ | ||||
|     public static readonly added_by_default = [ | ||||
|         "selected_element", | ||||
|         "gps_location", | ||||
|         "gps_location_history", | ||||
|         "home_location", | ||||
|         "gps_track", | ||||
|     ] | ||||
|     public static readonly no_include: string[] = [ | ||||
|         "range", | ||||
|     ] as const | ||||
|     /** | ||||
|      * Special layers which are not included in a theme by default | ||||
|      */ | ||||
|     public static readonly no_include = [ | ||||
|         "conflation", | ||||
|         "left_right_style", | ||||
|         "split_point", | ||||
|         "current_view", | ||||
|         "matchpoint", | ||||
|     ] | ||||
|     ] as const | ||||
|     /** | ||||
|      * Layer IDs of layers which have special properties through built-in hooks | ||||
|      */ | ||||
|     public static readonly priviliged_layers: string[] = [ | ||||
|     public static readonly priviliged_layers = [ | ||||
|         ...Constants.added_by_default, | ||||
|         "type_node", | ||||
|         "note", | ||||
|         "import_candidate", | ||||
|         "direction", | ||||
|         ...Constants.no_include, | ||||
|     ] | ||||
|     ] as const | ||||
| 
 | ||||
|     // The user journey states thresholds when a new feature gets unlocked
 | ||||
|     public static userJourney = { | ||||
|  |  | |||
|  | @ -255,7 +255,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> { | |||
|         const creator = new CreateNoteImportLayer() | ||||
|         for (let i1 = 0; i1 < allLayers.length; i1++) { | ||||
|             const layer = allLayers[i1] | ||||
|             if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { | ||||
|             if (layer.source === undefined) { | ||||
|                 // Priviliged layers are skipped
 | ||||
|                 continue | ||||
|             } | ||||
|  | @ -600,7 +600,7 @@ class PreparePersonalTheme extends DesugaringStep<LayoutConfigJson> { | |||
|         // All other preparations are done by the 'override-all'-block in personal.json
 | ||||
| 
 | ||||
|         json.layers = Array.from(this._state.sharedLayers.keys()) | ||||
|             .filter((l) => Constants.priviliged_layers.indexOf(l) < 0) | ||||
|             .filter((l) => this._state.sharedLayers.get(l).source !== null) | ||||
|             .filter((l) => this._state.publicLayers.has(l)) | ||||
|         return { | ||||
|             result: json, | ||||
|  |  | |||
|  | @ -845,7 +845,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|                 } | ||||
| 
 | ||||
|                 if (json.description === undefined) { | ||||
|                     if (Constants.priviliged_layers.indexOf(json.id) >= 0) { | ||||
|                     if (typeof json.source === null) { | ||||
|                         errors.push(context + ": A priviliged layer must have a description") | ||||
|                     } else { | ||||
|                         warnings.push(context + ": A builtin layer should have a description") | ||||
|  | @ -882,6 +882,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> { | |||
|             } | ||||
| 
 | ||||
|             if (json.presets !== undefined) { | ||||
|                 if (typeof json.source === "string") { | ||||
|                     throw "A special layer cannot have presets" | ||||
|                 } | ||||
|                 // Check that a preset will be picked up by the layer itself
 | ||||
|                 const baseTags = TagUtils.Tag(json.source.osmTags) | ||||
|                 for (let i = 0; i < json.presets.length; i++) { | ||||
|  |  | |||
|  | @ -77,4 +77,15 @@ export default interface PointRenderingConfigJson { | |||
|      * A snippet of css-classes. They can be space-separated | ||||
|      */ | ||||
|     cssClasses?: string | TagRenderingConfigJson | ||||
| 
 | ||||
|     /** | ||||
|      * If the map is pitched, the marker will stay parallel to the screen. | ||||
|      * Set to 'map' if you want to put it flattened on the map | ||||
|      */ | ||||
|     pitchAlignment?: "canvas" | "map" | TagRenderingConfigJson | ||||
| 
 | ||||
|     /** | ||||
|      * If the map is rotated, the icon will still point to the north if no rotation was applied | ||||
|      */ | ||||
|     rotationAlignment?: "map" | "canvas" | TagRenderingConfigJson | ||||
| } | ||||
|  |  | |||
|  | @ -105,7 +105,6 @@ export default class LayerConfig extends WithContextLoader { | |||
|             throw `${context}: The id of a layer should match [a-z0-9-_]*: ${json.id}` | ||||
|         } | ||||
| 
 | ||||
|         this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 | ||||
|         if ( | ||||
|             json.syncSelection !== undefined && | ||||
|             LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0 | ||||
|  | @ -120,9 +119,10 @@ export default class LayerConfig extends WithContextLoader { | |||
|             ) | ||||
|         } | ||||
|         this.syncSelection = json.syncSelection ?? "no" | ||||
|         if (typeof json.source !== "string") { | ||||
|             this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 | ||||
|             const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags") | ||||
| 
 | ||||
|         if (Constants.priviliged_layers.indexOf(this.id) < 0 && osmTags.isNegative()) { | ||||
|             if (osmTags.isNegative()) { | ||||
|                 throw ( | ||||
|                     context + | ||||
|                     "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + | ||||
|  | @ -130,14 +130,6 @@ export default class LayerConfig extends WithContextLoader { | |||
|                 ) | ||||
|             } | ||||
| 
 | ||||
|         if (json.source["geoJsonSource"] !== undefined) { | ||||
|             throw context + "Use 'geoJson' instead of 'geoJsonSource'" | ||||
|         } | ||||
| 
 | ||||
|         if (json.source["geojson"] !== undefined) { | ||||
|             throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)" | ||||
|         } | ||||
| 
 | ||||
|             this.source = new SourceConfig( | ||||
|                 { | ||||
|                     osmTags: osmTags, | ||||
|  | @ -148,9 +140,17 @@ export default class LayerConfig extends WithContextLoader { | |||
|                     mercatorCrs: json.source["mercatorCrs"], | ||||
|                     idKey: json.source["idKey"], | ||||
|                 }, | ||||
|             Constants.priviliged_layers.indexOf(this.id) > 0, | ||||
|                 json.id | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         if (json.source["geoJsonSource"] !== undefined) { | ||||
|             throw context + "Use 'geoJson' instead of 'geoJsonSource'" | ||||
|         } | ||||
| 
 | ||||
|         if (json.source["geojson"] !== undefined) { | ||||
|             throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)" | ||||
|         } | ||||
| 
 | ||||
|         this.allowSplit = json.allowSplit ?? false | ||||
|         this.name = Translations.T(json.name, translationContext + ".name") | ||||
|  | @ -597,7 +597,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|         } | ||||
| 
 | ||||
|         let overpassLink: BaseUIElement = undefined | ||||
|         if (Constants.priviliged_layers.indexOf(this.id) < 0) { | ||||
|         if (this.source !== undefined) { | ||||
|             try { | ||||
|                 overpassLink = new Link( | ||||
|                     "Execute on overpass", | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import Img from "../../UI/Base/Img" | |||
| import Combine from "../../UI/Base/Combine" | ||||
| import { VariableUiElement } from "../../UI/Base/VariableUIElement" | ||||
| import { OsmTags } from "../OsmFeature" | ||||
| import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" | ||||
| 
 | ||||
| export default class PointRenderingConfig extends WithContextLoader { | ||||
|     private static readonly allowed_location_codes = new Set<string>([ | ||||
|  | @ -32,6 +33,8 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|     public readonly rotation: TagRenderingConfig | ||||
|     public readonly cssDef: TagRenderingConfig | ||||
|     public readonly cssClasses?: TagRenderingConfig | ||||
|     public readonly pitchAlignment?: TagRenderingConfig | ||||
|     public readonly rotationAlignment?: TagRenderingConfig | ||||
| 
 | ||||
|     constructor(json: PointRenderingConfigJson, context: string) { | ||||
|         super(json, context) | ||||
|  | @ -88,6 +91,14 @@ export default class PointRenderingConfig extends WithContextLoader { | |||
|         this.iconSize = this.tr("iconSize", "40,40,center") | ||||
|         this.label = this.tr("label", undefined) | ||||
|         this.rotation = this.tr("rotation", "0") | ||||
|         if (json.pitchAlignment) { | ||||
|             console.log("Got a pitch alignment!", json.pitchAlignment) | ||||
|         } | ||||
|         this.pitchAlignment = this.tr("pitchAlignment", "canvas") | ||||
|         this.rotationAlignment = this.tr( | ||||
|             "rotationAlignment", | ||||
|             json.pitchAlignment === "map" ? "map" : "canvas" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -20,7 +20,6 @@ export default class SourceConfig { | |||
|             geojsonSourceLevel?: number | ||||
|             idKey?: string | ||||
|         }, | ||||
|         isSpecialLayer: boolean, | ||||
|         context?: string | ||||
|     ) { | ||||
|         let defined = 0 | ||||
|  | @ -51,7 +50,7 @@ export default class SourceConfig { | |||
|                 throw `Source defines a geojson-zoomLevel, but does not specify {x} nor {y} (or equivalent), this is probably a bug (in context ${context})` | ||||
|             } | ||||
|         } | ||||
|         if (params.osmTags !== undefined && !isSpecialLayer) { | ||||
|         if (params.osmTags !== undefined) { | ||||
|             const optimized = params.osmTags.optimize() | ||||
|             if (optimized === false) { | ||||
|                 throw ( | ||||
|  |  | |||
|  | @ -228,7 +228,7 @@ export class DownloadPanel extends Toggle { | |||
|             new Set(neededLayers) | ||||
|         ) | ||||
|         for (const tile of featureList) { | ||||
|             if (Constants.priviliged_layers.indexOf(tile.layer) >= 0) { | ||||
|             if (tile.layer !== undefined) { | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -31,7 +31,6 @@ export class GeolocationControl extends VariableUiElement { | |||
|                     return false | ||||
|                 } | ||||
|                 const timeDiff = (new Date().getTime() - date.getTime()) / 1000 | ||||
|                 console.log("Timediff", timeDiff) | ||||
|                 return timeDiff <= Constants.zoomToLocationTimeout | ||||
|             } | ||||
|         ) | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ export class MapPreview | |||
|         } | ||||
| 
 | ||||
|         const availableLayers = AllKnownLayouts.AllPublicLayers().filter( | ||||
|             (l) => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0 | ||||
|             (l) => l.name !== undefined && l.source !== undefined | ||||
|         ) | ||||
|         const layerPicker = new DropDown( | ||||
|             t.selectLayer, | ||||
|  |  | |||
|  | @ -14,12 +14,12 @@ import Constants from "../../Models/Constants" | |||
|  */ | ||||
| export class MapLibreAdaptor implements MapProperties { | ||||
|     private static maplibre_control_handlers = [ | ||||
|         "scrollZoom", | ||||
|         "boxZoom", | ||||
|         // "scrollZoom",
 | ||||
|         // "boxZoom",
 | ||||
|         // "doubleClickZoom",
 | ||||
|         "dragRotate", | ||||
|         "dragPan", | ||||
|         "keyboard", | ||||
|         "doubleClickZoom", | ||||
|         "touchZoomRotate", | ||||
|     ] | ||||
|     readonly location: UIEventSource<{ lon: number; lat: number }> | ||||
|  |  | |||
|  | @ -8,12 +8,13 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" | |||
| import { OsmTags } from "../../Models/OsmFeature" | ||||
| import FeatureSource from "../../Logic/FeatureSource/FeatureSource" | ||||
| import { BBox } from "../../Logic/BBox" | ||||
| import { Feature, LineString } from "geojson" | ||||
| import { Feature } from "geojson" | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen" | ||||
| import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" | ||||
| import { Utils } from "../../Utils" | ||||
| import * as range_layer from "../../assets/layers/range/range.json" | ||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||
| 
 | ||||
| class PointRenderingLayer { | ||||
|     private readonly _config: PointRenderingConfig | ||||
|     private readonly _fetchStore?: (id: string) => Store<OsmTags> | ||||
|  | @ -44,6 +45,16 @@ class PointRenderingLayer { | |||
|         const unseenKeys = new Set(cache.keys()) | ||||
|         for (const location of this._config.location) { | ||||
|             for (const feature of features) { | ||||
|                 if (feature?.geometry === undefined) { | ||||
|                     console.warn( | ||||
|                         "Got an invalid feature:", | ||||
|                         features, | ||||
|                         " while rendering", | ||||
|                         location, | ||||
|                         "of", | ||||
|                         this._config | ||||
|                     ) | ||||
|                 } | ||||
|                 const loc = GeoOperations.featureToCoordinateWithRenderingType( | ||||
|                     <any>feature, | ||||
|                     location | ||||
|  | @ -102,7 +113,14 @@ class PointRenderingLayer { | |||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         return new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map) | ||||
|         const marker = new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map) | ||||
|         store | ||||
|             .map((tags) => this._config.pitchAlignment.GetRenderValue(tags).Subs(tags).txt) | ||||
|             .addCallbackAndRun((pitchAligment) => marker.setPitchAlignment(pitchAligment)) | ||||
|         store | ||||
|             .map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt) | ||||
|             .addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(pitchAligment)) | ||||
|         return marker | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -118,13 +136,17 @@ class LineRenderingLayer { | |||
|         "offset", | ||||
|         "fill", | ||||
|         "fillColor", | ||||
|     ] | ||||
|     ] as const | ||||
| 
 | ||||
|     private static readonly lineConfigKeysColor = ["color", "fillColor"] as const | ||||
|     private static readonly lineConfigKeysNumber = ["width", "offset"] as const | ||||
|     private readonly _map: MlMap | ||||
|     private readonly _config: LineRenderingConfig | ||||
|     private readonly _visibility?: Store<boolean> | ||||
|     private readonly _fetchStore?: (id: string) => Store<OsmTags> | ||||
|     private readonly _onClick?: (id: string) => void | ||||
|     private readonly _layername: string | ||||
|     private readonly _listenerInstalledOn: Set<string> = new Set<string>() | ||||
| 
 | ||||
|     constructor( | ||||
|         map: MlMap, | ||||
|  | @ -145,6 +167,39 @@ class LineRenderingLayer { | |||
|         features.features.addCallbackAndRunD((features) => self.update(features)) | ||||
|     } | ||||
| 
 | ||||
|     private calculatePropsFor( | ||||
|         properties: Record<string, string> | ||||
|     ): Partial<Record<typeof LineRenderingLayer.lineConfigKeys[number], string>> { | ||||
|         const calculatedProps = {} | ||||
|         const config = this._config | ||||
| 
 | ||||
|         for (const key of LineRenderingLayer.lineConfigKeys) { | ||||
|             const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt | ||||
|             calculatedProps[key] = v | ||||
|         } | ||||
|         for (const key of LineRenderingLayer.lineConfigKeysColor) { | ||||
|             let v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt | ||||
|             if (v === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             console.log("Color", v) | ||||
|             if (v.length == 9 && v.startsWith("#")) { | ||||
|                 // This includes opacity
 | ||||
|                 calculatedProps[key + "-opacity"] = parseInt(v.substring(7), 16) / 256 | ||||
|                 v = v.substring(0, 7) | ||||
|                 console.log("Color >", v, calculatedProps[key + "-opacity"]) | ||||
|             } | ||||
|             calculatedProps[key] = v | ||||
|         } | ||||
|         for (const key of LineRenderingLayer.lineConfigKeysNumber) { | ||||
|             const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt | ||||
|             calculatedProps[key] = Number(v) | ||||
|         } | ||||
| 
 | ||||
|         console.log("Calculated props:", calculatedProps, "for", properties.id) | ||||
|         return calculatedProps | ||||
|     } | ||||
| 
 | ||||
|     private async update(features: Feature[]) { | ||||
|         const map = this._map | ||||
|         while (!map.isStyleLoaded()) { | ||||
|  | @ -158,31 +213,14 @@ class LineRenderingLayer { | |||
|             }, | ||||
|             promoteId: "id", | ||||
|         }) | ||||
|         for (let i = 0; i < features.length; i++) { | ||||
|             const feature = features[i] | ||||
|             const id = feature.properties.id ?? "" + i | ||||
|             const tags = this._fetchStore(id) | ||||
|             tags.addCallbackAndRunD((properties) => { | ||||
|                 const config = this._config | ||||
| 
 | ||||
|                 const calculatedProps = {} | ||||
|                 for (const key of LineRenderingLayer.lineConfigKeys) { | ||||
|                     const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt | ||||
|                     calculatedProps[key] = v | ||||
|                 } | ||||
| 
 | ||||
|                 map.setFeatureState({ source: this._layername, id }, calculatedProps) | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         map.addLayer({ | ||||
|             source: this._layername, | ||||
|             id: this._layername + "_line", | ||||
|             type: "line", | ||||
|             filter: ["in", ["geometry-type"], ["literal", ["LineString", "MultiLineString"]]], | ||||
|             layout: {}, | ||||
|             paint: { | ||||
|                 "line-color": ["feature-state", "color"], | ||||
|                 "line-opacity": ["feature-state", "color-opacity"], | ||||
|                 "line-width": ["feature-state", "width"], | ||||
|                 "line-offset": ["feature-state", "offset"], | ||||
|             }, | ||||
|  | @ -205,12 +243,49 @@ class LineRenderingLayer { | |||
|             layout: {}, | ||||
|             paint: { | ||||
|                 "fill-color": ["feature-state", "fillColor"], | ||||
|                 "fill-opacity": 0.1, | ||||
|             }, | ||||
|         }) | ||||
| 
 | ||||
|         for (let i = 0; i < features.length; i++) { | ||||
|             const feature = features[i] | ||||
|             const id = feature.properties.id ?? feature.id | ||||
|             console.log("ID is", id) | ||||
|             if (id === undefined) { | ||||
|                 console.trace( | ||||
|                     "Got a feature without ID; this causes rendering bugs:", | ||||
|                     feature, | ||||
|                     "from" | ||||
|                 ) | ||||
|                 continue | ||||
|             } | ||||
|             if (this._listenerInstalledOn.has(id)) { | ||||
|                 continue | ||||
|             } | ||||
|             if (this._fetchStore === undefined) { | ||||
|                 map.setFeatureState( | ||||
|                     { source: this._layername, id }, | ||||
|                     this.calculatePropsFor(feature.properties) | ||||
|                 ) | ||||
|             } else { | ||||
|                 const tags = this._fetchStore(id) | ||||
|                 this._listenerInstalledOn.add(id) | ||||
|                 tags.addCallbackAndRunD((properties) => { | ||||
|                     map.setFeatureState( | ||||
|                         { source: this._layername, id }, | ||||
|                         this.calculatePropsFor(properties) | ||||
|                     ) | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default class ShowDataLayer { | ||||
|     private static rangeLayer = new LayerConfig( | ||||
|         <LayerConfigJson>range_layer, | ||||
|         "ShowDataLayer.ts:range.json" | ||||
|     ) | ||||
|     private readonly _map: Store<MlMap> | ||||
|     private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } | ||||
|     private readonly _popupCache: Map<string, ScrollableFullScreen> | ||||
|  | @ -223,11 +298,6 @@ export default class ShowDataLayer { | |||
|         map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) | ||||
|     } | ||||
| 
 | ||||
|     private static rangeLayer = new LayerConfig( | ||||
|         <LayerConfigJson>range_layer, | ||||
|         "ShowDataLayer.ts:range.json" | ||||
|     ) | ||||
| 
 | ||||
|     public static showRange( | ||||
|         map: Store<MlMap>, | ||||
|         features: FeatureSource, | ||||
|  | @ -241,6 +311,9 @@ export default class ShowDataLayer { | |||
|     } | ||||
| 
 | ||||
|     private openOrReusePopup(id: string): void { | ||||
|         if (!this._popupCache || !this._options.fetchStore) { | ||||
|             return | ||||
|         } | ||||
|         if (this._popupCache.has(id)) { | ||||
|             this._popupCache.get(id).Activate() | ||||
|             return | ||||
|  | @ -267,11 +340,12 @@ export default class ShowDataLayer { | |||
|     private initDrawFeatures(map: MlMap) { | ||||
|         const { features, doShowLayer, fetchStore, buildPopup } = this._options | ||||
|         const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id) | ||||
|         for (const lineRenderingConfig of this._options.layer.lineRendering) { | ||||
|         for (let i = 0; i < this._options.layer.lineRendering.length; i++) { | ||||
|             const lineRenderingConfig = this._options.layer.lineRendering[i] | ||||
|             new LineRenderingLayer( | ||||
|                 map, | ||||
|                 features, | ||||
|                 "test", | ||||
|                 this._options.layer.id + "_linerendering_" + i, | ||||
|                 lineRenderingConfig, | ||||
|                 doShowLayer, | ||||
|                 fetchStore, | ||||
|  |  | |||
|  | @ -21,11 +21,13 @@ | |||
|   import Svg from "../Svg"; | ||||
|   import If from "./Base/If.svelte"; | ||||
|   import { GeolocationControl } from "./BigComponents/GeolocationControl.js"; | ||||
|   import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; | ||||
|   import { BBox } from "../Logic/BBox"; | ||||
|   import ShowDataLayer from "./Map/ShowDataLayer"; | ||||
|   import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||
| 
 | ||||
|   import type FeatureSource from "../Logic/FeatureSource/FeatureSource"; | ||||
|   import LayerState from "../Logic/State/LayerState"; | ||||
|   import Constants from "../Models/Constants"; | ||||
|   import type { Feature } from "geojson"; | ||||
|   export let layout: LayoutConfig; | ||||
| 
 | ||||
|   const maplibremap: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); | ||||
|  | @ -46,7 +48,7 @@ | |||
|     osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data | ||||
|   }); | ||||
|   const userRelatedState = new UserRelatedState(osmConnection, layout?.language); | ||||
|   const selectedElement = new UIEventSource<any>(undefined, "Selected element"); | ||||
|   const selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element"); | ||||
|   const geolocation = new GeoLocationHandler(geolocationState, selectedElement, mapproperties, userRelatedState.gpsLocationHistoryRetentionTime); | ||||
| 
 | ||||
|   const allElements = new ElementStorage(); | ||||
|  | @ -55,16 +57,19 @@ | |||
|     osmConnection, | ||||
|     historicalUserLocations: geolocation.historicalUserLocations | ||||
|   }, layout?.isLeftRightSensitive() ?? false); | ||||
|    | ||||
|   Map | ||||
|    | ||||
|   console.log("Setting up layerstate...") | ||||
|   const layerState = new LayerState(osmConnection, layout.layers, layout.id) | ||||
|   { | ||||
|     // Various actors that we don't need to reference  | ||||
|     // TODO enable new TitleHandler(selectedElement,layout,allElements) | ||||
|     new ChangeToElementsActor(changes, allElements); | ||||
|     new PendingChangesUploader(changes, selectedElement); | ||||
|     new SelectedElementTagsUpdater({ | ||||
|       allElements, changes, selectedElement, layoutToUse: layout, osmConnection | ||||
|     }); | ||||
|      | ||||
|      | ||||
|      | ||||
|     // Various initial setup | ||||
|     userRelatedState.markLayoutAsVisited(layout); | ||||
|     if(layout?.lockLocation){ | ||||
|  | @ -77,6 +82,36 @@ | |||
|       ) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     type AddedByDefaultTypes = typeof Constants.added_by_default[number] | ||||
|     /** | ||||
|      * A listing which maps the layerId onto the featureSource | ||||
|      */ | ||||
|     const empty = [] | ||||
|     const specialLayers : Record<AddedByDefaultTypes | "current_view", FeatureSource> = { | ||||
|       "home_location": userRelatedState.homeLocation, | ||||
|       gps_location: geolocation.currentUserLocation, | ||||
|       gps_location_history: geolocation.historicalUserLocations, | ||||
|       gps_track: geolocation.historicalUserLocationsTrack, | ||||
|       selected_element: new StaticFeatureSource(selectedElement.map(f => f === undefined ? empty : [f])), | ||||
|       range: new StaticFeatureSource(mapproperties.maxbounds.map(bbox => bbox === undefined ? empty : <Feature[]> [bbox.asGeoJson({id:"range"})])) , | ||||
|       current_view: new StaticFeatureSource(mapproperties.bounds.map(bbox => bbox === undefined ? empty : <Feature[]> [bbox.asGeoJson({id:"current_view"})])), | ||||
|     } | ||||
|     layerState.filteredLayers.get("range")?.isDisplayed?.syncWith(featureSwitches.featureSwitchIsTesting, true) | ||||
| console.log("RAnge fs", specialLayers.range) | ||||
|     specialLayers.range.features.addCallbackAndRun(fs => console.log("Range.features:", JSON.stringify(fs))) | ||||
|     layerState.filteredLayers.forEach((flayer) => { | ||||
|       const features = specialLayers[flayer.layerDef.id] | ||||
|       if(features === undefined){ | ||||
|         return | ||||
|       } | ||||
|       new ShowDataLayer(maplibremap, { | ||||
|         features, | ||||
|         doShowLayer: flayer.isDisplayed, | ||||
|         layer: flayer.layerDef, | ||||
|         selectedElement | ||||
|       }) | ||||
|     }) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
|  | @ -93,15 +128,12 @@ | |||
| </div> | ||||
| 
 | ||||
| <div class="absolute bottom-0 right-0 mb-4 mr-4"> | ||||
| 
 | ||||
|   <If condition={mapproperties.allowMoving}> | ||||
|   <MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}> | ||||
|     <ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte> | ||||
|   </MapControlButton> | ||||
|   <MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}> | ||||
|     <ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte> | ||||
|   </MapControlButton> | ||||
|   </If> | ||||
|   <If condition={featureSwitches.featureSwitchGeolocation}> | ||||
|     <MapControlButton> | ||||
|       <ToSvelte construct={() => new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte> | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
|         ] | ||||
|       }, | ||||
|       "iconSize": "40,40,center", | ||||
|       "pitchAlignment": "map", | ||||
|       "rotation": { | ||||
|         "render": "0deg", | ||||
|         "mappings": [ | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "id": "import_candidate", | ||||
|   "description": "Layer used in the importHelper", | ||||
|   "description": "Layer used as template in the importHelper", | ||||
|   "source":"special", | ||||
|   "mapRendering": [ | ||||
|     { | ||||
|  |  | |||
|  | @ -6,9 +6,9 @@ | |||
|   "name": null, | ||||
|   "mapRendering": [ | ||||
|     { | ||||
|       "width": 4, | ||||
|       "width": 3, | ||||
|       "fill": "no", | ||||
|       "color": "#ff000088" | ||||
|       "color": "#cc00cc" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  |  | |||
|  | @ -2,9 +2,7 @@ | |||
|   "id": "split_point", | ||||
|   "description": "Layer rendering the little scissors for the minimap in the 'splitRoadWizard'", | ||||
|   "minzoom": 1, | ||||
|   "source": { | ||||
|     "osmTags": "_split_point=yes" | ||||
|   }, | ||||
|   "source": "special", | ||||
|   "name": "Split point", | ||||
|   "title": "Split point", | ||||
|   "mapRendering": [ | ||||
|  |  | |||
|  | @ -1,10 +0,0 @@ | |||
| { | ||||
|   "id": "type_node", | ||||
|   "description": "This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only.", | ||||
|   "minzoom": 18, | ||||
|   "source": "special", | ||||
|   "mapRendering": null, | ||||
|   "name": "All OSM Nodes", | ||||
|   "title": "OSM node {id}", | ||||
|   "tagRendering": [] | ||||
| } | ||||
|  | @ -28,29 +28,6 @@ | |||
|     "minzoom": 19 | ||||
|   }, | ||||
|   "layers": [ | ||||
|     { | ||||
|       "builtin": "type_node", | ||||
|       "override": { | ||||
|         "calculatedTags": [ | ||||
|           "_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false", | ||||
|           "_is_part_of_grb_building=feat.get('parent_ways')?.some(p => p['source:geometry:ref'] !== undefined) ?? false", | ||||
|           "_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false", | ||||
|           "_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)", | ||||
|           "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false", | ||||
|           "_moveable=feat.get('_is_part_of_building') && !feat.get('_is_part_of_grb_building')" | ||||
|         ], | ||||
|         "mapRendering": [ | ||||
|           { | ||||
|             "icon": "square:#cc0", | ||||
|             "iconSize": "5,5,center", | ||||
|             "location": [ | ||||
|               "point" | ||||
|             ] | ||||
|           } | ||||
|         ], | ||||
|         "passAllFeatures": true | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "id": "osm-buildings", | ||||
|       "name": "All OSM-buildings", | ||||
|  |  | |||
|  | @ -114,7 +114,7 @@ function GenLayerOverviewText(): BaseUIElement { | |||
|     } | ||||
| 
 | ||||
|     const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter( | ||||
|         (layer) => Constants.priviliged_layers.indexOf(layer.id) < 0 | ||||
|         (layer) => layer.source === null | ||||
|     ) | ||||
| 
 | ||||
|     const builtinLayerIds: Set<string> = new Set<string>() | ||||
|  | @ -183,7 +183,7 @@ function GenOverviewsForSingleLayer( | |||
|     callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void | ||||
| ): void { | ||||
|     const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter( | ||||
|         (layer) => Constants.priviliged_layers.indexOf(layer.id) < 0 | ||||
|         (layer) => layer.source !== null | ||||
|     ) | ||||
|     const builtinLayerIds: Set<string> = new Set<string>() | ||||
|     allLayers.forEach((l) => builtinLayerIds.add(l.id)) | ||||
|  | @ -195,7 +195,7 @@ function GenOverviewsForSingleLayer( | |||
|         } | ||||
| 
 | ||||
|         for (const layer of layout.layers) { | ||||
|             if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { | ||||
|             if (layer.source === null) { | ||||
|                 continue | ||||
|             } | ||||
|             if (builtinLayerIds.has(layer.id)) { | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ async function main(includeTags = true) { | |||
|         if (layer.source["geoJson"] !== undefined && !layer.source["isOsmCache"]) { | ||||
|             continue | ||||
|         } | ||||
|         if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { | ||||
|         if (layer.source == null || typeof layer.source === "string") { | ||||
|             continue | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -132,7 +132,7 @@ function generateLayerUsage(layer: LayerConfig, layout: LayoutConfig): any[] { | |||
| function generateTagInfoEntry(layout: LayoutConfig): any { | ||||
|     const usedTags = [] | ||||
|     for (const layer of layout.layers) { | ||||
|         if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { | ||||
|         if (layer.source === null) { | ||||
|             continue | ||||
|         } | ||||
|         if (layer.source.geojsonSource !== undefined && layer.source.isOsmCacheLayer !== true) { | ||||
|  |  | |||
							
								
								
									
										7
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										7
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -3,11 +3,12 @@ import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" | |||
| import { FixedUiElement } from "./UI/Base/FixedUiElement" | ||||
| import { QueryParameters } from "./Logic/Web/QueryParameters" | ||||
| import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts" | ||||
| 
 | ||||
| import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" | ||||
| import * as benches from "./assets/generated/themes/benches.json" | ||||
| async function main() { | ||||
|     new FixedUiElement("Determining layout...").AttachTo("maindiv") | ||||
|     const qp = QueryParameters.GetQueryParameter("layout", "benches") | ||||
|     const layout = new AllKnownLayoutsLazy().get(qp.data) | ||||
|     const qp = QueryParameters.GetQueryParameter("layout", "") | ||||
|     const layout = new LayoutConfig(<any>benches, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 | ||||
|     console.log("Using layout", layout.id) | ||||
|     new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue