From 8e2f04c0d0e137928fe634d0dae9b13190f05c58 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 25 Mar 2023 02:48:24 +0100 Subject: [PATCH] refactoring: split all the states --- Logic/Actors/OverpassFeatureSource.ts | 2 +- Logic/Actors/TitleHandler.ts | 12 +- Logic/BBox.ts | 2 +- Logic/FeatureSource/FeaturePipeline.ts | 40 +--- Logic/State/FeaturePipelineState.ts | 4 +- Logic/State/LayerState.ts | 145 +++++++++++++ Logic/State/MapState.ts | 196 +----------------- Logic/State/UserRelatedState.ts | 28 ++- Models/Constants.ts | 21 +- Models/ThemeConfig/Conversion/PrepareTheme.ts | 4 +- Models/ThemeConfig/Conversion/Validation.ts | 5 +- .../Json/PointRenderingConfigJson.ts | 11 + Models/ThemeConfig/LayerConfig.ts | 50 ++--- Models/ThemeConfig/PointRenderingConfig.ts | 11 + Models/ThemeConfig/SourceConfig.ts | 3 +- UI/BigComponents/DownloadPanel.ts | 2 +- UI/BigComponents/GeolocationControl.ts | 1 - UI/ImportFlow/MapPreview.ts | 2 +- UI/Map/MapLibreAdaptor.ts | 6 +- UI/Map/ShowDataLayer.ts | 130 +++++++++--- UI/ThemeViewGUI.svelte | 64 ++++-- assets/layers/gps_location/gps_location.json | 1 + .../layers/home_location/home_location.json | 2 +- .../import_candidate/import_candidate.json | 2 +- assets/layers/range/range.json | 4 +- assets/layers/split_point/split_point.json | 6 +- assets/layers/type_node/type_node.json | 10 - assets/themes/grb/grb.json | 25 +-- scripts/generateDocs.ts | 6 +- scripts/generateStats.ts | 2 +- scripts/generateTaginfoProjectFiles.ts | 2 +- test.ts | 7 +- 32 files changed, 411 insertions(+), 395 deletions(-) create mode 100644 Logic/State/LayerState.ts delete mode 100644 assets/layers/type_node/type_node.json diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index bd2e9c4b0e..bf8c44464b 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -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) { diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 729f172c16..442158374b 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -7,14 +7,9 @@ import { ElementStorage } from "../ElementStorage" import { Utils } from "../../Utils" export default class TitleHandler { - constructor(state: { - selectedElement: Store - layoutToUse: LayoutConfig - allElements: ElementStorage - }) { - const currentTitle: Store = state.selectedElement.map( + constructor(selectedElement: Store, layout: LayoutConfig, allElements: ElementStorage) { + const currentTitle: Store = 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(tags) + allElements.getEventSourceById(tags.id) ?? new UIEventSource(tags) const title = new TagRenderingAnswer(tagsSource, layer.title, {}) return ( new Combine([defaultTitle, " | ", title]).ConstructElement() diff --git a/Logic/BBox.ts b/Logic/BBox.ts index c23f2073ca..3a875196e2 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -189,7 +189,7 @@ export class BBox { public asGeoJson(properties: T): Feature { return { type: "Feature", - properties: properties, + properties, geometry: this.asGeometry(), } } diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index edfac8fbdc..2492c0c107 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -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(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) diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 5f7fe819d8..429952f379 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -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 diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts new file mode 100644 index 0000000000..46fd6989c5 --- /dev/null +++ b/Logic/State/LayerState.ts @@ -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 = new UIEventSource( + [], + "globalFilters" + ) + + /** + * Which layers are enabled in the current theme and what filters are applied onto them + */ + public readonly filteredLayers: Map + 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 { + 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 + 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>( + new Map() + ), + } + 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, + }) + } +} diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index ad29cfaa6f..0e695d356a 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -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 = new UIEventSource( - [], - "filteredLayers" - ) - - /** - * Filters which apply onto all layers - */ - public globalFilters: UIEventSource = new UIEventSource([], "globalFilters") - /** * Which overlays are shown */ @@ -80,16 +53,6 @@ export default class MapState { this.backgroundLayer = new UIEventSource(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( - MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection) - ) this.AddAllOverlaysToMap(this.leafletMap) this.initCurrentView() this.initSelectedElement() - - new TitleHandler(this) } public AddAllOverlaysToMap(leafletMap: UIEventSource) { @@ -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 = this.currentBounds.map((bounds) => { + const features: Store = 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 { - 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 - 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>( - new Map() - ), - } - 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 - } } diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index c178aa984f..9f6f115a68 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -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 = Stores.ListStabilized( this.osmConnection.userDetails.map((userDetails) => { if (userDetails === undefined) { return undefined @@ -198,21 +199,18 @@ export default class UserRelatedState { return empty } return [ - { - feature: { - type: "Feature", - properties: { - id: "home", - "user:home": "yes", - _lon: homeLonLat[0], - _lat: homeLonLat[1], - }, - geometry: { - type: "Point", - coordinates: homeLonLat, - }, + { + type: "Feature", + properties: { + id: "home", + "user:home": "yes", + _lon: homeLonLat[0], + _lat: homeLonLat[1], + }, + geometry: { + type: "Point", + coordinates: homeLonLat, }, - freshness: new Date(), }, ] }) diff --git a/Models/Constants.ts b/Models/Constants.ts index fe4f4f594d..8aeb95f969 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -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 = { diff --git a/Models/ThemeConfig/Conversion/PrepareTheme.ts b/Models/ThemeConfig/Conversion/PrepareTheme.ts index 15e41503f4..ff9745db8c 100644 --- a/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -255,7 +255,7 @@ class AddImportLayers extends DesugaringStep { 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 { // 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, diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 765b095d68..5c31778e6c 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -845,7 +845,7 @@ export class ValidateLayer extends DesugaringStep { } 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 { } 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++) { diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts index 17e243924e..6741b822c4 100644 --- a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts @@ -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 } diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 03be4abee6..599aa3fb31 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -86,9 +86,9 @@ export default class LayerConfig extends WithContextLoader { 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 - }else if (json.source.osmTags === undefined) { + } else if (json.source.osmTags === undefined) { throw ( "Layer " + 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}` } - this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 if ( json.syncSelection !== undefined && LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0 @@ -120,13 +119,28 @@ export default class LayerConfig extends WithContextLoader { ) } 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()) { - 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, {}) + 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"], + }, + 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)" } - 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.name = Translations.T(json.name, translationContext + ".name") if (json.units !== undefined && !Array.isArray(json.units)) { @@ -250,7 +250,7 @@ export default class LayerConfig extends WithContextLoader { | "osmbasedmap" | "historicphoto" | string - )[] + )[] if (typeof pr.preciseInput.preferredBackground === "string") { preferredBackground = [pr.preciseInput.preferredBackground] } else { @@ -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", diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index 9e0fdd5590..e8a467bf83 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -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([ @@ -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" + ) } /** diff --git a/Models/ThemeConfig/SourceConfig.ts b/Models/ThemeConfig/SourceConfig.ts index 1badbc57b6..32fb0333b8 100644 --- a/Models/ThemeConfig/SourceConfig.ts +++ b/Models/ThemeConfig/SourceConfig.ts @@ -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 ( diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index a2685016a4..d07f95d255 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -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 } diff --git a/UI/BigComponents/GeolocationControl.ts b/UI/BigComponents/GeolocationControl.ts index ee5465b12d..f1aea540c6 100644 --- a/UI/BigComponents/GeolocationControl.ts +++ b/UI/BigComponents/GeolocationControl.ts @@ -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 } ) diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index c000c7a68a..ce8c2ae513 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -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, diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index 3b9aba3ac3..2b2f4053f1 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -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 }> diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index c5e1ee15a5..2f2d820a79 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -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 @@ -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( 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 private readonly _fetchStore?: (id: string) => Store private readonly _onClick?: (id: string) => void private readonly _layername: string + private readonly _listenerInstalledOn: Set = new Set() constructor( map: MlMap, @@ -145,6 +167,39 @@ class LineRenderingLayer { features.features.addCallbackAndRunD((features) => self.update(features)) } + private calculatePropsFor( + properties: Record + ): Partial> { + 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( + range_layer, + "ShowDataLayer.ts:range.json" + ) private readonly _map: Store private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } private readonly _popupCache: Map @@ -223,11 +298,6 @@ export default class ShowDataLayer { map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) } - private static rangeLayer = new LayerConfig( - range_layer, - "ShowDataLayer.ts:range.json" - ) - public static showRange( map: Store, 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, diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 6ef909c514..d2dcd750c9 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -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 = new UIEventSource(undefined); @@ -46,7 +48,7 @@ osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data }); const userRelatedState = new UserRelatedState(osmConnection, layout?.language); - const selectedElement = new UIEventSource(undefined, "Selected element"); + const selectedElement = new UIEventSource(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){ @@ -76,7 +81,37 @@ featureSwitches.featureSwitchIsTesting ) } - + + + type AddedByDefaultTypes = typeof Constants.added_by_default[number] + /** + * A listing which maps the layerId onto the featureSource + */ + const empty = [] + const specialLayers : Record = { + "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 : [bbox.asGeoJson({id:"range"})])) , + current_view: new StaticFeatureSource(mapproperties.bounds.map(bbox => bbox === undefined ? empty : [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 + }) + }) } @@ -93,15 +128,12 @@
- - - mapproperties.zoom.update(z => z+1)}> - - - mapproperties.zoom.update(z => z-1)}> - - - + mapproperties.zoom.update(z => z+1)}> + + + mapproperties.zoom.update(z => z-1)}> + + new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}> diff --git a/assets/layers/gps_location/gps_location.json b/assets/layers/gps_location/gps_location.json index a5beb0913d..44a028e913 100644 --- a/assets/layers/gps_location/gps_location.json +++ b/assets/layers/gps_location/gps_location.json @@ -15,6 +15,7 @@ ] }, "iconSize": "40,40,center", + "pitchAlignment": "map", "rotation": { "render": "0deg", "mappings": [ diff --git a/assets/layers/home_location/home_location.json b/assets/layers/home_location/home_location.json index b9fc29509b..75a7e704c6 100644 --- a/assets/layers/home_location/home_location.json +++ b/assets/layers/home_location/home_location.json @@ -2,7 +2,7 @@ "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.", "minzoom": 0, - "source":"special", + "source": "special", "mapRendering": [ { "icon": { diff --git a/assets/layers/import_candidate/import_candidate.json b/assets/layers/import_candidate/import_candidate.json index 52ed5ba3b9..63059ad4bc 100644 --- a/assets/layers/import_candidate/import_candidate.json +++ b/assets/layers/import_candidate/import_candidate.json @@ -1,6 +1,6 @@ { "id": "import_candidate", - "description": "Layer used in the importHelper", + "description": "Layer used as template in the importHelper", "source":"special", "mapRendering": [ { diff --git a/assets/layers/range/range.json b/assets/layers/range/range.json index 1ea7aa4590..a68b56ba05 100644 --- a/assets/layers/range/range.json +++ b/assets/layers/range/range.json @@ -6,9 +6,9 @@ "name": null, "mapRendering": [ { - "width": 4, + "width": 3, "fill": "no", - "color": "#ff000088" + "color": "#cc00cc" } ] } diff --git a/assets/layers/split_point/split_point.json b/assets/layers/split_point/split_point.json index 52d9a22a50..9202df71ca 100644 --- a/assets/layers/split_point/split_point.json +++ b/assets/layers/split_point/split_point.json @@ -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": [ @@ -17,4 +15,4 @@ "iconSize": "30,30,center" } ] -} \ No newline at end of file +} diff --git a/assets/layers/type_node/type_node.json b/assets/layers/type_node/type_node.json deleted file mode 100644 index 20679e2d73..0000000000 --- a/assets/layers/type_node/type_node.json +++ /dev/null @@ -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": [] -} diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 91083c2bfb..4d87ff694d 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -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", @@ -771,4 +748,4 @@ "overpassMaxZoom": 17, "osmApiTileSize": 17, "credits": "Pieter Vander Vennet" -} \ No newline at end of file +} diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 94f80a5e2f..0399c4bf47 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -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 = new Set() @@ -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 = new Set() 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)) { diff --git a/scripts/generateStats.ts b/scripts/generateStats.ts index a86006412e..ab121bf7ad 100644 --- a/scripts/generateStats.ts +++ b/scripts/generateStats.ts @@ -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 } diff --git a/scripts/generateTaginfoProjectFiles.ts b/scripts/generateTaginfoProjectFiles.ts index a76803bc89..785f978f76 100644 --- a/scripts/generateTaginfoProjectFiles.ts +++ b/scripts/generateTaginfoProjectFiles.ts @@ -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) { diff --git a/test.ts b/test.ts index c2d7915487..b1375d4ab6 100644 --- a/test.ts +++ b/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(benches, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) console.log("Using layout", layout.id) new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") }