diff --git a/assets/layers/artwork/artwork.json b/assets/layers/artwork/artwork.json index 7ed51de84..e89dd7751 100644 --- a/assets/layers/artwork/artwork.json +++ b/assets/layers/artwork/artwork.json @@ -661,7 +661,11 @@ "freeform": { "key": "artist_name" }, - "condition": "artist:wikidata=", + "condition": { + "and": [ + "artist:wikidata=" + ] + }, "id": "artwork-artist_name", "labels": [ "artwork-question" @@ -744,7 +748,11 @@ "wikipedia", { "id": "artwork_subject", - "condition": "subject:wikidata~*", + "condition": { + "and": [ + "subject:wikidata~*" + ] + }, "question": { "en": "What does this artwork depict?", "de": "Was zeigt dieses Kunstwerk?", @@ -856,9 +864,13 @@ ] }, { - "builtin": "bench.*bench-questions", + "builtin": "bench.bench-questions", "override": { - "condition": "amenity=bench" + "condition": { + "and": [ + "amenity=bench" + ] + } } } ], diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index 110ddfb2d..9a0d7fd32 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -331,8 +331,7 @@ "he": "חומר: {material}" }, "freeform": { - "key": "material", - "addExtraTags": [] + "key": "material" }, "mappings": [ { @@ -1045,11 +1044,15 @@ "bench-questions" ], "condition": { - "or": [ - "historic=memorial", - "inscription~*", - "memorial=bench", - "tourism=artwork" + "and": [ + { + "or": [ + "historic=memorial", + "inscription~*", + "memorial=bench", + "tourism=artwork" + ] + } ] }, "question": { @@ -1112,7 +1115,11 @@ { "builtin": "artwork.*artwork-question", "override": { - "condition": "tourism=artwork" + "condition": { + "and": [ + "tourism=artwork" + ] + } } } ], diff --git a/assets/layers/ghost_bike/ghost_bike.json b/assets/layers/ghost_bike/ghost_bike.json index 6088e9ded..829591532 100644 --- a/assets/layers/ghost_bike/ghost_bike.json +++ b/assets/layers/ghost_bike/ghost_bike.json @@ -327,6 +327,6 @@ }, "allowMove": { "enableRelocation": false, - "enableImproveAccuraccy": true + "enableImproveAccuracy": true } } diff --git a/assets/layers/memorial/license_info.json b/assets/layers/memorial/license_info.json index a02ef7b01..9227d6a8f 100644 --- a/assets/layers/memorial/license_info.json +++ b/assets/layers/memorial/license_info.json @@ -1,4 +1,14 @@ [ + { + "path": "memorial.svg", + "license": "CC0-1.0", + "authors": [ + "OSM-Carto" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Memorial-16.svg" + ] + }, { "path": "plaque.svg", "license": "CC0-1.0", diff --git a/assets/layers/memorial/memorial.json b/assets/layers/memorial/memorial.json index c49171f44..a3516e29e 100644 --- a/assets/layers/memorial/memorial.json +++ b/assets/layers/memorial/memorial.json @@ -2,15 +2,34 @@ "id": "memorial", "description": "Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on", "source": { - "osmTags": "memorial=plaque" + "osmTags": { + "or": [ + "memorial~*", + "historic=memorial" + ] + } + }, + "name": { + "en": "Memorials" }, "title": { "render": { "en": "Memorial plaque", - "de": "Gedenktafel", "ca": "Placa commemorativa", - "cs": "Pamětní deska" - } + "cs": "Pamětní deska", + "de": "Gedenktafel" + }, + "mappings": [ + { + "if": "memorial=plaque", + "then": { + "en": "Memorial plaque", + "de": "Gedenktafel", + "ca": "Placa commemorativa", + "cs": "Pamětní deska" + } + } + ] }, "pointRendering": [ { @@ -24,33 +43,169 @@ "color": "white" }, { - "icon": "./assets/layers/memorial/plaque.svg" + "icon": { + "render": "./assets/layers/memorial/memorial.svg", + "mappings": [ + { + "if": "memorial=plaque", + "then": "./assets/layers/memorial/plaque.svg" + }, + { + "if": { + "or": [ + "memorial=bench", + "amenity=bench" + ] + }, + "then": "./assets/layers/bench/bench.svg" + } + ] + } } ] } ], "lineRendering": [], "tagRenderings": [ + "images", + { + "id": "memorial-type", + "question": { + "en": "What type of memorial is this?" + }, + "mappings": [ + { + "if": "memorial=statue", + "then": { + "en": "This is a statue" + }, + "addExtraTags": [ + "tourism=artwork", + "artwork=statue" + ] + }, + { + "if": "memorial=plaque", + "then": { + "en": "This is a plaque" + } + }, + { + "if": "memorial=bench", + "then": { + "en": "This is a commemorative bench" + }, + "addExtraTags": [ + "amenity=bench" + ] + }, + { + "if": "memorial=ghost_bike", + "then": { + "en": "This is a ghost bike - a bicycle painted white to remember a cyclist whom deceased because of a car crash" + } + } + ] + }, { "id": "inscription", "question": { "en": "What is the inscription of this plaque?", - "de": "Wie lautet die Inschrift auf dieser Gedenktafel?", "ca": "Quina és la inscripció d'aquesta placa?", - "cs": "Jaký je nápis na této desce?" + "cs": "Jaký je nápis na této desce?", + "de": "Wie lautet die Inschrift auf dieser Gedenktafel?" }, + "#:condition": "Benches have a separate inscription question", + "condition": "memorial!=bench", "render": { "en": "The inscription on this plaque reads:

{inscription}

", - "de": "Die Inschrift auf dieser Gedenktafel lautet:

{inscription}

", "ca": "La inscripció d'aquesta placa diu:

{inscription}

", - "cs": "Nápis na této desce zní:

{inscription}

" + "cs": "Nápis na této desce zní:

{inscription}

", + "de": "Die Inschrift auf dieser Gedenktafel lautet:

{inscription}

" }, "freeform": { "key": "inscription", "type": "text" + }, + "mappings": [ + { + "if": "not:inscription=yes", + "then": { + "en": "This memorial does not have an inscription" + }, + "addExtraTags": [ + "inscription=" + ] + } + ] + }, + { + "id": "wikidata", + "freeform": { + "key": "subject:wikidata", + "type": "wikidata", + "helperArgs": [ + "subject;memorial:conflict" + ] + }, + "question": { + "en": "What is the Wikipedia page about the person or event that is remembered here?" + }, + "questionHint": { + "en": "If the person or event does not have a Wikipedia page or Wikidata entity, skip this question." + }, + "render": { + "special": { + "type": "wikipedia", + "keyToShowWikipediaFor": "subject:wikidata" + }, + "before": { + "en": "

Wikipedia page about the remembered event or person

" + } + } + }, + { + "question": { + "en": "When was this memorial installed?" + }, + "render": { + "nl": "Geplaatst op {start_date}", + "en": "Placed on {start_date}", + "it": "Piazzata in data {start_date}", + "fr": "Placé le {start_date}", + "ru": "Установлен {start_date}", + "de": "Aufgestellt am {start_date}", + "ca": "Col·locat el {start_date}", + "cs": "Umístěno {start_date}" + }, + "freeform": { + "key": "start_date", + "type": "date" + }, + "id": "start_date" + }, + { + "builtin": "bench.bench-questions", + "override": { + "condition": { + "+and": [ + "amenity=bench" + ] + } } } ], + "presets": [ + { + "title": { + "en": "a memorial" + }, + "tags": [ + "historic=memorial" + ] + } + ], + "minzoom": 9, "deletion": true, "allowMove": { "enableImproveAccuracy": true, diff --git a/assets/layers/memorial/memorial.svg b/assets/layers/memorial/memorial.svg new file mode 100644 index 000000000..81760a8f6 --- /dev/null +++ b/assets/layers/memorial/memorial.svg @@ -0,0 +1,59 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/assets/layers/memorial/memorial.svg.license b/assets/layers/memorial/memorial.svg.license new file mode 100644 index 000000000..eb7905b0c --- /dev/null +++ b/assets/layers/memorial/memorial.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: OSM-Carto +SPDX-License-Identifier: CC0 \ No newline at end of file diff --git a/assets/themes/memorials/memorials.json b/assets/themes/memorials/memorials.json new file mode 100644 index 000000000..fd7b166d9 --- /dev/null +++ b/assets/themes/memorials/memorials.json @@ -0,0 +1,29 @@ +{ + "id": "memorials", + "icon": "./assets/layers/memorial/memorial.svg", + "description": { + "en": "Memorials are physical objects permantently placed in the public space to remember a person or event. They can be a wide range of objects, such as statues, plaques, paintings, military objects (such as tanks), ..." + }, + "title": { + "en": "Memorials" + }, + "layers": [ + { + "builtin": [ + "ghost_bike", + "memorial" + ], + "override": { + "minzoom": 9 + } + }, + { + "builtin": [ + "bench" + ], + "override": { + "minzoom": 18 + } + } + ] +} diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index e3a0ac247..2921b547f 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -91,7 +91,7 @@ export default class LayerConfig extends WithContextLoader { mercatorCrs: json.source["mercatorCrs"], idKey: json.source["idKey"], }, - json.id + json.id, ) } @@ -106,8 +106,8 @@ export default class LayerConfig extends WithContextLoader { } this.units = [].concat( ...(json.units ?? []).map((unitJson, i) => - Unit.fromJson(unitJson, `${context}.unit[${i}]`) - ) + Unit.fromJson(unitJson, `${context}.unit[${i}]`), + ), ) if (json.description !== undefined) { @@ -122,7 +122,7 @@ export default class LayerConfig extends WithContextLoader { if (json.calculatedTags !== undefined) { if (!official) { console.warn( - `Unofficial theme ${this.id} with custom javascript! This is a security risk` + `Unofficial theme ${this.id} with custom javascript! This is a security risk`, ) } this.calculatedTags = [] @@ -191,7 +191,7 @@ export default class LayerConfig extends WithContextLoader { tags: pr.tags.map((t) => TagUtils.SimpleTag(t)), description: Translations.T( pr.description, - `${translationContext}.presets.${i}.description` + `${translationContext}.presets.${i}.description`, ), preciseInput: preciseInput, exampleImages: pr.exampleImages, @@ -205,7 +205,7 @@ export default class LayerConfig extends WithContextLoader { if (json.lineRendering) { this.lineRendering = Utils.NoNull(json.lineRendering).map( - (r, i) => new LineRenderingConfig(r, `${context}[${i}]`) + (r, i) => new LineRenderingConfig(r, `${context}[${i}]`), ) } else { this.lineRendering = [] @@ -213,7 +213,7 @@ export default class LayerConfig extends WithContextLoader { if (json.pointRendering) { this.mapRendering = Utils.NoNull(json.pointRendering).map( - (r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`) + (r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`), ) } else { this.mapRendering = [] @@ -225,7 +225,7 @@ export default class LayerConfig extends WithContextLoader { r.location.has("centroid") || r.location.has("projected_centerpoint") || r.location.has("start") || - r.location.has("end") + r.location.has("end"), ) if ( @@ -247,7 +247,7 @@ export default class LayerConfig extends WithContextLoader { Constants.priviliged_layers.indexOf(this.id) < 0 && this.source !== null /*library layer*/ && !this.source?.geojsonSource?.startsWith( - "https://api.openstreetmap.org/api/0.6/notes.json" + "https://api.openstreetmap.org/api/0.6/notes.json", ) ) { throw ( @@ -266,7 +266,7 @@ export default class LayerConfig extends WithContextLoader { typeof tr !== "string" && tr["builtin"] === undefined && tr["id"] === undefined && - tr["rewrite"] === undefined + tr["rewrite"] === undefined, ) ?? [] if (missingIds?.length > 0 && official) { console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds) @@ -277,8 +277,8 @@ export default class LayerConfig extends WithContextLoader { (tr, i) => new TagRenderingConfig( tr, - this.id + ".tagRenderings[" + i + "]" - ) + this.id + ".tagRenderings[" + i + "]", + ), ) if ( @@ -352,7 +352,7 @@ export default class LayerConfig extends WithContextLoader { public GetBaseTags(): Record { return TagUtils.changeAsProperties( - this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }] + this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }], ) } @@ -365,7 +365,7 @@ export default class LayerConfig extends WithContextLoader { neededLayer: string }[] = [], addedByDefault = false, - canBeIncluded = true + canBeIncluded = true, ): BaseUIElement { const extraProps: (string | BaseUIElement)[] = [] @@ -374,32 +374,32 @@ export default class LayerConfig extends WithContextLoader { if (canBeIncluded) { if (addedByDefault) { extraProps.push( - "**This layer is included automatically in every theme. This layer might contain no points**" + "**This layer is included automatically in every theme. This layer might contain no points**", ) } if (this.shownByDefault === false) { extraProps.push( - "This layer is not visible by default and must be enabled in the filter by the user. " + "This layer is not visible by default and must be enabled in the filter by the user. ", ) } if (this.title === undefined) { extraProps.push( - "Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable." + "Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable.", ) } if (this.name === undefined && this.shownByDefault === false) { extraProps.push( - "This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-=true" + "This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-=true", ) } if (this.name === undefined) { extraProps.push( - "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`" + "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`", ) } if (this.mapRendering.length === 0) { extraProps.push( - "Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`" + "Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`", ) } @@ -411,23 +411,28 @@ export default class LayerConfig extends WithContextLoader { : undefined, "This layer is loaded from an external source, namely ", new FixedUiElement(this.source.geojsonSource).SetClass("code"), - ]) + ]), ) } } else { extraProps.push( - "This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data." + "This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.", ) } let usingLayer: BaseUIElement[] = [] - if (usedInThemes?.length > 0 && !addedByDefault) { - usingLayer = [ - new Title("Themes using this layer", 4), - new List( - (usedInThemes ?? []).map((id) => new Link(id, "https://mapcomplete.org/" + id)) - ), - ] + if (!addedByDefault) { + + if (usedInThemes?.length > 0) { + usingLayer = [ + new Title("Themes using this layer", 4), + new List( + (usedInThemes ?? []).map((id) => new Link(id, "https://mapcomplete.org/" + id)), + ), + ] + } else if(this.source !== null) { + usingLayer = [new FixedUiElement("No themes use this layer")] + } } for (const dep of dependencies) { @@ -438,7 +443,7 @@ export default class LayerConfig extends WithContextLoader { " into the layout as it depends on it: ", dep.reason, "(" + dep.context + ")", - ]) + ]), ) } @@ -447,7 +452,7 @@ export default class LayerConfig extends WithContextLoader { new Combine([ "This layer is needed as dependency for layer", new Link(revDep, "#" + revDep), - ]) + ]), ) } @@ -459,14 +464,14 @@ export default class LayerConfig extends WithContextLoader { return undefined } const embedded: (Link | string)[] = values.values?.map((v) => - Link.OsmWiki(values.key, v, true).SetClass("mr-2") + Link.OsmWiki(values.key, v, true).SetClass("mr-2"), ) ?? ["_no preset options defined, or no values in them_"] return [ new Combine([ new Link( "", "https://taginfo.openstreetmap.org/keys/" + values.key + "#values", - true + true, ), Link.OsmWiki(values.key), ]).SetClass("flex"), @@ -475,7 +480,7 @@ export default class LayerConfig extends WithContextLoader { : new Link(values.type, "../SpecialInputElements.md#" + values.type), new Combine(embedded).SetClass("flex"), ] - }) + }), ) let quickOverview: BaseUIElement = undefined @@ -485,7 +490,7 @@ export default class LayerConfig extends WithContextLoader { "this quick overview is incomplete", new Table( ["attribute", "type", "values which are supported by this layer"], - tableRows + tableRows, ).SetClass("zebra-table"), ]).SetClass("flex-col flex") } @@ -499,7 +504,7 @@ export default class LayerConfig extends WithContextLoader { (mr) => mr.RenderIcon(new ImmutableStore({ id: "node/-1" }), { includeBadges: false, - }).html + }).html, ) .find((i) => i !== undefined) } @@ -511,7 +516,7 @@ export default class LayerConfig extends WithContextLoader { "Execute on overpass", Overpass.AsOverpassTurboLink(this.source.osmTags.optimize()) .replaceAll("(", "%28") - .replaceAll(")", "%29") + .replaceAll(")", "%29"), ) } catch (e) { console.error("Could not generate overpasslink for " + this.id) @@ -533,19 +538,19 @@ export default class LayerConfig extends WithContextLoader { const parts = neededTags["and"] tagsDescription.push( "Elements must match **all** of the following expressions:", - parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n") + parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"), ) } else if (neededTags["or"]) { const parts = neededTags["or"] tagsDescription.push( "Elements must match **any** of the following expressions:", - parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n") + parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"), ) } else { tagsDescription.push( "Elements must match the expression **" + - neededTags.asHumanString(true, false, {}) + - "**" + neededTags.asHumanString(true, false, {}) + + "**", ) } @@ -556,7 +561,7 @@ export default class LayerConfig extends WithContextLoader { return new Combine([ new Combine([new Title(this.id, 1), iconImg, this.description, "\n"]).SetClass( - "flex flex-col" + "flex flex-col", ), new List(extraProps), ...usingLayer,