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") { |             if (typeof layer === "string") { | ||||||
|                 throw "A layer was not expanded!" |                 throw "A layer was not expanded!" | ||||||
|             } |             } | ||||||
|             if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { |             if (layer.source === undefined) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             if (this.state.locationControl.data.zoom < layer.minzoom) { |             if (this.state.locationControl.data.zoom < layer.minzoom) { | ||||||
|  |  | ||||||
|  | @ -7,14 +7,9 @@ import { ElementStorage } from "../ElementStorage" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| 
 | 
 | ||||||
| export default class TitleHandler { | export default class TitleHandler { | ||||||
|     constructor(state: { |     constructor(selectedElement: Store<any>, layout: LayoutConfig, allElements: ElementStorage) { | ||||||
|         selectedElement: Store<any> |         const currentTitle: Store<string> = selectedElement.map( | ||||||
|         layoutToUse: LayoutConfig |  | ||||||
|         allElements: ElementStorage |  | ||||||
|     }) { |  | ||||||
|         const currentTitle: Store<string> = state.selectedElement.map( |  | ||||||
|             (selected) => { |             (selected) => { | ||||||
|                 const layout = state.layoutToUse |  | ||||||
|                 const defaultTitle = layout?.title?.txt ?? "MapComplete" |                 const defaultTitle = layout?.title?.txt ?? "MapComplete" | ||||||
| 
 | 
 | ||||||
|                 if (selected === undefined) { |                 if (selected === undefined) { | ||||||
|  | @ -28,8 +23,7 @@ export default class TitleHandler { | ||||||
|                     } |                     } | ||||||
|                     if (layer.source.osmTags.matchesProperties(tags)) { |                     if (layer.source.osmTags.matchesProperties(tags)) { | ||||||
|                         const tagsSource = |                         const tagsSource = | ||||||
|                             state.allElements.getEventSourceById(tags.id) ?? |                             allElements.getEventSourceById(tags.id) ?? new UIEventSource<any>(tags) | ||||||
|                             new UIEventSource<any>(tags) |  | ||||||
|                         const title = new TagRenderingAnswer(tagsSource, layer.title, {}) |                         const title = new TagRenderingAnswer(tagsSource, layer.title, {}) | ||||||
|                         return ( |                         return ( | ||||||
|                             new Combine([defaultTitle, " | ", title]).ConstructElement() |                             new Combine([defaultTitle, " | ", title]).ConstructElement() | ||||||
|  |  | ||||||
|  | @ -189,7 +189,7 @@ export class BBox { | ||||||
|     public asGeoJson<T>(properties: T): Feature<Polygon, T> { |     public asGeoJson<T>(properties: T): Feature<Polygon, T> { | ||||||
|         return { |         return { | ||||||
|             type: "Feature", |             type: "Feature", | ||||||
|             properties: properties, |             properties, | ||||||
|             geometry: this.asGeometry(), |             geometry: this.asGeometry(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ export default class FeaturePipeline { | ||||||
|     public readonly relationTracker: RelationsTracker |     public readonly relationTracker: RelationsTracker | ||||||
|     /** |     /** | ||||||
|      * Keeps track of all raw OSM-nodes. |      * 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 |     public readonly fullNodeDatabase?: FullNodeDatabaseSource | ||||||
|     private readonly overpassUpdater: OverpassFeatureSource |     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)
 |             // 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) { |         for (const filteredLayer of state.filteredLayers.data) { | ||||||
|             const id = filteredLayer.layerDef.id |             const id = filteredLayer.layerDef.id | ||||||
|             const source = filteredLayer.layerDef.source |             const source = filteredLayer.layerDef.source | ||||||
|  | @ -160,36 +152,6 @@ export default class FeaturePipeline { | ||||||
|                 continue |                 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) |             const localTileSaver = new SaveTileToLocalStorageActor(filteredLayer) | ||||||
|             this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver) |             this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
| import ShowDataLayer from "../../UI/Map/ShowDataLayer" | 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 |      * 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) { |     constructor(layoutToUse: LayoutConfig) { | ||||||
|         super(layoutToUse) |  | ||||||
| 
 |  | ||||||
|         const clustering = layoutToUse?.clustering |         const clustering = layoutToUse?.clustering | ||||||
|         this.featureAggregator = TileHierarchyAggregator.createHierarchy(this) |         this.featureAggregator = TileHierarchyAggregator.createHierarchy(this) | ||||||
|         const clusterCounter = this.featureAggregator |         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 { Store, UIEventSource } from "../UIEventSource" | ||||||
| import Attribution from "../../UI/BigComponents/Attribution" | import FilteredLayer from "../../Models/FilteredLayer" | ||||||
| import BaseUIElement from "../../UI/BaseUIElement" |  | ||||||
| import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" |  | ||||||
| import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" | import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" | ||||||
| import { QueryParameters } from "../Web/QueryParameters" | import { QueryParameters } from "../Web/QueryParameters" | ||||||
| import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" | import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" | ||||||
| import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" | import FeatureSource, { 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 StaticFeatureSource, { | import StaticFeatureSource, { | ||||||
|     TiledStaticFeatureSource, |     TiledStaticFeatureSource, | ||||||
| } from "../FeatureSource/Sources/StaticFeatureSource" | } from "../FeatureSource/Sources/StaticFeatureSource" | ||||||
| import { OsmConnection } from "../Osm/OsmConnection" |  | ||||||
| import { Feature } from "geojson" | import { Feature } from "geojson" | ||||||
| import { Map as MlMap } from "maplibre-gl" |  | ||||||
| import { GlobalFilter } from "../../Models/GlobalFilter" |  | ||||||
| import { MapProperties } from "../../Models/MapProperties" | import { MapProperties } from "../../Models/MapProperties" | ||||||
| import ShowDataLayer from "../../UI/Map/ShowDataLayer" |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Contains all the leaflet-map related state |  * Contains all the leaflet-map related state | ||||||
|  */ |  */ | ||||||
| export default class MapState { | export default class MapState { | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Last location where a click was registered |      * Last location where a click was registered | ||||||
|      */ |      */ | ||||||
|  | @ -46,21 +34,6 @@ export default class MapState { | ||||||
|      */ |      */ | ||||||
|     public selectedElementsLayer: FeatureSourceForLayer & Tiled |     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 |      * Which overlays are shown | ||||||
|      */ |      */ | ||||||
|  | @ -80,16 +53,6 @@ export default class MapState { | ||||||
|         this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer) |         this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer) | ||||||
|         this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id)) |         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.overlayToggles = | ||||||
|             this.layoutToUse?.tileLayerSources |             this.layoutToUse?.tileLayerSources | ||||||
|                 ?.filter((c) => c.name !== undefined) |                 ?.filter((c) => c.name !== undefined) | ||||||
|  | @ -101,16 +64,11 @@ export default class MapState { | ||||||
|                         "Wether or not the overlay " + c.id + " is shown" |                         "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.AddAllOverlaysToMap(this.leafletMap) | ||||||
| 
 | 
 | ||||||
|         this.initCurrentView() |         this.initCurrentView() | ||||||
|         this.initSelectedElement() |         this.initSelectedElement() | ||||||
| 
 |  | ||||||
|         new TitleHandler(this) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { |     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { | ||||||
|  | @ -128,48 +86,23 @@ export default class MapState { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     private static initCurrentView(mapproperties: MapProperties): FeatureSource { | ||||||
|     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 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let i = 0 |         let i = 0 | ||||||
|         const self = this |         const features: Store<Feature[]> = mapproperties.bounds.map((bounds) => { | ||||||
|         const features: Store<Feature[]> = this.currentBounds.map((bounds) => { |  | ||||||
|             if (bounds === undefined) { |             if (bounds === undefined) { | ||||||
|                 return [] |                 return [] | ||||||
|             } |             } | ||||||
|             i++ |             i++ | ||||||
|             const feature = { |             return [ | ||||||
|                 type: "Feature", |                 bounds.asGeoJson({ | ||||||
|                 properties: { |  | ||||||
|                     id: "current_view-" + i, |                     id: "current_view-" + i, | ||||||
|                     current_view: "yes", |                     current_view: "yes", | ||||||
|                     zoom: "" + self.locationControl.data.zoom, |                     zoom: "" + mapproperties.zoom.data, | ||||||
|                 }, |                 }), | ||||||
|                 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] |  | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         this.currentView = new TiledStaticFeatureSource(features, currentViewLayer) |         return new StaticFeatureSource(features) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private initSelectedElement() { |     private initSelectedElement() { | ||||||
|  | @ -197,113 +130,4 @@ export default class MapState { | ||||||
|         }) |         }) | ||||||
|         this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef) |         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 { Changes } from "../Osm/Changes" | ||||||
| import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" | import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource" | 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, |  * 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 { |     private initHomeLocation(): FeatureSource { | ||||||
|         const empty = [] |         const empty = [] | ||||||
|         const feature = Stores.ListStabilized( |         const feature: Store<Feature[]> = Stores.ListStabilized( | ||||||
|             this.osmConnection.userDetails.map((userDetails) => { |             this.osmConnection.userDetails.map((userDetails) => { | ||||||
|                 if (userDetails === undefined) { |                 if (userDetails === undefined) { | ||||||
|                     return undefined |                     return undefined | ||||||
|  | @ -198,21 +199,18 @@ export default class UserRelatedState { | ||||||
|                 return empty |                 return empty | ||||||
|             } |             } | ||||||
|             return [ |             return [ | ||||||
|                 { |                 <Feature>{ | ||||||
|                     feature: { |                     type: "Feature", | ||||||
|                         type: "Feature", |                     properties: { | ||||||
|                         properties: { |                         id: "home", | ||||||
|                             id: "home", |                         "user:home": "yes", | ||||||
|                             "user:home": "yes", |                         _lon: homeLonLat[0], | ||||||
|                             _lon: homeLonLat[0], |                         _lat: homeLonLat[1], | ||||||
|                             _lat: homeLonLat[1], |                     }, | ||||||
|                         }, |                     geometry: { | ||||||
|                         geometry: { |                         type: "Point", | ||||||
|                             type: "Point", |                         coordinates: homeLonLat, | ||||||
|                             coordinates: homeLonLat, |  | ||||||
|                         }, |  | ||||||
|                     }, |                     }, | ||||||
|                     freshness: new Date(), |  | ||||||
|                 }, |                 }, | ||||||
|             ] |             ] | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|  | @ -26,31 +26,30 @@ export default class Constants { | ||||||
|         // Doesn't support nwr: "https://overpass.openstreetmap.fr/api/interpreter"
 |         // 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", |         "selected_element", | ||||||
|         "gps_location", |         "gps_location", | ||||||
|         "gps_location_history", |         "gps_location_history", | ||||||
|         "home_location", |         "home_location", | ||||||
|         "gps_track", |         "gps_track", | ||||||
|     ] |         "range", | ||||||
|     public static readonly no_include: string[] = [ |     ] as const | ||||||
|  |     /** | ||||||
|  |      * Special layers which are not included in a theme by default | ||||||
|  |      */ | ||||||
|  |     public static readonly no_include = [ | ||||||
|         "conflation", |         "conflation", | ||||||
|         "left_right_style", |  | ||||||
|         "split_point", |         "split_point", | ||||||
|         "current_view", |         "current_view", | ||||||
|         "matchpoint", |         "matchpoint", | ||||||
|     ] |     ] as const | ||||||
|     /** |     /** | ||||||
|      * Layer IDs of layers which have special properties through built-in hooks |      * 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, |         ...Constants.added_by_default, | ||||||
|         "type_node", |  | ||||||
|         "note", |  | ||||||
|         "import_candidate", |  | ||||||
|         "direction", |  | ||||||
|         ...Constants.no_include, |         ...Constants.no_include, | ||||||
|     ] |     ] as const | ||||||
| 
 | 
 | ||||||
|     // The user journey states thresholds when a new feature gets unlocked
 |     // The user journey states thresholds when a new feature gets unlocked
 | ||||||
|     public static userJourney = { |     public static userJourney = { | ||||||
|  |  | ||||||
|  | @ -255,7 +255,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> { | ||||||
|         const creator = new CreateNoteImportLayer() |         const creator = new CreateNoteImportLayer() | ||||||
|         for (let i1 = 0; i1 < allLayers.length; i1++) { |         for (let i1 = 0; i1 < allLayers.length; i1++) { | ||||||
|             const layer = allLayers[i1] |             const layer = allLayers[i1] | ||||||
|             if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { |             if (layer.source === undefined) { | ||||||
|                 // Priviliged layers are skipped
 |                 // Priviliged layers are skipped
 | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|  | @ -600,7 +600,7 @@ class PreparePersonalTheme extends DesugaringStep<LayoutConfigJson> { | ||||||
|         // All other preparations are done by the 'override-all'-block in personal.json
 |         // All other preparations are done by the 'override-all'-block in personal.json
 | ||||||
| 
 | 
 | ||||||
|         json.layers = Array.from(this._state.sharedLayers.keys()) |         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)) |             .filter((l) => this._state.publicLayers.has(l)) | ||||||
|         return { |         return { | ||||||
|             result: json, |             result: json, | ||||||
|  |  | ||||||
|  | @ -845,7 +845,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (json.description === undefined) { |                 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") |                         errors.push(context + ": A priviliged layer must have a description") | ||||||
|                     } else { |                     } else { | ||||||
|                         warnings.push(context + ": A builtin layer should have a description") |                         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 (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
 |                 // Check that a preset will be picked up by the layer itself
 | ||||||
|                 const baseTags = TagUtils.Tag(json.source.osmTags) |                 const baseTags = TagUtils.Tag(json.source.osmTags) | ||||||
|                 for (let i = 0; i < json.presets.length; i++) { |                 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 |      * A snippet of css-classes. They can be space-separated | ||||||
|      */ |      */ | ||||||
|     cssClasses?: string | TagRenderingConfigJson |     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 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -86,9 +86,9 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|             throw "Layer " + this.id + " does not define a source section (" + context + ")" |             throw "Layer " + this.id + " does not define a source section (" + context + ")" | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(json.source === "special" || json.source === "special:library"){ |         if (json.source === "special" || json.source === "special:library") { | ||||||
|             this.source = null |             this.source = null | ||||||
|         }else if (json.source.osmTags === undefined) { |         } else if (json.source.osmTags === undefined) { | ||||||
|             throw ( |             throw ( | ||||||
|                 "Layer " + |                 "Layer " + | ||||||
|                 this.id + |                 this.id + | ||||||
|  | @ -105,7 +105,6 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|             throw `${context}: The id of a layer should match [a-z0-9-_]*: ${json.id}` |             throw `${context}: The id of a layer should match [a-z0-9-_]*: ${json.id}` | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 |  | ||||||
|         if ( |         if ( | ||||||
|             json.syncSelection !== undefined && |             json.syncSelection !== undefined && | ||||||
|             LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0 |             LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0 | ||||||
|  | @ -120,13 +119,28 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         this.syncSelection = json.syncSelection ?? "no" |         this.syncSelection = json.syncSelection ?? "no" | ||||||
|         const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags") |         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 (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" + | ||||||
|  |                     osmTags.asHumanString(false, false, {}) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|         if (Constants.priviliged_layers.indexOf(this.id) < 0 && osmTags.isNegative()) { |             this.source = new SourceConfig( | ||||||
|             throw ( |                 { | ||||||
|                 context + |                     osmTags: osmTags, | ||||||
|                 "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" + |                     geojsonSource: json.source["geoJson"], | ||||||
|                 osmTags.asHumanString(false, false, {}) |                     geojsonSourceLevel: json.source["geoJsonZoomLevel"], | ||||||
|  |                     overpassScript: json.source["overpassScript"], | ||||||
|  |                     isOsmCache: json.source["isOsmCache"], | ||||||
|  |                     mercatorCrs: json.source["mercatorCrs"], | ||||||
|  |                     idKey: json.source["idKey"], | ||||||
|  |                 }, | ||||||
|  |                 json.id | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -138,20 +152,6 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|             throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)" |             throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)" | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.source = new SourceConfig( |  | ||||||
|             { |  | ||||||
|                 osmTags: osmTags, |  | ||||||
|                 geojsonSource: json.source["geoJson"], |  | ||||||
|                 geojsonSourceLevel: json.source["geoJsonZoomLevel"], |  | ||||||
|                 overpassScript: json.source["overpassScript"], |  | ||||||
|                 isOsmCache: json.source["isOsmCache"], |  | ||||||
|                 mercatorCrs: json.source["mercatorCrs"], |  | ||||||
|                 idKey: json.source["idKey"], |  | ||||||
|             }, |  | ||||||
|             Constants.priviliged_layers.indexOf(this.id) > 0, |  | ||||||
|             json.id |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         this.allowSplit = json.allowSplit ?? false |         this.allowSplit = json.allowSplit ?? false | ||||||
|         this.name = Translations.T(json.name, translationContext + ".name") |         this.name = Translations.T(json.name, translationContext + ".name") | ||||||
|         if (json.units !== undefined && !Array.isArray(json.units)) { |         if (json.units !== undefined && !Array.isArray(json.units)) { | ||||||
|  | @ -250,7 +250,7 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|                     | "osmbasedmap" |                     | "osmbasedmap" | ||||||
|                     | "historicphoto" |                     | "historicphoto" | ||||||
|                     | string |                     | string | ||||||
|                 )[] |                     )[] | ||||||
|                 if (typeof pr.preciseInput.preferredBackground === "string") { |                 if (typeof pr.preciseInput.preferredBackground === "string") { | ||||||
|                     preferredBackground = [pr.preciseInput.preferredBackground] |                     preferredBackground = [pr.preciseInput.preferredBackground] | ||||||
|                 } else { |                 } else { | ||||||
|  | @ -597,7 +597,7 @@ export default class LayerConfig extends WithContextLoader { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let overpassLink: BaseUIElement = undefined |         let overpassLink: BaseUIElement = undefined | ||||||
|         if (Constants.priviliged_layers.indexOf(this.id) < 0) { |         if (this.source !== undefined) { | ||||||
|             try { |             try { | ||||||
|                 overpassLink = new Link( |                 overpassLink = new Link( | ||||||
|                     "Execute on overpass", |                     "Execute on overpass", | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import Img from "../../UI/Base/Img" | ||||||
| import Combine from "../../UI/Base/Combine" | import Combine from "../../UI/Base/Combine" | ||||||
| import { VariableUiElement } from "../../UI/Base/VariableUIElement" | import { VariableUiElement } from "../../UI/Base/VariableUIElement" | ||||||
| import { OsmTags } from "../OsmFeature" | import { OsmTags } from "../OsmFeature" | ||||||
|  | import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" | ||||||
| 
 | 
 | ||||||
| export default class PointRenderingConfig extends WithContextLoader { | export default class PointRenderingConfig extends WithContextLoader { | ||||||
|     private static readonly allowed_location_codes = new Set<string>([ |     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 rotation: TagRenderingConfig | ||||||
|     public readonly cssDef: TagRenderingConfig |     public readonly cssDef: TagRenderingConfig | ||||||
|     public readonly cssClasses?: TagRenderingConfig |     public readonly cssClasses?: TagRenderingConfig | ||||||
|  |     public readonly pitchAlignment?: TagRenderingConfig | ||||||
|  |     public readonly rotationAlignment?: TagRenderingConfig | ||||||
| 
 | 
 | ||||||
|     constructor(json: PointRenderingConfigJson, context: string) { |     constructor(json: PointRenderingConfigJson, context: string) { | ||||||
|         super(json, context) |         super(json, context) | ||||||
|  | @ -88,6 +91,14 @@ export default class PointRenderingConfig extends WithContextLoader { | ||||||
|         this.iconSize = this.tr("iconSize", "40,40,center") |         this.iconSize = this.tr("iconSize", "40,40,center") | ||||||
|         this.label = this.tr("label", undefined) |         this.label = this.tr("label", undefined) | ||||||
|         this.rotation = this.tr("rotation", "0") |         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 |             geojsonSourceLevel?: number | ||||||
|             idKey?: string |             idKey?: string | ||||||
|         }, |         }, | ||||||
|         isSpecialLayer: boolean, |  | ||||||
|         context?: string |         context?: string | ||||||
|     ) { |     ) { | ||||||
|         let defined = 0 |         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})` |                 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() |             const optimized = params.osmTags.optimize() | ||||||
|             if (optimized === false) { |             if (optimized === false) { | ||||||
|                 throw ( |                 throw ( | ||||||
|  |  | ||||||
|  | @ -228,7 +228,7 @@ export class DownloadPanel extends Toggle { | ||||||
|             new Set(neededLayers) |             new Set(neededLayers) | ||||||
|         ) |         ) | ||||||
|         for (const tile of featureList) { |         for (const tile of featureList) { | ||||||
|             if (Constants.priviliged_layers.indexOf(tile.layer) >= 0) { |             if (tile.layer !== undefined) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -31,7 +31,6 @@ export class GeolocationControl extends VariableUiElement { | ||||||
|                     return false |                     return false | ||||||
|                 } |                 } | ||||||
|                 const timeDiff = (new Date().getTime() - date.getTime()) / 1000 |                 const timeDiff = (new Date().getTime() - date.getTime()) / 1000 | ||||||
|                 console.log("Timediff", timeDiff) |  | ||||||
|                 return timeDiff <= Constants.zoomToLocationTimeout |                 return timeDiff <= Constants.zoomToLocationTimeout | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ export class MapPreview | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const availableLayers = AllKnownLayouts.AllPublicLayers().filter( |         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( |         const layerPicker = new DropDown( | ||||||
|             t.selectLayer, |             t.selectLayer, | ||||||
|  |  | ||||||
|  | @ -14,12 +14,12 @@ import Constants from "../../Models/Constants" | ||||||
|  */ |  */ | ||||||
| export class MapLibreAdaptor implements MapProperties { | export class MapLibreAdaptor implements MapProperties { | ||||||
|     private static maplibre_control_handlers = [ |     private static maplibre_control_handlers = [ | ||||||
|         "scrollZoom", |         // "scrollZoom",
 | ||||||
|         "boxZoom", |         // "boxZoom",
 | ||||||
|  |         // "doubleClickZoom",
 | ||||||
|         "dragRotate", |         "dragRotate", | ||||||
|         "dragPan", |         "dragPan", | ||||||
|         "keyboard", |         "keyboard", | ||||||
|         "doubleClickZoom", |  | ||||||
|         "touchZoomRotate", |         "touchZoomRotate", | ||||||
|     ] |     ] | ||||||
|     readonly location: UIEventSource<{ lon: number; lat: number }> |     readonly location: UIEventSource<{ lon: number; lat: number }> | ||||||
|  |  | ||||||
|  | @ -8,12 +8,13 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" | ||||||
| import { OsmTags } from "../../Models/OsmFeature" | import { OsmTags } from "../../Models/OsmFeature" | ||||||
| import FeatureSource from "../../Logic/FeatureSource/FeatureSource" | import FeatureSource from "../../Logic/FeatureSource/FeatureSource" | ||||||
| import { BBox } from "../../Logic/BBox" | import { BBox } from "../../Logic/BBox" | ||||||
| import { Feature, LineString } from "geojson" | import { Feature } from "geojson" | ||||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen" | import ScrollableFullScreen from "../Base/ScrollableFullScreen" | ||||||
| import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" | import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import * as range_layer from "../../assets/layers/range/range.json" | import * as range_layer from "../../assets/layers/range/range.json" | ||||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
|  | 
 | ||||||
| class PointRenderingLayer { | class PointRenderingLayer { | ||||||
|     private readonly _config: PointRenderingConfig |     private readonly _config: PointRenderingConfig | ||||||
|     private readonly _fetchStore?: (id: string) => Store<OsmTags> |     private readonly _fetchStore?: (id: string) => Store<OsmTags> | ||||||
|  | @ -44,6 +45,16 @@ class PointRenderingLayer { | ||||||
|         const unseenKeys = new Set(cache.keys()) |         const unseenKeys = new Set(cache.keys()) | ||||||
|         for (const location of this._config.location) { |         for (const location of this._config.location) { | ||||||
|             for (const feature of features) { |             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( |                 const loc = GeoOperations.featureToCoordinateWithRenderingType( | ||||||
|                     <any>feature, |                     <any>feature, | ||||||
|                     location |                     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", |         "offset", | ||||||
|         "fill", |         "fill", | ||||||
|         "fillColor", |         "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 _map: MlMap | ||||||
|     private readonly _config: LineRenderingConfig |     private readonly _config: LineRenderingConfig | ||||||
|     private readonly _visibility?: Store<boolean> |     private readonly _visibility?: Store<boolean> | ||||||
|     private readonly _fetchStore?: (id: string) => Store<OsmTags> |     private readonly _fetchStore?: (id: string) => Store<OsmTags> | ||||||
|     private readonly _onClick?: (id: string) => void |     private readonly _onClick?: (id: string) => void | ||||||
|     private readonly _layername: string |     private readonly _layername: string | ||||||
|  |     private readonly _listenerInstalledOn: Set<string> = new Set<string>() | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         map: MlMap, |         map: MlMap, | ||||||
|  | @ -145,6 +167,39 @@ class LineRenderingLayer { | ||||||
|         features.features.addCallbackAndRunD((features) => self.update(features)) |         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[]) { |     private async update(features: Feature[]) { | ||||||
|         const map = this._map |         const map = this._map | ||||||
|         while (!map.isStyleLoaded()) { |         while (!map.isStyleLoaded()) { | ||||||
|  | @ -158,31 +213,14 @@ class LineRenderingLayer { | ||||||
|             }, |             }, | ||||||
|             promoteId: "id", |             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({ |         map.addLayer({ | ||||||
|             source: this._layername, |             source: this._layername, | ||||||
|             id: this._layername + "_line", |             id: this._layername + "_line", | ||||||
|             type: "line", |             type: "line", | ||||||
|             filter: ["in", ["geometry-type"], ["literal", ["LineString", "MultiLineString"]]], |  | ||||||
|             layout: {}, |  | ||||||
|             paint: { |             paint: { | ||||||
|                 "line-color": ["feature-state", "color"], |                 "line-color": ["feature-state", "color"], | ||||||
|  |                 "line-opacity": ["feature-state", "color-opacity"], | ||||||
|                 "line-width": ["feature-state", "width"], |                 "line-width": ["feature-state", "width"], | ||||||
|                 "line-offset": ["feature-state", "offset"], |                 "line-offset": ["feature-state", "offset"], | ||||||
|             }, |             }, | ||||||
|  | @ -205,12 +243,49 @@ class LineRenderingLayer { | ||||||
|             layout: {}, |             layout: {}, | ||||||
|             paint: { |             paint: { | ||||||
|                 "fill-color": ["feature-state", "fillColor"], |                 "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 { | export default class ShowDataLayer { | ||||||
|  |     private static rangeLayer = new LayerConfig( | ||||||
|  |         <LayerConfigJson>range_layer, | ||||||
|  |         "ShowDataLayer.ts:range.json" | ||||||
|  |     ) | ||||||
|     private readonly _map: Store<MlMap> |     private readonly _map: Store<MlMap> | ||||||
|     private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } |     private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } | ||||||
|     private readonly _popupCache: Map<string, ScrollableFullScreen> |     private readonly _popupCache: Map<string, ScrollableFullScreen> | ||||||
|  | @ -223,11 +298,6 @@ export default class ShowDataLayer { | ||||||
|         map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) |         map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static rangeLayer = new LayerConfig( |  | ||||||
|         <LayerConfigJson>range_layer, |  | ||||||
|         "ShowDataLayer.ts:range.json" |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     public static showRange( |     public static showRange( | ||||||
|         map: Store<MlMap>, |         map: Store<MlMap>, | ||||||
|         features: FeatureSource, |         features: FeatureSource, | ||||||
|  | @ -241,6 +311,9 @@ export default class ShowDataLayer { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private openOrReusePopup(id: string): void { |     private openOrReusePopup(id: string): void { | ||||||
|  |         if (!this._popupCache || !this._options.fetchStore) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|         if (this._popupCache.has(id)) { |         if (this._popupCache.has(id)) { | ||||||
|             this._popupCache.get(id).Activate() |             this._popupCache.get(id).Activate() | ||||||
|             return |             return | ||||||
|  | @ -267,11 +340,12 @@ export default class ShowDataLayer { | ||||||
|     private initDrawFeatures(map: MlMap) { |     private initDrawFeatures(map: MlMap) { | ||||||
|         const { features, doShowLayer, fetchStore, buildPopup } = this._options |         const { features, doShowLayer, fetchStore, buildPopup } = this._options | ||||||
|         const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id) |         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( |             new LineRenderingLayer( | ||||||
|                 map, |                 map, | ||||||
|                 features, |                 features, | ||||||
|                 "test", |                 this._options.layer.id + "_linerendering_" + i, | ||||||
|                 lineRenderingConfig, |                 lineRenderingConfig, | ||||||
|                 doShowLayer, |                 doShowLayer, | ||||||
|                 fetchStore, |                 fetchStore, | ||||||
|  |  | ||||||
|  | @ -21,11 +21,13 @@ | ||||||
|   import Svg from "../Svg"; |   import Svg from "../Svg"; | ||||||
|   import If from "./Base/If.svelte"; |   import If from "./Base/If.svelte"; | ||||||
|   import { GeolocationControl } from "./BigComponents/GeolocationControl.js"; |   import { GeolocationControl } from "./BigComponents/GeolocationControl.js"; | ||||||
|   import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; |  | ||||||
|   import { BBox } from "../Logic/BBox"; |   import { BBox } from "../Logic/BBox"; | ||||||
|   import ShowDataLayer from "./Map/ShowDataLayer"; |   import ShowDataLayer from "./Map/ShowDataLayer"; | ||||||
|   import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; |   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; |   export let layout: LayoutConfig; | ||||||
| 
 | 
 | ||||||
|   const maplibremap: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); |   const maplibremap: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); | ||||||
|  | @ -46,7 +48,7 @@ | ||||||
|     osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data |     osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data | ||||||
|   }); |   }); | ||||||
|   const userRelatedState = new UserRelatedState(osmConnection, layout?.language); |   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 geolocation = new GeoLocationHandler(geolocationState, selectedElement, mapproperties, userRelatedState.gpsLocationHistoryRetentionTime); | ||||||
| 
 | 
 | ||||||
|   const allElements = new ElementStorage(); |   const allElements = new ElementStorage(); | ||||||
|  | @ -55,16 +57,19 @@ | ||||||
|     osmConnection, |     osmConnection, | ||||||
|     historicalUserLocations: geolocation.historicalUserLocations |     historicalUserLocations: geolocation.historicalUserLocations | ||||||
|   }, layout?.isLeftRightSensitive() ?? false); |   }, layout?.isLeftRightSensitive() ?? false); | ||||||
|    |   console.log("Setting up layerstate...") | ||||||
|   Map |   const layerState = new LayerState(osmConnection, layout.layers, layout.id) | ||||||
|    |  | ||||||
|   { |   { | ||||||
|     // Various actors that we don't need to reference  |     // Various actors that we don't need to reference  | ||||||
|  |     // TODO enable new TitleHandler(selectedElement,layout,allElements) | ||||||
|     new ChangeToElementsActor(changes, allElements); |     new ChangeToElementsActor(changes, allElements); | ||||||
|     new PendingChangesUploader(changes, selectedElement); |     new PendingChangesUploader(changes, selectedElement); | ||||||
|     new SelectedElementTagsUpdater({ |     new SelectedElementTagsUpdater({ | ||||||
|       allElements, changes, selectedElement, layoutToUse: layout, osmConnection |       allElements, changes, selectedElement, layoutToUse: layout, osmConnection | ||||||
|     }); |     }); | ||||||
|  |      | ||||||
|  |      | ||||||
|  |      | ||||||
|     // Various initial setup |     // Various initial setup | ||||||
|     userRelatedState.markLayoutAsVisited(layout); |     userRelatedState.markLayoutAsVisited(layout); | ||||||
|     if(layout?.lockLocation){ |     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> | </script> | ||||||
| 
 | 
 | ||||||
|  | @ -93,15 +128,12 @@ | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <div class="absolute bottom-0 right-0 mb-4 mr-4"> | <div class="absolute bottom-0 right-0 mb-4 mr-4"> | ||||||
| 
 |   <MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}> | ||||||
|   <If condition={mapproperties.allowMoving}> |     <ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte> | ||||||
|     <MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}> |   </MapControlButton> | ||||||
|       <ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte> |   <MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}> | ||||||
|     </MapControlButton> |     <ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte> | ||||||
|     <MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}> |   </MapControlButton> | ||||||
|       <ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte> |  | ||||||
|     </MapControlButton> |  | ||||||
|   </If> |  | ||||||
|   <If condition={featureSwitches.featureSwitchGeolocation}> |   <If condition={featureSwitches.featureSwitchGeolocation}> | ||||||
|     <MapControlButton> |     <MapControlButton> | ||||||
|       <ToSvelte construct={() => new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte> |       <ToSvelte construct={() => new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte> | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
|         ] |         ] | ||||||
|       }, |       }, | ||||||
|       "iconSize": "40,40,center", |       "iconSize": "40,40,center", | ||||||
|  |       "pitchAlignment": "map", | ||||||
|       "rotation": { |       "rotation": { | ||||||
|         "render": "0deg", |         "render": "0deg", | ||||||
|         "mappings": [ |         "mappings": [ | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
|   "id": "home_location", |   "id": "home_location", | ||||||
|   "description": "Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.", |   "description": "Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.", | ||||||
|   "minzoom": 0, |   "minzoom": 0, | ||||||
|   "source":"special", |   "source": "special", | ||||||
|   "mapRendering": [ |   "mapRendering": [ | ||||||
|     { |     { | ||||||
|       "icon": { |       "icon": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "id": "import_candidate", |   "id": "import_candidate", | ||||||
|   "description": "Layer used in the importHelper", |   "description": "Layer used as template in the importHelper", | ||||||
|   "source":"special", |   "source":"special", | ||||||
|   "mapRendering": [ |   "mapRendering": [ | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -6,9 +6,9 @@ | ||||||
|   "name": null, |   "name": null, | ||||||
|   "mapRendering": [ |   "mapRendering": [ | ||||||
|     { |     { | ||||||
|       "width": 4, |       "width": 3, | ||||||
|       "fill": "no", |       "fill": "no", | ||||||
|       "color": "#ff000088" |       "color": "#cc00cc" | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,9 +2,7 @@ | ||||||
|   "id": "split_point", |   "id": "split_point", | ||||||
|   "description": "Layer rendering the little scissors for the minimap in the 'splitRoadWizard'", |   "description": "Layer rendering the little scissors for the minimap in the 'splitRoadWizard'", | ||||||
|   "minzoom": 1, |   "minzoom": 1, | ||||||
|   "source": { |   "source": "special", | ||||||
|     "osmTags": "_split_point=yes" |  | ||||||
|   }, |  | ||||||
|   "name": "Split point", |   "name": "Split point", | ||||||
|   "title": "Split point", |   "title": "Split point", | ||||||
|   "mapRendering": [ |   "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 |     "minzoom": 19 | ||||||
|   }, |   }, | ||||||
|   "layers": [ |   "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", |       "id": "osm-buildings", | ||||||
|       "name": "All OSM-buildings", |       "name": "All OSM-buildings", | ||||||
|  |  | ||||||
|  | @ -114,7 +114,7 @@ function GenLayerOverviewText(): BaseUIElement { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter( |     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>() |     const builtinLayerIds: Set<string> = new Set<string>() | ||||||
|  | @ -183,7 +183,7 @@ function GenOverviewsForSingleLayer( | ||||||
|     callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void |     callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void | ||||||
| ): void { | ): void { | ||||||
|     const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter( |     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>() |     const builtinLayerIds: Set<string> = new Set<string>() | ||||||
|     allLayers.forEach((l) => builtinLayerIds.add(l.id)) |     allLayers.forEach((l) => builtinLayerIds.add(l.id)) | ||||||
|  | @ -195,7 +195,7 @@ function GenOverviewsForSingleLayer( | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const layer of layout.layers) { |         for (const layer of layout.layers) { | ||||||
|             if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { |             if (layer.source === null) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             if (builtinLayerIds.has(layer.id)) { |             if (builtinLayerIds.has(layer.id)) { | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ async function main(includeTags = true) { | ||||||
|         if (layer.source["geoJson"] !== undefined && !layer.source["isOsmCache"]) { |         if (layer.source["geoJson"] !== undefined && !layer.source["isOsmCache"]) { | ||||||
|             continue |             continue | ||||||
|         } |         } | ||||||
|         if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { |         if (layer.source == null || typeof layer.source === "string") { | ||||||
|             continue |             continue | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -132,7 +132,7 @@ function generateLayerUsage(layer: LayerConfig, layout: LayoutConfig): any[] { | ||||||
| function generateTagInfoEntry(layout: LayoutConfig): any { | function generateTagInfoEntry(layout: LayoutConfig): any { | ||||||
|     const usedTags = [] |     const usedTags = [] | ||||||
|     for (const layer of layout.layers) { |     for (const layer of layout.layers) { | ||||||
|         if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { |         if (layer.source === null) { | ||||||
|             continue |             continue | ||||||
|         } |         } | ||||||
|         if (layer.source.geojsonSource !== undefined && layer.source.isOsmCacheLayer !== true) { |         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 { FixedUiElement } from "./UI/Base/FixedUiElement" | ||||||
| import { QueryParameters } from "./Logic/Web/QueryParameters" | import { QueryParameters } from "./Logic/Web/QueryParameters" | ||||||
| import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts" | import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts" | ||||||
| 
 | import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" | ||||||
|  | import * as benches from "./assets/generated/themes/benches.json" | ||||||
| async function main() { | async function main() { | ||||||
|     new FixedUiElement("Determining layout...").AttachTo("maindiv") |     new FixedUiElement("Determining layout...").AttachTo("maindiv") | ||||||
|     const qp = QueryParameters.GetQueryParameter("layout", "benches") |     const qp = QueryParameters.GetQueryParameter("layout", "") | ||||||
|     const layout = new AllKnownLayoutsLazy().get(qp.data) |     const layout = new LayoutConfig(<any>benches, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 | ||||||
|     console.log("Using layout", layout.id) |     console.log("Using layout", layout.id) | ||||||
|     new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") |     new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue