refactoring: more state splitting, basic layoutFeatureSource

This commit is contained in:
Pieter Vander Vennet 2023-03-26 05:58:28 +02:00
parent 8e2f04c0d0
commit b94a8f5745
54 changed files with 1067 additions and 1969 deletions

View file

@ -11,19 +11,145 @@ import Constants from "../Models/Constants"
import { TagUtils } from "./Tags/TagUtils"
import { Feature, LineString } from "geojson"
import { OsmObject } from "./Osm/OsmObject"
import { OsmTags } from "../Models/OsmFeature"
import { UIEventSource } from "./UIEventSource"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
export class SimpleMetaTagger {
export abstract class SimpleMetaTagger {
public readonly keys: string[]
public readonly doc: string
public readonly isLazy: boolean
public readonly includesDates: boolean
public readonly applyMetaTagsOnFeature: (feature: any, layer: LayerConfig, state) => boolean
/***
* A function that adds some extra data to a feature
* @param docs: what does this extra data do?
* @param f: apply the changes. Returns true if something changed
*/
protected constructor(docs: {
keys: string[]
doc: string
/**
* Set this flag if the data is volatile or date-based.
* It'll _won't_ be cached in this case
*/
includesDates?: boolean
isLazy?: boolean
cleanupRetagger?: boolean
}) {
this.keys = docs.keys
this.doc = docs.doc
this.isLazy = docs.isLazy
this.includesDates = docs.includesDates ?? false
if (!docs.cleanupRetagger) {
for (const key of docs.keys) {
if (!key.startsWith("_") && key.toLowerCase().indexOf("theme") < 0) {
throw `Incorrect key for a calculated meta value '${key}': it should start with underscore (_)`
}
}
}
}
/**
* Applies the metatag-calculation, returns 'true' if the upstream source needs to be pinged
* @param feature
* @param layer
* @param tagsStore
* @param state
*/
public abstract applyMetaTagsOnFeature(
feature: any,
layer: LayerConfig,
tagsStore: UIEventSource<Record<string, string>>,
state: { layout: LayoutConfig }
): boolean
}
export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
/**
* Disable this metatagger, e.g. for caching or tests
* This is a bit a work-around
*/
public static enabled = true
constructor() {
super({
keys: ["_referencing_ways"],
isLazy: true,
doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ",
})
}
public applyMetaTagsOnFeature(feature, layer, tags, state) {
if (!ReferencingWaysMetaTagger.enabled) {
return false
}
//this function has some extra code to make it work in SimpleAddUI.ts to also work for newly added points
const id = feature.properties.id
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) ?? []
const wayIds = referencingWays.map((w) => "way/" + w.id)
wayIds.sort()
const wayIdsStr = wayIds.join(";")
if (wayIdsStr !== "" && currentTagsSource.data["_referencing_ways"] !== wayIdsStr) {
currentTagsSource.data["_referencing_ways"] = wayIdsStr
currentTagsSource.ping()
}
})
return true
}
}
export class CountryTagger extends SimpleMetaTagger {
private static readonly coder = new CountryCoder(
Constants.countryCoderEndpoint,
Utils.downloadJson
)
public runningTasks: Set<any> = new Set<any>()
constructor() {
super({
keys: ["_country"],
doc: "The country code of the property (with latlon2country)",
includesDates: false,
})
}
applyMetaTagsOnFeature(feature, _, state) {
let centerPoint: any = GeoOperations.centerpoint(feature)
const runningTasks = this.runningTasks
const lat = centerPoint.geometry.coordinates[1]
const lon = centerPoint.geometry.coordinates[0]
runningTasks.add(feature)
CountryTagger.coder
.GetCountryCodeAsync(lon, lat)
.then((countries) => {
runningTasks.delete(feature)
try {
const oldCountry = feature.properties["_country"]
feature.properties["_country"] = countries[0].trim().toLowerCase()
if (oldCountry !== feature.properties["_country"]) {
const tagsSource = state?.allElements?.getEventSourceById(
feature.properties.id
)
tagsSource?.ping()
}
} catch (e) {
console.warn(e)
}
})
.catch((_) => {
runningTasks.delete(feature)
})
return false
}
}
class InlineMetaTagger extends SimpleMetaTagger {
constructor(
docs: {
keys: string[]
@ -36,115 +162,26 @@ export class SimpleMetaTagger {
isLazy?: boolean
cleanupRetagger?: boolean
},
f: (feature: any, layer: LayerConfig, state) => boolean
f: (
feature: any,
layer: LayerConfig,
tagsStore: UIEventSource<OsmTags>,
state: { layout: LayoutConfig }
) => boolean
) {
this.keys = docs.keys
this.doc = docs.doc
this.isLazy = docs.isLazy
super(docs)
this.applyMetaTagsOnFeature = f
this.includesDates = docs.includesDates ?? false
if (!docs.cleanupRetagger) {
for (const key of docs.keys) {
if (!key.startsWith("_") && key.toLowerCase().indexOf("theme") < 0) {
throw `Incorrect key for a calculated meta value '${key}': it should start with underscore (_)`
}
}
}
}
public readonly applyMetaTagsOnFeature: (
feature: any,
layer: LayerConfig,
tagsStore: UIEventSource<OsmTags>,
state: { layout: LayoutConfig }
) => boolean
}
export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
/**
* Disable this metatagger, e.g. for caching or tests
* This is a bit a work-around
*/
public static enabled = true
constructor() {
super(
{
keys: ["_referencing_ways"],
isLazy: true,
doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ",
},
(feature, _, state) => {
if (!ReferencingWaysMetaTagger.enabled) {
return false
}
//this function has some extra code to make it work in SimpleAddUI.ts to also work for newly added points
const id = feature.properties.id
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) ?? []
const wayIds = referencingWays.map((w) => "way/" + w.id)
wayIds.sort()
const wayIdsStr = wayIds.join(";")
if (
wayIdsStr !== "" &&
currentTagsSource.data["_referencing_ways"] !== wayIdsStr
) {
currentTagsSource.data["_referencing_ways"] = wayIdsStr
currentTagsSource.ping()
}
})
return true
}
)
}
}
export class CountryTagger extends SimpleMetaTagger {
private static readonly coder = new CountryCoder(
Constants.countryCoderEndpoint,
Utils.downloadJson
)
public runningTasks: Set<any>
constructor() {
const runningTasks = new Set<any>()
super(
{
keys: ["_country"],
doc: "The country code of the property (with latlon2country)",
includesDates: false,
},
(feature, _, state) => {
let centerPoint: any = GeoOperations.centerpoint(feature)
const lat = centerPoint.geometry.coordinates[1]
const lon = centerPoint.geometry.coordinates[0]
runningTasks.add(feature)
CountryTagger.coder
.GetCountryCodeAsync(lon, lat)
.then((countries) => {
runningTasks.delete(feature)
try {
const oldCountry = feature.properties["_country"]
feature.properties["_country"] = countries[0].trim().toLowerCase()
if (oldCountry !== feature.properties["_country"]) {
const tagsSource = state?.allElements?.getEventSourceById(
feature.properties.id
)
tagsSource?.ping()
}
} catch (e) {
console.warn(e)
}
})
.catch((_) => {
runningTasks.delete(feature)
})
return false
}
)
this.runningTasks = runningTasks
}
}
export default class SimpleMetaTaggers {
public static readonly objectMetaInfo = new SimpleMetaTagger(
public static readonly objectMetaInfo = new InlineMetaTagger(
{
keys: [
"_last_edit:contributor",
@ -180,7 +217,7 @@ export default class SimpleMetaTaggers {
}
)
public static country = new CountryTagger()
public static geometryType = new SimpleMetaTagger(
public static geometryType = new InlineMetaTagger(
{
keys: ["_geometry:type"],
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`",
@ -191,6 +228,7 @@ export default class SimpleMetaTaggers {
return changed
}
)
public static referencingWays = new ReferencingWaysMetaTagger()
private static readonly cardinalDirections = {
N: 0,
NNE: 22.5,
@ -209,7 +247,7 @@ export default class SimpleMetaTaggers {
NW: 315,
NNW: 337.5,
}
private static latlon = new SimpleMetaTagger(
private static latlon = new InlineMetaTagger(
{
keys: ["_lat", "_lon"],
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)",
@ -225,13 +263,13 @@ export default class SimpleMetaTaggers {
return true
}
)
private static layerInfo = new SimpleMetaTagger(
private static layerInfo = new InlineMetaTagger(
{
doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.",
keys: ["_layer"],
includesDates: false,
},
(feature, _, layer) => {
(feature, layer) => {
if (feature.properties._layer === layer.id) {
return false
}
@ -239,7 +277,7 @@ export default class SimpleMetaTaggers {
return true
}
)
private static noBothButLeftRight = new SimpleMetaTagger(
private static noBothButLeftRight = new InlineMetaTagger(
{
keys: [
"sidewalk:left",
@ -251,7 +289,7 @@ export default class SimpleMetaTaggers {
includesDates: false,
cleanupRetagger: true,
},
(feature, state, layer) => {
(feature, layer) => {
if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) {
return
}
@ -259,7 +297,7 @@ export default class SimpleMetaTaggers {
return SimpleMetaTaggers.removeBothTagging(feature.properties)
}
)
private static surfaceArea = new SimpleMetaTagger(
private static surfaceArea = new InlineMetaTagger(
{
keys: ["_surface", "_surface:ha"],
doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways",
@ -292,7 +330,7 @@ export default class SimpleMetaTaggers {
return true
}
)
private static levels = new SimpleMetaTagger(
private static levels = new InlineMetaTagger(
{
doc: "Extract the 'level'-tag into a normalized, ';'-separated value",
keys: ["_level"],
@ -311,15 +349,14 @@ export default class SimpleMetaTaggers {
return true
}
)
private static canonicalize = new SimpleMetaTagger(
private static canonicalize = new InlineMetaTagger(
{
doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)",
keys: ["Theme-defined keys"],
},
(feature, _, state) => {
(feature, _, __, state) => {
const units = Utils.NoNull(
[].concat(...(state?.layoutToUse?.layers?.map((layer) => layer.units) ?? []))
[].concat(...(state?.layout?.layers?.map((layer) => layer.units) ?? []))
)
if (units.length == 0) {
return
@ -369,7 +406,7 @@ export default class SimpleMetaTaggers {
return rewritten
}
)
private static lngth = new SimpleMetaTagger(
private static lngth = new InlineMetaTagger(
{
keys: ["_length", "_length:km"],
doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter",
@ -383,14 +420,14 @@ export default class SimpleMetaTaggers {
return true
}
)
private static isOpen = new SimpleMetaTagger(
private static isOpen = new InlineMetaTagger(
{
keys: ["_isOpen"],
doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
includesDates: true,
isLazy: true,
},
(feature, _, state) => {
(feature) => {
if (Utils.runningFromConsole) {
// We are running from console, thus probably creating a cache
// isOpen is irrelevant
@ -438,11 +475,9 @@ export default class SimpleMetaTaggers {
}
},
})
const tagsSource = state.allElements.getEventSourceById(feature.properties.id)
}
)
private static directionSimplified = new SimpleMetaTagger(
private static directionSimplified = new InlineMetaTagger(
{
keys: ["_direction:numerical", "_direction:leftright"],
doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map",
@ -466,8 +501,7 @@ export default class SimpleMetaTaggers {
return true
}
)
private static directionCenterpoint = new SimpleMetaTagger(
private static directionCenterpoint = new InlineMetaTagger(
{
keys: ["_direction:centerpoint"],
isLazy: true,
@ -500,8 +534,7 @@ export default class SimpleMetaTaggers {
return true
}
)
private static currentTime = new SimpleMetaTagger(
private static currentTime = new InlineMetaTagger(
{
keys: ["_now:date", "_now:datetime"],
doc: "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely",
@ -523,9 +556,6 @@ export default class SimpleMetaTaggers {
return true
}
)
public static referencingWays = new ReferencingWaysMetaTagger()
public static metatags: SimpleMetaTagger[] = [
SimpleMetaTaggers.latlon,
SimpleMetaTaggers.layerInfo,
@ -543,9 +573,6 @@ export default class SimpleMetaTaggers {
SimpleMetaTaggers.levels,
SimpleMetaTaggers.referencingWays,
]
public static readonly lazyTags: string[] = [].concat(
...SimpleMetaTaggers.metatags.filter((tagger) => tagger.isLazy).map((tagger) => tagger.keys)
)
/**
* Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme.