From 3db87232f288b878ca6c335b76f635aabc8a4759 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 14 Apr 2023 17:53:08 +0200 Subject: [PATCH] Refactoring: add metatagging --- Logic/ExtraFunctions.ts | 56 ++++---- .../Actors/MetaTagRecalculator.ts | 124 ------------------ Logic/MetaTagging.ts | 30 +++++ Logic/Osm/OsmConnection.ts | 2 +- Logic/SimpleMetaTagger.ts | 7 +- Logic/State/LayerState.ts | 3 +- Logic/State/UserRelatedState.ts | 24 +++- Models/FilteredLayer.ts | 2 +- .../Json/TagRenderingConfigJson.ts | 7 + Models/ThemeViewState.ts | 11 +- test.ts | 26 +--- 11 files changed, 101 insertions(+), 191 deletions(-) delete mode 100644 Logic/FeatureSource/Actors/MetaTagRecalculator.ts diff --git a/Logic/ExtraFunctions.ts b/Logic/ExtraFunctions.ts index f0a7d795b..80311e375 100644 --- a/Logic/ExtraFunctions.ts +++ b/Logic/ExtraFunctions.ts @@ -12,8 +12,8 @@ export interface ExtraFuncParams { * Note that more features then requested can be given back. * Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...] */ - getFeaturesWithin: (layerId: string, bbox: BBox) => Feature[][] - getFeatureById: (id: string) => Feature + getFeaturesWithin: (layerId: string, bbox: BBox) => Feature>[] + getFeatureById: (id: string) => Feature> } /** @@ -52,26 +52,24 @@ class EnclosingFunc implements ExtraFunction { if (otherFeaturess.length === 0) { continue } - for (const otherFeatures of otherFeaturess) { - for (const otherFeature of otherFeatures) { - if (seenIds.has(otherFeature.properties.id)) { - continue - } - seenIds.add(otherFeature.properties.id) - if ( - otherFeature.geometry.type !== "Polygon" && - otherFeature.geometry.type !== "MultiPolygon" - ) { - continue - } - if ( - GeoOperations.completelyWithin( - feat, - >otherFeature - ) - ) { - result.push({ feat: otherFeature }) - } + for (const otherFeature of otherFeaturess) { + if (seenIds.has(otherFeature.properties.id)) { + continue + } + seenIds.add(otherFeature.properties.id) + if ( + otherFeature.geometry.type !== "Polygon" && + otherFeature.geometry.type !== "MultiPolygon" + ) { + continue + } + if ( + GeoOperations.completelyWithin( + feat, + >otherFeature + ) + ) { + result.push({ feat: otherFeature }) } } } @@ -154,14 +152,12 @@ class IntersectionFunc implements ExtraFunction { if (otherLayers.length === 0) { continue } - for (const tile of otherLayers) { - for (const otherFeature of tile) { - const intersections = GeoOperations.LineIntersections(feat, otherFeature) - if (intersections.length === 0) { - continue - } - result.push({ feat: otherFeature, intersections }) + for (const otherFeature of otherLayers) { + const intersections = GeoOperations.LineIntersections(feat, otherFeature) + if (intersections.length === 0) { + continue } + result.push({ feat: otherFeature, intersections }) } } @@ -329,7 +325,7 @@ class ClosestNObjectFunc implements ExtraFunction { const uniqueTagsMatch = otherFeature.properties[uniqueTag] !== undefined && closestFeature.feat.properties[uniqueTag] === - otherFeature.properties[uniqueTag] + otherFeature.properties[uniqueTag] if (uniqueTagsMatch) { targetIndex = -1 if (closestFeature.distance > distance) { diff --git a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts deleted file mode 100644 index 0cda95578..000000000 --- a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import MetaTagging from "../../MetaTagging" -import { ExtraFuncParams } from "../../ExtraFunctions" -import { BBox } from "../../BBox" -import { UIEventSource } from "../../UIEventSource" - -/** - * Concerned with the logic of updating the right layer at the right time - */ -class MetatagUpdater { - public readonly neededLayerBboxes = new Map() - private source: FeatureSourceForLayer & Tiled - private readonly params: ExtraFuncParams - private state: { allElements?: ElementStorage } - - private readonly isDirty = new UIEventSource(false) - - constructor( - source: FeatureSourceForLayer & Tiled, - state: { allElements?: ElementStorage }, - featurePipeline: FeaturePipeline - ) { - this.state = state - this.source = source - const self = this - this.params = { - getFeatureById(id) { - return state.allElements.ContainingFeatures.get(id) - }, - getFeaturesWithin(layerId, bbox) { - // We keep track of the BBOX that this source needs - let oldBbox: BBox = self.neededLayerBboxes.get(layerId) - if (oldBbox === undefined) { - self.neededLayerBboxes.set(layerId, bbox) - } else if (!bbox.isContainedIn(oldBbox)) { - self.neededLayerBboxes.set(layerId, oldBbox.unionWith(bbox)) - } - return featurePipeline.GetFeaturesWithin(layerId, bbox) - }, - } - this.isDirty.stabilized(100).addCallback((dirty) => { - if (dirty) { - self.updateMetaTags() - } - }) - this.source.features.addCallbackAndRunD((_) => self.isDirty.setData(true)) - } - - public requestUpdate() { - this.isDirty.setData(true) - } - - public updateMetaTags() { - const features = this.source.features.data - - if (features.length === 0) { - this.isDirty.setData(false) - return - } - MetaTagging.addMetatags(features, this.params, this.source.layer.layerDef, this.state) - this.isDirty.setData(false) - } -} - -export default class MetaTagRecalculator { - private _state: { - allElements?: ElementStorage - } - private _featurePipeline: FeaturePipeline - private readonly _alreadyRegistered: Set = new Set< - FeatureSourceForLayer & Tiled - >() - private readonly _notifiers: MetatagUpdater[] = [] - - /** - * The meta tag recalculator receives tiles of layers via the 'registerSource'-function. - * It keeps track of which sources have had their share calculated, and which should be re-updated if some other data is loaded - */ - constructor( - state: { allElements?: ElementStorage; currentView: FeatureSourceForLayer & Tiled }, - featurePipeline: FeaturePipeline - ) { - this._featurePipeline = featurePipeline - this._state = state - - if (state.currentView !== undefined) { - const currentViewUpdater = new MetatagUpdater( - state.currentView, - this._state, - this._featurePipeline - ) - this._alreadyRegistered.add(state.currentView) - this._notifiers.push(currentViewUpdater) - state.currentView.features.addCallback((_) => { - console.debug("Requesting an update for currentView") - currentViewUpdater.updateMetaTags() - }) - } - } - - public registerSource(source: FeatureSourceForLayer & Tiled) { - if (source === undefined) { - return - } - if (this._alreadyRegistered.has(source)) { - return - } - this._alreadyRegistered.add(source) - this._notifiers.push(new MetatagUpdater(source, this._state, this._featurePipeline)) - const self = this - source.features.addCallbackAndRunD((_) => { - const layerName = source.layer.layerDef.id - for (const updater of self._notifiers) { - const neededBbox = updater.neededLayerBboxes.get(layerName) - if (neededBbox == undefined) { - continue - } - if (source.bbox === undefined || neededBbox.overlapsWith(source.bbox)) { - updater.requestUpdate() - } - } - }) - } -} diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index ae59c95c0..2344b32ef 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -4,6 +4,8 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig" import { Feature } from "geojson" import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" +import { GeoIndexedStoreForLayer } from "./FeatureSource/Actors/GeoIndexedStore" +import { IndexedFeatureSource } from "./FeatureSource/FeatureSource" /** * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... @@ -15,6 +17,34 @@ export default class MetaTagging { private static readonly stopErrorOutputAt = 10 private static retaggingFuncCache = new Map void)[]>() + constructor(state: { + layout: LayoutConfig + perLayer: ReadonlyMap + indexedFeatures: IndexedFeatureSource + featureProperties: FeaturePropertiesStore + }) { + const params: ExtraFuncParams = { + getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id), + getFeaturesWithin: (layerId, bbox) => + state.perLayer.get(layerId).GetFeaturesWithin(bbox), + } + for (const layer of state.layout.layers) { + if (layer.source === null) { + continue + } + const featureSource = state.perLayer.get(layer.id) + featureSource.features?.addCallbackAndRunD((features) => { + MetaTagging.addMetatags( + features, + params, + layer, + state.layout, + state.featureProperties + ) + }) + } + } + /** * This method (re)calculates all metatags and calculated tags on every given object. * The given features should be part of the given layer diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 7145a0334..745daa645 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -237,7 +237,7 @@ export class OsmConnection { userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 ) data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 ) data.img = undefined diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index ae8f9c8df..1fe54e32a 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -95,6 +95,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { if (!id.startsWith("node/")) { return false } + console.trace("Downloading referencing ways for", feature.properties.id) OsmObject.DownloadReferencingWays(id).then((referencingWays) => { const currentTagsSource = state.allElements?.getEventSourceById(id) ?? [] @@ -111,7 +112,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { } } -export class CountryTagger extends SimpleMetaTagger { +class CountryTagger extends SimpleMetaTagger { private static readonly coder = new CountryCoder( Constants.countryCoderEndpoint, Utils.downloadJson @@ -182,7 +183,7 @@ class InlineMetaTagger extends SimpleMetaTagger { } } -export class RewriteMetaInfoTags extends SimpleMetaTagger { +class RewriteMetaInfoTags extends SimpleMetaTagger { constructor() { super({ keys: [ @@ -267,8 +268,6 @@ export default class SimpleMetaTaggers { const lon = centerPoint.geometry.coordinates[0] feature.properties["_lat"] = "" + lat feature.properties["_lon"] = "" + lon - feature._lon = lon // This is dirty, I know - feature._lat = lat return true } ) diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts index 268cdfaf7..fc1f67135 100644 --- a/Logic/State/LayerState.ts +++ b/Logic/State/LayerState.ts @@ -69,6 +69,7 @@ export default class LayerState { console.warn( "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs ) - filteredLayers.set(layer.id, toReuse) + const copy = new FilteredLayer(layer, toReuse.appliedFilters, toReuse.isDisplayed) + filteredLayers.set(layer.id, copy) } } diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 21399d8a0..a6879f4f8 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -13,6 +13,7 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import usersettings from "../../assets/generated/layers/usersettings.json" import Locale from "../../UI/i18n/Locale" import LinkToWeblate from "../../UI/Base/LinkToWeblate" +import FeatureSwitchState from "./FeatureSwitchState" /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -57,7 +58,8 @@ export default class UserRelatedState { constructor( osmConnection: OsmConnection, availableLanguages?: string[], - layout?: LayoutConfig + layout?: LayoutConfig, + featureSwitches?: FeatureSwitchState ) { this.osmConnection = osmConnection { @@ -97,7 +99,7 @@ export default class UserRelatedState { this.homeLocation = this.initHomeLocation() - this.preferencesAsTags = this.initAmendedPrefs(layout) + this.preferencesAsTags = this.initAmendedPrefs(layout, featureSwitches) } public GetUnofficialTheme(id: string): @@ -241,7 +243,10 @@ export default class UserRelatedState { * Initialize the 'amended preferences'. * This is inherently a dirty and chaotic method, as it shoves many properties into this EventSourcd * */ - private initAmendedPrefs(layout?: LayoutConfig): UIEventSource> { + private initAmendedPrefs( + layout?: LayoutConfig, + featureSwitches?: FeatureSwitchState + ): UIEventSource> { const amendedPrefs = new UIEventSource>({ _theme: layout?.id, _backend: this.osmConnection.Backend(), @@ -357,6 +362,19 @@ export default class UserRelatedState { } }) + for (const key in featureSwitches) { + if (featureSwitches[key].addCallbackAndRun) { + featureSwitches[key].addCallbackAndRun((v) => { + const oldV = amendedPrefs.data["__" + key] + if (oldV === v) { + return + } + amendedPrefs.data["__" + key] = "" + v + amendedPrefs.ping() + }) + } + } + return amendedPrefs } } diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index e8ccd60a8..9e4d83b31 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -35,7 +35,7 @@ export default class FilteredLayer { constructor( layer: LayerConfig, - appliedFilters?: Map>, + appliedFilters?: ReadonlyMap>, isDisplayed?: UIEventSource ) { this.layerDef = layer diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 80d3209f6..d42de17d0 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -76,6 +76,13 @@ export interface TagRenderingConfigJson { * */ condition?: TagConfigJson + /** + * If set, this tag will be evaluated agains the _usersettings/application state_ table. + * Enable 'show debug info' in user settings to see available options. + * Note that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_ + */ + metacondition?: TagConfigJson + /** * Allow freeform text input from the user */ diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index b63508ed4..16217e6d3 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -41,6 +41,7 @@ import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexe import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" import { MenuState } from "./MenuState" +import MetaTagging from "../Logic/MetaTagging" /** * @@ -101,7 +102,12 @@ export default class ThemeViewState implements SpecialVisualizationState { ), osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, }) - this.userRelatedState = new UserRelatedState(this.osmConnection, layout?.language, layout) + this.userRelatedState = new UserRelatedState( + this.osmConnection, + layout?.language, + layout, + this.featureSwitches + ) this.selectedElement = new UIEventSource(undefined, "Selected element") this.selectedLayer = new UIEventSource(undefined, "Selected layer") this.geolocation = new GeoLocationHandler( @@ -323,10 +329,9 @@ export default class ThemeViewState implements SpecialVisualizationState { /** * Setup various services for which no reference are needed - * @private */ private initActors() { - // Various actors that we don't need to reference + new MetaTagging(this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new ChangeToElementsActor(this.changes, this.featureProperties) new PendingChangesUploader(this.changes, this.selectedElement) diff --git a/test.ts b/test.ts index 59243ea89..bed47cf8b 100644 --- a/test.ts +++ b/test.ts @@ -1,24 +1,10 @@ -import SvelteUIElement from "./UI/Base/SvelteUIElement" -import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" -import { FixedUiElement } from "./UI/Base/FixedUiElement" import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" import * as theme from "./assets/generated/themes/shops.json" import ThemeViewState from "./Models/ThemeViewState" import Combine from "./UI/Base/Combine" import SpecialVisualizations from "./UI/SpecialVisualizations" -import AddNewPoint from "./UI/Popup/AddNewPoint/AddNewPoint.svelte" -import UserProfile from "./UI/BigComponents/UserProfile.svelte" -async function main() { - new FixedUiElement("").AttachTo("extradiv") - const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) - const state = new ThemeViewState(layout) - - const main = new SvelteUIElement(ThemeViewGUI, { state }) - main.AttachTo("maindiv") -} - -async function testspecial() { +function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) const state = new ThemeViewState(layout) @@ -28,12 +14,4 @@ async function testspecial() { new Combine(all).AttachTo("maindiv") } -async function test() { - const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) - const state = new ThemeViewState(layout) - new SvelteUIElement(UserProfile, { osmConnection: state.osmConnection }).AttachTo("maindiv") -} - -/* -test().then((_) => {}) /*/ -main().then((_) => {}) //*/ +testspecial()