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 { 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() delete proto.filter 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 = 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 = 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 ${TagUtils.Tag({ or: conditions, }).asHumanString()} 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 = new Map< string, TagRenderingConfigJson[] >() const path = "./src/assets/generated/layers/icons.json" if (existsSync(path)) { const config = JSON.parse(readFileSync(path, "utf8")) for (const tagRendering of config.tagRenderings) { const qtr = 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() 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 = 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: tr.render, }) } } else { mappings.push({ if: tags, then: >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()