gadm-ts/dist/names.js
2026-03-23 17:35:02 +01:00

170 lines
15 KiB
JavaScript

/**
* Names lookup — port of Python pygadm.Names class.
*
* Searches the GADM parquet database for admin areas by name or GID code.
* Returns matching rows with NAME_{level} and GID_{level} columns.
*/
import { loadDatabase } from './database.js';
// ---------- helpers ----------
/** Simple Levenshtein distance for fuzzy matching */
function levenshtein(a, b) {
const m = a.length, n = b.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
for (let i = 0; i <= m; i++)
dp[i][0] = i;
for (let j = 0; j <= n; j++)
dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
dp[i][j] = a[i - 1] === b[j - 1]
? dp[i - 1][j - 1]
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
}
}
return dp[m][n];
}
/** Find the N closest matches to `query` from a list of candidates */
function closestMatches(query, candidates, n = 5) {
const q = query.toLowerCase();
const scored = candidates.map(c => ({ val: c, dist: levenshtein(q, c.toLowerCase()) }));
scored.sort((a, b) => a.dist - b.dist);
return scored.slice(0, n).map(s => s.val);
}
// ---------- main function ----------
/**
* Look up administrative area names/codes in the GADM database.
*
* Port of Python `pygadm.Names(name, admin, content_level, complete)`.
*/
export async function getNames(opts = {}) {
const { name, admin, contentLevel: rawContentLevel = -1, complete = false } = opts;
if (name && admin) {
throw new Error('"name" and "admin" cannot be set at the same time.');
}
const db = await loadDatabase();
let contentLevel = rawContentLevel;
if (name || admin) {
const isName = !!name;
const id = (name || admin);
const colPrefix = isName ? 'NAME_' : 'GID_';
// Search all 6 levels for the id
const matchedLevel = findLevel(db, colPrefix, id);
if (matchedLevel === -1) {
// Not found — suggest closest matches
const allIds = collectAllIds(db, colPrefix);
const close = closestMatches(id, allIds, 5);
const formatted = isName
? close.map(c => capitalize(c))
: close.map(c => c.toUpperCase());
throw new Error(`The requested "${id}" is not part of GADM. ` +
`The closest matches are: ${formatted.join(', ')}.`);
}
const level = matchedLevel;
// Filter to rows matching this id at the found level
const col = `${colPrefix}${level}`;
let subDf = db.filter(r => caseEqual(r[col], id));
// Find max available level in this subset
const maxLevel = findMaxLevel(subDf);
// Resolve content level
if (contentLevel === -1) {
contentLevel = level;
}
else if (contentLevel < level) {
// Requested level higher than area — fallback
contentLevel = level;
}
if (contentLevel > maxLevel) {
contentLevel = maxLevel;
}
// Deduplicate at content level
const nameCol = `NAME_${contentLevel}`;
const gidCol = `GID_${contentLevel}`;
const seen = new Set();
subDf = subDf.filter(r => {
if (!r[nameCol])
return false; // skip empty
const key = `${r[nameCol]}|${r[gidCol]}`;
if (seen.has(key))
return false;
seen.add(key);
return true;
});
// Select columns
const columns = complete
? ['NAME_0', 'GID_0', 'NAME_1', 'GID_1', 'NAME_2', 'GID_2', 'NAME_3', 'GID_3', 'NAME_4', 'GID_4', 'NAME_5', 'GID_5']
.filter((_, i) => Math.floor(i / 2) <= contentLevel)
: [nameCol, gidCol];
const rows = complete ? subDf : subDf.map(r => {
const out = {};
for (const c of columns)
out[c] = r[c] ?? '';
return out;
});
return { rows, level: contentLevel, columns };
}
// No name/admin — return world-level data
contentLevel = contentLevel === -1 ? 0 : contentLevel;
const nameCol = `NAME_${contentLevel}`;
const gidCol = `GID_${contentLevel}`;
const seen = new Set();
const rows = db.filter(r => {
if (!r[nameCol])
return false;
const key = `${r[nameCol]}|${r[gidCol]}`;
if (seen.has(key))
return false;
seen.add(key);
return true;
});
const columns = [nameCol, gidCol];
const filtered = rows.map(r => {
const out = {};
for (const c of columns)
out[c] = r[c] ?? '';
return out;
});
return { rows: filtered, level: contentLevel, columns };
}
// ---------- internal helpers ----------
function caseEqual(a, b) {
if (!a)
return false;
return a.toLowerCase() === b.toLowerCase();
}
function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
/** Find which level (0-5) contains the given id */
function findLevel(db, colPrefix, id) {
const idLower = id.toLowerCase();
for (let level = 0; level < 6; level++) {
const col = `${colPrefix}${level}`;
if (db.some(r => r[col]?.toLowerCase() === idLower)) {
return level;
}
}
return -1;
}
/** Collect all unique ids across all levels for fuzzy matching */
function collectAllIds(db, colPrefix) {
const ids = new Set();
for (let level = 0; level < 6; level++) {
const col = `${colPrefix}${level}`;
for (const r of db) {
if (r[col])
ids.add(r[col].toLowerCase());
}
}
return [...ids];
}
/** Find the maximum level that has non-empty data */
function findMaxLevel(rows) {
for (let level = 5; level >= 0; level--) {
const col = `GID_${level}`;
if (rows.some(r => r[col] && r[col] !== '')) {
return level;
}
}
return 0;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmFtZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbmFtZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7O0dBS0c7QUFDSCxPQUFPLEVBQUUsWUFBWSxFQUFnQixNQUFNLGVBQWUsQ0FBQztBQXdCM0QsZ0NBQWdDO0FBRWhDLHFEQUFxRDtBQUNyRCxTQUFTLFdBQVcsQ0FBQyxDQUFTLEVBQUUsQ0FBUztJQUNyQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ2pDLE1BQU0sRUFBRSxHQUFlLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDakYsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUU7UUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzFDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFO1FBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMxQyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDMUIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQzFCLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMs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