forked from MapComplete/MapComplete
Made mapRenderings rewritable
This commit is contained in:
parent
9f81628f64
commit
75abd18d90
4 changed files with 185 additions and 31 deletions
|
@ -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 {LayerConfigJson} from "../Json/LayerConfigJson";
|
||||||
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson";
|
import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson";
|
||||||
import {Utils} from "../../../Utils";
|
import {Utils} from "../../../Utils";
|
||||||
import Translations from "../../../UI/i18n/Translations";
|
import Translations from "../../../UI/i18n/Translations";
|
||||||
import {Translation} from "../../../UI/i18n/Translation";
|
import {Translation} from "../../../UI/i18n/Translation";
|
||||||
|
import RewritableConfigJson from "../Json/RewritableConfigJson";
|
||||||
|
|
||||||
class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> {
|
class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> {
|
||||||
private readonly _state: DesugaringContext;
|
private readonly _state: DesugaringContext;
|
||||||
|
|
||||||
constructor(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;
|
this._state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,17 +149,17 @@ class ExpandGroupRewrite extends Conversion<{
|
||||||
|
|
||||||
constructor(state: DesugaringContext) {
|
constructor(state: DesugaringContext) {
|
||||||
super(
|
super(
|
||||||
"Converts a rewrite config for tagRenderings into the expanded form",[],
|
"Converts a rewrite config for tagRenderings into the expanded form", [],
|
||||||
"ExpandGroupRewrite"
|
"ExpandGroupRewrite"
|
||||||
);
|
);
|
||||||
this._expandSubTagRenderings = new ExpandTagRendering(state)
|
this._expandSubTagRenderings = new ExpandTagRendering(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
convert( json:
|
convert(json:
|
||||||
{
|
{
|
||||||
rewrite:
|
rewrite:
|
||||||
{ sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
|
{ sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
|
||||||
} | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings?: string[] } {
|
} | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings?: string[] } {
|
||||||
|
|
||||||
if (json["rewrite"] === undefined) {
|
if (json["rewrite"] === undefined) {
|
||||||
return {result: [<TagRenderingConfigJson>json], errors: [], warnings: []}
|
return {result: [<TagRenderingConfigJson>json], errors: [], warnings: []}
|
||||||
|
@ -167,32 +169,32 @@ class ExpandGroupRewrite extends Conversion<{
|
||||||
{ sourceString: string[]; into: (string | any)[][] };
|
{ sourceString: string[]; into: (string | any)[][] };
|
||||||
renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
|
renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
|
||||||
}>json;
|
}>json;
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const errors = []
|
const errors = []
|
||||||
|
|
||||||
if(!Array.isArray(config.rewrite.sourceString)){
|
if (!Array.isArray(config.rewrite.sourceString)) {
|
||||||
let extra = "";
|
let extra = "";
|
||||||
if(typeof config.rewrite.sourceString === "string"){
|
if (typeof config.rewrite.sourceString === "string") {
|
||||||
extra=`<br/>Try <span class='literal-code'>"sourceString": [ "${config.rewrite.sourceString}" ] </span> instead (note the [ and ])`
|
extra = `<br/>Try <span class='literal-code'>"sourceString": [ "${config.rewrite.sourceString}" ] </span> instead (note the [ and ])`
|
||||||
}
|
}
|
||||||
const msg = context+"<br/>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 + "<br/>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)
|
errors.push(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const expectedLength = config.rewrite.sourceString.length
|
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];
|
const targets = config.rewrite.into[i];
|
||||||
if(!Array.isArray(targets)){
|
if (!Array.isArray(targets)) {
|
||||||
errors.push(`${context}.rewrite.into[${i}] should be an array of values, but it is a `+typeof targets)
|
errors.push(`${context}.rewrite.into[${i}] should be an array of values, but it is a ` + typeof targets)
|
||||||
} else if(targets.length !== expectedLength){
|
} else if (targets.length !== expectedLength) {
|
||||||
errors.push(`${context}.rewrite.into[${i}]:<br/>The rewrite specified ${config.rewrite.sourceString} as sourcestring, which consists of ${expectedLength} values. The target ${JSON.stringify(targets)} has ${targets.length} items`)
|
errors.push(`${context}.rewrite.into[${i}]:<br/>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"){
|
if (typeof targets[0] !== "string") {
|
||||||
errors.push(context+".rewrite.into["+i+"]: expected a string as first rewrite value values, but got "+targets[0])
|
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 subRenderings: TagRenderingConfigJson[] = [].concat(...subRenderingsRes.result);
|
||||||
const errors = subRenderingsRes.errors;
|
const errors = subRenderingsRes.errors;
|
||||||
const warnings = subRenderingsRes.warnings;
|
const warnings = subRenderingsRes.warnings;
|
||||||
|
@ -217,7 +219,7 @@ class ExpandGroupRewrite extends Conversion<{
|
||||||
const sourceStrings = config.rewrite.sourceString;
|
const sourceStrings = config.rewrite.sourceString;
|
||||||
for (const targets of config.rewrite.into) {
|
for (const targets of config.rewrite.into) {
|
||||||
const groupName = targets[0];
|
const groupName = targets[0];
|
||||||
if(typeof groupName !== "string"){
|
if (typeof groupName !== "string") {
|
||||||
throw "The first string of 'targets' should always be a string"
|
throw "The first string of 'targets' should always be a string"
|
||||||
}
|
}
|
||||||
const trs: TagRenderingConfigJson[] = []
|
const trs: TagRenderingConfigJson[] = []
|
||||||
|
@ -227,7 +229,7 @@ class ExpandGroupRewrite extends Conversion<{
|
||||||
for (let i = 0; i < sourceStrings.length; i++) {
|
for (let i = 0; i < sourceStrings.length; i++) {
|
||||||
const source = sourceStrings[i]
|
const source = sourceStrings[i]
|
||||||
const target = targets[i] // This is a string OR a translation
|
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
|
rewritten.group = rewritten.group ?? groupName
|
||||||
trs.push(rewritten)
|
trs.push(rewritten)
|
||||||
|
@ -253,7 +255,7 @@ class ExpandGroupRewrite extends Conversion<{
|
||||||
rewrittenPerGroup.forEach((group, _) => {
|
rewrittenPerGroup.forEach((group, _) => {
|
||||||
group.forEach(tr => {
|
group.forEach(tr => {
|
||||||
if (tr.id === undefined || tr.id === "") {
|
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<T> extends Conversion<T | RewritableConfigJson<T>, T[]> {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super("Applies a rewrite", [], "ExpandRewrite");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Used for left|right group creation and replacement.
|
/* 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' */
|
* 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<T>(keyToRewrite: string, target: string | any, tr: T): T {
|
||||||
|
|
||||||
const isTranslation = typeof target !== "string"
|
const isTranslation = typeof target !== "string"
|
||||||
|
|
||||||
|
@ -293,8 +304,35 @@ class ExpandGroupRewrite extends Conversion<{
|
||||||
|
|
||||||
return replaceRecursive(tr)
|
return replaceRecursive(tr)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
convert(json: T | RewritableConfigJson<T>, context: string): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||||
|
|
||||||
|
if (json["rewrite"] === undefined) {
|
||||||
|
|
||||||
|
// not a rewrite
|
||||||
|
return {result: [(<T>json)]}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rewrite = <RewritableConfigJson<T>>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<LayerConfigJson> {
|
export class PrepareLayer extends Fuse<LayerConfigJson> {
|
||||||
constructor(state: DesugaringContext) {
|
constructor(state: DesugaringContext) {
|
||||||
|
@ -302,6 +340,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
|
||||||
"Fully prepares and expands a layer for the LayerConfig.",
|
"Fully prepares and expands a layer for the LayerConfig.",
|
||||||
new OnEveryConcat("tagRenderings", new ExpandGroupRewrite(state)),
|
new OnEveryConcat("tagRenderings", new ExpandGroupRewrite(state)),
|
||||||
new OnEveryConcat("tagRenderings", new ExpandTagRendering(state)),
|
new OnEveryConcat("tagRenderings", new ExpandTagRendering(state)),
|
||||||
|
new OnEveryConcat("mapRendering", new ExpandRewrite()),
|
||||||
new SetDefault("titleIcons", ["defaults"]),
|
new SetDefault("titleIcons", ["defaults"]),
|
||||||
new OnEveryConcat("titleIcons", new ExpandTagRendering(state))
|
new OnEveryConcat("titleIcons", new ExpandTagRendering(state))
|
||||||
);
|
);
|
||||||
|
|
|
@ -179,7 +179,7 @@ export interface LayerConfigJson {
|
||||||
/**
|
/**
|
||||||
* Visualisation of the items on the map
|
* Visualisation of the items on the map
|
||||||
*/
|
*/
|
||||||
mapRendering: null | (PointRenderingConfigJson | LineRenderingConfigJson)[]
|
mapRendering: null | (PointRenderingConfigJson | LineRenderingConfigJson | RewritableConfigJson<LineRenderingConfigJson | PointRenderingConfigJson>)[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, this layer will pass all the features it receives onto the next layer.
|
* If set, this layer will pass all the features it receives onto the next layer.
|
||||||
|
|
|
@ -1,5 +1,43 @@
|
||||||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
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<T> {
|
export default interface RewritableConfigJson<T> {
|
||||||
rewrite: {
|
rewrite: {
|
||||||
sourceString: string[],
|
sourceString: string[],
|
||||||
|
|
|
@ -6,6 +6,9 @@ import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
||||||
import {DetectMappingsWithImages, DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation";
|
import {DetectMappingsWithImages, DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation";
|
||||||
import * as Assert from "assert";
|
import * as Assert from "assert";
|
||||||
import {ExtractImages, FixImages} from "../Models/ThemeConfig/Conversion/FixImages";
|
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 {
|
export default class LegacyThemeLoaderSpec extends T {
|
||||||
|
|
||||||
|
@ -502,7 +505,7 @@ export default class LegacyThemeLoaderSpec extends T {
|
||||||
}, "test");
|
}, "test");
|
||||||
const images = r.result
|
const images = r.result
|
||||||
T.isTrue(images.length > 0, "No images found");
|
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");
|
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", () => {
|
["Rotation and colours is not detected as image", () => {
|
||||||
|
@ -511,7 +514,7 @@ export default class LegacyThemeLoaderSpec extends T {
|
||||||
{
|
{
|
||||||
mapRendering: [
|
mapRendering: [
|
||||||
{
|
{
|
||||||
"location":["point","centroid"],
|
"location": ["point", "centroid"],
|
||||||
"icon": "pin:black",
|
"icon": "pin:black",
|
||||||
rotation: 180,
|
rotation: 180,
|
||||||
iconSize: "40,40,center"
|
iconSize: "40,40,center"
|
||||||
|
@ -522,9 +525,83 @@ export default class LegacyThemeLoaderSpec extends T {
|
||||||
}, "test");
|
}, "test");
|
||||||
const images = r.result
|
const images = r.result
|
||||||
T.isTrue(images.length > 0, "No images found");
|
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");
|
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: <LineRenderingConfigJson>{
|
||||||
|
"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<string, TagRenderingConfigJson>(),
|
||||||
|
sharedLayers: new Map<string, LayerConfigJson>()
|
||||||
|
})
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue