forked from MapComplete/MapComplete
		
	Themes: move generated themes into assets, remove known_themes, support pruning of borrowed icons
This commit is contained in:
		
							parent
							
								
									310b973846
								
							
						
					
					
						commit
						ee64d84d27
					
				
					 18 changed files with 431 additions and 400 deletions
				
			
		|  | @ -1,14 +1,4 @@ | |||
| import { | ||||
|     Concat, | ||||
|     Conversion, | ||||
|     DesugaringContext, | ||||
|     DesugaringStep, | ||||
|     Each, | ||||
|     FirstOf, | ||||
|     Fuse, | ||||
|     On, | ||||
|     SetDefault, | ||||
| } from "./Conversion" | ||||
| import { Concat, DesugaringContext, DesugaringStep, Each, FirstOf, Fuse, On, SetDefault } from "./Conversion" | ||||
| import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||
| import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | ||||
| import { Utils } from "../../../Utils" | ||||
|  | @ -17,7 +7,6 @@ import SpecialVisualizations from "../../../UI/SpecialVisualizations" | |||
| import Translations from "../../../UI/i18n/Translations" | ||||
| import { Translation } from "../../../UI/i18n/Translation" | ||||
| import tagrenderingconfigmeta from "../../../../src/assets/schemas/tagrenderingconfigmeta.json" | ||||
| import { AddContextToTranslations } from "./AddContextToTranslations" | ||||
| import FilterConfigJson from "../Json/FilterConfigJson" | ||||
| import { TagConfigJson } from "../Json/TagConfigJson" | ||||
| import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson" | ||||
|  | @ -30,6 +19,7 @@ import { ConversionContext } from "./ConversionContext" | |||
| import { ExpandRewrite } from "./ExpandRewrite" | ||||
| import { TagUtils } from "../../../Logic/Tags/TagUtils" | ||||
| import { ExpandFilter, PruneFilters } from "./ExpandFilter" | ||||
| import { ExpandTagRendering } from "./ExpandTagRendering" | ||||
| 
 | ||||
