Refactoring: add metatagging

This commit is contained in:
Pieter Vander Vennet 2023-04-14 17:53:08 +02:00
parent adab786375
commit 3db87232f2
11 changed files with 101 additions and 191 deletions

View file

@ -12,8 +12,8 @@ export interface ExtraFuncParams {
* Note that more features then requested can be given back. * Note that more features then requested can be given back.
* Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...] * Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...]
*/ */
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, { id: string }>[][] getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, Record<string, string>>[]
getFeatureById: (id: string) => Feature<Geometry, { id: string }> getFeatureById: (id: string) => Feature<Geometry, Record<string, string>>
} }
/** /**
@ -52,8 +52,7 @@ class EnclosingFunc implements ExtraFunction {
if (otherFeaturess.length === 0) { if (otherFeaturess.length === 0) {
continue continue
} }
for (const otherFeatures of otherFeaturess) { for (const otherFeature of otherFeaturess) {
for (const otherFeature of otherFeatures) {
if (seenIds.has(otherFeature.properties.id)) { if (seenIds.has(otherFeature.properties.id)) {
continue continue
} }
@ -74,7 +73,6 @@ class EnclosingFunc implements ExtraFunction {
} }
} }
} }
}
return result return result
} }
@ -154,8 +152,7 @@ class IntersectionFunc implements ExtraFunction {
if (otherLayers.length === 0) { if (otherLayers.length === 0) {
continue continue
} }
for (const tile of otherLayers) { for (const otherFeature of otherLayers) {
for (const otherFeature of tile) {
const intersections = GeoOperations.LineIntersections(feat, otherFeature) const intersections = GeoOperations.LineIntersections(feat, otherFeature)
if (intersections.length === 0) { if (intersections.length === 0) {
continue continue
@ -163,7 +160,6 @@ class IntersectionFunc implements ExtraFunction {
result.push({ feat: otherFeature, intersections }) result.push({ feat: otherFeature, intersections })
} }
} }
}
return result return result
} }

View file

@ -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<string /*layerId*/, BBox>()
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 <any>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<FeatureSourceForLayer & Tiled> = 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()
}
}
})
}
}

View file

