forked from MapComplete/MapComplete
		
	Feature(NSI): support using NSI-images as icon, test on shops
This commit is contained in:
		
							parent
							
								
									80192f003a
								
							
						
					
					
						commit
						b913ea867f
					
				
					 14 changed files with 1173790 additions and 71 deletions
				
			
		| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "#":"no-translations",
 | 
					  "#":"no-translations",
 | 
				
			||||||
 | 
					  "#no-index": "yes",
 | 
				
			||||||
  "#dont-translate": "*",
 | 
					  "#dont-translate": "*",
 | 
				
			||||||
  "#filter": "no-auto",
 | 
					  "#filter": "no-auto",
 | 
				
			||||||
  "pointRendering": [
 | 
					  "pointRendering": [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										569064
									
								
								assets/layers/nsi_brand/nsi_brand.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										569064
									
								
								assets/layers/nsi_brand/nsi_brand.json
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										604545
									
								
								assets/layers/nsi_operator/nsi_operator.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										604545
									
								
								assets/layers/nsi_operator/nsi_operator.json
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
					@ -109,7 +109,7 @@
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "icon": {
 | 
					          "icon": {
 | 
				
			||||||
            "builtin": "id_presets.shop_rendering",
 | 
					            "builtin": "nsi_brand.icon",
 | 
				
			||||||
            "override": {
 | 
					            "override": {
 | 
				
			||||||
              "render": "./assets/layers/id_presets/maki-shop.svg",
 | 
					              "render": "./assets/layers/id_presets/maki-shop.svg",
 | 
				
			||||||
              "+mappings": [
 | 
					              "+mappings": [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,7 +95,7 @@
 | 
				
			||||||
    "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate",
 | 
					    "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate",
 | 
				
			||||||
    "generate:layouts": "vite-node scripts/generateLayouts.ts",
 | 
					    "generate:layouts": "vite-node scripts/generateLayouts.ts",
 | 
				
			||||||
    "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts",
 | 
					    "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts",
 | 
				
			||||||
    "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts",
 | 
					    "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateLayerOverview.ts",
 | 
				
			||||||
    "generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map",
 | 
					    "generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map",
 | 
				
			||||||
    "refresh:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --force",
 | 
					    "refresh:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --force",
 | 
				
			||||||
    "generate:licenses": "vite-node scripts/generateLicenseInfo.ts -- --no-fail",
 | 
					    "generate:licenses": "vite-node scripts/generateLicenseInfo.ts -- --no-fail",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,10 +5,9 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
 | 
				
			||||||
import ScriptUtils from "./ScriptUtils"
 | 
					import ScriptUtils from "./ScriptUtils"
 | 
				
			||||||
import { Utils } from "../src/Utils"
 | 
					import { Utils } from "../src/Utils"
 | 
				
			||||||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
import FilterConfigJson, { FilterConfigOptionJson } from "../src/Models/ThemeConfig/Json/FilterConfigJson"
 | 
					import { FilterConfigOptionJson } from "../src/Models/ThemeConfig/Json/FilterConfigJson"
 | 
				
			||||||
import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson"
 | 
					 | 
				
			||||||
import { TagUtils } from "../src/Logic/Tags/TagUtils"
 | 
					import { TagUtils } from "../src/Logic/Tags/TagUtils"
 | 
				
			||||||
import { And } from "../src/Logic/Tags/And"
 | 
					import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DownloadNsiLogos extends Script {
 | 
					class DownloadNsiLogos extends Script {
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
| 
						 | 
					@ -129,39 +128,61 @@ class DownloadNsiLogos extends Script {
 | 
				
			||||||
    private async generateRendering(type: string) {
 | 
					    private async generateRendering(type: string) {
 | 
				
			||||||
        const nsi = await NameSuggestionIndex.getNsiIndex()
 | 
					        const nsi = await NameSuggestionIndex.getNsiIndex()
 | 
				
			||||||
        const items = nsi.allPossible(type)
 | 
					        const items = nsi.allPossible(type)
 | 
				
			||||||
        const brandPrefix = [type, "name", "alt_name", "operator","brand"]
 | 
					 | 
				
			||||||
        const filterOptions: FilterConfigOptionJson[] = items.map(item => {
 | 
					        const filterOptions: FilterConfigOptionJson[] = items.map(item => {
 | 
				
			||||||
            let brandDetection: string[] = []
 | 
					 | 
				
			||||||
            let required: string[] = []
 | 
					 | 
				
			||||||
            const tags: Record<string, string> = item.tags
 | 
					 | 
				
			||||||
            for (const k in tags) {
 | 
					 | 
				
			||||||
                if (brandPrefix.some(br => k === br || k.startsWith(br + ":"))) {
 | 
					 | 
				
			||||||
                    brandDetection.push(k + "=" + tags[k])
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    required.push(k + "=" + tags[k])
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const osmTags = <TagConfigJson>TagUtils.optimzeJson({ and: [...required, { or: brandDetection }] })
 | 
					 | 
				
			||||||
            return ({
 | 
					            return ({
 | 
				
			||||||
                question: item.displayName,
 | 
					                question: item.displayName,
 | 
				
			||||||
                icon: nsi.getIconUrl(item, type),
 | 
					                icon: nsi.getIconUrl(item, type),
 | 
				
			||||||
                osmTags,
 | 
					                osmTags: NameSuggestionIndex.asFilterTags(item),
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        const mappings = items.map(item => ({
 | 
				
			||||||
 | 
					            if: NameSuggestionIndex.asFilterTags(item),
 | 
				
			||||||
 | 
					            then: nsi.getIconUrl(item, type),
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("Checking for shadow-mappings...")
 | 
				
			||||||
 | 
					        for (let i = mappings.length - 1; i >= 0 ; i--) {
 | 
				
			||||||
 | 
					            const condition = TagUtils.Tag(mappings[i].if)
 | 
				
			||||||
 | 
					            if(i % 100 === 0){
 | 
				
			||||||
 | 
					                console.log("Checking for shadow-mappings...",i,"/",mappings.length )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const shadowsSomething = mappings.some((m,j)     => {
 | 
				
			||||||
 | 
					                if(i===j ){
 | 
				
			||||||
 | 
					                    return false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return condition.shadows(TagUtils.Tag(m.if))
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            // If this one matches, the other one will match as well
 | 
				
			||||||
 | 
					            // We can thus remove this one in favour of the other one
 | 
				
			||||||
 | 
					            if(shadowsSomething){
 | 
				
			||||||
 | 
					                mappings.splice(i, 1)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const iconsTr: TagRenderingConfigJson = <any>{
 | 
				
			||||||
 | 
					            strict: true,
 | 
				
			||||||
 | 
					            id: "icon",
 | 
				
			||||||
 | 
					            mappings,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const config: LayerConfigJson = {
 | 
					        const config: LayerConfigJson = {
 | 
				
			||||||
            "#dont-translate": "*",
 | 
					            "#dont-translate": "*",
 | 
				
			||||||
 | 
					            "#no-index": "yes",
 | 
				
			||||||
            id: "nsi_" + type,
 | 
					            id: "nsi_" + type,
 | 
				
			||||||
            source: "special:library",
 | 
					            source: "special:library",
 | 
				
			||||||
            description: {
 | 
					            description: {
 | 
				
			||||||
                en: "Exposes part of the NSI to reuse in other themes, e.g. for rendering",
 | 
					                en: "Exposes part of the NSI to reuse in other themes, e.g. for rendering",
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            pointRendering: null,
 | 
					            pointRendering: null,
 | 
				
			||||||
 | 
					            tagRenderings: [
 | 
				
			||||||
 | 
					                iconsTr,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            filter: [
 | 
					            filter: [
 | 
				
			||||||
               <any> {
 | 
					                <any>{
 | 
				
			||||||
                    id: type,
 | 
					                    id: type,
 | 
				
			||||||
                    strict: true,
 | 
					                    strict: true,
 | 
				
			||||||
                    options: [{question: type}, ...filterOptions],
 | 
					                    options: [{ question: type }, ...filterOptions],
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            allowMove: false,
 | 
					            allowMove: false,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -497,6 +497,8 @@ class LayerOverviewUtils extends Script {
 | 
				
			||||||
            priviliged.delete(key)
 | 
					            priviliged.delete(key)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // These two get a free pass
 | 
					        // These two get a free pass
 | 
				
			||||||
        priviliged.delete("summary")
 | 
					        priviliged.delete("summary")
 | 
				
			||||||
        priviliged.delete("last_click")
 | 
					        priviliged.delete("last_click")
 | 
				
			||||||
| 
						 | 
					@ -527,7 +529,7 @@ class LayerOverviewUtils extends Script {
 | 
				
			||||||
            writeFileSync(
 | 
					            writeFileSync(
 | 
				
			||||||
                "./src/assets/generated/known_layers.json",
 | 
					                "./src/assets/generated/known_layers.json",
 | 
				
			||||||
                JSON.stringify({
 | 
					                JSON.stringify({
 | 
				
			||||||
                    layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"),
 | 
					                    layers: Array.from(sharedLayers.values()).filter((l) => !(l["#no-index"] === "yes")),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,20 @@
 | 
				
			||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import { Utils } from "../Utils"
 | 
					import { Utils } from "../Utils"
 | 
				
			||||||
import known_layers from "../assets/generated/known_layers.json"
 | 
					import * as known_layers from "../assets/generated/known_layers.json"
 | 
				
			||||||
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AllSharedLayers {
 | 
					export class AllSharedLayers {
 | 
				
			||||||
    public static sharedLayers: Map<string, LayerConfig> = AllSharedLayers.getSharedLayers()
 | 
					    public static sharedLayers: Map<string, LayerConfig> = AllSharedLayers.getSharedLayers()
 | 
				
			||||||
    public static getSharedLayersConfigs(): Map<string, LayerConfigJson> {
 | 
					    public static getSharedLayersConfigs(): Map<string, LayerConfigJson> {
 | 
				
			||||||
        const sharedLayers = new Map<string, LayerConfigJson>()
 | 
					        const sharedLayers = new Map<string, LayerConfigJson>()
 | 
				
			||||||
        for (const layer of known_layers["layers"]) {
 | 
					        for (const layer of (known_layers).layers) {
 | 
				
			||||||
            // @ts-ignore
 | 
					            if(layer.id === undefined){
 | 
				
			||||||
            sharedLayers.set(layer.id, layer)
 | 
					                console.error("Layer without id! "+JSON.stringify(layer).slice(0,80), known_layers.layers.length)
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }else{
 | 
				
			||||||
 | 
					                console.log("Loaded",layer.id)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            sharedLayers.set(layer.id, <any> layer)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return sharedLayers
 | 
					        return sharedLayers
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -690,6 +690,17 @@ export class TagUtils {
 | 
				
			||||||
        return result
 | 
					        return result
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static removeKnownParts(tag: TagsFilter, known: TagsFilter, valueOfKnown = true): TagsFilter | boolean{
 | 
				
			||||||
 | 
					        const tagOrBool = And.construct([tag]).optimize()
 | 
				
			||||||
 | 
					        if(tagOrBool === true || tagOrBool === false){
 | 
				
			||||||
 | 
					            return tagOrBool
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(tagOrBool instanceof And){
 | 
				
			||||||
 | 
					            return tagOrBool.removePhraseConsideredKnown(known, valueOfKnown)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return tagOrBool
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns `true` if at least one element of the 'guards' shadows one element of the 'listToFilter'.
 | 
					     * Returns `true` if at least one element of the 'guards' shadows one element of the 'listToFilter'.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@ import { Mapping } from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
import { Tag } from "../Tags/Tag"
 | 
					import { Tag } from "../Tags/Tag"
 | 
				
			||||||
import { TypedTranslation } from "../../UI/i18n/Translation"
 | 
					import { TypedTranslation } from "../../UI/i18n/Translation"
 | 
				
			||||||
import { RegexTag } from "../Tags/RegexTag"
 | 
					import { RegexTag } from "../Tags/RegexTag"
 | 
				
			||||||
 | 
					import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
 | 
				
			||||||
 | 
					import { TagUtils } from "../Tags/TagUtils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Main name suggestion index file
 | 
					 * Main name suggestion index file
 | 
				
			||||||
| 
						 | 
					@ -52,6 +54,7 @@ export interface NSIItem {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NameSuggestionIndex {
 | 
					export default class NameSuggestionIndex {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static readonly supportedTypes = ["brand", "flag", "operator", "transit"] as const
 | 
					    public static readonly supportedTypes = ["brand", "flag", "operator", "transit"] as const
 | 
				
			||||||
    private readonly nsiFile: Readonly<NSIFile>
 | 
					    private readonly nsiFile: Readonly<NSIFile>
 | 
				
			||||||
    private readonly nsiWdFile: Readonly<
 | 
					    private readonly nsiWdFile: Readonly<
 | 
				
			||||||
| 
						 | 
					@ -399,4 +402,27 @@ export default class NameSuggestionIndex {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return icon
 | 
					        return icon
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    private static readonly brandPrefix = ["name", "alt_name", "operator","brand"] as const
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * An NSI-item might have tags such as `name=X`, `alt_name=brand X`,  `brand=X`, `brand:wikidata`, `shop=Y`, `service:abc=yes`
 | 
				
			||||||
 | 
					     * Many of those tags are all added, but having only one of them is a good indication that it should match this item.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This method is a heuristic which attempts to move all the brand-related tags into an `or` but still requiring the `shop` and other tags
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * (More of an extension method on NSIItem)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    static asFilterTags(item: NSIItem): string | { and: TagConfigJson[] } | { or: TagConfigJson[] } {
 | 
				
			||||||
 | 
					        let brandDetection: string[] = []
 | 
				
			||||||
 | 
					        let required: string[] = []
 | 
				
			||||||
 | 
					        const tags: Record<string, string> = item.tags
 | 
				
			||||||
 | 
					        for (const k in tags) {
 | 
				
			||||||
 | 
					            if (NameSuggestionIndex.brandPrefix.some(br => k === br || k.startsWith(br + ":"))) {
 | 
				
			||||||
 | 
					                brandDetection.push(k + "=" + tags[k])
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                required.push(k + "=" + tags[k])
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return <TagConfigJson>TagUtils.optimzeJson({ and: [...required, { or: brandDetection }] })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,11 +36,8 @@ export class PruneFilters extends DesugaringStep<LayerConfigJson>{
 | 
				
			||||||
            if(!option.osmTags){
 | 
					            if(!option.osmTags){
 | 
				
			||||||
                return option
 | 
					                return option
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let basetags: TagsFilter = <TagsFilter> And.construct([TagUtils.Tag(option.osmTags)]).optimize()
 | 
					            let basetags = TagUtils.Tag(option.osmTags)
 | 
				
			||||||
            if(basetags instanceof And){
 | 
					            return {...option, osmTags: (<TagsFilter>TagUtils.removeKnownParts(basetags ,sourceTags)).asJson()}
 | 
				
			||||||
                basetags = <TagsFilter> basetags.removePhraseConsideredKnown(sourceTags, true)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return {...option, osmTags: basetags.asJson()}
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        const countAfter = newOptions.length
 | 
					        const countAfter = newOptions.length
 | 
				
			||||||
        if(countAfter !== countBefore){
 | 
					        if(countAfter !== countBefore){
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -159,11 +159,15 @@ class ExpandTagRendering extends Conversion<
 | 
				
			||||||
        ctx: ConversionContext
 | 
					        ctx: ConversionContext
 | 
				
			||||||
    ): QuestionableTagRenderingConfigJson[] {
 | 
					    ): QuestionableTagRenderingConfigJson[] {
 | 
				
			||||||
        const trs = this.convertOnce(spec, ctx)
 | 
					        const trs = this.convertOnce(spec, ctx)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        const result = []
 | 
					        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) {
 | 
					        for (const tr of trs) {
 | 
				
			||||||
            if (typeof tr === "string" || tr["builtin"] !== undefined) {
 | 
					            if (typeof tr === "string" || tr["builtin"] !== undefined) {
 | 
				
			||||||
                const stable = this.convert(tr, ctx.inOperation("recursive_resolve"))
 | 
					                const stable = this.convert(tr, ctx.inOperation("recursive_resolve"))
 | 
				
			||||||
 | 
					                    .map(tr => this.pruneMappings(tr, ctx))
 | 
				
			||||||
                result.push(...stable)
 | 
					                result.push(...stable)
 | 
				
			||||||
                if (this._options?.addToContext) {
 | 
					                if (this._options?.addToContext) {
 | 
				
			||||||
                    for (const tr of stable) {
 | 
					                    for (const tr of stable) {
 | 
				
			||||||
| 
						 | 
					@ -181,6 +185,40 @@ class ExpandTagRendering extends Conversion<
 | 
				
			||||||
        return result
 | 
					        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 {
 | 
					    private lookup(name: string, ctx: ConversionContext): TagRenderingConfigJson[] | undefined {
 | 
				
			||||||
        const direct = this.directLookup(name)
 | 
					        const direct = this.directLookup(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -285,41 +323,40 @@ class ExpandTagRendering extends Conversion<
 | 
				
			||||||
        const state = this._state
 | 
					        const state = this._state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (typeof tr === "string") {
 | 
					        if (typeof tr === "string") {
 | 
				
			||||||
            let lookup
 | 
					 | 
				
			||||||
            if (this._state.tagRenderings !== null) {
 | 
					            if (this._state.tagRenderings !== null) {
 | 
				
			||||||
                lookup = this.lookup(tr, ctx)
 | 
					                const lookup = this.lookup(tr, ctx)
 | 
				
			||||||
 | 
					                if(lookup){
 | 
				
			||||||
 | 
					                    return lookup
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (lookup === undefined) {
 | 
					            if (
 | 
				
			||||||
                if (
 | 
					                this._state.sharedLayers?.size > 0 &&
 | 
				
			||||||
                    this._state.sharedLayers?.size > 0 &&
 | 
					                ctx.path.at(-1) !== "icon" &&
 | 
				
			||||||
                    ctx.path.at(-1) !== "icon" &&
 | 
					                !ctx.path.find((p) => p === "pointRendering")
 | 
				
			||||||
                    !ctx.path.find((p) => p === "pointRendering")
 | 
					            ) {
 | 
				
			||||||
                ) {
 | 
					                ctx.warn(
 | 
				
			||||||
                    ctx.warn(
 | 
					                    `A literal rendering was detected: ${tr}
 | 
				
			||||||
                        `A literal rendering was detected: ${tr}
 | 
					 | 
				
			||||||
                      Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
 | 
					                      Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
 | 
				
			||||||
                            Array.from(state.sharedLayers.keys()).join(", ")
 | 
					                    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, ""),
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return lookup
 | 
					
 | 
				
			||||||
 | 
					            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) {
 | 
					        if (tr["builtin"] !== undefined) {
 | 
				
			||||||
| 
						 | 
					@ -356,6 +393,9 @@ class ExpandTagRendering extends Conversion<
 | 
				
			||||||
                    let candidates = Array.from(state.tagRenderings.keys())
 | 
					                    let candidates = Array.from(state.tagRenderings.keys())
 | 
				
			||||||
                    if (name.indexOf(".") > 0) {
 | 
					                    if (name.indexOf(".") > 0) {
 | 
				
			||||||
                        const [layerName] = name.split(".")
 | 
					                        const [layerName] = name.split(".")
 | 
				
			||||||
 | 
					                        if(layerName === undefined){
 | 
				
			||||||
 | 
					                            ctx.err("Layername is undefined", name)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                        let layer = state.sharedLayers.get(layerName)
 | 
					                        let layer = state.sharedLayers.get(layerName)
 | 
				
			||||||
                        if (layerName === this._self?.id) {
 | 
					                        if (layerName === this._self?.id) {
 | 
				
			||||||
                            layer = this._self
 | 
					                            layer = this._self
 | 
				
			||||||
| 
						 | 
					@ -363,7 +403,7 @@ class ExpandTagRendering extends Conversion<
 | 
				
			||||||
                        if (layer === undefined) {
 | 
					                        if (layer === undefined) {
 | 
				
			||||||
                            const candidates = Utils.sortedByLevenshteinDistance(
 | 
					                            const candidates = Utils.sortedByLevenshteinDistance(
 | 
				
			||||||
                                layerName,
 | 
					                                layerName,
 | 
				
			||||||
                                Array.from(state.sharedLayers.keys()),
 | 
					                                Utils.NoNull(Array.from(state.sharedLayers.keys())),
 | 
				
			||||||
                                (s) => s
 | 
					                                (s) => s
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                            if (state.sharedLayers.size === 0) {
 | 
					                            if (state.sharedLayers.size === 0) {
 | 
				
			||||||
| 
						 | 
					@ -1017,7 +1057,8 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
 | 
				
			||||||
class PreparePointRendering extends Fuse<PointRenderingConfigJson> {
 | 
					class PreparePointRendering extends Fuse<PointRenderingConfigJson> {
 | 
				
			||||||
    constructor(state: DesugaringContext, layer: LayerConfigJson) {
 | 
					    constructor(state: DesugaringContext, layer: LayerConfigJson) {
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            "Prepares point renderings by expanding 'icon' and 'iconBadges'",
 | 
					            "Prepares point renderings by expanding 'icon' and 'iconBadges'." +
 | 
				
			||||||
 | 
					            " A tagRendering from the host tagRenderings will be substituted in",
 | 
				
			||||||
            new On(
 | 
					            new On(
 | 
				
			||||||
                "marker",
 | 
					                "marker",
 | 
				
			||||||
                new Each(
 | 
					                new Each(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,13 +110,11 @@ export class DoesImageExist extends DesugaringStep<string> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!this._knownImagePaths.has(image)) {
 | 
					        if (!this._knownImagePaths.has(image)) {
 | 
				
			||||||
            if (this.doesPathExist === undefined) {
 | 
					            if (this.doesPathExist === undefined || image.indexOf("nsi/logos/") >= 0) {
 | 
				
			||||||
 | 
					                // pass
 | 
				
			||||||
 | 
					            } else if (!this.doesPathExist(image) ) {
 | 
				
			||||||
                context.err(
 | 
					                context.err(
 | 
				
			||||||
                    `Image with path ${image} not found or not attributed; it is used in ${context}`
 | 
					                    `Image with path ${image} does not exist.\n     Check for typo's and missing directories in the path. `
 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            } else if (!this.doesPathExist(image)) {
 | 
					 | 
				
			||||||
                context.err(
 | 
					 | 
				
			||||||
                    `Image with path ${image} does not exist.\n     Check for typo's and missing directories in the path.`
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                context.err(
 | 
					                context.err(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,16 @@ import { TagConfigJson } from "./TagConfigJson"
 | 
				
			||||||
export interface IconConfigJson {
 | 
					export interface IconConfigJson {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * question: What icon should be used?
 | 
					     * question: What icon should be used?
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * To reuse icons from a different layer of a library:
 | 
				
			||||||
 | 
					     * - The library layer has, within tagRenderings one which will output the URL of the image (e.g. mappings: {"if": "shop=xyz", then: "./assets/icons/shop_xyz.png"})
 | 
				
			||||||
 | 
					     * - Use "layer_id.tagrendering_id"
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * Note that if you reuse icons from a different icon set, you'll probably want to use `override` to set a default rendering
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
     * types: <span class="text-lg font-bold">Use a different icon depending on the value of some attributes</span> ; icon
 | 
					     * types: <span class="text-lg font-bold">Use a different icon depending on the value of some attributes</span> ; icon
 | 
				
			||||||
     * suggestions: return Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i}))
 | 
					     * suggestions: return [ "nsi_brand.icon", "nsi_operator.icon", "id_presets.shop_rendering", ...Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i}))]
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    icon: string | MinimalTagRenderingConfigJson | { builtin: string; override: any }
 | 
					    icon: string | MinimalTagRenderingConfigJson | { builtin: string; override: any }
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue