spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1/**2 * Offline city-level geocoding for projects (and facilities without coordinates): a metro seed (name / alias) or the3 * curated city table gives CITY-level coordinates, never more precise. Nothing is guessed: unknown city → null.4 */5import { normalizeName, type GeoPoint } from "@dci/core";6import { CITIES } from "../connectors/cloud/city-coords.js";7import type { Tx } from "./common.js";8import { loadMetros } from "./metros.js";910const cityIndex = new Map<string, { lat: number; lng: number; iso: string; precision: GeoPoint["precision"]; name: string }>();11for (const c of CITIES) {12 for (const a of [c.city, ...(c.aliases ?? [])]) {13 const k = `${c.iso}|${normalizeName(a)}`;14 if (!cityIndex.has(k)) cityIndex.set(k, { lat: c.lat, lng: c.lng, iso: c.iso, precision: c.precision ?? "city", name: c.city });15 }16}1718export interface GeocodeInput { city?: string | null; regionName?: string | null; countryIso2?: string | null }1920/** City-level point for a place name, or null. `source` records how it was derived so provenance stays honest. */21export async function geocodeCity(tx: Tx, i: GeocodeInput): Promise<(GeoPoint & { metroId?: string | null; label: string }) | null> {22 const city = i.city?.trim();23 if (!city) return null;24 const norm = normalizeName(city);25 if (!norm) return null;26 const metros = await loadMetros(tx);27 const metroHit = metros.find((m) => (!i.countryIso2 || m.countryIso2 === i.countryIso2) && m.normalizedAliases.includes(norm));28 if (metroHit) return { lat: metroHit.lat, lng: metroHit.lng, precision: "metro", source: `geocoder:metro:${metroHit.slug}`, metroId: metroHit.id, label: metroHit.name };29 if (i.countryIso2) {30 const hit = cityIndex.get(`${i.countryIso2}|${norm}`);31 if (hit) return { lat: hit.lat, lng: hit.lng, precision: hit.precision === "exact" ? "city" : hit.precision, source: "geocoder:city-table", label: hit.name };32 } else {33 const hits = [...cityIndex.entries()].filter(([k]) => k.endsWith(`|${norm}`));34 if (hits.length === 1) { const h = hits[0]![1]; return { lat: h.lat, lng: h.lng, precision: h.precision === "exact" ? "city" : h.precision, source: "geocoder:city-table", label: h.name }; }35 }36 // a county / parish in the lexicon may be a metro alias without the suffix37 const stripped = normalizeName(city.replace(/\b(county|parish|township|borough)\b/gi, ""));38 if (stripped && stripped !== norm) {39 const m2 = metros.find((m) => (!i.countryIso2 || m.countryIso2 === i.countryIso2) && m.normalizedAliases.includes(stripped));40 if (m2) return { lat: m2.lat, lng: m2.lng, precision: "approximate", source: `geocoder:metro:${m2.slug}`, metroId: m2.id, label: m2.name };41 }42 return null;43}44