mapcomplete/scripts/generateFavouritesLayer.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

349 lines
13 KiB
TypeScript
Raw Normal View History

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"
2024-07-16 19:31:00 +02:00
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)
}
}
}
2024-03-12 23:35:07 +01:00
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) {
2024-07-21 10:52:51 +02:00
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",
2023-12-02 03:18:53 +01:00
"all_tags",
2024-07-21 10:52:51 +02:00
...addedByDefault,
])
2023-12-02 03:18:53 +01:00
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
}
2023-12-02 03:18:53 +01:00
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"]]),
}
2023-12-02 03:18:53 +01:00
generatedTagRenderings.push(newTr)
blacklistedIds.add(newTr.id)
continue
}
}
if (!trPerId.has(id)) {
const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
2023-12-02 03:18:53 +01:00
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 = [
2023-12-02 03:18:53 +01:00
"images",
...generatedTagRenderings,
...proto.tagRenderings,
"questions",
allTags,
]
}
/**
* const titleIcons = new GenerateFavouritesLayer().generateTitleIcons()
* JSON.stringify(titleIcons).indexOf("icons.defaults") // => -1
* */
private generateTitleIcons(): TagRenderingConfigJson[] {
2024-07-16 19:31:00 +02:00
const iconsLibrary: Map<string, TagRenderingConfigJson[]> = new Map<
2023-12-06 12:12:53 +01:00
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)
}
}
}
2024-07-16 19:31:00 +02:00
const titleIcons: TagRenderingConfigJson[] = []
2023-12-02 03:18:53 +01:00
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
}
2024-03-12 23:35:07 +01:00
if (titleIcon.id === "iconsdefaults") {
continue
}
2023-12-02 03:18:53 +01:00
if (titleIcon.id === "rating") {
if (!seenTitleIcons.has("rating")) {
titleIcons.unshift(...iconsLibrary.get("rating"))
2023-12-02 03:18:53 +01:00
seenTitleIcons.add("rating")
}
continue
}
if (seenTitleIcons.has(titleIcon.id)) {
continue
}
2024-07-21 10:52:51 +02:00
if (titleIcon.id === undefined) {
continue
}
2023-12-02 03:18:53 +01:00
seenTitleIcons.add(titleIcon.id)
console.log("Adding title icon", titleIcon.id)
titleIcons.push(titleIcon)
2023-12-02 03:18:53 +01:00
}
}
titleIcons.push(...(iconsLibrary.get("defaults") ?? []))
return titleIcons
2023-12-02 03:18:53 +01:00
}
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()