From 75abd18d90f47b8cc7a756d1ac31586eced4894c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 28 Feb 2022 18:52:28 +0100 Subject: [PATCH] Made mapRenderings rewritable --- Models/ThemeConfig/Conversion/PrepareLayer.ts | 91 +++++++++++++------ Models/ThemeConfig/Json/LayerConfigJson.ts | 2 +- .../ThemeConfig/Json/RewritableConfigJson.ts | 38 ++++++++ test/LegacyThemeLoader.spec.ts | 85 ++++++++++++++++- 4 files changed, 185 insertions(+), 31 deletions(-) diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 88fc0276e..9e75fb802 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -1,14 +1,16 @@ -import {Conversion, DesugaringContext, Fuse, OnEveryConcat, SetDefault} from "./Conversion"; +import {Conversion, DesugaringContext, Fuse, OnEvery, OnEveryConcat, SetDefault} from "./Conversion"; import {LayerConfigJson} from "../Json/LayerConfigJson"; import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"; import {Utils} from "../../../Utils"; import Translations from "../../../UI/i18n/Translations"; import {Translation} from "../../../UI/i18n/Translation"; +import RewritableConfigJson from "../Json/RewritableConfigJson"; class ExpandTagRendering extends Conversion { private readonly _state: DesugaringContext; + constructor(state: DesugaringContext) { - super("Converts a tagRenderingSpec into the full tagRendering", [],"ExpandTagRendering"); + super("Converts a tagRenderingSpec into the full tagRendering", [], "ExpandTagRendering"); this._state = state; } @@ -147,17 +149,17 @@ class ExpandGroupRewrite extends Conversion<{ constructor(state: DesugaringContext) { super( - "Converts a rewrite config for tagRenderings into the expanded form",[], + "Converts a rewrite config for tagRenderings into the expanded form", [], "ExpandGroupRewrite" ); this._expandSubTagRenderings = new ExpandTagRendering(state) } - convert( json: - { - rewrite: - { sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[] - } | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings?: string[] } { + convert(json: + { + rewrite: + { sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[] + } | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings?: string[] } { if (json["rewrite"] === undefined) { return {result: [json], errors: [], warnings: []} @@ -167,32 +169,32 @@ class ExpandGroupRewrite extends Conversion<{ { sourceString: string[]; into: (string | any)[][] }; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[] }>json; - + { const errors = [] - if(!Array.isArray(config.rewrite.sourceString)){ + if (!Array.isArray(config.rewrite.sourceString)) { let extra = ""; - if(typeof config.rewrite.sourceString === "string"){ - extra=`
Try "sourceString": [ "${config.rewrite.sourceString}" ] instead (note the [ and ])` + if (typeof config.rewrite.sourceString === "string") { + extra = `
Try "sourceString": [ "${config.rewrite.sourceString}" ] instead (note the [ and ])` } - const msg = context+"
Invalid format: a rewrite block is defined, but the 'sourceString' should be an array of strings, but it is a "+typeof config.rewrite.sourceString + extra + const msg = context + "
Invalid format: a rewrite block is defined, but the 'sourceString' should be an array of strings, but it is a " + typeof config.rewrite.sourceString + extra errors.push(msg) } const expectedLength = config.rewrite.sourceString.length - for (let i = 0; i < config.rewrite.into.length; i++){ + for (let i = 0; i < config.rewrite.into.length; i++) { const targets = config.rewrite.into[i]; - if(!Array.isArray(targets)){ - errors.push(`${context}.rewrite.into[${i}] should be an array of values, but it is a `+typeof targets) - } else if(targets.length !== expectedLength){ + if (!Array.isArray(targets)) { + errors.push(`${context}.rewrite.into[${i}] should be an array of values, but it is a ` + typeof targets) + } else if (targets.length !== expectedLength) { errors.push(`${context}.rewrite.into[${i}]:
The rewrite specified ${config.rewrite.sourceString} as sourcestring, which consists of ${expectedLength} values. The target ${JSON.stringify(targets)} has ${targets.length} items`) - if(typeof targets[0] !== "string"){ - errors.push(context+".rewrite.into["+i+"]: expected a string as first rewrite value values, but got "+targets[0]) + if (typeof targets[0] !== "string") { + errors.push(context + ".rewrite.into[" + i + "]: expected a string as first rewrite value values, but got " + targets[0]) - } + } } } @@ -205,7 +207,7 @@ class ExpandGroupRewrite extends Conversion<{ } } - const subRenderingsRes = <{ result: TagRenderingConfigJson[][], errors, warnings }> this._expandSubTagRenderings.convertAll(config.renderings, context); + const subRenderingsRes = <{ result: TagRenderingConfigJson[][], errors, warnings }>this._expandSubTagRenderings.convertAll(config.renderings, context); const subRenderings: TagRenderingConfigJson[] = [].concat(...subRenderingsRes.result); const errors = subRenderingsRes.errors; const warnings = subRenderingsRes.warnings; @@ -217,7 +219,7 @@ class ExpandGroupRewrite extends Conversion<{ const sourceStrings = config.rewrite.sourceString; for (const targets of config.rewrite.into) { const groupName = targets[0]; - if(typeof groupName !== "string"){ + if (typeof groupName !== "string") { throw "The first string of 'targets' should always be a string" } const trs: TagRenderingConfigJson[] = [] @@ -227,7 +229,7 @@ class ExpandGroupRewrite extends Conversion<{ for (let i = 0; i < sourceStrings.length; i++) { const source = sourceStrings[i] const target = targets[i] // This is a string OR a translation - rewritten = this.prepConfig(source, target, rewritten) + rewritten = ExpandRewrite.RewriteParts(source, target, rewritten) } rewritten.group = rewritten.group ?? groupName trs.push(rewritten) @@ -253,7 +255,7 @@ class ExpandGroupRewrite extends Conversion<{ rewrittenPerGroup.forEach((group, _) => { group.forEach(tr => { if (tr.id === undefined || tr.id === "") { - errors.push("A tagrendering has an empty ID after expanding the tag; the tagrendering is: "+JSON.stringify(tr)) + errors.push("A tagrendering has an empty ID after expanding the tag; the tagrendering is: " + JSON.stringify(tr)) } }) }) @@ -264,9 +266,18 @@ class ExpandGroupRewrite extends Conversion<{ }; } +} + +class ExpandRewrite extends Conversion, T[]> { + + constructor() { + super("Applies a rewrite", [], "ExpandRewrite"); + } + + /* Used for left|right group creation and replacement. * Every 'keyToRewrite' will be replaced with 'target' recursively. This substitution will happen in place in the object 'tr' */ - private prepConfig(keyToRewrite: string, target: string | any, tr: TagRenderingConfigJson): TagRenderingConfigJson { + public static RewriteParts(keyToRewrite: string, target: string | any, tr: T): T { const isTranslation = typeof target !== "string" @@ -293,8 +304,35 @@ class ExpandGroupRewrite extends Conversion<{ return replaceRecursive(tr) } -} + convert(json: T | RewritableConfigJson, context: string): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } { + + if (json["rewrite"] === undefined) { + + // not a rewrite + return {result: [(json)]} + } + + const rewrite = >json; + let toRewrite: T = rewrite.renderings + const keysToRewrite = rewrite.rewrite + const ts : T[] = [] + + for (let i = 0; i < keysToRewrite.into[0].length; i++){ + let t = Utils.Clone(rewrite.renderings) + for (let i1 = 0; i1 < keysToRewrite.sourceString.length; i1++){ + const key = keysToRewrite.sourceString[i1]; + const target = keysToRewrite.into[i1][i] + t = ExpandRewrite.RewriteParts(key, target, t) + } + ts.push(t) + } + + + return {result: ts}; + } + +} export class PrepareLayer extends Fuse { constructor(state: DesugaringContext) { @@ -302,6 +340,7 @@ export class PrepareLayer extends Fuse { "Fully prepares and expands a layer for the LayerConfig.", new OnEveryConcat("tagRenderings", new ExpandGroupRewrite(state)), new OnEveryConcat("tagRenderings", new ExpandTagRendering(state)), + new OnEveryConcat("mapRendering", new ExpandRewrite()), new SetDefault("titleIcons", ["defaults"]), new OnEveryConcat("titleIcons", new ExpandTagRendering(state)) ); diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index dce00b7c0..805efd0ac 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -179,7 +179,7 @@ export interface LayerConfigJson { /** * Visualisation of the items on the map */ - mapRendering: null | (PointRenderingConfigJson | LineRenderingConfigJson)[] + mapRendering: null | (PointRenderingConfigJson | LineRenderingConfigJson | RewritableConfigJson)[] /** * If set, this layer will pass all the features it receives onto the next layer. diff --git a/Models/ThemeConfig/Json/RewritableConfigJson.ts b/Models/ThemeConfig/Json/RewritableConfigJson.ts index 23fa11b6a..7efc900b3 100644 --- a/Models/ThemeConfig/Json/RewritableConfigJson.ts +++ b/Models/ThemeConfig/Json/RewritableConfigJson.ts @@ -1,5 +1,43 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; +/** + * Rewrites and multiplies the given renderings of type T. + * + * For example: + * + * + * ``` + * { + * rewrite: { + * sourceString: ["key", "a|b|c"], + * into: [ + * ["X","Y", "Z"], + * [0,1,2] + * ], + * renderings: { + * "key":"a|b|c" + * } + * } + * } + * ``` + * will result in _three_ copies (as the values to rewrite into have three values, namely: + * + * [ + * { + * // The first pair: key --> X, a|b|c --> 0 + * "X": 0 + * }, + * { + * "Y": 1 + * }, + * { + * "Z": 2 + * } + * + * ] + * + * + */ export default interface RewritableConfigJson { rewrite: { sourceString: string[], diff --git a/test/LegacyThemeLoader.spec.ts b/test/LegacyThemeLoader.spec.ts index 5abd0ba49..5de6c625c 100644 --- a/test/LegacyThemeLoader.spec.ts +++ b/test/LegacyThemeLoader.spec.ts @@ -6,6 +6,9 @@ import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme"; import {DetectMappingsWithImages, DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation"; import * as Assert from "assert"; import {ExtractImages, FixImages} from "../Models/ThemeConfig/Conversion/FixImages"; +import {PrepareLayer} from "../Models/ThemeConfig/Conversion/PrepareLayer"; +import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; +import LineRenderingConfigJson from "../Models/ThemeConfig/Json/LineRenderingConfigJson"; export default class LegacyThemeLoaderSpec extends T { @@ -502,7 +505,7 @@ export default class LegacyThemeLoaderSpec extends T { }, "test"); const images = r.result T.isTrue(images.length > 0, "No images found"); - T.isTrue(images.findIndex(img => img =="./assets/layers/bike_parking/staple.svg") >= 0, "staple.svg not mentioned"); + T.isTrue(images.findIndex(img => img == "./assets/layers/bike_parking/staple.svg") >= 0, "staple.svg not mentioned"); T.isTrue(images.findIndex(img => img == "./assets/layers/bike_parking/bollard.svg") >= 0, "bollard.svg not mentioned"); }], ["Rotation and colours is not detected as image", () => { @@ -511,7 +514,7 @@ export default class LegacyThemeLoaderSpec extends T { { mapRendering: [ { - "location":["point","centroid"], + "location": ["point", "centroid"], "icon": "pin:black", rotation: 180, iconSize: "40,40,center" @@ -522,9 +525,83 @@ export default class LegacyThemeLoaderSpec extends T { }, "test"); const images = r.result T.isTrue(images.length > 0, "No images found"); - T.isTrue(images.length < 2, "To much images found: "+images.join(", ")); + T.isTrue(images.length < 2, "To much images found: " + images.join(", ")); T.isTrue(images[0] === "pin", "pin not mentioned"); - }] + }], + ["Test expansion in map renderings", () => { + const exampleLayer: LayerConfigJson = { + id: "testlayer", + source: { + osmTags: "key=value" + }, + mapRendering: [ + { + "rewrite": { + sourceString: ["left|right", "lr_offset"], + into: [ + ["left", "right"], + [-6, +6] + ] + }, + renderings: { + "color": { + "render": "#888", + "mappings": [ + { + "if": "parking:condition:left|right=free", + "then": "#299921" + }, + { + "if": "parking:condition:left|right=disc", + "then": "#219991" + } + ] + }, + "offset": "lr_offset" + } + } + ] + } + const prep = new PrepareLayer({ + tagRenderings: new Map(), + sharedLayers: new Map() + }) + const result = prep.convertStrict(exampleLayer, "test") + + const expected = { + "id": "testlayer", + "source": {"osmTags": "key=value"}, + "mapRendering": [{ + "color": { + "render": "#888", + "mappings": [{ + "if": "parking:condition:left=free", + "then": "#299921" + }, + {"if": "parking:condition:left=disc", + "then": "#219991"}] + }, + "offset": "-6" + }, { + "color": { + "render": "#888", + "mappings": [{ + "if": "parking:condition:right=free", + "then": "#299921" + }, + {"if": "parking:condition:right=disc", + "then": "#219991"}] + }, + "offset": "6" + }], + "titleIcons": [{"render": "defaults", "id": "defaults"}] + } + + + Assert.equal(JSON.stringify(result), JSON.stringify(expected)) + } + + ] ] ); }