forked from MapComplete/MapComplete
This commit is contained in:
parent
52d4adee84
commit
4e73d9b5f9
1 changed files with 104 additions and 30 deletions
|
@ -5,7 +5,7 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||||
import { CountryCoder } from "latlon2country"
|
import { CountryCoder } from "latlon2country"
|
||||||
import Constants from "../Models/Constants"
|
import Constants from "../Models/Constants"
|
||||||
import { TagUtils } from "./Tags/TagUtils"
|
import { TagUtils } from "./Tags/TagUtils"
|
||||||
import { Feature, LineString } from "geojson"
|
import { Feature, LineString, MultiPolygon, Polygon } from "geojson"
|
||||||
import { OsmTags } from "../Models/OsmFeature"
|
import { OsmTags } from "../Models/OsmFeature"
|
||||||
import { UIEventSource } from "./UIEventSource"
|
import { UIEventSource } from "./UIEventSource"
|
||||||
import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
|
import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
|
||||||
|
@ -80,7 +80,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
|
||||||
super({
|
super({
|
||||||
keys: ["_referencing_ways"],
|
keys: ["_referencing_ways"],
|
||||||
isLazy: true,
|
isLazy: true,
|
||||||
doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. ",
|
doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. "
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class CountryTagger extends SimpleMetaTagger {
|
||||||
super({
|
super({
|
||||||
keys: ["_country"],
|
keys: ["_country"],
|
||||||
doc: "The country codes of the of the country/countries that the feature is located in (with latlon2country). Might contain _multiple_ countries, separated by a `;`",
|
doc: "The country codes of the of the country/countries that the feature is located in (with latlon2country). Might contain _multiple_ countries, separated by a `;`",
|
||||||
includesDates: false,
|
includesDates: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,9 +213,9 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
|
||||||
"_last_edit:changeset",
|
"_last_edit:changeset",
|
||||||
"_last_edit:timestamp",
|
"_last_edit:timestamp",
|
||||||
"_version_number",
|
"_version_number",
|
||||||
"_backend",
|
"_backend"
|
||||||
],
|
],
|
||||||
doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass",
|
doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +244,69 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NormalizePanoramax extends SimpleMetaTagger {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
keys: ["panoramax"],
|
||||||
|
doc: "Converts a `panoramax=hash1;hash2;hash3;...` into `panoramax=hash1`,`panoramax:0=hash1`...",
|
||||||
|
isLazy: false,
|
||||||
|
cleanupRetagger: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private addValue(comesFromKey: string, tags: Record<string, string>, hashesToAdd: string[], postfix?: string) {
|
||||||
|
let basekey = "panoramax"
|
||||||
|
if (postfix) {
|
||||||
|
basekey = "panoramax:" + postfix
|
||||||
|
}
|
||||||
|
let index = -1
|
||||||
|
for (let i = 0; i < hashesToAdd.length; i++) {
|
||||||
|
let k = basekey
|
||||||
|
do {
|
||||||
|
if (index >= 0) {
|
||||||
|
k = `${basekey}:${index}`
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
} while (k !== comesFromKey && tags[k])
|
||||||
|
tags[k] = hashesToAdd[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* const tags = new UIEventSource({panoramax: "abc;def;ghi", "panoramax:2": "xyz;uvw", "panoramax:streetsign":"a;b;c"})
|
||||||
|
* const _ = undefined
|
||||||
|
* new NormalizePanoramax().applyMetaTagsOnFeature(_, _, tags, _)
|
||||||
|
* tags.data // => {"panoramax": "abc", "panoramax:0" : "def", "panoramax:1": "ghi", "panoramax:2":"xyz", "panoramax:3":"uvw", "panoramax:streetsign":"a", "panoramax:streetsign:0":"b","panoramax:streetsign:1": "c"}
|
||||||
|
*/
|
||||||
|
applyMetaTagsOnFeature(feature: Feature, layer: LayerConfig, tags: UIEventSource<Record<string, string>>): boolean {
|
||||||
|
const tgs = tags.data
|
||||||
|
let somethingChanged = false
|
||||||
|
for (const key in tgs) {
|
||||||
|
if (!(key === "panoramax" || key.startsWith("panoramax:"))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const v = tgs[key]
|
||||||
|
if (v.indexOf(";") < 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const parts = v.split(";")
|
||||||
|
if (key === "panoramax" || key.match("panoramax:[0-9]+")) {
|
||||||
|
this.addValue(key, tgs, parts)
|
||||||
|
somethingChanged = true
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const postfix = key.match(/panoramax:([^:]+)(:[0-9]+)?/)?.[1]
|
||||||
|
if (postfix) {
|
||||||
|
this.addValue(key, tgs, parts, postfix)
|
||||||
|
somethingChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return somethingChanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class SimpleMetaTaggers {
|
export default class SimpleMetaTaggers {
|
||||||
/**
|
/**
|
||||||
* A simple metatagger which rewrites various metatags as needed
|
* A simple metatagger which rewrites various metatags as needed
|
||||||
|
@ -253,7 +316,7 @@ export default class SimpleMetaTaggers {
|
||||||
public static geometryType = new InlineMetaTagger(
|
public static geometryType = new InlineMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_geometry:type"],
|
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`",
|
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`"
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
const changed = feature.properties["_geometry:type"] === feature.geometry.type
|
const changed = feature.properties["_geometry:type"] === feature.geometry.type
|
||||||
|
@ -262,6 +325,7 @@ export default class SimpleMetaTaggers {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public static referencingWays = new ReferencingWaysMetaTagger()
|
public static referencingWays = new ReferencingWaysMetaTagger()
|
||||||
|
private static normalizePanoramax = new NormalizePanoramax()
|
||||||
private static readonly cardinalDirections = {
|
private static readonly cardinalDirections = {
|
||||||
N: 0,
|
N: 0,
|
||||||
NNE: 22.5,
|
NNE: 22.5,
|
||||||
|
@ -278,12 +342,12 @@ export default class SimpleMetaTaggers {
|
||||||
W: 270,
|
W: 270,
|
||||||
WNW: 292.5,
|
WNW: 292.5,
|
||||||
NW: 315,
|
NW: 315,
|
||||||
NNW: 337.5,
|
NNW: 337.5
|
||||||
}
|
}
|
||||||
private static latlon = new InlineMetaTagger(
|
private static latlon = new InlineMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_lat", "_lon"],
|
keys: ["_lat", "_lon"],
|
||||||
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)",
|
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
const centerPoint = GeoOperations.centerpoint(feature)
|
const centerPoint = GeoOperations.centerpoint(feature)
|
||||||
|
@ -298,7 +362,7 @@ export default class SimpleMetaTaggers {
|
||||||
{
|
{
|
||||||
doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.",
|
doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.",
|
||||||
keys: ["_layer"],
|
keys: ["_layer"],
|
||||||
includesDates: false,
|
includesDates: false
|
||||||
},
|
},
|
||||||
(feature, layer) => {
|
(feature, layer) => {
|
||||||
if (feature.properties._layer === layer.id) {
|
if (feature.properties._layer === layer.id) {
|
||||||
|
@ -314,11 +378,11 @@ export default class SimpleMetaTaggers {
|
||||||
"sidewalk:left",
|
"sidewalk:left",
|
||||||
"sidewalk:right",
|
"sidewalk:right",
|
||||||
"generic_key:left:property",
|
"generic_key:left:property",
|
||||||
"generic_key:right:property",
|
"generic_key:right:property"
|
||||||
],
|
],
|
||||||
doc: "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined",
|
doc: "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined",
|
||||||
includesDates: false,
|
includesDates: false,
|
||||||
cleanupRetagger: true,
|
cleanupRetagger: true
|
||||||
},
|
},
|
||||||
(feature, layer) => {
|
(feature, layer) => {
|
||||||
if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) {
|
if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) {
|
||||||
|
@ -332,11 +396,15 @@ export default class SimpleMetaTaggers {
|
||||||
{
|
{
|
||||||
keys: ["_surface"],
|
keys: ["_surface"],
|
||||||
doc: "The surface area of the feature in square meters. Not set on points and ways",
|
doc: "The surface area of the feature in square meters. Not set on points and ways",
|
||||||
isLazy: true,
|
isLazy: true
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
|
if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const f = <Feature<Polygon | MultiPolygon>>feature
|
||||||
Utils.AddLazyProperty(feature.properties, "_surface", () => {
|
Utils.AddLazyProperty(feature.properties, "_surface", () => {
|
||||||
return "" + GeoOperations.surfaceAreaInSqMeters(feature)
|
return "" + GeoOperations.surfaceAreaInSqMeters(f)
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -346,11 +414,15 @@ export default class SimpleMetaTaggers {
|
||||||
{
|
{
|
||||||
keys: ["_surface:ha"],
|
keys: ["_surface:ha"],
|
||||||
doc: "The surface area of the feature in hectare. Not set on points and ways",
|
doc: "The surface area of the feature in hectare. Not set on points and ways",
|
||||||
isLazy: true,
|
isLazy: true
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
|
if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const f = <Feature<Polygon | MultiPolygon>>feature
|
||||||
Utils.AddLazyProperty(feature.properties, "_surface:ha", () => {
|
Utils.AddLazyProperty(feature.properties, "_surface:ha", () => {
|
||||||
const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature)
|
const sqMeters = GeoOperations.surfaceAreaInSqMeters(f)
|
||||||
return "" + Math.floor(sqMeters / 1000) / 10
|
return "" + Math.floor(sqMeters / 1000) / 10
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -360,7 +432,7 @@ export default class SimpleMetaTaggers {
|
||||||
private static levels = new InlineMetaTagger(
|
private static levels = new InlineMetaTagger(
|
||||||
{
|
{
|
||||||
doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.",
|
doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.",
|
||||||
keys: ["_level"],
|
keys: ["_level"]
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
let somethingChanged = false
|
let somethingChanged = false
|
||||||
|
@ -395,7 +467,7 @@ export default class SimpleMetaTaggers {
|
||||||
private static canonicalize = new InlineMetaTagger(
|
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)",
|
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"],
|
keys: ["Theme-defined keys"]
|
||||||
},
|
},
|
||||||
(feature, _, __, state) => {
|
(feature, _, __, state) => {
|
||||||
const units = Utils.NoNull(
|
const units = Utils.NoNull(
|
||||||
|
@ -452,7 +524,7 @@ export default class SimpleMetaTaggers {
|
||||||
private static lngth = new InlineMetaTagger(
|
private static lngth = new InlineMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_length", "_length:km"],
|
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",
|
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"
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
const l = GeoOperations.lengthInMeters(feature)
|
const l = GeoOperations.lengthInMeters(feature)
|
||||||
|
@ -468,7 +540,7 @@ export default class SimpleMetaTaggers {
|
||||||
keys: ["_isOpen"],
|
keys: ["_isOpen"],
|
||||||
doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
|
doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
|
||||||
includesDates: true,
|
includesDates: true,
|
||||||
isLazy: true,
|
isLazy: true
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
if (Utils.runningFromConsole) {
|
if (Utils.runningFromConsole) {
|
||||||
|
@ -507,8 +579,8 @@ export default class SimpleMetaTaggers {
|
||||||
lon: lon,
|
lon: lon,
|
||||||
address: {
|
address: {
|
||||||
country_code: tags._country.toLowerCase(),
|
country_code: tags._country.toLowerCase(),
|
||||||
state: undefined,
|
state: undefined
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
<any>{ tag_key: "opening_hours" }
|
<any>{ tag_key: "opening_hours" }
|
||||||
)
|
)
|
||||||
|
@ -520,14 +592,14 @@ export default class SimpleMetaTaggers {
|
||||||
delete tags._isOpen
|
delete tags._isOpen
|
||||||
tags["_isOpen"] = "parse_error"
|
tags["_isOpen"] = "parse_error"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
private static directionSimplified = new InlineMetaTagger(
|
private static directionSimplified = new InlineMetaTagger(
|
||||||
{
|
{
|
||||||
keys: ["_direction:numerical", "_direction:leftright"],
|
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",
|
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"
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
const tags = feature.properties
|
const tags = feature.properties
|
||||||
|
@ -552,7 +624,7 @@ export default class SimpleMetaTaggers {
|
||||||
{
|
{
|
||||||
keys: ["_direction:centerpoint"],
|
keys: ["_direction:centerpoint"],
|
||||||
isLazy: true,
|
isLazy: true,
|
||||||
doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint.",
|
doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint."
|
||||||
},
|
},
|
||||||
(feature: Feature) => {
|
(feature: Feature) => {
|
||||||
if (feature.geometry.type !== "LineString") {
|
if (feature.geometry.type !== "LineString") {
|
||||||
|
@ -575,7 +647,7 @@ export default class SimpleMetaTaggers {
|
||||||
delete feature.properties["_direction:centerpoint"]
|
delete feature.properties["_direction:centerpoint"]
|
||||||
feature.properties["_direction:centerpoint"] = bearing
|
feature.properties["_direction:centerpoint"] = bearing
|
||||||
return bearing
|
return bearing
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -585,7 +657,7 @@ export default class SimpleMetaTaggers {
|
||||||
{
|
{
|
||||||
keys: ["_now:date", "_now:datetime"],
|
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",
|
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",
|
||||||
includesDates: true,
|
includesDates: true
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
@ -609,7 +681,7 @@ export default class SimpleMetaTaggers {
|
||||||
keys: ["_last_edit:passed_time"],
|
keys: ["_last_edit:passed_time"],
|
||||||
doc: "Gives the number of seconds since the last edit. Note that this will _not_ update, but rather be the number of seconds elapsed at the moment this tag is read first",
|
doc: "Gives the number of seconds since the last edit. Note that this will _not_ update, but rather be the number of seconds elapsed at the moment this tag is read first",
|
||||||
isLazy: true,
|
isLazy: true,
|
||||||
includesDates: true,
|
includesDates: true
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
Utils.AddLazyProperty(feature.properties, "_last_edit:passed_time", () => {
|
Utils.AddLazyProperty(feature.properties, "_last_edit:passed_time", () => {
|
||||||
|
@ -628,7 +700,7 @@ export default class SimpleMetaTaggers {
|
||||||
{
|
{
|
||||||
keys: ["_currency"],
|
keys: ["_currency"],
|
||||||
doc: "Adds the currency valid for the object, based on country or explicit tagging. Can be a single currency or a semicolon-separated list of currencies. Empty if no currency is found.",
|
doc: "Adds the currency valid for the object, based on country or explicit tagging. Can be a single currency or a semicolon-separated list of currencies. Empty if no currency is found.",
|
||||||
isLazy: true,
|
isLazy: true
|
||||||
},
|
},
|
||||||
(feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
|
(feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
|
||||||
if (tagsStore === undefined) {
|
if (tagsStore === undefined) {
|
||||||
|
@ -670,6 +742,7 @@ export default class SimpleMetaTaggers {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
public static metatags: SimpleMetaTagger[] = [
|
public static metatags: SimpleMetaTagger[] = [
|
||||||
SimpleMetaTaggers.latlon,
|
SimpleMetaTaggers.latlon,
|
||||||
SimpleMetaTaggers.layerInfo,
|
SimpleMetaTaggers.layerInfo,
|
||||||
|
@ -689,6 +762,7 @@ export default class SimpleMetaTaggers {
|
||||||
SimpleMetaTaggers.referencingWays,
|
SimpleMetaTaggers.referencingWays,
|
||||||
SimpleMetaTaggers.timeSinceLastEdit,
|
SimpleMetaTaggers.timeSinceLastEdit,
|
||||||
SimpleMetaTaggers.currency,
|
SimpleMetaTaggers.currency,
|
||||||
|
SimpleMetaTaggers.normalizePanoramax
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -770,8 +844,8 @@ export default class SimpleMetaTaggers {
|
||||||
[
|
[
|
||||||
"Metatags are extra tags available, in order to display more data or to give better questions.",
|
"Metatags are extra tags available, in order to display more data or to give better questions.",
|
||||||
"They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.",
|
"They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.",
|
||||||
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object",
|
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object"
|
||||||
].join("\n"),
|
].join("\n")
|
||||||
]
|
]
|
||||||
|
|
||||||
subElements.push("## Metatags calculated by MapComplete")
|
subElements.push("## Metatags calculated by MapComplete")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue