UNPKG

name-suggestion-index

Version:

Canonical common brand names for OpenStreetMap

1,070 lines (1,062 loc) 33.1 kB
var __create = Object.create; var __getProtoOf = Object.getPrototypeOf; var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __hasOwnProp = Object.prototype.hasOwnProperty; function __accessProp(key) { return this[key]; } var __toESMCache_node; var __toESMCache_esm; var __toESM = (mod, isNodeMode, target) => { var canCache = mod != null && typeof mod === "object"; if (canCache) { var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap; var cached = cache.get(mod); if (cached) return cached; } target = mod != null ? __create(__getProtoOf(mod)) : {}; const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; for (let key of __getOwnPropNames(mod)) if (!__hasOwnProp.call(to, key)) __defProp(to, key, { get: __accessProp.bind(mod, key), enumerable: true }); if (canCache) cache.set(mod, to); return to; }; var __toCommonJS = (from) => { var entry = (__moduleCache ??= new WeakMap).get(from), desc; if (entry) return entry; entry = __defProp({}, "__esModule", { value: true }); if (from && typeof from === "object" || typeof from === "function") { for (var key of __getOwnPropNames(from)) if (!__hasOwnProp.call(entry, key)) __defProp(entry, key, { get: __accessProp.bind(from, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } __moduleCache.set(from, entry); return entry; }; var __moduleCache; var __returnValue = (v) => v; function __exportSetter(name, newValue) { this[name] = __returnValue.bind(null, newValue); } var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true, configurable: true, set: __exportSetter.bind(all, name) }); }; // src/nsi.ts var exports_nsi = {}; __export(exports_nsi, { stemmer: () => stemmer, simplify: () => simplify, buildJOSMPresets: () => buildJOSMPresets, buildIDPresets: () => buildIDPresets, Matcher: () => Matcher }); module.exports = __toCommonJS(exports_nsi); // lib/matcher.ts var import_location_conflation = require("@rapideditor/location-conflation"); // lib/simplify.ts var import_diacritics = __toESM(require("diacritics")); function simplify(str) { if (typeof str !== "string") return ""; return import_diacritics.default.remove(str.replace(/&/g, "and").replace(/(İ|i̇)/ig, "i").replace(/[\s\-=_!"#%'*{},.\/:;?\(\)\[\]@\\$\^*+<>«»~`’\u00a1\u00a7\u00b6\u00b7\u00bf\u037e\u0387\u055a-\u055f\u0589\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964\u0965\u0970\u0af0\u0df4\u0e4f\u0e5a\u0e5b\u0f04-\u0f12\u0f14\u0f85\u0fd0-\u0fd4\u0fd9\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u166d\u166e\u16eb-\u16ed\u1735\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u1805\u1807-\u180a\u1944\u1945\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e\u1c7f\u1cc0-\u1cc7\u1cd3\u2000-\u206f\u2cf9-\u2cfc\u2cfe\u2cff\u2d70\u2e00-\u2e7f\u3001-\u3003\u303d\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uaaf0\uaaf1\uabeb\ufe10-\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49-\ufe4c\ufe50-\ufe52\ufe54-\ufe57\ufe5f-\ufe61\ufe68\ufe6a\ufe6b\ufeff\uff01-\uff03\uff05-\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65]+/g, "").toLowerCase()); } // config/matchGroups.json var matchGroups_default = { matchGroups: { adult_gaming_centre: [ "amenity/casino", "amenity/gambling", "leisure/adult_gaming_centre" ], bar: [ "amenity/bar", "amenity/pub", "amenity/restaurant" ], beauty: [ "shop/beauty", "shop/hairdresser_supply" ], bed: [ "shop/bed", "shop/furniture" ], beverages: [ "shop/alcohol", "shop/beer", "shop/beverages", "shop/kiosk", "shop/wine" ], camping: [ "tourism/camp_site", "tourism/caravan_site" ], car_parts: [ "shop/car_parts", "shop/car_repair", "shop/tires", "shop/tyres" ], clinic: [ "amenity/clinic", "amenity/doctors", "healthcare/clinic", "healthcare/laboratory", "healthcare/physiotherapist", "healthcare/sample_collection", "healthcare/dialysis" ], convenience: [ "shop/beauty", "shop/chemist", "shop/convenience", "shop/cosmetics", "shop/grocery", "shop/kiosk", "shop/newsagent", "shop/perfumery" ], coworking: [ "amenity/coworking_space", "office/coworking", "office/coworking_space" ], dentist: [ "amenity/dentist", "amenity/doctors", "healthcare/dentist" ], electronics: [ "office/telecommunication", "shop/appliance", "shop/computer", "shop/electronics", "shop/hifi", "shop/kiosk", "shop/mobile", "shop/mobile_phone", "shop/telecommunication", "shop/video_games" ], estate_agents: [ "office/estate_agent", "shop/estate_agent", "office/mortgage", "office/financial" ], fabric: [ "shop/fabric", "shop/haberdashery", "shop/sewing" ], fashion: [ "shop/accessories", "shop/bag", "shop/boutique", "shop/clothes", "shop/department_store", "shop/fashion", "shop/fashion_accessories", "shop/sports", "shop/shoes" ], financial: [ "amenity/bank", "office/accountant", "office/financial", "office/financial_advisor", "office/tax_advisor", "shop/tax" ], fitness: [ "leisure/fitness_centre", "leisure/fitness_center", "leisure/sports_centre", "leisure/sports_center" ], food: [ "amenity/cafe", "amenity/fast_food", "amenity/ice_cream", "amenity/restaurant", "shop/bakery", "shop/candy", "shop/chocolate", "shop/coffee", "shop/confectionary", "shop/confectionery", "shop/deli", "shop/food", "shop/kiosk", "shop/ice_cream", "shop/pastry", "shop/tea", "shop/beverages" ], fuel: [ "amenity/fuel", "shop/gas", "shop/convenience;gas", "shop/gas;convenience" ], gift: [ "shop/gift", "shop/card", "shop/cards", "shop/kiosk", "shop/stationery" ], glass: [ "craft/window_construction", "craft/glaziery", "shop/car_repair" ], hardware: [ "shop/bathroom_furnishing", "shop/carpet", "shop/diy", "shop/doityourself", "shop/doors", "shop/electrical", "shop/flooring", "shop/hardware", "shop/hardware_store", "shop/power_tools", "shop/tool_hire", "shop/tools", "shop/trade" ], health_food: [ "shop/health", "shop/health_food", "shop/herbalist", "shop/nutrition_supplements" ], hobby: [ "shop/electronics", "shop/hobby", "shop/books", "shop/games", "shop/collector", "shop/toys", "shop/model", "shop/video_games", "shop/anime" ], hospital: [ "amenity/doctors", "amenity/hospital", "healthcare/hospital" ], houseware: [ "shop/houseware", "shop/interior_decoration" ], water_rescue: [ "amenity/lifeboat_station", "emergency/lifeboat_station", "emergency/marine_rescue", "emergency/water_rescue" ], locksmith: [ "craft/key_cutter", "craft/locksmith", "shop/locksmith" ], lodging: [ "tourism/guest_house", "tourism/hotel", "tourism/motel" ], money_transfer: [ "amenity/money_transfer", "shop/money_transfer" ], music: ["shop/music", "shop/musical_instrument"], office_supplies: [ "shop/office_supplies", "shop/stationary", "shop/stationery" ], outdoor: [ "shop/clothes", "shop/outdoor", "shop/sports" ], parcel_locker: [ "amenity/parcel_locker", "amenity/vending_machine" ], pharmacy: [ "amenity/doctors", "amenity/pharmacy", "healthcare/pharmacy", "shop/chemist" ], playground: [ "amenity/theme_park", "leisure/amusement_arcade", "leisure/playground" ], rental: [ "amenity/bicycle_rental", "amenity/boat_rental", "amenity/car_rental", "amenity/truck_rental", "amenity/vehicle_rental", "shop/kiosk", "shop/plant_hire", "shop/rental", "shop/tool_hire" ], school: [ "amenity/childcare", "amenity/college", "amenity/kindergarten", "amenity/language_school", "amenity/prep_school", "amenity/school", "amenity/university" ], storage: [ "shop/storage_units", "shop/storage_rental" ], substation: [ "power/station", "power/substation", "power/sub_station" ], supermarket: [ "shop/food", "shop/frozen_food", "shop/greengrocer", "shop/grocery", "shop/supermarket", "shop/wholesale" ], thrift: [ "shop/charity", "shop/clothes", "shop/second_hand" ], tobacco: [ "shop/e-cigarette", "shop/tobacco" ], variety_store: [ "shop/variety_store", "shop/discount", "shop/convenience" ], vending: [ "amenity/vending_machine", "shop/kiosk", "shop/vending_machine" ], weight_loss: [ "amenity/clinic", "amenity/doctors", "amenity/weight_clinic", "healthcare/counselling", "leisure/fitness_centre", "office/therapist", "shop/beauty", "shop/diet", "shop/food", "shop/health_food", "shop/herbalist", "shop/nutrition", "shop/nutrition_supplements", "shop/weight_loss" ], wholesale: [ "shop/wholesale", "shop/supermarket", "shop/department_store" ] } }; // config/genericWords.json var genericWords_default = { genericWords: [ "^(barn|bazaa?r|bench|bou?tique|building|casa|church)$", "^(baseball|basketball|football|soccer|softball|tennis(halle)?)\\s?(field|court)?$", "^(club|green|out|ware)\\s?house$", "^(driveway|el árbol|fountain|generic|golf|government|graveyard)$", "^(fixme|n\\s?\\/?\\s?a|name|no\\s?name|none|null|temporary|test|unknown)$", "^(hofladen|librairie|magazine?|maison|toko)$", "^(mobile home|skate)?\\s?park$", "^(obuwie|pond|pool|sale|shops?|sklep|stores?)$", "^\\?+$", "^private$", "^tattoo( studio)?$", "^windmill$", "^церковная( лавка)?$" ] }; // config/trees.json var trees_default = { trees: { brands: { emoji: "🍔", mainTag: "brand:wikidata", sourceTags: ["brand", "name"], nameTags: { primary: "^(name|name:[\\w-]+)$", alternate: "^(brand|brand:[\\w-]+|brand:short:[\\w-]+|operator|operator:[\\w-]+|operator:short:[\\w-]+|\\w+_name|\\w+_name:[\\w-]+)$" } }, flags: { emoji: "🚩", mainTag: "flag:wikidata", nameTags: { primary: "^(flag:name|flag:name:[\\w-]+)$", alternate: "^(country|country:[\\w-]+|flag|flag:[\\w-]+|subject|subject:[\\w-]+)$" } }, operators: { emoji: "💼", mainTag: "operator:wikidata", sourceTags: ["operator"], nameTags: { primary: "^(name|name:[\\w-]+|operator|operator:[\\w-]+)$", alternate: "^(brand|brand:[\\w-]+|brand:short:[\\w-]+|operator:short:[\\w-]+|\\w+_name|\\w+_name:[\\w-]+)$" } }, transit: { emoji: "🚇", mainTag: "network:wikidata", sourceTags: ["network"], nameTags: { primary: "^network$", alternate: "^(operator|operator:[\\w-]+|operator:short:[\\w-]+|network:[\\w-]+|network:short:[\\w-]+|\\w+_name|\\w+_name:[\\w-]+)$" } } } }; // lib/matcher.ts var matchGroups = matchGroups_default.matchGroups; var trees = trees_default.trees; class Matcher { matchIndex; genericWords = new Map; loco; itemLocationSetID; warnings = []; constructor() { this.matchIndex = undefined; this.genericWords = new Map; for (const s of genericWords_default.genericWords || []) { this.genericWords.set(s, new RegExp(s, "i")); } this.loco = undefined; this.itemLocationSetID = undefined; this.warnings = []; } buildMatchIndex(data) { if (this.matchIndex) return; const matchIndex = new Map; this.matchIndex = matchIndex; const seenTree = new Map; const skipGenericKVMatches = (t, k, v) => { return t === "flags" || t === "transit" || k === "landuse" || v === "atm" || v === "bicycle_parking" || v === "car_sharing" || v === "caravan_site" || v === "charging_station" || v === "dog_park" || v === "parking" || v === "phone" || v === "playground" || v === "post_box" || v === "public_bookcase" || v === "recycling" || v === "vending_machine"; }; const insertName = (which, t, kv, nsimple, itemID) => { if (!nsimple) { this.warnings.push(`Warning: skipping empty ${which} name for item ${t}/${kv}: ${itemID}`); return; } let branch = matchIndex.get(kv); if (!branch) { branch = { primary: new Map, alternate: new Map, excludeGeneric: new Map, excludeNamed: new Map }; matchIndex.set(kv, branch); } let leaf = branch[which].get(nsimple); if (!leaf) { leaf = new Set; branch[which].set(nsimple, leaf); } leaf.add(itemID); if (!/yes$/.test(kv)) { const kvnsimple = `${kv}/${nsimple}`; const existing = seenTree.get(kvnsimple); if (existing && existing !== t) { const items = Array.from(leaf); this.warnings.push(`Duplicate cache key "${kvnsimple}" in trees "${t}" and "${existing}", check items: ${items}`); return; } seenTree.set(kvnsimple, t); } }; for (const tkv of Object.keys(data)) { const category = data[tkv]; const parts = tkv.split("/", 3); const t = parts[0]; const k = parts[1]; const v = parts[2]; const thiskv = `${k}/${v}`; const tree = trees[t]; let branch = matchIndex.get(thiskv); if (!branch) { branch = { primary: new Map, alternate: new Map, excludeGeneric: new Map, excludeNamed: new Map }; matchIndex.set(thiskv, branch); } const properties = category.properties || {}; const exclude = properties.exclude || {}; for (const s of exclude.generic || []) branch.excludeGeneric.set(s, new RegExp(s, "i")); for (const s of exclude.named || []) branch.excludeNamed.set(s, new RegExp(s, "i")); const excludeRegexes = [...branch.excludeGeneric.values(), ...branch.excludeNamed.values()]; const items = category.items; if (!Array.isArray(items) || !items.length) continue; const primaryName = new RegExp(tree.nameTags.primary, "i"); const alternateName = new RegExp(tree.nameTags.alternate, "i"); const notName = /:(colou?r|type|forward|backward|left|right|etymology|pronunciation|signed|wikipedia)$/i; const skipGenericKV = skipGenericKVMatches(t, k, v); const genericKV = new Set([`${k}/yes`, `building/yes`]); const matchGroupKV = new Set; for (const matchGroup of Object.values(matchGroups)) { const inGroup = matchGroup.some((otherkv) => otherkv === thiskv); if (!inGroup) continue; for (const otherkv of matchGroup) { if (otherkv === thiskv) continue; matchGroupKV.add(otherkv); const otherk = otherkv.split("/", 2)[0]; genericKV.add(`${otherk}/yes`); } } for (const item of items) { if (!item.id) continue; if (Array.isArray(item.matchTags) && item.matchTags.length) { item.matchTags = item.matchTags.filter((matchTag) => !matchGroupKV.has(matchTag) && matchTag !== thiskv && !genericKV.has(matchTag)); if (!item.matchTags.length) delete item.matchTags; } let kvTags = [`${thiskv}`].concat(item.matchTags || []); if (!skipGenericKV) { kvTags = kvTags.concat(Array.from(genericKV)); } for (const osmkey of Object.keys(item.tags)) { if (notName.test(osmkey)) continue; const osmvalue = item.tags[osmkey]; if (!osmvalue || excludeRegexes.some((regex) => regex.test(osmvalue))) continue; if (primaryName.test(osmkey)) { for (const kv of kvTags) insertName("primary", t, kv, simplify(osmvalue), item.id); } else if (alternateName.test(osmkey)) { for (const kv of kvTags) insertName("alternate", t, kv, simplify(osmvalue), item.id); } } const keepMatchNames = new Set; for (const matchName of item.matchNames || []) { const nsimple = simplify(matchName); for (const kv of kvTags) { const branch2 = matchIndex.get(kv); const primaryLeaf = branch2 && branch2.primary.get(nsimple); const alternateLeaf = branch2 && branch2.alternate.get(nsimple); const inPrimary = primaryLeaf && primaryLeaf.has(item.id); const inAlternate = alternateLeaf && alternateLeaf.has(item.id); if (!inPrimary && !inAlternate) { insertName("alternate", t, kv, nsimple, item.id); keepMatchNames.add(matchName); } } } if (keepMatchNames.size) { item.matchNames = Array.from(keepMatchNames); } else { delete item.matchNames; } } } } buildLocationIndex(data, loco) { loco = loco ?? new import_location_conflation.LocationConflation; this.loco = loco; const itemLocationSetID = new Map; this.itemLocationSetID = itemLocationSetID; for (const tkv of Object.keys(data)) { const items = data[tkv].items; if (!Array.isArray(items) || !items.length) continue; const registered = loco.registerLocationSets(items); for (const item of registered) { itemLocationSetID.set(item.id, item.locationSetID); } } } match(k, v, n, loc) { if (!this.matchIndex) { throw new Error("match: matchIndex not built."); } const matchIndex = this.matchIndex; const loco = this.loco; const itemLocationSetID = this.itemLocationSetID; let validHere = null; if (Array.isArray(loc) && loco) { validHere = loco.locationSetsAt(loc); } const nsimple = simplify(n); const seen = new Set; const results = []; const byAreaAscending = (hitA, hitB) => { return (hitA.area || 0) - (hitB.area || 0); }; const byAreaDescending = (hitA, hitB) => { return (hitB.area || 0) - (hitA.area || 0); }; const isValidLocation = (hit) => { if (!validHere || !itemLocationSetID) return true; const locationSetID = itemLocationSetID.get(hit.itemID); return locationSetID ? validHere.has(locationSetID) : false; }; const tryMatch = (which, kv) => { const branch = matchIndex.get(kv); if (!branch) return false; if (which === "exclude") { let regex = [...branch.excludeNamed.values()].find((regex2) => regex2.test(n)); if (regex) { results.push({ match: "excludeNamed", pattern: String(regex), kv }); return false; } regex = [...branch.excludeGeneric.values()].find((regex2) => regex2.test(n)); if (regex) { results.push({ match: "excludeGeneric", pattern: String(regex), kv }); return false; } return false; } const leaf = branch[which].get(nsimple); if (!leaf || !leaf.size) return false; if (!(which === "primary" || which === "alternate")) return false; let hits = []; for (const itemID of [...leaf]) { let area = Infinity; if (loco && itemLocationSetID) { const setID = itemLocationSetID.get(itemID); if (setID) { area = loco.getLocationSetArea(setID) ?? Infinity; } } hits.push({ match: which, itemID, area, kv, nsimple }); } let sortFn = byAreaDescending; if (validHere) { hits = hits.filter(isValidLocation); sortFn = byAreaAscending; } if (!hits.length) return false; for (const hit of hits.sort(sortFn)) { if (seen.has(hit.itemID)) continue; seen.add(hit.itemID); results.push(hit); } return true; }; const gatherResults = (which) => { const kv = `${k}/${v}`; let didMatch = tryMatch(which, kv); if (didMatch) return; for (const matchGroup of Object.values(matchGroups)) { const inGroup = matchGroup.some((otherkv) => otherkv === kv); if (!inGroup) continue; for (const otherkv of matchGroup) { if (otherkv === kv) continue; didMatch = tryMatch(which, otherkv); if (didMatch) return; } } if (which === "exclude") { const regex = [...this.genericWords.values()].find((regex2) => regex2.test(n)); if (regex) { results.push({ match: "excludeGeneric", pattern: String(regex) }); return; } } }; gatherResults("primary"); gatherResults("alternate"); if (results.length) return results; gatherResults("exclude"); return results.length ? results : null; } getWarnings() { return this.warnings; } } // lib/stemmer.ts function stemmer(str) { if (typeof str !== "string") return ""; const noise = [ /ban(k|c)(a|o)?/ig, /банк/ig, /coop/ig, /express/ig, /(gas|fuel)/ig, /wireless/ig, /(shop|store)/ig ]; str = noise.reduce((acc, regex) => acc.replace(regex, ""), str); return simplify(str); } // lib/sort_object.ts var withLocale = new Intl.Collator("en-US").compare; function sortObject(obj) { if (!obj) return null; const sorted = {}; const keys = Object.keys(obj).sort(keyCompare); for (const k of keys) { const v = obj[k]; sorted[k] = Array.isArray(v) ? v.sort(withLocale) : v; } return sorted; function keyCompare(a, b) { const qid = /^Q(\d+)$/; const aMatch = a.match(qid); const bMatch = b.match(qid); if (aMatch && bMatch) { return parseInt(aMatch[1], 10) - parseInt(bMatch[1], 10); } else { return withLocale(a, b); } } } // lib/presets_id.ts var withLocale2 = new Intl.Collator("en-US").compare; var trees2 = trees_default.trees; var presetPathOverrides = { "highway/bus_stop": "public_transport/platform/bus_point", "amenity/ferry_terminal": "public_transport/station_ferry", "amenity/college": "education/college", "amenity/driving_school": "education/driving_school", "amenity/dancing_school": "education/dancing_school", "amenity/kindergarten": "education/kindergarten", "amenity/language_school": "education/language_school", "amenity/music_school": "education/music_school", "amenity/prep_school": "education/prep_school", "amenity/school": "education/school", "amenity/university": "education/university", "emergency/water_rescue": "emergency/lifeboat_station" }; var semicolonSplittedKeys = ["beauty", "clothes", "cuisine", "healthcare:speciality", "social_facility", "sport", "vending", "waste"]; var preferCommons = { Q177054: true, Q524757: true, Q779845: true, Q1205312: true, Q10443115: true, Q38076: true }; function resolvePresetPath(tkv, k, v, kv, ferryIndex) { if (tkv === "transit/route/ferry") { return ferryIndex === 0 ? "type/route/ferry" : "route/ferry"; } if (k === "route") return `type/route/${v}`; return presetPathOverrides[kv] || kv; } function pickBestChildPreset(childPresets, tags) { if (childPresets.size === 0) return {}; if (childPresets.size === 1) { const entry = childPresets.entries().next().value; return entry ? { presetID: entry[0], preset: entry[1] } : {}; } let matchTagsCount = 0; let matchSemicolonRating = 0; let matchPresetPath; let matchPreset; for (const [checkPresetPath, checkPreset] of childPresets) { const checkPresetTags = Object.entries(checkPreset.tags); let currentMatchSemicolonRating = 0; const isPresetMatch = checkPresetTags.every((kv) => { const osmKey = kv[0]; const osmVal = kv[1]; const nsiVal = tags[osmKey]; if (!nsiVal) return false; if (semicolonSplittedKeys.includes(osmKey)) { const vals = nsiVal.split(";"); const findResult = vals.indexOf(osmVal); if (findResult === -1) return false; currentMatchSemicolonRating -= findResult; return true; } return osmVal === nsiVal; }); if (isPresetMatch && (checkPresetTags.length > matchTagsCount || checkPresetTags.length === matchTagsCount && currentMatchSemicolonRating > matchSemicolonRating)) { matchTagsCount = checkPresetTags.length; matchSemicolonRating = currentMatchSemicolonRating; matchPresetPath = checkPresetPath; matchPreset = checkPreset; } } return matchPreset && matchPresetPath ? { presetID: matchPresetPath, preset: matchPreset } : {}; } function pickLogoURL(qid, wikidata) { const logoURLs = wikidata[qid] && wikidata[qid].logos; if (!logoURLs) return; if (logoURLs.wikidata && preferCommons[qid]) return logoURLs.wikidata; if (logoURLs.facebook) return logoURLs.facebook; return logoURLs.wikidata; } function collectTerms(item, primaryName, alternateName, notName) { const terms = new Set(item.matchNames || []); for (const osmkey of Object.keys(item.tags)) { if (osmkey === "name") continue; if (notName.test(osmkey)) continue; if (primaryName.test(osmkey) || alternateName.test(osmkey)) { terms.add(item.tags[osmkey].toLowerCase()); } } return terms; } function buildFields(t, preset, preserveTags) { const fields = preset.originalFields || preset.fields || []; if (!preserveTags.some((s) => s === "^name")) return fields; if (t === "brands") return ["name", "brand", ...fields]; if (t === "operators") return ["name", "operator", ...fields]; return fields; } function buildIDPresets(data, opts) { const sourcePresets = opts.sourcePresets; const wikidata = opts.wikidata || {}; const dissolved = opts.dissolved || {}; const targetPresets = {}; const missing = new Set; const paths = Object.keys(data); let ferryCount = 0; if (data["transit/route/ferry"]) { paths.push("transit/route/ferry"); } for (const tkv of paths.sort(withLocale2)) { const properties = data[tkv].properties || {}; const items = data[tkv].items; if (!Array.isArray(items) || !items.length) continue; const [t, k, v] = tkv.split("/", 3); const tree = trees2[t]; const kv = `${k}/${v}`; const ferryIndex = tkv === "transit/route/ferry" ? ferryCount++ : 0; const presetPath = resolvePresetPath(tkv, k, v, kv, ferryIndex); const wdTag = tree.mainTag; const primaryName = new RegExp(tree.nameTags.primary, "i"); const alternateName = new RegExp(tree.nameTags.alternate, "i"); const notName = /:(colour|type|left|right|etymology|pronunciation|wikipedia|wikidata)$/i; const childPresets = new Map; for (const checkPath in sourcePresets) { if (checkPath.startsWith(presetPath)) { childPresets.set(checkPath, sourcePresets[checkPath]); } } for (const item of items) { const tags = item.tags; const qid = tags[wdTag]; if (!qid || !/^Q\d+$/.test(qid)) continue; let { presetID, preset } = pickBestChildPreset(childPresets, tags); if (!preset) { presetID = k; preset = sourcePresets[presetID]; missing.add(tkv); } if (!preset) continue; const terms = collectTerms(item, primaryName, alternateName, notName); const targetID = `${presetID}/${item.id}`; const targetTags = {}; targetTags[wdTag] = tags[wdTag]; for (const presetKey in preset.tags) { targetTags[presetKey] = tags[presetKey] || preset.tags[presetKey]; } const logoURL = pickLogoURL(qid, wikidata); const preserveTags = item.preserveTags || properties.preserveTags || []; const targetPreset = { name: item.displayName, locationSet: item.locationSet, icon: preset.icon, geometry: preset.geometry, fields: buildFields(t, preset, preserveTags), moreFields: preset.moreFields || [], tags: sortObject(targetTags), matchScore: 2 }; if (logoURL) targetPreset.imageURL = logoURL; if (terms.size) targetPreset.terms = Array.from(terms).sort(withLocale2); if (preset.reference) targetPreset.reference = preset.reference; if (dissolved[item.id]) targetPreset.searchable = false; if (preserveTags.length) targetPreset.preserveTags = preserveTags; targetPreset.tags = sortObject(targetTags); targetPreset.addTags = sortObject(Object.assign({}, item.tags, targetTags)); targetPresets[targetID] = targetPreset; } } return { presets: targetPresets, missing: Array.from(missing).sort(withLocale2) }; } // lib/presets_josm.ts var import_fast_xml_builder = __toESM(require("fast-xml-builder")); var xmlBuilderOptions = { ignoreAttributes: false, suppressEmptyNode: true }; var trees3 = trees_default.trees; var withLocale3 = new Intl.Collator("en-US").compare; function buildJOSMPresets(data, opts) { const dissolved = opts.dissolved || {}; const topGroup = { "@_name": "Name Suggestion Index", group: [] }; const xml = { "?xml": { "@_version": "1.0", "@_encoding": "UTF-8" }, presets: { "@_xmlns": "http://josm.openstreetmap.de/tagging-preset-1.0", "@_author": "Name Suggestion Index", "@_shortdescription": "Name Suggestion Index", "@_description": opts.description, "@_link": "https://github.com/osmlab/name-suggestion-index", "@_version": opts.version, group: topGroup } }; let tPrev, kPrev, vPrev; let tGroup; let kGroup; let vGroup; const paths = Object.keys(data).sort(withLocale3); for (const tkv of paths) { const [t, k, v] = tkv.split("/", 3); const wdTag = trees3[t].mainTag; const items = (data[tkv].items || []).filter((item) => { const qid = item.tags[wdTag]; if (!qid || !/^Q\d+$/.test(qid)) return false; if (dissolved[item.id]) return false; return true; }); if (!items.length) continue; const tChanged = t !== tPrev; const kChanged = tChanged || k !== kPrev; const vChanged = kChanged || v !== vPrev; if (tChanged) { tGroup = { "@_name": t, group: [] }; topGroup.group.push(tGroup); } if (kChanged) { kGroup = { "@_name": k, group: [] }; tGroup.group.push(kGroup); } if (vChanged) { vGroup = { "@_name": v, item: [] }; kGroup.group.push(vGroup); } let presetType; if (t === "flags") { presetType = "node"; } else if (k === "route") { if (v === "ferry") { presetType = "way,closedway,relation"; } else { presetType = "relation"; } } else if (k === "power" && (v === "line" || v === "minor_line")) { presetType = "way,closedway"; } else if (k === "power" && (v === "pole" || v === "tower")) { presetType = "node"; } else { presetType = "node,closedway,multipolygon"; } for (const item of items) { vGroup.item.push({ "@_name": item.displayName, "@_type": presetType, key: Object.entries(item.tags).map(([osmkey, osmvalue]) => ({ "@_key": osmkey, "@_value": osmvalue })) }); } tPrev = t; kPrev = k; vPrev = v; } return { serialize(opts2) { const builder = new import_fast_xml_builder.default({ ...xmlBuilderOptions, format: opts2?.prettyPrint === true }); return builder.build(xml); } }; } //# debugId=B8BCD5248FFF3BA264756E2164756E21 //# sourceMappingURL=nsi.cjs.map