@ -4,6 +4,8 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import { Feature } from "geojson" import { Feature } from "geojson"
import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore" import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" 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, ... * 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 readonly stopErrorOutputAt = 10
private static retaggingFuncCache = new Map<string, ((feature: Feature) => void)[]>() private static retaggingFuncCache = new Map<string, ((feature: Feature) => void)[]>()
constructor(state: {
layout: LayoutConfig
perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
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. * This method (re)calculates all metatags and calculated tags on every given object.
* The given features should be part of the given layer * The given features should be part of the given layer

View file

@ -237,7 +237,7 @@ export class OsmConnection {
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0
) )
data.tracesCount = Number.parseInt( data.tracesCount = Number.parseInt(
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0
) )
data.img = undefined data.img = undefined

View file

@ -95,6 +95,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
if (!id.startsWith("node/")) { if (!id.startsWith("node/")) {
return false return false
} }
console.trace("Downloading referencing ways for", feature.properties.id) console.trace("Downloading referencing ways for", feature.properties.id)
OsmObject.DownloadReferencingWays(id).then((referencingWays) => { OsmObject.DownloadReferencingWays(id).then((referencingWays) => {
const currentTagsSource = state.allElements?.getEventSourceById(id) ?? [] 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( private static readonly coder = new CountryCoder(
Constants.countryCoderEndpoint, Constants.countryCoderEndpoint,
Utils.downloadJson Utils.downloadJson
@ -182,7 +183,7 @@ class InlineMetaTagger extends SimpleMetaTagger {
} }
} }
export class RewriteMetaInfoTags extends SimpleMetaTagger { class RewriteMetaInfoTags extends SimpleMetaTagger {
constructor() { constructor() {
super({ super({
keys: [ keys: [
@ -267,8 +268,6 @@ export default class SimpleMetaTaggers {
const lon = centerPoint.geometry.coordinates[0] const lon = centerPoint.geometry.coordinates[0]
feature.properties["_lat"] = "" + lat feature.properties["_lat"] = "" + lat
feature.properties["_lon"] = "" + lon feature.properties["_lon"] = "" + lon
feature._lon = lon // This is dirty, I know
feature._lat = lat
return true return true
} }
) )

View file

@ -69,6 +69,7 @@ export default class LayerState {
console.warn( console.warn(
"Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs "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)
} }
} }

View file

@ -13,6 +13,7 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import usersettings from "../../assets/generated/layers/usersettings.json" import usersettings from "../../assets/generated/layers/usersettings.json"
import Locale from "../../UI/i18n/Locale" import Locale from "../../UI/i18n/Locale"
import LinkToWeblate from "../../UI/Base/LinkToWeblate" 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, * 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( constructor(
osmConnection: OsmConnection, osmConnection: OsmConnection,
availableLanguages?: string[], availableLanguages?: string[],
layout?: LayoutConfig layout?: LayoutConfig,
featureSwitches?: FeatureSwitchState
) { ) {
this.osmConnection = osmConnection this.osmConnection = osmConnection
{ {
@ -97,7 +99,7 @@ export default class UserRelatedState {
this.homeLocation = this.initHomeLocation() this.homeLocation = this.initHomeLocation()
this.preferencesAsTags = this.initAmendedPrefs(layout) this.preferencesAsTags = this.initAmendedPrefs(layout, featureSwitches)
} }
public GetUnofficialTheme(id: string): public GetUnofficialTheme(id: string):
@ -241,7 +243,10 @@ export default class UserRelatedState {
* Initialize the 'amended preferences'. * Initialize the 'amended preferences'.
* This is inherently a dirty and chaotic method, as it shoves many properties into this EventSourcd * This is inherently a dirty and chaotic method, as it shoves many properties into this EventSourcd
* */ * */
private initAmendedPrefs(layout?: LayoutConfig): UIEventSource<Record<string, string>> { private initAmendedPrefs(
layout?: LayoutConfig,
featureSwitches?: FeatureSwitchState
): UIEventSource<Record<string, string>> {
const amendedPrefs = new UIEventSource<Record<string, string>>({ const amendedPrefs = new UIEventSource<Record<string, string>>({
_theme: layout?.id, _theme: layout?.id,
_backend: this.osmConnection.Backend(), _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 return amendedPrefs
} }
} }

View file

@ -35,7 +35,7 @@ export default class FilteredLayer {
constructor( constructor(
layer: LayerConfig, layer: LayerConfig,
appliedFilters?: Map<string, UIEventSource<undefined | number | string>>, appliedFilters?: ReadonlyMap<string, UIEventSource<undefined | number | string>>,
isDisplayed?: UIEventSource<boolean> isDisplayed?: UIEventSource<boolean>
) { ) {
this.layerDef = layer this.layerDef = layer

View file

@ -76,6 +76,13 @@ export interface TagRenderingConfigJson {
* */ * */
condition?: TagConfigJson 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 * Allow freeform text input from the user
*/ */

View file

@ -41,6 +41,7 @@ import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexe
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource"
import { MenuState } from "./MenuState" 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, 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<Feature | undefined>(undefined, "Selected element") this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer") this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
this.geolocation = new GeoLocationHandler( this.geolocation = new GeoLocationHandler(
@ -323,10 +329,9 @@ export default class ThemeViewState implements SpecialVisualizationState {
/** /**
* Setup various services for which no reference are needed * Setup various services for which no reference are needed
* @private
*/ */
private initActors() { private initActors() {
// Various actors that we don't need to reference new MetaTagging(this)
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
new ChangeToElementsActor(this.changes, this.featureProperties) new ChangeToElementsActor(this.changes, this.featureProperties)
new PendingChangesUploader(this.changes, this.selectedElement) new PendingChangesUploader(this.changes, this.selectedElement)

26
test.ts
View file

@ -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 LayoutConfig from "./Models/ThemeConfig/LayoutConfig"
import * as theme from "./assets/generated/themes/shops.json" import * as theme from "./assets/generated/themes/shops.json"
import ThemeViewState from "./Models/ThemeViewState" import ThemeViewState from "./Models/ThemeViewState"
import Combine from "./UI/Base/Combine" import Combine from "./UI/Base/Combine"
import SpecialVisualizations from "./UI/SpecialVisualizations" import SpecialVisualizations from "./UI/SpecialVisualizations"
import AddNewPoint from "./UI/Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./UI/BigComponents/UserProfile.svelte"
async function main() { function testspecial() {
new FixedUiElement("").AttachTo("extradiv")
const layout = new LayoutConfig(<any>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() {
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
const state = new ThemeViewState(layout) const state = new ThemeViewState(layout)
@ -28,12 +14,4 @@ async function testspecial() {
new Combine(all).AttachTo("maindiv") new Combine(all).AttachTo("maindiv")
} }
async function test() { testspecial()
const layout = new LayoutConfig(<any>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((_) => {}) //*/