From cf7e005fd1e45e34ff71bafbb62ecdf39e36b7c7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 27 Apr 2025 22:54:51 +0200 Subject: [PATCH] Refactoring: split NameSuggestionIndex into parts --- package.json | 2 +- .../{generateStats.ts => generateNsiStats.ts} | 8 +- scripts/nsiLogos.ts | 18 +- src/Logic/Web/NameSuggestionIndex.ts | 191 ++++++++---------- .../Conversion/MiscTagRenderingChecks.ts | 23 +-- 5 files changed, 103 insertions(+), 139 deletions(-) rename scripts/{generateStats.ts => generateNsiStats.ts} (97%) diff --git a/package.json b/package.json index 974c6b8581..3a6f567f7c 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "velopark:download": "vite-node scripts/velopark/veloParkToGeojson.ts ", "### 1": "CODE AND DATA MAINTAINENCE", "download:nsi-logos": "mkdir -p ./public/assets/data/nsi && vite-node scripts/nsiLogos.ts -- download prune addExtensions patch || npm run download:nsi-logos # This script crashes often without the possibility to correct - hence the auto retry with OR", - "download:nsi": "npm i name-suggestion-index && vite-node scripts/generateStats.ts && npm run download:nsi-logos && vite-node scripts/nsiLogos.ts -- all", + "download:nsi": "npm i name-suggestion-index && vite-node scripts/generateNsiStats.ts && npm run download:nsi-logos && vite-node scripts/nsiLogos.ts -- all", "download:editor-layer-index": "mkdir -p ./public/assets/data/ && vite-node scripts/downloadEli.ts", "download:stats": "vite-node scripts/GenerateSeries.ts", "download:community-index": "vite-node scripts/downloadCommunityIndex.ts ", diff --git a/scripts/generateStats.ts b/scripts/generateNsiStats.ts similarity index 97% rename from scripts/generateStats.ts rename to scripts/generateNsiStats.ts index d082a9daed..a9db6a0817 100644 --- a/scripts/generateStats.ts +++ b/scripts/generateNsiStats.ts @@ -12,7 +12,7 @@ import TagInfo from "../src/Logic/Web/TagInfo" import { TagsFilter } from "../src/Logic/Tags/TagsFilter" -class GenerateStats extends Script { +class GenerateNsiStats extends Script { async createOptimizationFile(includeTags = true) { ScriptUtils.fixUtils() const layers = known_layers["layers"] @@ -139,7 +139,7 @@ class GenerateStats extends Script { path ) } - const nsi = await NameSuggestionIndex.getNsiIndex("./assets/data/nsi/") + const nsi = await NameSuggestionIndex.singleton() const allBrandNames: string[] = Utils.Dedup( nsi.allPossible(type).map((item) => item.tags[type]) ) @@ -182,7 +182,7 @@ class GenerateStats extends Script { ) } - async main(_: string[]) { + async main() { const target = "./public/assets/data/nsi/" const basepath = target + "stats/" { @@ -202,4 +202,4 @@ class GenerateStats extends Script { } } -new GenerateStats().run() +new GenerateNsiStats().run() diff --git a/scripts/nsiLogos.ts b/scripts/nsiLogos.ts index 4d3904fe78..04c5104da4 100644 --- a/scripts/nsiLogos.ts +++ b/scripts/nsiLogos.ts @@ -1,5 +1,5 @@ import Script from "./Script" -import NameSuggestionIndex, { NSIItem } from "../src/Logic/Web/NameSuggestionIndex" +import NameSuggestionIndex, { NamgeSuggestionWikidata, NSIItem } from "../src/Logic/Web/NameSuggestionIndex" import * as nsiWD from "../node_modules/name-suggestion-index/dist/wikidata.min.json" import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs" import ScriptUtils from "./ScriptUtils" @@ -42,8 +42,8 @@ class NsiLogos extends Script { let path = basePath + nsiItem.id const logos = nsiWD["wikidata"][nsiItem?.tags?.[type + ":wikidata"]]?.logos - const nsi = await NameSuggestionIndex.getNsiIndex("./assets/data/nsi/") - if (nsi.isSvg(nsiItem, type)) { + const nsiWd = new NamgeSuggestionWikidata(nsiWD) + if (nsiWd.isSvg(nsiItem, type)) { path = path + ".svg" } @@ -99,13 +99,17 @@ class NsiLogos extends Script { return false } + private async getAllPossibleNsiItems(type: string): Promise { + const nsi = await NameSuggestionIndex.singleton() + return nsi.allPossible(type) + } + /** * Returns * @param type */ async downloadFor(type: string): Promise<{ downloadCount: number; errored: number }> { - const nsi = await NameSuggestionIndex.getNsiIndex("./assets/data/nsi/") - const items = nsi.allPossible(type) + const items = await this.getAllPossibleNsiItems(type) const basePath = "./public/assets/data/nsi/logos/" let downloadCount = 0 let errored = 0 @@ -158,8 +162,8 @@ class NsiLogos extends Script { } private async generateRendering(type: string) { - const nsi = await NameSuggestionIndex.getNsiIndex("./assets/data/nsi/") - const items = nsi.allPossible(type) + const nsi = await NameSuggestionIndex.singleton() + const items = await this.getAllPossibleNsiItems(type) const filterOptions: FilterConfigOptionJson[] = items.map((item) => { return { question: item.displayName, diff --git a/src/Logic/Web/NameSuggestionIndex.ts b/src/Logic/Web/NameSuggestionIndex.ts index 104e4bb3a3..02bfe424db 100644 --- a/src/Logic/Web/NameSuggestionIndex.ts +++ b/src/Logic/Web/NameSuggestionIndex.ts @@ -55,14 +55,18 @@ export interface NSIItem { ext?: string } -export class NameSuggestionIndexLight { - public static readonly supportedTypes = ["brand", "flag", "operator", "transit"] as const +const supportedTypes = ["brand", "flag", "operator", "transit"] as const +export type NsiSupportedType = typeof supportedTypes[number] + +export default class NameSuggestionIndex { + public static readonly supportedTypes: ReadonlyArray = ["brand", "flag", "operator", "transit"] as const protected readonly _serverLocation: string protected readonly nsiFile: Readonly private readonly loco: LocationConflation // Some additional boundaries - private static initedLight: NameSuggestionIndexLight = undefined + private static inited: NameSuggestionIndex = undefined + private _supportedTypes: NsiSupportedType[] - protected constructor( + private constructor( serverLocation: string, nsiFile: Readonly, features: Readonly @@ -70,7 +74,7 @@ export class NameSuggestionIndexLight { this._serverLocation = serverLocation this.nsiFile = nsiFile this.loco = new LocationConflation(features) - NameSuggestionIndexLight.initedLight = this + NameSuggestionIndex.inited = this } /** @@ -258,9 +262,9 @@ export class NameSuggestionIndexLight { }) } - public static async singleton(): Promise { - if (NameSuggestionIndexLight.initedLight) { - return NameSuggestionIndexLight.initedLight + public static async singleton(): Promise { + if (NameSuggestionIndex.inited) { + return NameSuggestionIndex.inited } const endpoint = Constants.nsiLogosEndpoint ?? "./assets/data/nsi/" const [nsi, features] = await Promise.all( @@ -269,70 +273,14 @@ export class NameSuggestionIndexLight { endpoint + "featureCollection.min.json" ].map((url) => Utils.downloadJsonCached(url, 1000 * 60 * 60 * 24 * 30)) ) - return new NameSuggestionIndexLight( + return new NameSuggestionIndex( endpoint, nsi, features ) } -} - -export default class NameSuggestionIndex extends NameSuggestionIndexLight { - private readonly nsiWdFile: Readonly< - Record< - string, - { - logos: { wikidata?: string; facebook?: string } - } - > - > - - - private _supportedTypes: string[] - private static inited: NameSuggestionIndex = undefined - - private constructor( - serverLocation: string, - nsiFile: Readonly, - nsiWdFile: Readonly< - Record< - string, - { - logos: { wikidata?: string; facebook?: string } - } - > - >, - features: Readonly - ) { - super(serverLocation, nsiFile, features) - this.nsiWdFile = nsiWdFile - NameSuggestionIndex.inited = this - } - - - public static async getNsiIndex(endPoint ?: string): Promise { - if (NameSuggestionIndex.inited) { - return NameSuggestionIndex.inited - } - endPoint ??= Constants.nsiLogosEndpoint ?? "./assets/data/nsi/" - const [nsi, nsiWd, features] = await Promise.all( - [ - endPoint + "nsi.min.json", - endPoint + "wikidata.min.json", - endPoint + "featureCollection.min.json" - ].map((url) => Utils.downloadJsonCached(url, 1000 * 60 * 60 * 24 * 30)) - ) - return new NameSuggestionIndex( - Constants.nsiLogosEndpoint, - nsi, - nsiWd["wikidata"], - features - ) - } - - - public supportedTypes(): string[] { + public supportedTypes(): NsiSupportedType[] { if (this._supportedTypes) { return this._supportedTypes } @@ -342,35 +290,13 @@ export default class NameSuggestionIndex extends NameSuggestionIndexLight { if (s.endsWith("s")) { s = s.substring(0, s.length - 1) } - return s + return s }) return this._supportedTypes } - - public isSvg(nsiItem: NSIItem, type: string): boolean | undefined { - if (this.nsiWdFile === undefined) { - throw ( - "nsiWdi file is not loaded, cannot determine if " + nsiItem.id + " has an SVG image" - ) - } - const logos = this.nsiWdFile[nsiItem?.tags?.[type + ":wikidata"]]?.logos - if (!logos) { - return undefined - } - if (logos.facebook) { - return false - } - const url: string = logos.wikidata - if (url.toLowerCase().endsWith(".svg")) { - return true - } - return false - } - - public supportedTags( - type: "operator" | "brand" | "flag" | "transit" | string + type: NsiSupportedType ): Record { const tags: Record = {} const keys = Object.keys(this.nsiFile.nsi) @@ -389,11 +315,17 @@ export default class NameSuggestionIndex extends NameSuggestionIndexLight { return tags } + + public static async getNsiIndex(endPoint ?: string): Promise { + + } + + /** * Returns a list of all brands/operators - * @param type + * @param type One of the supported types, e.g. "brand" or "operator" */ - public allPossible(type: string): NSIItem[] { + public allPossible(type: NsiSupportedType): NSIItem[] { const options: NSIItem[] = [] const tags = this.supportedTags(type) for (const osmKey in tags) { @@ -412,8 +344,8 @@ export default class NameSuggestionIndex extends NameSuggestionIndexLight { /** * - * @param country: a string containing one or more country codes, separated by ";" - * @param location: center point of the feature, should be [lon, lat] + * @param country a string containing one or more country codes, separated by ";" + * @param location center point of the feature, should be [lon, lat] */ public getSuggestionsFor( type: string, @@ -436,21 +368,10 @@ export default class NameSuggestionIndex extends NameSuggestionIndexLight { sortByFrequency: boolean } ): Promise { - const nsi = await NameSuggestionIndexLight.singleton() + const nsi = await NameSuggestionIndex.singleton() return nsi.generateMappings(key, tags, country, center, options) } - /** - * Where can we find the URL on the world wide web? - * Probably facebook! Don't use in the website, might expose people - * @param nsiItem - * @param type - */ - private getIconExternalUrl(nsiItem: NSIItem, type: string): string { - const logos = this.nsiWdFile[nsiItem.tags[type + ":wikidata"]]?.logos - return logos?.facebook ?? logos?.wikidata - } - private static readonly brandPrefix = ["name", "alt_name", "operator", "brand"] as const @@ -478,3 +399,61 @@ export default class NameSuggestionIndex extends NameSuggestionIndexLight { return TagUtils.optimzeJson({ and: [...required, { or: brandDetection }] }) } } + +export class NamgeSuggestionWikidata { + private readonly nsiWdFile: Readonly< + Record< + string, + { + logos: { wikidata?: string; facebook?: string } + } + > + > + + constructor( + nsiWdFile: Readonly< + Record< + string, + { + logos: { wikidata?: string; facebook?: string } + } + > + > + ) { + this.nsiWdFile = nsiWdFile + } + + public isSvg(nsiItem: NSIItem, type: string): boolean | undefined { + if (this.nsiWdFile === undefined) { + throw ( + "nsiWdi file is not loaded, cannot determine if " + nsiItem.id + " has an SVG image" + ) + } + const logos = this.nsiWdFile[nsiItem?.tags?.[type + ":wikidata"]]?.logos + if (!logos) { + return undefined + } + if (logos.facebook) { + return false + } + const url: string = logos.wikidata + if (url.toLowerCase().endsWith(".svg")) { + return true + } + return false + } + + + /** + * Where can we find the URL on the world wide web? + * Probably facebook! Don't use in the website, might expose people + * @param nsiItem + * @param type + */ + private getIconExternalUrl(nsiItem: NSIItem, type: string): string { + const logos = this.nsiWdFile[nsiItem.tags[type + ":wikidata"]]?.logos + return logos?.facebook ?? logos?.wikidata + } + +} + diff --git a/src/Models/ThemeConfig/Conversion/MiscTagRenderingChecks.ts b/src/Models/ThemeConfig/Conversion/MiscTagRenderingChecks.ts index e6a932603a..9214deb639 100644 --- a/src/Models/ThemeConfig/Conversion/MiscTagRenderingChecks.ts +++ b/src/Models/ThemeConfig/Conversion/MiscTagRenderingChecks.ts @@ -1,16 +1,11 @@ import { DesugaringStep } from "./Conversion" import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson" -import { - MappingConfigJson, - QuestionableTagRenderingConfigJson, -} from "../Json/QuestionableTagRenderingConfigJson" +import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConversionContext } from "./ConversionContext" import { Translation } from "../../../UI/i18n/Translation" -import { TagUtils } from "../../../Logic/Tags/TagUtils" import Validators from "../../../UI/InputElement/Validators" import { CheckTranslation } from "./Validation" -import NameSuggestionIndex from "../../../Logic/Web/NameSuggestionIndex" export class MiscTagRenderingChecks extends DesugaringStep { private readonly _layerConfig: LayerConfigJson @@ -195,21 +190,7 @@ export class MiscTagRenderingChecks extends DesugaringStepjson.freeform.key) >= 0 - ) { - const tags = TagUtils.TagD(this._layerConfig?.source?.["osmTags"])?.usedTags() - /* const suggestions = nameSuggestionIndexBundled.getSuggestionsFor(json.freeform.key, tags) - if (suggestions === undefined) { - context - .enters("freeform", "type") - .err( - "No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are " + - tags.map((t) => new Tag(t.key, t.value).asHumanString()).join(" ; ") - ) - }*/ - } else if (json.freeform["type"] === "nsi") { + if (json.freeform["type"] === "nsi") { context .enters("freeform", "type") .warn(