forked from MapComplete/MapComplete
refactoring: more state splitting, basic layoutFeatureSource
This commit is contained in:
parent
8e2f04c0d0
commit
b94a8f5745
54 changed files with 1067 additions and 1969 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue