From 584fb3cb57e77ea4382fda152d8604ef8293f9b1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 12 Feb 2024 12:39:35 +0100 Subject: [PATCH] Themes: add proper 'image along way' possibility --- assets/layers/aerialway/aerialway.json | 36 ++++- assets/layers/ski_piste/ski_piste.json | 3 +- assets/png/license_info.json | 16 +++ assets/png/twoway.png | Bin 0 -> 8641 bytes assets/png/twoway.png.license | 2 + assets/png/twoway.svg | 58 ++++++++ assets/png/twoway.svg.license | 2 + assets/themes/ski/ski.json | 2 +- package.json | 2 +- src/Logic/Tags/And.ts | 5 + src/Logic/Tags/Or.ts | 5 + src/Logic/Tags/RegexTag.ts | 8 ++ src/Logic/Tags/Tag.ts | 18 ++- src/Logic/Tags/TagsFilter.ts | 3 + .../Json/LineRenderingConfigJson.ts | 11 ++ src/Models/ThemeConfig/LineRenderingConfig.ts | 26 +++- src/UI/Map/ShowDataLayer.ts | 132 +++++++++--------- 17 files changed, 259 insertions(+), 70 deletions(-) create mode 100644 assets/png/twoway.png create mode 100644 assets/png/twoway.png.license create mode 100644 assets/png/twoway.svg create mode 100644 assets/png/twoway.svg.license diff --git a/assets/layers/aerialway/aerialway.json b/assets/layers/aerialway/aerialway.json index bc2427975..0b5944c70 100644 --- a/assets/layers/aerialway/aerialway.json +++ b/assets/layers/aerialway/aerialway.json @@ -134,6 +134,27 @@ } }, "opening_hours", + { + "id": "oneway", + "question": { + "en": "In what direction can this aerialway be taken?" + }, + "mappings": [ + { + "if": "oneway=yes", + "alsoShowIf": "oneway=", + "then": { + "en": "This aerialway can only be taken to the top" + } + }, + { + "if": "oneway=no", + "then": { + "en": "This aerialway can be taken in both directions" + } + } + ] + }, { "id": "length", "render": { @@ -144,7 +165,20 @@ "lineRendering": [ { "width": "4", - "color": "black" + "color": "black", + "imageAlongWay": [ { + "if": "oneway=no", + "then": "./assets/png/twoway.png" + },{ + "if": { + "or": [ + "oneway=yes", + "oneway=" + ] + }, + "then": "./assets/png/oneway.png" + } + ] } ], "id": "aerialway", diff --git a/assets/layers/ski_piste/ski_piste.json b/assets/layers/ski_piste/ski_piste.json index 1cd365916..b2063eab1 100644 --- a/assets/layers/ski_piste/ski_piste.json +++ b/assets/layers/ski_piste/ski_piste.json @@ -96,7 +96,8 @@ "then": "gray" } ] - } + }, + "imageAlongWay": "./assets/png/oneway.png" } ], "id": "ski_piste", diff --git a/assets/png/license_info.json b/assets/png/license_info.json index d5571eb0c..548e52793 100644 --- a/assets/png/license_info.json +++ b/assets/png/license_info.json @@ -14,5 +14,21 @@ "Pieter Vander Vennet" ], "sources": [] + }, + { + "path": "twoway.png", + "license": "CC0-1.0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, + { + "path": "twoway.svg", + "license": "CC0-1.0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] } ] \ No newline at end of file diff --git a/assets/png/twoway.png b/assets/png/twoway.png new file mode 100644 index 0000000000000000000000000000000000000000..e57d53302658964cfcf286f03fb8bc0b48b6d9cb GIT binary patch literal 8641 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 literal 0 HcmV?d00001 diff --git a/assets/png/twoway.png.license b/assets/png/twoway.png.license new file mode 100644 index 000000000..2452bee1e --- /dev/null +++ b/assets/png/twoway.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Pieter Vander Vennet +SPDX-License-Identifier: CC0 \ No newline at end of file diff --git a/assets/png/twoway.svg b/assets/png/twoway.svg new file mode 100644 index 000000000..7a28a60e2 --- /dev/null +++ b/assets/png/twoway.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + diff --git a/assets/png/twoway.svg.license b/assets/png/twoway.svg.license new file mode 100644 index 000000000..2452bee1e --- /dev/null +++ b/assets/png/twoway.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Pieter Vander Vennet +SPDX-License-Identifier: CC0 \ No newline at end of file diff --git a/assets/themes/ski/ski.json b/assets/themes/ski/ski.json index 1209804a7..2211febf9 100644 --- a/assets/themes/ski/ski.json +++ b/assets/themes/ski/ski.json @@ -7,7 +7,7 @@ "en": "Everything you need to go skiing" }, "icon": "./assets/layers/aerialway/chair_lift.svg", - "enableTerrain": true, + "enableTerrain": false, "layers": [ "ski_piste", "aerialway", diff --git a/package.json b/package.json index 7b8e6915c..3eaa0a661 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.37.3", + "version": "0.37.4", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index dff0919f8..6654ac99e 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -4,6 +4,7 @@ import { TagUtils } from "./TagUtils" import { Tag } from "./Tag" import { RegexTag } from "./RegexTag" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { ExpressionSpecification } from "maplibre-gl" export class And extends TagsFilter { public and: TagsFilter[] @@ -429,4 +430,8 @@ export class And extends TagsFilter { f(this) this.and.forEach((sub) => sub.visit(f)) } + + asMapboxExpression(): ExpressionSpecification { + return ["all", ...this.and.map(t => t.asMapboxExpression())] + } } diff --git a/src/Logic/Tags/Or.ts b/src/Logic/Tags/Or.ts index a0c0f6622..2bda9681c 100644 --- a/src/Logic/Tags/Or.ts +++ b/src/Logic/Tags/Or.ts @@ -2,6 +2,7 @@ import { TagsFilter } from "./TagsFilter" import { TagUtils } from "./TagUtils" import { And } from "./And" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { ExpressionSpecification } from "maplibre-gl" export class Or extends TagsFilter { public or: TagsFilter[] @@ -288,4 +289,8 @@ export class Or extends TagsFilter { f(this) this.or.forEach((t) => t.visit(f)) } + + asMapboxExpression(): ExpressionSpecification { + return ["any", ...this.or.map(t => t.asMapboxExpression())] + } } diff --git a/src/Logic/Tags/RegexTag.ts b/src/Logic/Tags/RegexTag.ts index f024f8251..4f3c82f9b 100644 --- a/src/Logic/Tags/RegexTag.ts +++ b/src/Logic/Tags/RegexTag.ts @@ -1,6 +1,7 @@ import { Tag } from "./Tag" import { TagsFilter } from "./TagsFilter" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { ExpressionSpecification } from "maplibre-gl" export class RegexTag extends TagsFilter { public readonly key: RegExp | string @@ -357,4 +358,11 @@ export class RegexTag extends TagsFilter { visit(f: (TagsFilter) => void) { f(this) } + + asMapboxExpression(): ExpressionSpecification { + if(typeof this.key=== "string" && typeof this.value === "string" ) { + return [this.invert ? "!=" : "==", ["get",this.key], this.value] + } + throw "TODO" + } } diff --git a/src/Logic/Tags/Tag.ts b/src/Logic/Tags/Tag.ts index b532b7053..cd8146ccd 100644 --- a/src/Logic/Tags/Tag.ts +++ b/src/Logic/Tags/Tag.ts @@ -1,10 +1,12 @@ import { Utils } from "../../Utils" import { TagsFilter } from "./TagsFilter" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { ExpressionSpecification } from "maplibre-gl" export class Tag extends TagsFilter { public key: string public value: string + constructor(key: string, value: string) { super() this.key = key @@ -63,7 +65,7 @@ export class Tag extends TagsFilter { asOverpass(): string[] { if (this.value === "") { // NOT having this key - return ['[!"' + this.key + '"]'] + return ["[!\"" + this.key + "\"]"] } return [`["${this.key}"="${this.value}"]`] } @@ -81,7 +83,7 @@ export class Tag extends TagsFilter { asHumanString( linkToWiki?: boolean, shorten?: boolean, - currentProperties?: Record + currentProperties?: Record, ) { let v = this.value if (typeof v !== "string") { @@ -165,4 +167,16 @@ export class Tag extends TagsFilter { visit(f: (tagsFilter: TagsFilter) => void) { f(this) } + + asMapboxExpression(): ExpressionSpecification { + if (this.value === "") { + return [ + "any", + ["!", ["has", this.key]], + ["==", ["get", this.key], ""], + ] + + } + return ["==", ["get", this.key], this.value] + } } diff --git a/src/Logic/Tags/TagsFilter.ts b/src/Logic/Tags/TagsFilter.ts index e925a76ef..c75b7f6f4 100644 --- a/src/Logic/Tags/TagsFilter.ts +++ b/src/Logic/Tags/TagsFilter.ts @@ -1,4 +1,5 @@ import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { ExpressionSpecification } from "maplibre-gl" export abstract class TagsFilter { abstract asOverpass(): string[] @@ -63,4 +64,6 @@ export abstract class TagsFilter { * Walks the entire tree, every tagsFilter will be passed into the function once */ abstract visit(f: (tagsFilter: TagsFilter) => void) + + abstract asMapboxExpression(): ExpressionSpecification } diff --git a/src/Models/ThemeConfig/Json/LineRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/LineRenderingConfigJson.ts index ffc5d1ea8..e7bbc86dd 100644 --- a/src/Models/ThemeConfig/Json/LineRenderingConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LineRenderingConfigJson.ts @@ -1,4 +1,7 @@ import { MinimalTagRenderingConfigJson } from "./TagRenderingConfigJson" +import { MappingConfigJson } from "./QuestionableTagRenderingConfigJson" +import { TagsFilter } from "../../../Logic/Tags/TagsFilter" +import { TagConfigJson } from "./TagConfigJson" /** * The LineRenderingConfig gives all details onto how to render a single line of a feature. @@ -74,4 +77,12 @@ export default interface LineRenderingConfigJson { * type: int */ offset?: number | MinimalTagRenderingConfigJson + /** + * question: What PNG-image should be shown along the way? + * + * ifunset: no image is shown along the way + * suggestions: [{if: "./assets/png/oneway.png", then: "Show a oneway error"}] + * type: image + */ + imageAlongWay?: {if: TagConfigJson, then: string}[] | string } diff --git a/src/Models/ThemeConfig/LineRenderingConfig.ts b/src/Models/ThemeConfig/LineRenderingConfig.ts index 57f01e8d1..bb52e2cff 100644 --- a/src/Models/ThemeConfig/LineRenderingConfig.ts +++ b/src/Models/ThemeConfig/LineRenderingConfig.ts @@ -1,7 +1,8 @@ import WithContextLoader from "./WithContextLoader" import TagRenderingConfig from "./TagRenderingConfig" -import { Utils } from "../../Utils" import LineRenderingConfigJson from "./Json/LineRenderingConfigJson" +import { TagUtils } from "../../Logic/Tags/TagUtils" +import { TagsFilter } from "../../Logic/Tags/TagsFilter" export default class LineRenderingConfig extends WithContextLoader { public readonly color: TagRenderingConfig @@ -12,6 +13,7 @@ export default class LineRenderingConfig extends WithContextLoader { public readonly fill: TagRenderingConfig public readonly fillColor: TagRenderingConfig public readonly leftRightSensitive: boolean + public readonly imageAlongWay: { if?: TagsFilter, then: string }[] constructor(json: LineRenderingConfigJson, context: string) { super(json, context) @@ -21,6 +23,28 @@ export default class LineRenderingConfig extends WithContextLoader { this.lineCap = this.tr("lineCap", "round") this.fill = this.tr("fill", undefined) this.fillColor = this.tr("fillColor", undefined) + this.imageAlongWay = [] + if (json.imageAlongWay) { + if (typeof json.imageAlongWay === "string") { + this.imageAlongWay.push({ + then: json.imageAlongWay, + }) + } else { + for (let i = 0; i < json.imageAlongWay.length; i++) { + const imgAlong = json.imageAlongWay[i] + const ctx = context + ".imageAlongWay[" + i + "]" + if(!imgAlong.then.endsWith(".png")){ + throw "An imageAlongWay should always be a PNG image" + } + this.imageAlongWay.push( + { + if: TagUtils.Tag(imgAlong.if, ctx), + then: imgAlong.then, + }, + ) + } + } + } if (typeof json.offset === "string") { json.offset = parseFloat(json.offset) diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index e8824f417..3bae53507 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -1,5 +1,5 @@ import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import type { Map as MlMap } from "maplibre-gl" +import type { AddLayerObject, Map as MlMap } from "maplibre-gl" import { GeoJSONSource, Marker } from "maplibre-gl" import { ShowDataLayerOptions } from "./ShowDataLayerOptions" import { GeoOperations } from "../../Logic/GeoOperations" @@ -15,6 +15,7 @@ import * as range_layer from "../../../assets/layers/range/range.json" import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource" +import { TagsFilter } from "../../Logic/Tags/TagsFilter" class PointRenderingLayer { private readonly _config: PointRenderingConfig @@ -36,7 +37,7 @@ class PointRenderingLayer { visibility?: Store, fetchStore?: (id: string) => Store>, onClick?: (feature: Feature) => void, - selectedElement?: Store<{ properties: { id?: string } }> + selectedElement?: Store<{ properties: { id?: string } }>, ) { this._visibility = visibility this._config = config @@ -89,7 +90,7 @@ class PointRenderingLayer { " while rendering", location, "of", - this._config + this._config, ) } const id = feature.properties.id + "-" + location @@ -97,7 +98,7 @@ class PointRenderingLayer { const loc = GeoOperations.featureToCoordinateWithRenderingType( feature, - location + location, ) if (loc === undefined) { continue @@ -153,7 +154,7 @@ class PointRenderingLayer { if (this._onClick) { const self = this - el.addEventListener("click", function (ev) { + el.addEventListener("click", function(ev) { ev.preventDefault() self._onClick(feature) // Workaround to signal the MapLibreAdaptor to ignore this click @@ -221,7 +222,7 @@ class LineRenderingLayer { config: LineRenderingConfig, visibility?: Store, fetchStore?: (id: string) => Store>, - onClick?: (feature: Feature) => void + onClick?: (feature: Feature) => void, ) { this._layername = layername this._map = map @@ -235,53 +236,60 @@ class LineRenderingLayer { map.on("styledata", () => self.update(features.features)) } - private async addSymbolLayer(sourceId: string, url: string = "./assets/png/oneway.png") { - const map = this._map - const imgId = url.replaceAll(/[/.-]/g, "_") - - if (map.getImage(imgId) === undefined) { - await new Promise((resolve, reject) => { - map.loadImage(url, (err, image) => { - if (err) { - console.error("Could not add symbol layer to line due to", err) - reject(err) - return - } - map.addImage(imgId, image) - resolve() - }) - }) - } - - map.addLayer({ - "id": "symbol-layer_" + this._layername + "-" + imgId, - 'type': 'symbol', - 'source': sourceId, - 'layout': { - 'symbol-placement': 'line', - 'symbol-spacing': 10, - 'icon-allow-overlap': true, - 'icon-rotation-alignment':'map', - 'icon-pitch-alignment':'map', - 'icon-image': imgId, - 'icon-size': 0.055, - 'visibility': 'visible' - } - }); - - } - public destruct(): void { this._map.removeLayer(this._layername + "_polygon") } + private async addSymbolLayer(sourceId: string, imageAlongWay: { if?: TagsFilter, then: string }[]) { + const map = this._map + await Promise.allSettled(imageAlongWay.map(async (img, i) => { + const imgId = img.then.replaceAll(/[/.-]/g, "_") + if (map.getImage(imgId) === undefined) { + await new Promise((resolve, reject) => { + map.loadImage(img.then, (err, image) => { + if (err) { + console.error("Could not add symbol layer to line due to", err) + return + } + map.addImage(imgId, image) + resolve() + }) + }) + } + + + const spec: AddLayerObject = { + "id": "symbol-layer_" + this._layername + "-" + i, + "type": "symbol", + "source": sourceId, + "layout": { + "symbol-placement": "line", + "symbol-spacing": 10, + "icon-allow-overlap": true, + "icon-rotation-alignment": "map", + "icon-pitch-alignment": "map", + "icon-image": imgId, + "icon-size": 0.055, + }, + } + const filter = img.if?.asMapboxExpression() + console.log(">>>", this._layername, imgId, img.if, "-->", filter) + if (filter) { + spec.filter = filter + } + map.addLayer(spec) + })) + + + } + /** * Calculate the feature-state for maplibre * @param properties * @private */ private calculatePropsFor( - properties: Record + properties: Record, ): Partial> { const config = this._config @@ -357,11 +365,8 @@ class LineRenderingLayer { }, }) - if(this._layername.startsWith("mapcomplete_ski_piste") || this._layername.startsWith("mapcomplete_aerialway")){ - // TODO FIXME properly enable this so that more layers can use this if appropriate - this.addSymbolLayer(this._layername) - }else{ - console.log("No oneway arrow for", this._layername) + if (this._config.imageAlongWay) { + this.addSymbolLayer(this._layername, this._config.imageAlongWay) } @@ -372,7 +377,7 @@ class LineRenderingLayer { } map.setFeatureState( { source: this._layername, id: feature.properties.id }, - this.calculatePropsFor(feature.properties) + this.calculatePropsFor(feature.properties), ) } @@ -415,7 +420,7 @@ class LineRenderingLayer { "Error while setting visibility of layers ", linelayer, polylayer, - e + e, ) } }) @@ -436,7 +441,7 @@ class LineRenderingLayer { console.trace( "Got a feature without ID; this causes rendering bugs:", feature, - "from" + "from", ) LineRenderingLayer.missingIdTriggered = true } @@ -448,7 +453,7 @@ class LineRenderingLayer { if (this._fetchStore === undefined) { map.setFeatureState( { source: this._layername, id }, - this.calculatePropsFor(feature.properties) + this.calculatePropsFor(feature.properties), ) } else { const tags = this._fetchStore(id) @@ -465,7 +470,7 @@ class LineRenderingLayer { } map.setFeatureState( { source: this._layername, id }, - this.calculatePropsFor(properties) + this.calculatePropsFor(properties), ) }) } @@ -489,7 +494,7 @@ export default class ShowDataLayer { layer: LayerConfig drawMarkers?: true | boolean drawLines?: true | boolean - } + }, ) { this._options = options const self = this @@ -500,7 +505,7 @@ export default class ShowDataLayer { mlmap: UIEventSource, features: FeatureSource, layers: LayerConfig[], - options?: Partial + options?: Partial, ) { const perLayer: PerLayerFeatureSourceSplitter = new PerLayerFeatureSourceSplitter( @@ -508,7 +513,7 @@ export default class ShowDataLayer { features, { constructStore: (features, layer) => new SimpleFeatureSource(layer, features), - } + }, ) perLayer.forEach((fs) => { new ShowDataLayer(mlmap, { @@ -522,7 +527,7 @@ export default class ShowDataLayer { public static showRange( map: Store, features: FeatureSource, - doShowLayer?: Store + doShowLayer?: Store, ): ShowDataLayer { return new ShowDataLayer(map, { layer: ShowDataLayer.rangeLayer, @@ -531,7 +536,8 @@ export default class ShowDataLayer { }) } - public destruct() {} + public destruct() { + } private zoomToCurrentFeatures(map: MlMap) { if (this._options.zoomToFeatures) { @@ -552,9 +558,9 @@ export default class ShowDataLayer { (this._options.layer.title === undefined ? undefined : (feature: Feature) => { - selectedElement?.setData(feature) - selectedLayer?.setData(this._options.layer) - }) + selectedElement?.setData(feature) + selectedLayer?.setData(this._options.layer) + }) if (this._options.drawLines !== false) { for (let i = 0; i < this._options.layer.lineRendering.length; i++) { const lineRenderingConfig = this._options.layer.lineRendering[i] @@ -565,7 +571,7 @@ export default class ShowDataLayer { lineRenderingConfig, doShowLayer, fetchStore, - onClick + onClick, ) this.onDestroy.push(l.destruct) } @@ -580,7 +586,7 @@ export default class ShowDataLayer { doShowLayer, fetchStore, onClick, - selectedElement + selectedElement, ) } }