name-suggestion-index
Version:
Canonical common brand names for OpenStreetMap
1,070 lines (1,062 loc) • 33.1 kB
JavaScript
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