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.
* Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...]
*/
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, { id: string }>[][]
getFeatureById: (id: string) => Feature<Geometry, { id: string }>
getFeaturesWithin: (layerId: string, bbox: BBox) => Feature<Geometry, Record<string, string>>[]
getFeatureById: (id: string) => Feature<Geometry, Record<string, string>>
}
/**
@ -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(
<Feature>feat,
<Feature<Polygon | MultiPolygon, any>>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(
<Feature>feat,
<Feature<Polygon | MultiPolygon, any>>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) {

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 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<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.
* 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
)
data.tracesCount = Number.parseInt(
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0
)
data.img = undefined

View file

@ -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
}
)

View file

@ -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)
}
}

View file

@ -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<Record<string, string>> {
private initAmendedPrefs(
layout?: LayoutConfig,
featureSwitches?: FeatureSwitchState
): UIEventSource<Record<string, string>> {
const amendedPrefs = new UIEventSource<Record<string, string>>({
_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
}
}

View file

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

View file

@ -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
*/

View file

@ -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<Feature | undefined>(undefined, "Selected element")
this.selectedLayer = new UIEventSource<LayerConfig>(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)

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 * 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(<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() {
function testspecial() {
const layout = new LayoutConfig(<any>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(<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((_) => {}) //*/
testspecial()