forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			344 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			344 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import Script from "./Script"
 | 
						|
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
 | 
						|
import { existsSync, readFileSync, writeFileSync } from "fs"
 | 
						|
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
 | 
						|
import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts"
 | 
						|
import { Utils } from "../src/Utils"
 | 
						|
import {
 | 
						|
    MappingConfigJson,
 | 
						|
    QuestionableTagRenderingConfigJson,
 | 
						|
} from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
 | 
						|
import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson"
 | 
						|
import { TagUtils } from "../src/Logic/Tags/TagUtils"
 | 
						|
import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
 | 
						|
import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
 | 
						|
import * as questions from "../assets/layers/questions/questions.json"
 | 
						|
export class GenerateFavouritesLayer extends Script {
 | 
						|
    private readonly layers: LayerConfigJson[] = []
 | 
						|
 | 
						|
    constructor() {
 | 
						|
        super("Prepares the 'favourites'-layer")
 | 
						|
        const allThemes = new AllKnownLayoutsLazy(false).values()
 | 
						|
        for (const theme of allThemes) {
 | 
						|
            if (theme.hideFromOverview) {
 | 
						|
                continue
 | 
						|
            }
 | 
						|
            for (const layer of theme.layers) {
 | 
						|
                if (!layer.source) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if (layer.source.geojsonSource) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                const layerConfig = AllSharedLayers.getSharedLayersConfigs().get(layer.id)
 | 
						|
                if (!layerConfig) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                this.layers.push(layerConfig)
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    async main(args: string[]): Promise<void> {
 | 
						|
        console.log("Generating the favourite layer: stealing _all_ tagRenderings")
 | 
						|
        const proto = this.readLayer("favourite/favourite.proto.json")
 | 
						|
        this.addTagRenderings(proto)
 | 
						|
        this.addTitle(proto)
 | 
						|
        proto.titleIcons = this.generateTitleIcons()
 | 
						|
        const targetContent = JSON.stringify(proto, null, "  ")
 | 
						|
        const path = "./assets/layers/favourite/favourite.json"
 | 
						|
        if (existsSync(path)) {
 | 
						|
            if (readFileSync(path, "utf8") === targetContent) {
 | 
						|
                console.log(
 | 
						|
                    "Already existing favourite layer is identical to the generated one, not writing"
 | 
						|
                )
 | 
						|
                return
 | 
						|
            }
 | 
						|
        }
 | 
						|
        console.log("Written favourite layer to", path)
 | 
						|
        writeFileSync(path, targetContent)
 | 
						|
    }
 | 
						|
 | 
						|
    private sortMappings(mappings: MappingConfigJson[]): MappingConfigJson[] {
 | 
						|
        const sortedMappings: MappingConfigJson[] = [...mappings]
 | 
						|
        sortedMappings.sort((a, b) => {
 | 
						|
            const aTag = TagUtils.Tag(a.if)
 | 
						|
            const bTag = TagUtils.Tag(b.if)
 | 
						|
            const aPop = TagUtils.GetPopularity(aTag)
 | 
						|
            const bPop = TagUtils.GetPopularity(bTag)
 | 
						|
            return aPop - bPop
 | 
						|
        })
 | 
						|
 | 
						|
        return sortedMappings
 | 
						|
    }
 | 
						|
 | 
						|
    private addTagRenderings(proto: LayerConfigJson) {
 | 
						|
        const addedByDefault = (<{labels: string[], id: string}[]> questions.tagRenderings)
 | 
						|
            .filter(tr => tr?.["labels"]?.indexOf("added_by_default") > 0 || tr?.["labels"]?.indexOf("added_by_default_top") > 0 )
 | 
						|
            .map(tr => tr.id)
 | 
						|
        const blacklistedIds = new Set([
 | 
						|
            "images",
 | 
						|
            "questions",
 | 
						|
            "mapillary",
 | 
						|
            "leftover-questions",
 | 
						|
            "last_edit",
 | 
						|
            "minimap",
 | 
						|
            "move-button",
 | 
						|
            "delete-button",
 | 
						|
            "all-tags",
 | 
						|
            "all_tags",
 | 
						|
                ...addedByDefault
 | 
						|
        ])
 | 
						|
 | 
						|
        const generatedTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = []
 | 
						|
        const trPerId = new Map<
 | 
						|
            string,
 | 
						|
            { conditions: TagConfigJson[]; tr: QuestionableTagRenderingConfigJson }
 | 
						|
        >()
 | 
						|
        for (const layerConfig of this.layers) {
 | 
						|
            if (!layerConfig.tagRenderings) {
 | 
						|
                continue
 | 
						|
            }
 | 
						|
            for (const tagRendering of layerConfig.tagRenderings) {
 | 
						|
                if (typeof tagRendering === "string") {
 | 
						|
                    if (blacklistedIds.has(tagRendering)) {
 | 
						|
                        continue
 | 
						|
                    }
 | 
						|
                    generatedTagRenderings.push(tagRendering)
 | 
						|
                    blacklistedIds.add(tagRendering)
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if (tagRendering["builtin"]) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                const id = tagRendering.id
 | 
						|
                if (blacklistedIds.has(id)) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if (trPerId.has(id)) {
 | 
						|
                    const old = trPerId.get(id).tr
 | 
						|
 | 
						|
                    // We need to figure out if this was a 'recycled' tag rendering or just happens to have the same id
 | 
						|
                    function isSame(fieldName: string) {
 | 
						|
                        return old[fieldName]?.["en"] === tagRendering[fieldName]?.["en"]
 | 
						|
                    }
 | 
						|
 | 
						|
                    const sameQuestion = isSame("question") && isSame("render")
 | 
						|
                    if (!sameQuestion) {
 | 
						|
                        const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
 | 
						|
                        newTr.id = layerConfig.id + "_" + newTr.id
 | 
						|
                        if (blacklistedIds.has(newTr.id)) {
 | 
						|
                            continue
 | 
						|
                        }
 | 
						|
                        newTr.condition = {
 | 
						|
                            and: Utils.NoNull([newTr.condition, layerConfig.source["osmTags"]]),
 | 
						|
                        }
 | 
						|
                        generatedTagRenderings.push(newTr)
 | 
						|
                        blacklistedIds.add(newTr.id)
 | 
						|
                        continue
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (!trPerId.has(id)) {
 | 
						|
                    const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
 | 
						|
                    generatedTagRenderings.push(newTr)
 | 
						|
                    trPerId.set(newTr.id, { tr: newTr, conditions: [] })
 | 
						|
                }
 | 
						|
                const conditions = trPerId.get(id).conditions
 | 
						|
                if (tagRendering["condition"]) {
 | 
						|
                    conditions.push({
 | 
						|
                        and: [tagRendering["condition"], layerConfig.source["osmTags"]],
 | 
						|
                    })
 | 
						|
                } else {
 | 
						|
                    conditions.push(layerConfig.source["osmTags"])
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        for (const { tr, conditions } of Array.from(trPerId.values())) {
 | 
						|
            const optimized = TagUtils.optimzeJson({ or: conditions })
 | 
						|
            if (optimized === true) {
 | 
						|
                continue
 | 
						|
            }
 | 
						|
            if (optimized === false) {
 | 
						|
                throw "Optimized into 'false', this is weird..."
 | 
						|
            }
 | 
						|
            tr.condition = optimized
 | 
						|
        }
 | 
						|
 | 
						|
        const allTags: QuestionableTagRenderingConfigJson = {
 | 
						|
            id: "all-tags",
 | 
						|
            render: { "*": "{all_tags()}" },
 | 
						|
 | 
						|
            metacondition: {
 | 
						|
                or: [
 | 
						|
                    "__featureSwitchIsDebugging=true",
 | 
						|
                    "mapcomplete-show_tags=full",
 | 
						|
                    "mapcomplete-show_debug=yes",
 | 
						|
                ],
 | 
						|
            },
 | 
						|
        }
 | 
						|
        proto.tagRenderings = [
 | 
						|
            "images",
 | 
						|
            ...generatedTagRenderings,
 | 
						|
            ...proto.tagRenderings,
 | 
						|
            "questions",
 | 
						|
            allTags,
 | 
						|
        ]
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * const titleIcons = new GenerateFavouritesLayer().generateTitleIcons()
 | 
						|
     * JSON.stringify(titleIcons).indexOf("icons.defaults") // => -1
 | 
						|
     * */
 | 
						|
    private generateTitleIcons(): TagRenderingConfigJson[] {
 | 
						|
        const iconsLibrary: Map<string, TagRenderingConfigJson[]> = new Map<
 | 
						|
            string,
 | 
						|
            TagRenderingConfigJson[]
 | 
						|
        >()
 | 
						|
        const path = "./src/assets/generated/layers/icons.json"
 | 
						|
        if (existsSync(path)) {
 | 
						|
            const config = <LayerConfigJson>JSON.parse(readFileSync(path, "utf8"))
 | 
						|
            for (const tagRendering of config.tagRenderings) {
 | 
						|
                const qtr = <QuestionableTagRenderingConfigJson>tagRendering
 | 
						|
                const id = qtr.id
 | 
						|
                if (id) {
 | 
						|
                    iconsLibrary.set(id, [qtr])
 | 
						|
                }
 | 
						|
                for (const label of tagRendering["labels"] ?? []) {
 | 
						|
                    if (!iconsLibrary.has(label)) {
 | 
						|
                        iconsLibrary.set(label, [])
 | 
						|
                    }
 | 
						|
                    iconsLibrary.get(label).push(qtr)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        const titleIcons: TagRenderingConfigJson[] = []
 | 
						|
        const seenTitleIcons = new Set<string>()
 | 
						|
        for (const layer of this.layers) {
 | 
						|
            for (const titleIcon of layer.titleIcons) {
 | 
						|
                if (typeof titleIcon === "string") {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if (titleIcon["labels"]?.indexOf("defaults") >= 0) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if (titleIcon.id === "iconsdefaults") {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
 | 
						|
                if (titleIcon.id === "rating") {
 | 
						|
                    if (!seenTitleIcons.has("rating")) {
 | 
						|
                        titleIcons.unshift(...iconsLibrary.get("rating"))
 | 
						|
                        seenTitleIcons.add("rating")
 | 
						|
                    }
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if (seenTitleIcons.has(titleIcon.id)) {
 | 
						|
                    continue
 | 
						|
                }
 | 
						|
                if(titleIcon.id === undefined){
 | 
						|
                   continue
 | 
						|
                }
 | 
						|
                seenTitleIcons.add(titleIcon.id)
 | 
						|
                console.log("Adding title icon", titleIcon.id)
 | 
						|
                titleIcons.push(titleIcon)
 | 
						|
            }
 | 
						|
        }
 | 
						|
        titleIcons.push(...(iconsLibrary.get("defaults") ?? []))
 | 
						|
        return titleIcons
 | 
						|
    }
 | 
						|
 | 
						|
    private addTitle(proto: LayerConfigJson) {
 | 
						|
        let mappings: MappingConfigJson[] = []
 | 
						|
        for (const layer of this.layers) {
 | 
						|
            const t = layer.title
 | 
						|
            const tags: TagConfigJson = layer.source["osmTags"]
 | 
						|
            if (!t) {
 | 
						|
                continue
 | 
						|
            }
 | 
						|
            if (typeof t === "string") {
 | 
						|
                mappings.push({ if: tags, then: t })
 | 
						|
            } else if (t["render"] !== undefined || t["mappings"] !== undefined) {
 | 
						|
                const tr = <TagRenderingConfigJson>t
 | 
						|
                for (let i = 0; i < (tr.mappings ?? []).length; i++) {
 | 
						|
                    const mapping = tr.mappings[i]
 | 
						|
                    const optimized = TagUtils.optimzeJson({
 | 
						|
                        and: [mapping.if, tags],
 | 
						|
                    })
 | 
						|
                    if (optimized === false) {
 | 
						|
                        console.warn(
 | 
						|
                            "The following tags yielded 'false':",
 | 
						|
                            JSON.stringify(mapping.if),
 | 
						|
                            JSON.stringify(tags)
 | 
						|
                        )
 | 
						|
                        continue
 | 
						|
                    }
 | 
						|
                    if (optimized === true) {
 | 
						|
                        console.error(
 | 
						|
                            "The following tags yielded 'false':",
 | 
						|
                            JSON.stringify(mapping.if),
 | 
						|
                            JSON.stringify(tags)
 | 
						|
                        )
 | 
						|
                        throw "Tags for title optimized to true"
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (!mapping.then) {
 | 
						|
                        throw (
 | 
						|
                            "The title has a missing 'then' for mapping " +
 | 
						|
                            i +
 | 
						|
                            " in layer " +
 | 
						|
                            layer.id
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    mappings.push({
 | 
						|
                        if: optimized,
 | 
						|
                        then: mapping.then,
 | 
						|
                    })
 | 
						|
                }
 | 
						|
                if (tr.render) {
 | 
						|
                    mappings.push({
 | 
						|
                        if: tags,
 | 
						|
                        then: <Translatable>tr.render,
 | 
						|
                    })
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                mappings.push({ if: tags, then: <Record<string, string>>t })
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        mappings = this.sortMappings(mappings)
 | 
						|
 | 
						|
        if (proto.title["mappings"]) {
 | 
						|
            mappings.unshift(...proto.title["mappings"])
 | 
						|
        }
 | 
						|
        if (proto.title["render"]) {
 | 
						|
            mappings.push({
 | 
						|
                if: "id~*",
 | 
						|
                then: proto.title["render"],
 | 
						|
            })
 | 
						|
        }
 | 
						|
 | 
						|
        for (const mapping of mappings) {
 | 
						|
            const opt = TagUtils.optimzeJson(mapping.if)
 | 
						|
            if (typeof opt === "boolean") {
 | 
						|
                continue
 | 
						|
            }
 | 
						|
            mapping.if = opt
 | 
						|
        }
 | 
						|
 | 
						|
        proto.title = {
 | 
						|
            mappings,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private readLayer(path: string): LayerConfigJson {
 | 
						|
        try {
 | 
						|
            return JSON.parse(readFileSync("./assets/layers/" + path, "utf8"))
 | 
						|
        } catch (e) {
 | 
						|
            console.error("Could not read ./assets/layers/" + path)
 | 
						|
            throw e
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
new GenerateFavouritesLayer().run()
 |