@fusedashlabs/ui9000-workspace
Version:
UI9000 workspace MCP. One tool, show_workspace, draws a chart from your table. Set TYPESAFE_API_KEY to let Jev choose the chart.
295 lines (294 loc) • 13.8 kB
JavaScript
import { GEOJSON_KEYS } from "./geojsonKeys.js";
import { getRegionIdFromFeatureProperties } from "./getRegionIdFromFeatureProperties.js";
import { normalizeDataValue } from "./normalizeDataValue.js";
// Natural Earth admin-0 long forms vs the names datasets actually carry.
// Used to index extra "Country,Unit" composites for admin-1 joins.
const COUNTRY_SYNONYMS = {
"United States of America": ["United States", "USA", "US"],
"United Republic of Tanzania": ["Tanzania"],
"Hong Kong S.A.R.": ["Hong Kong"],
"Ivory Coast": ["Côte d'Ivoire", "Cote d'Ivoire"],
"Cape Verde": ["Cabo Verde"],
"Republic of the Congo": ["Congo", "Congo-Brazzaville"],
"Democratic Republic of the Congo": ["DR Congo", "DRC", "Congo (Kinshasa)"],
"West Bank": ["Palestine", "State of Palestine"],
"Caribbean Netherlands": ["Netherlands"],
"The Bahamas": ["Bahamas"],
"Republic of Serbia": ["Serbia"],
Macedonia: ["North Macedonia"],
"East Timor": ["Timor-Leste", "Timor Leste"],
"Federated States of Micronesia": ["Micronesia"],
"Guinea Bissau": ["Guinea-Bissau"],
Swaziland: ["Eswatini"],
"Czech Republic": ["Czechia"],
"South Korea": ["Korea, Rep.", "Republic of Korea"],
"North Korea": ["Korea, North", "DPRK"],
Burma: ["Myanmar"],
"United Kingdom": ["UK", "Great Britain"],
};
// Natural Earth `name` is "Czech Rep."; datasets and ISO use "Czechia".
const COUNTRY_LABEL_ALIASES = {
"Czech Rep.": ["Czechia", "Czech Republic"],
"Czech Republic": ["Czechia", "Czech Rep."],
Czechia: ["Czech Republic", "Czech Rep."],
};
function countryLabelAliases(seeds) {
const out = new Set();
for (const seed of seeds) {
if (typeof seed !== "string" || !seed)
continue;
for (const alias of COUNTRY_LABEL_ALIASES[seed] ?? [])
out.add(alias);
}
return [...out];
}
/**
* Creates an index for O(1) lookup instead of O(n) find()
*/
export const createFeaturesIndex = (features, mapType) => {
const byId = new Map();
const byDataValue = new Map();
const byNormalizedValue = new Map();
if (!features?.length || !mapType) {
return { byId, byDataValue, byNormalizedValue };
}
// Within-country duplicate city names make the 2-part "Country,City" key
// ambiguous; an arbitrary winner silently colors the wrong unit, so those
// keys are dropped after indexing (the 3-part form stays authoritative).
const ambiguousTwoPartKeys = new Set();
const ambiguousBare = new Set();
const ambiguousBareNorm = new Set();
features.forEach((feature) => {
const regionId = getRegionIdFromFeatureProperties(feature.properties, mapType);
if (!regionId)
return;
const regionIdStr = String(regionId);
byId.set(regionIdStr, feature);
const dataValue = getDataValueFromFeature(feature, mapType);
if (dataValue) {
const normalized = normalizeDataValue(dataValue, mapType);
byDataValue.set(dataValue.toLowerCase(), regionIdStr);
byNormalizedValue.set(normalized, regionIdStr);
// For city mapType, also create 2-part format (country,city) for backward compatibility
if (mapType === "city" && feature.properties) {
const country = feature.properties.COUNTRY;
const state = feature.properties.NAME_1; // State/Region name
const cityName = feature.properties.NAME_2; // City name
// Add index entry for country,cityName format
if (country && cityName) {
const twoPartFormat = `${country},${cityName}`;
const normalizedTwoPart = normalizeDataValue(twoPartFormat, mapType);
const existing = byNormalizedValue.get(normalizedTwoPart);
if (existing && existing !== regionIdStr) {
ambiguousTwoPartKeys.add(normalizedTwoPart);
}
else {
byNormalizedValue.set(normalizedTwoPart, regionIdStr);
}
}
// Also add index entry for country,stateName format (for cases like
// "Romania, Bucharest"). Never let this backfill overwrite a real
// "Country,City" key — that was silently re-pointing every capital
// that shares its state's name (Amman, Dakar, Banjul, ...) at a
// sibling city in the same state.
if (country && state && state !== cityName) {
const stateFormat = `${country},${state}`;
const normalizedState = normalizeDataValue(stateFormat, mapType);
if (!byNormalizedValue.has(normalizedState)) {
byNormalizedValue.set(normalizedState, regionIdStr);
}
}
}
// County rows often arrive as "Country,County" with no state part —
// e.g. the Romanian counties in the boundary set carry an empty NAME_1,
// so the 3-part composite can't represent them and they would only be
// reachable by bare name. Index the 2-part form too; within-country
// duplicate county names (US has hundreds) make it ambiguous, so those
// keys are dropped the same way the city block does.
if (mapType === "county" && feature.properties) {
const country = feature.properties.COUNTRY;
const countyName = feature.properties.NAME_2;
if (country && countyName) {
const countryForms = [country, ...(COUNTRY_SYNONYMS[country] ?? [])];
for (const countryForm of countryForms) {
const twoPartFormat = `${countryForm},${countyName}`;
const normalizedTwoPart = normalizeDataValue(twoPartFormat, mapType);
const existing = byNormalizedValue.get(normalizedTwoPart);
if (existing && existing !== regionIdStr) {
ambiguousTwoPartKeys.add(normalizedTwoPart);
}
else {
byNormalizedValue.set(normalizedTwoPart, regionIdStr);
}
}
}
}
// Region rows often carry a "Country,Region" composite; index it so a
// correctly-prefixed value disambiguates same-named admin-1 units.
if ((mapType === "region" || mapType === "province") &&
feature.properties) {
const country = feature.properties.admin;
const regionName = feature.properties.name;
const unitNames = [
regionName,
feature.properties.name_en,
...String(feature.properties.aliases ?? "").split("|"),
].filter((n) => typeof n === "string" && n.length > 0);
const countryForms = country
? [country, ...(COUNTRY_SYNONYMS[country] ?? [])]
: [];
for (const countryForm of countryForms) {
for (const unitName of unitNames) {
const composite = normalizeDataValue(`${countryForm},${unitName}`, mapType);
if (!byNormalizedValue.has(composite)) {
byNormalizedValue.set(composite, regionIdStr);
}
}
}
// English names and curated alias lists (e.g. the Romanian
// historical regions: Moldavia, Transylvania, Transilvanie).
const aliasValues = [
feature.properties.name_en,
...String(feature.properties.aliases ?? "").split("|"),
];
for (const alias of aliasValues) {
if (typeof alias !== "string" || !alias || alias === regionName) {
continue;
}
const aliasLower = alias.toLowerCase();
if (!byDataValue.has(aliasLower)) {
byDataValue.set(aliasLower, regionIdStr);
}
const aliasNorm = normalizeDataValue(alias, mapType);
if (!byNormalizedValue.has(aliasNorm)) {
byNormalizedValue.set(aliasNorm, regionIdStr);
}
}
}
// Generated data often carries the bare unit name without the country
// prefix the state/province composite index keys on.
if ((mapType === "state" || mapType === "province") &&
feature.properties) {
const bareName = feature.properties[GEOJSON_KEYS[mapType].state] ??
feature.properties[GEOJSON_KEYS[mapType].name];
if (typeof bareName === "string" && bareName) {
const bareLower = bareName.toLowerCase();
const bareNorm = normalizeDataValue(bareName, mapType);
rememberUnique(byDataValue, bareLower, regionIdStr, ambiguousBare);
rememberUnique(byNormalizedValue, bareNorm, regionIdStr, ambiguousBareNorm);
}
}
// Natural Earth's primary `name` is the short form ("United States"),
// but datasets often carry the long/formal one ("United States of
// America"), so index those aliases too.
if (mapType === "country" && feature.properties) {
const aliasValues = [
...["name_long", "formal_en", "brk_name", "admin", "geounit"].map((key) => feature.properties?.[key]),
...String(feature.properties.aliases ?? "").split("|"),
...countryLabelAliases([
dataValue,
feature.properties.name,
feature.properties.admin,
feature.properties.name_long,
feature.properties.brk_name,
]),
...countrySynonyms([
feature.properties.name,
feature.properties.admin,
feature.properties.name_long,
]),
feature.properties.iso_a2,
feature.properties.iso_a3,
feature.properties.postal,
feature.properties.abbrev,
];
for (const alias of aliasValues) {
if (typeof alias === "string" && alias && alias !== dataValue) {
const aliasLower = alias.toLowerCase();
if (!byDataValue.has(aliasLower)) {
byDataValue.set(aliasLower, regionIdStr);
}
const aliasNormalized = normalizeDataValue(alias, mapType);
if (!byNormalizedValue.has(aliasNormalized)) {
byNormalizedValue.set(aliasNormalized, regionIdStr);
}
}
}
}
}
});
for (const key of ambiguousTwoPartKeys) {
byNormalizedValue.delete(key);
}
for (const key of ambiguousBare)
byDataValue.delete(key);
for (const key of ambiguousBareNorm)
byNormalizedValue.delete(key);
return { byId, byDataValue, byNormalizedValue };
};
function rememberUnique(map, key, regionId, ambiguous) {
if (!key || ambiguous.has(key))
return;
const prev = map.get(key);
if (prev === undefined) {
map.set(key, regionId);
return;
}
if (prev !== regionId)
ambiguous.add(key);
}
function countrySynonyms(names) {
const out = [];
for (const name of names) {
if (typeof name !== "string")
continue;
const extras = COUNTRY_SYNONYMS[name];
if (extras)
out.push(...extras);
}
return out;
}
const getDataValueFromFeature = (feature, mapType) => {
const props = feature.properties;
if (!props)
return undefined;
const keys = GEOJSON_KEYS[mapType];
if (!keys)
return undefined;
const getProp = (key) => props[keys[key]];
const buildComposite = (parts, fallbackKey) => {
const values = parts.map(getProp);
const allPresent = values.every((v) => v != null && v !== "");
return allPresent ? values.join(",") : getProp(fallbackKey);
};
const buildCityComposite = () => {
const country = getProp("country");
const state = getProp("state");
const name = getProp("name");
// Try full 3-part format: country, state, city
if (country && state && name) {
return `${country},${state},${name}`;
}
// Try 2-part format: country, city (when state is missing)
if (country && name) {
return `${country},${name}`;
}
// Fallback to just city name
return name;
};
switch (mapType) {
case "city":
return buildCityComposite();
case "county":
return buildComposite(["country", "state", "county"], "county");
case "state":
case "province":
// global admin-1: "Country,Province" composite — same pattern as US state
return buildComposite(["country", "state"], "state");
case "country":
case "region":
return getProp("name");
default:
return undefined;
}
};