| class AddFiltersFromTagRenderings extends DesugaringStep<LayerConfigJson> { | ||||
|     constructor() { | ||||
|  | @ -103,359 +93,6 @@ class AddFiltersFromTagRenderings extends DesugaringStep<LayerConfigJson> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| class ExpandTagRendering extends Conversion< | ||||
|     | string | ||||
|     | TagRenderingConfigJson | ||||
|     | { | ||||
|           builtin: string | string[] | ||||
|           override: any | ||||
|       }, | ||||
|     TagRenderingConfigJson[] | ||||
| > { | ||||
|     private readonly _state: DesugaringContext | ||||
|     private readonly _tagRenderingsByLabel: Map<string, TagRenderingConfigJson[]> | ||||
|     // Only used for self-reference
 | ||||
|     private readonly _self: LayerConfigJson | ||||
|     private readonly _options: { | ||||
|         /* If true, will copy the 'osmSource'-tags into the condition */ | ||||
|         applyCondition?: true | boolean | ||||
|         noHardcodedStrings?: false | boolean | ||||
|         addToContext?: false | boolean | ||||
|     } | ||||
| 
 | ||||
|     constructor( | ||||
|         state: DesugaringContext, | ||||
|         self: LayerConfigJson, | ||||
|         options?: { | ||||
|             applyCondition?: true | boolean | ||||
|             noHardcodedStrings?: false | boolean | ||||
|             // If set, a question will be added to the 'sharedTagRenderings'. Should only be used for 'questions.json'
 | ||||
|             addToContext?: false | boolean | ||||
|         } | ||||
|     ) { | ||||
|         super( | ||||
|             "Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question and reusing the builtins", | ||||
|             [], | ||||
|             "ExpandTagRendering" | ||||
|         ) | ||||
|         this._state = state | ||||
|         this._self = self | ||||
|         this._options = options | ||||
|         this._tagRenderingsByLabel = new Map<string, TagRenderingConfigJson[]>() | ||||
|         for (const trconfig of state.tagRenderings?.values() ?? []) { | ||||
|             for (const label of trconfig["labels"] ?? []) { | ||||
|                 let withLabel = this._tagRenderingsByLabel.get(label) | ||||
|                 if (withLabel === undefined) { | ||||
|                     withLabel = [] | ||||
|                     this._tagRenderingsByLabel.set(label, withLabel) | ||||
|                 } | ||||
|                 withLabel.push(trconfig) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public convert( | ||||
|         spec: string | any, | ||||
|         ctx: ConversionContext | ||||
|     ): QuestionableTagRenderingConfigJson[] { | ||||
|         const trs = this.convertOnce(spec, ctx) | ||||
|         const result = [] | ||||
|         if(!Array.isArray(trs)){ | ||||
|             ctx.err("Result of lookup for "+spec+" is not iterable; got "+trs) | ||||
|             return undefined | ||||
|         } | ||||
|         for (const tr of trs) { | ||||
|             if (typeof tr === "string" || tr["builtin"] !== undefined) { | ||||
|                 const stable = this.convert(tr, ctx.inOperation("recursive_resolve")) | ||||
|                     .map(tr => this.pruneMappings(tr, ctx)) | ||||
|                 result.push(...stable) | ||||
|                 if (this._options?.addToContext) { | ||||
|                     for (const tr of stable) { | ||||
|                         this._state.tagRenderings?.set(tr.id, tr) | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 result.push(tr) | ||||
|                 if (this._options?.addToContext) { | ||||
|                     this._state.tagRenderings?.set(tr["id"], <QuestionableTagRenderingConfigJson>tr) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return result | ||||
|     } | ||||
| 
 | ||||
|     private pruneMappings(tagRendering: QuestionableTagRenderingConfigJson, ctx: ConversionContext): QuestionableTagRenderingConfigJson{ | ||||
|         if(!tagRendering["strict"]){ | ||||
|             return tagRendering | ||||
|         } | ||||
|         const before = tagRendering.mappings?.length ?? 0 | ||||
| 
 | ||||
|         const alwaysTags = TagUtils.Tag(this._self.source["osmTags"]) | ||||
|         const newMappings = tagRendering.mappings?.filter(mapping => { | ||||
|             const condition = TagUtils.Tag( mapping.if) | ||||
|             return condition.shadows(alwaysTags); | ||||
| 
 | ||||
|         }).map(mapping => { | ||||
|             const newIf =TagUtils.removeKnownParts( | ||||
|                 TagUtils.Tag(mapping.if), alwaysTags            ) | ||||
|             if(typeof  newIf === "boolean"){ | ||||
|                 throw "Invalid removeKnownParts" | ||||
|             } | ||||
|             return { | ||||
|                 ...mapping, | ||||
|                 if: newIf.asJson() | ||||
|             } | ||||
|         }) | ||||
|         const after = newMappings?.length ?? 0 | ||||
|         if(before - after > 0){ | ||||
|             ctx.info(`Pruned mappings for ${tagRendering.id}, from ${before} to ${after} (removed ${before - after})`) | ||||
|         } | ||||
|         const tr = { | ||||
|             ...tagRendering, | ||||
|             mappings: newMappings | ||||
|         } | ||||
|         delete tr["strict"] | ||||
|         return tr | ||||
|     } | ||||
| 
 | ||||
|     private lookup(name: string, ctx: ConversionContext): TagRenderingConfigJson[] | undefined { | ||||
|         const direct = this.directLookup(name) | ||||
| 
 | ||||
|         if (direct === undefined) { | ||||
|             return undefined | ||||
|         } | ||||
|         const result: TagRenderingConfigJson[] = [] | ||||
|         for (const tagRenderingConfigJson of direct) { | ||||
|             const nm: string | string[] | undefined = tagRenderingConfigJson["builtin"] | ||||
|             if (nm !== undefined) { | ||||
|                 let indirect: TagRenderingConfigJson[] | ||||
|                 if (typeof nm === "string") { | ||||
|                     indirect = this.lookup(nm, ctx) | ||||
|                 } else { | ||||
|                     indirect = [].concat(...nm.map((n) => this.lookup(n, ctx))) | ||||
|                 } | ||||
|                 for (let foundTr of indirect) { | ||||
|                     foundTr = Utils.Clone<any>(foundTr) | ||||
|                     ctx.MergeObjectsForOverride(tagRenderingConfigJson["override"] ?? {}, foundTr) | ||||
|                     foundTr["id"] = tagRenderingConfigJson["id"] ?? foundTr["id"] | ||||
|                     result.push(foundTr) | ||||
|                 } | ||||
|             } else { | ||||
|                 result.push(tagRenderingConfigJson) | ||||
|             } | ||||
|         } | ||||
|         return result | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Looks up a tagRendering or group of tagRenderings based on the name. | ||||
|      */ | ||||
|     private directLookup(name: string): TagRenderingConfigJson[] | undefined { | ||||
|         const state = this._state | ||||
|         if (state.tagRenderings.has(name)) { | ||||
|             return [state.tagRenderings.get(name)] | ||||
|         } | ||||
|         if (this._tagRenderingsByLabel.has(name)) { | ||||
|             return this._tagRenderingsByLabel.get(name) | ||||
|         } | ||||
| 
 | ||||
|         if (name.indexOf(".") < 0) { | ||||
|             return undefined | ||||
|         } | ||||
| 
 | ||||
|         const spl = name.split(".") | ||||
|         let layer = state.sharedLayers?.get(spl[0]) | ||||
|         if (spl[0] === this._self?.id) { | ||||
|             layer = this._self | ||||
|         } | ||||
| 
 | ||||
|         if (spl.length !== 2 || !layer) { | ||||
|             return undefined | ||||
|         } | ||||
| 
 | ||||
|         const id = spl[1] | ||||
| 
 | ||||
|         const layerTrs = <TagRenderingConfigJson[]>( | ||||
|             layer.tagRenderings.filter((tr) => tr["id"] !== undefined) | ||||
|         ) | ||||
|         let matchingTrs: TagRenderingConfigJson[] | ||||
|         if (id === "*") { | ||||
|             matchingTrs = layerTrs | ||||
|         } else if (id.startsWith("*")) { | ||||
|             const id_ = id.substring(1) | ||||
|             matchingTrs = layerTrs.filter((tr) => tr["labels"]?.indexOf(id_) >= 0) | ||||
|         } else { | ||||
|             matchingTrs = layerTrs.filter((tr) => tr["id"] === id || tr["labels"]?.indexOf(id) >= 0) | ||||
|         } | ||||
| 
 | ||||
|         const contextWriter = new AddContextToTranslations<TagRenderingConfigJson>("layers:") | ||||
|         for (let i = 0; i < matchingTrs.length; i++) { | ||||
|             let found: TagRenderingConfigJson = Utils.Clone(matchingTrs[i]) | ||||
|             if (this._options?.applyCondition) { | ||||
|                 // The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown
 | ||||
|                 if (typeof layer.source !== "string") { | ||||
|                     if (found.condition === undefined) { | ||||
|                         found.condition = layer.source["osmTags"] | ||||
|                     } else { | ||||
|                         found.condition = { and: [found.condition, layer.source["osmTags"]] } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             found = contextWriter.convertStrict( | ||||
|                 found, | ||||
|                 ConversionContext.construct( | ||||
|                     [layer.id, "tagRenderings", found["id"]], | ||||
|                     ["AddContextToTranslations"] | ||||
|                 ) | ||||
|             ) | ||||
|             matchingTrs[i] = found | ||||
|         } | ||||
| 
 | ||||
|         if (matchingTrs.length !== 0) { | ||||
|             return matchingTrs | ||||
|         } | ||||
|         return undefined | ||||
|     } | ||||
| 
 | ||||
|     private convertOnce(tr: string | any, ctx: ConversionContext): TagRenderingConfigJson[] { | ||||
|         const state = this._state | ||||
| 
 | ||||
|         if (typeof tr === "string") { | ||||
|             if (this._state.tagRenderings !== null) { | ||||
|                 const lookup = this.lookup(tr, ctx) | ||||
|                 if(lookup){ | ||||
|                     return lookup | ||||
|                 } | ||||
|             } | ||||
|             if ( | ||||
|                 this._state.sharedLayers?.size > 0 && | ||||
|                 ctx.path.at(-1) !== "icon" && | ||||
|                 !ctx.path.find((p) => p === "pointRendering") | ||||
|             ) { | ||||
|                 ctx.warn( | ||||
|                     `A literal rendering was detected: ${tr} | ||||
|                       Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
 | ||||
|                     Array.from(state.sharedLayers.keys()).join(", "), | ||||
|                 ) | ||||
|             } | ||||
| 
 | ||||
|             if (this._options?.noHardcodedStrings && this._state?.sharedLayers?.size > 0) { | ||||
|                 ctx.err( | ||||
|                     "Detected an invocation to a builtin tagRendering, but this tagrendering was not found: " + | ||||
|                     tr + | ||||
|                     " \n    Did you perhaps forget to add the layer as prefix, such as `icons." + | ||||
|                     tr + | ||||
|                     "`? ", | ||||
|                 ) | ||||
|             } | ||||
| 
 | ||||
|             return [ | ||||
|                 <any>{ | ||||
|                     render: tr, | ||||
|                     id: tr.replace(/[^a-zA-Z0-9]/g, ""), | ||||
|                 }, | ||||
|             ] | ||||
|         } | ||||
| 
 | ||||
|         if (tr["builtin"] !== undefined) { | ||||
|             let names: string | string[] = tr["builtin"] | ||||
|             if (typeof names === "string") { | ||||
|                 names = [names] | ||||
|             } | ||||
| 
 | ||||
|             if (this._state.tagRenderings === null) { | ||||
|                 return [] | ||||
|             } | ||||
| 
 | ||||
|             for (const key of Object.keys(tr)) { | ||||
|                 if ( | ||||
|                     key === "builtin" || | ||||
|                     key === "override" || | ||||
|                     key === "id" || | ||||
|                     key.startsWith("#") | ||||
|                 ) { | ||||
|                     continue | ||||
|                 } | ||||
|                 ctx.err( | ||||
|                     "An object calling a builtin can only have keys `builtin` or `override`, but a key with name `" + | ||||
|                         key + | ||||
|                         "` was found. This won't be picked up! The full object is: " + | ||||
|                         JSON.stringify(tr) | ||||
|                 ) | ||||
|             } | ||||
| 
 | ||||
|             const trs: TagRenderingConfigJson[] = [] | ||||
|             for (const name of names) { | ||||
|                 const lookup = this.lookup(name, ctx) | ||||
|                 if (lookup === undefined) { | ||||
|                     let candidates = Array.from(state.tagRenderings.keys()) | ||||
|                     if (name.indexOf(".") > 0) { | ||||
|                         const [layerName] = name.split(".") | ||||
|                         if(layerName === undefined){ | ||||
|                             ctx.err("Layername is undefined", name) | ||||
|                         } | ||||
|                         let layer = state.sharedLayers.get(layerName) | ||||
|                         if (layerName === this._self?.id) { | ||||
|                             layer = this._self | ||||
|                         } | ||||
|                         if (layer === undefined) { | ||||
|                             const candidates = Utils.sortedByLevenshteinDistance( | ||||
|                                 layerName, | ||||
|                                 Utils.NoNull(Array.from(state.sharedLayers.keys())), | ||||
|                                 (s) => s | ||||
|                             ) | ||||
|                             if (state.sharedLayers.size === 0) { | ||||
|                                 ctx.warn( | ||||
|                                     "BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " + | ||||
|                                         name + | ||||
|                                         ": layer " + | ||||
|                                         layerName + | ||||
|                                         " not found for now, but ignoring as this is a bootstrapping run. " | ||||
|                                 ) | ||||
|                             } else { | ||||
|                                 ctx.err( | ||||
|                                     ": While reusing tagrendering: " + | ||||
|                                         name + | ||||
|                                         ": layer " + | ||||
|                                         layerName + | ||||
|                                         " not found. Maybe you meant one of " + | ||||
|                                         candidates.slice(0, 3).join(", ") | ||||
|                                 ) | ||||
|                             } | ||||
|                             continue | ||||
|                         } | ||||
|                         candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map( | ||||
|                             (id) => layerName + "." + id | ||||
|                         ) | ||||
|                     } | ||||
|                     candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i) | ||||
|                     ctx.err( | ||||
|                         "The tagRendering with identifier " + | ||||
|                             name + | ||||
|                             " was not found.\n\tDid you mean one of " + | ||||
|                             candidates.join(", ") + | ||||
|                             "?\n(Hint: did you add a new label and are you trying to use this label at the same time? Run 'reset:layeroverview' first" | ||||
|                     ) | ||||
|                     continue | ||||
|                 } | ||||
|                 for (let foundTr of lookup) { | ||||
|                     foundTr = Utils.Clone<any>(foundTr) | ||||
|                     ctx.MergeObjectsForOverride(tr["override"] ?? {}, foundTr) | ||||
|                     if (names.length == 1) { | ||||
|                         foundTr["id"] = tr["id"] ?? foundTr["id"] | ||||
|                     } | ||||
|                     trs.push(foundTr) | ||||
|                 } | ||||
|             } | ||||
|             return trs | ||||
|         } | ||||
| 
 | ||||
|         return [tr] | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> { | ||||
|     constructor() { | ||||
|         super( | ||||
|  | @ -1114,19 +751,19 @@ class ExpandMarkerRenderings extends DesugaringStep<IconConfigJson> { | |||
|     } | ||||
| 
 | ||||
|     convert(json: IconConfigJson, context: ConversionContext): IconConfigJson { | ||||
|         const expander = new ExpandTagRendering(this._state, this._layer) | ||||
|         const expander = new ExpandTagRendering(this._state, this._layer, {applyCondition: false}) | ||||
|         const result: IconConfigJson = { icon: undefined, color: undefined } | ||||
|         if (json.icon && json.icon["builtin"]) { | ||||
|             result.icon = <MinimalTagRenderingConfigJson>( | ||||
|                 expander.convert(<any>json.icon, context.enter("icon"))[0] | ||||
|             ) | ||||
|             ) ?? json.icon | ||||
|         } else { | ||||
|             result.icon = json.icon | ||||
|         } | ||||
|         if (json.color && json.color["builtin"]) { | ||||
|             result.color = <MinimalTagRenderingConfigJson>( | ||||
|                 expander.convert(<any>json.color, context.enter("color"))[0] | ||||
|             ) | ||||
|             ) ?? json.color | ||||
|         } else { | ||||
|             result.color = json.color | ||||
|         } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue