import type { GeoPrecision } from "@dci/core"; /** * Curated city → coordinates table for the cloud / hyperscaler connectors. * * Coordinates are CITY-level markers (town centre, ±a few km), never facility locations. Entries with * `precision: "metro"` or `"approximate"` are wider areas (a metro, a county, a state region) that a * provider names instead of a city; they are flagged as such so the UI never claims exactness. * Providers only ever publish the city/area, so nothing here can be more precise than the city. */ export interface CityEntry { /** canonical display name */ city: string; iso: string; lat: number; lng: number; /** state / province / region (display) */ region?: string; precision?: GeoPrecision; aliases?: string[]; } const C = (city: string, iso: string, lat: number, lng: number, region?: string, aliases?: string[], precision?: GeoPrecision): CityEntry => ({ city, iso, lat, lng, region, aliases, precision }); export const CITIES: CityEntry[] = [ // ---- North America (US) ---- C("Ashburn", "US", 39.04, -77.49, "Virginia"), C("Northern Virginia", "US", 39.04, -77.49, "Virginia", ["N. Virginia", "Virginia"], "metro"), C("Loudoun County", "US", 39.09, -77.64, "Virginia", ["Loundoun County"], "approximate"), C("Prince William County", "US", 38.7, -77.48, "Virginia", undefined, "approximate"), C("Chesterfield County", "US", 37.38, -77.59, "Virginia", undefined, "approximate"), C("Henrico", "US", 37.55, -77.4, "Virginia", ["Henrico County"], "approximate"), C("Boydton", "US", 36.67, -78.39, "Virginia"), C("Columbus", "US", 39.96, -83.0, "Ohio"), C("New Albany", "US", 40.08, -82.81, "Ohio"), C("Lancaster", "US", 39.71, -82.6, "Ohio"), C("Bowling Green", "US", 41.37, -83.65, "Ohio"), C("Chicago", "US", 41.88, -87.63, "Illinois"), C("DeKalb", "US", 41.93, -88.75, "Illinois"), C("Dallas", "US", 32.78, -96.8, "Texas"), C("Fort Worth", "US", 32.75, -97.33, "Texas"), C("Houston", "US", 29.76, -95.37, "Texas"), C("San Antonio", "US", 29.42, -98.49, "Texas"), C("El Paso", "US", 31.76, -106.49, "Texas"), C("Temple", "US", 31.1, -97.34, "Texas"), C("Midlothian", "US", 32.48, -97.0, "Texas"), C("Red Oak", "US", 32.52, -96.8, "Texas"), C("Pampa", "US", 35.54, -100.96, "Texas"), C("Armstrong County", "US", 34.97, -101.36, "Texas", undefined, "approximate"), C("Haskell County", "US", 33.18, -99.73, "Texas", undefined, "approximate"), C("Wilbarger County", "US", 34.08, -99.24, "Texas", undefined, "approximate"), C("Phoenix", "US", 33.45, -112.07, "Arizona"), C("Mesa", "US", 33.42, -111.83, "Arizona"), C("Goodyear", "US", 33.44, -112.36, "Arizona"), C("Los Angeles", "US", 34.05, -118.24, "California"), C("San Jose", "US", 37.34, -121.89, "California"), C("Silicon Valley", "US", 37.39, -121.96, "California", undefined, "metro"), C("Santa Clara", "US", 37.35, -121.96, "California"), C("Las Vegas", "US", 36.17, -115.14, "Nevada"), C("Henderson", "US", 36.04, -114.98, "Nevada"), C("Storey County", "US", 39.45, -119.53, "Nevada", undefined, "approximate"), C("Salt Lake City", "US", 40.76, -111.89, "Utah"), C("Eagle Mountain", "US", 40.31, -112.01, "Utah"), C("Denver", "US", 39.74, -104.99, "Colorado"), C("Seattle", "US", 47.61, -122.33, "Washington"), C("Quincy", "US", 47.23, -119.85, "Washington"), C("Portland", "US", 45.52, -122.68, "Oregon"), C("The Dalles", "US", 45.59, -121.18, "Oregon"), C("Prineville", "US", 44.3, -120.83, "Oregon"), C("Boardman", "US", 45.84, -119.7, "Oregon"), C("Honolulu", "US", 21.31, -157.86, "Hawaii"), C("Minneapolis", "US", 44.98, -93.27, "Minnesota"), C("Rosemount", "US", 44.74, -93.13, "Minnesota"), C("Hermantown", "US", 46.81, -92.24, "Minnesota"), C("Pine Island", "US", 44.2, -92.65, "Minnesota"), C("Kansas City", "US", 39.1, -94.58, "Missouri"), C("New Florence", "US", 38.91, -91.45, "Missouri"), C("Council Bluffs", "US", 41.26, -95.86, "Iowa"), C("Cedar Rapids", "US", 41.98, -91.67, "Iowa"), C("Altoona", "US", 41.64, -93.47, "Iowa"), C("Des Moines", "US", 41.59, -93.62, "Iowa"), C("West Des Moines", "US", 41.58, -93.71, "Iowa"), C("Omaha", "US", 41.26, -95.94, "Nebraska"), C("Papillion", "US", 41.15, -96.04, "Nebraska"), C("Sarpy", "US", 41.11, -96.11, "Nebraska", ["Sarpy County"], "approximate"), C("Lincoln", "US", 40.81, -96.68, "Nebraska"), C("Cheyenne", "US", 41.14, -104.82, "Wyoming"), C("Atlanta", "US", 33.75, -84.39, "Georgia"), C("Douglas County", "US", 33.7, -84.77, "Georgia", undefined, "approximate"), C("Columbia County", "US", 33.55, -82.26, "Georgia", undefined, "approximate"), C("LaGrange", "US", 33.04, -85.03, "Georgia"), C("Stanton Springs", "US", 33.6, -83.72, "Georgia", undefined, "approximate"), C("Miami", "US", 25.76, -80.19, "Florida"), C("Boston", "US", 42.36, -71.06, "Massachusetts"), C("Washington, D.C.", "US", 38.91, -77.04, "District of Columbia", ["Washington DC", "Washington D.C.", "Washington, DC"]), C("New York City", "US", 40.71, -74.01, "New York", ["New York"]), C("Philadelphia", "US", 39.95, -75.17, "Pennsylvania"), C("Moncks Corner", "US", 33.2, -80.01, "South Carolina"), C("Berkeley County", "US", 33.2, -79.95, "South Carolina", undefined, "approximate"), C("Dorchester County", "US", 33.17, -80.54, "South Carolina", undefined, "approximate"), C("The Lowcountry", "US", 33.16, -80.01, "South Carolina", undefined, "approximate"), C("Aiken", "US", 33.56, -81.72, "South Carolina"), C("Lenoir", "US", 35.91, -81.54, "North Carolina"), C("Forest City", "US", 35.33, -81.87, "North Carolina"), C("Huntsville", "US", 34.73, -86.59, "Alabama"), C("Montgomery", "US", 32.38, -86.31, "Alabama"), C("Jackson County", "US", 34.78, -85.99, "Alabama", undefined, "approximate"), C("Gallatin", "US", 36.39, -86.45, "Tennessee"), C("Montgomery County", "US", 36.5, -87.38, "Tennessee", undefined, "approximate"), C("Fort Wayne", "US", 41.08, -85.14, "Indiana"), C("Michigan City", "US", 41.71, -86.9, "Indiana"), C("Morgan County", "US", 39.48, -86.45, "Indiana", undefined, "approximate"), C("Jeffersonville", "US", 38.28, -85.74, "Indiana"), C("Lebanon", "US", 40.05, -86.47, "Indiana"), C("Van Buren Township", "US", 42.22, -83.5, "Michigan", undefined, "approximate"), C("Tulsa", "US", 36.15, -95.99, "Oklahoma"), C("Stillwater", "US", 36.12, -97.06, "Oklahoma"), C("Mayes County", "US", 36.3, -95.23, "Oklahoma", ["Pryor"], "approximate"), C("Muskogee County", "US", 35.62, -95.37, "Oklahoma", undefined, "approximate"), C("Richland Parish", "US", 32.42, -91.76, "Louisiana", undefined, "approximate"), C("West Memphis", "US", 35.15, -90.18, "Arkansas"), C("Kuna", "US", 43.49, -116.42, "Idaho"), C("Los Lunas", "US", 34.81, -106.73, "New Mexico"), C("Beaver Dam", "US", 43.46, -88.84, "Wisconsin"), C("Mount Pleasant", "US", 42.72, -87.89, "Wisconsin"), // ---- Canada ---- C("Toronto", "CA", 43.65, -79.38, "Ontario"), C("Montréal", "CA", 45.5, -73.57, "Québec", ["Montreal"]), C("Québec City", "CA", 46.81, -71.21, "Québec", ["Quebec City", "Quebec"]), C("Calgary", "CA", 51.05, -114.07, "Alberta"), C("Sturgeon County", "CA", 53.78, -113.55, "Alberta", undefined, "approximate"), C("Vancouver", "CA", 49.28, -123.12, "British Columbia"), // ---- Latin America ---- C("Querétaro", "MX", 20.59, -100.39, "Querétaro", ["Queretaro", "Querétaro State"]), C("Monterrey", "MX", 25.69, -100.32, "Nuevo León"), C("São Paulo", "BR", -23.55, -46.63, "São Paulo", ["Sao Paulo", "Sau Paulo", "São Paulo State"]), C("Osasco", "BR", -23.53, -46.79, "São Paulo"), C("Vinhedo", "BR", -23.03, -46.98, "São Paulo"), C("Rio de Janeiro", "BR", -22.91, -43.17, "Rio de Janeiro"), C("Santiago", "CL", -33.45, -70.67, "Santiago Metropolitan"), C("Quilicura", "CL", -33.36, -70.73, "Santiago Metropolitan"), C("Valparaíso", "CL", -33.05, -71.62, "Valparaíso", ["Valparaiso"]), C("Bogotá", "CO", 4.71, -74.07, "Bogotá", ["Bogota"]), C("Buenos Aires", "AR", -34.6, -58.38), C("Lima", "PE", -12.05, -77.04), C("Canelones", "UY", -34.52, -56.28, "Canelones"), // ---- Europe ---- C("Dublin", "IE", 53.35, -6.26, "Leinster"), C("Clonee", "IE", 53.41, -6.44, "Meath"), C("London", "GB", 51.51, -0.13, "England"), C("Cardiff", "GB", 51.48, -3.18, "Wales"), C("Newport", "GB", 51.58, -2.99, "Wales"), C("Waltham Cross", "GB", 51.69, -0.03, "England"), C("Amsterdam", "NL", 52.37, 4.9, "North Holland"), C("Eemshaven", "NL", 53.45, 6.83, "Groningen"), C("Groningen", "NL", 53.22, 6.57, "Groningen"), C("Middenmeer", "NL", 52.81, 4.99, "North Holland"), C("Winschoten", "NL", 53.14, 7.03, "Groningen"), C("Brussels", "BE", 50.85, 4.35, "Brussels-Capital"), C("St. Ghislain", "BE", 50.45, 3.82, "Hainaut", ["Saint-Ghislain", "St Ghislain"]), C("Farciennes", "BE", 50.43, 4.54, "Hainaut"), C("Paris", "FR", 48.86, 2.35, "Île-de-France"), C("Marseille", "FR", 43.3, 5.37, "Provence-Alpes-Côte d'Azur"), C("Frankfurt", "DE", 50.11, 8.68, "Hesse", ["Frankfurt am Main"]), C("Hanau", "DE", 50.13, 8.92, "Hesse"), C("Dietzenbach", "DE", 50.02, 8.79, "Hesse"), C("Berlin", "DE", 52.52, 13.41, "Berlin"), C("Hamburg", "DE", 53.55, 9.99, "Hamburg"), C("Magdeburg", "DE", 52.13, 11.63, "Saxony-Anhalt"), C("Zurich", "CH", 47.38, 8.54, "Zürich", ["Zürich"]), C("Geneva", "CH", 46.2, 6.14, "Geneva"), C("Vienna", "AT", 48.21, 16.37, "Vienna"), C("Kronstorf", "AT", 48.14, 14.46, "Upper Austria"), C("Milan", "IT", 45.46, 9.19, "Lombardy"), C("Turin", "IT", 45.07, 7.69, "Piedmont"), C("Madrid", "ES", 40.42, -3.7, "Community of Madrid"), C("Aragón", "ES", 41.65, -0.89, "Aragón", ["Aragon", "Zaragoza"], "metro"), C("Lisbon", "PT", 38.72, -9.14), C("Stockholm", "SE", 59.33, 18.07, "Stockholm"), C("Gävle", "SE", 60.67, 17.14, "Gävleborg", ["Gävle and Sandviken", "Gavle"]), C("Luleå", "SE", 65.58, 22.16, "Norrbotten", ["Lulea"]), C("Horndal", "SE", 60.3, 16.42, "Dalarna"), C("Oslo", "NO", 59.91, 10.75, "Oslo"), C("Skien", "NO", 59.21, 9.61, "Telemark"), C("Copenhagen", "DK", 55.68, 12.57, "Capital Region"), C("Odense", "DK", 55.4, 10.39, "Southern Denmark"), C("Fredericia", "DK", 55.57, 9.75, "Southern Denmark"), C("Helsinki", "FI", 60.17, 24.94, "Uusimaa"), C("Hamina", "FI", 60.57, 27.2, "Kymenlaakso"), C("Muhos", "FI", 64.81, 25.99, "North Ostrobothnia"), C("Vaala", "FI", 64.56, 26.84, "Kainuu"), C("Warsaw", "PL", 52.23, 21.01, "Masovian"), C("Athens", "GR", 37.98, 23.73, "Attica"), C("Istanbul", "TR", 41.01, 28.98), C("Jovanovac", "RS", 44.06, 20.95, "Šumadija", ["Kragujevac"], "approximate"), // ---- Middle East & Africa ---- C("Tel Aviv", "IL", 32.09, 34.78), C("Jerusalem", "IL", 31.77, 35.21), C("Dubai", "AE", 25.2, 55.27, "Dubai"), C("Abu Dhabi", "AE", 24.45, 54.38, "Abu Dhabi"), C("Doha", "QA", 25.29, 51.53), C("Riyadh", "SA", 24.71, 46.68, "Riyadh"), C("Jeddah", "SA", 21.49, 39.19, "Makkah"), C("Dammam", "SA", 26.43, 50.1, "Eastern Province"), C("Manama", "BH", 26.23, 50.59), C("Muscat", "OM", 23.59, 58.41), C("Johannesburg", "ZA", -26.2, 28.05, "Gauteng"), C("Cape Town", "ZA", -33.92, 18.42, "Western Cape"), C("Casablanca", "MA", 33.57, -7.59), C("Lagos", "NG", 6.52, 3.38, "Lagos"), C("Nairobi", "KE", -1.29, 36.82), // ---- Asia Pacific ---- C("Tokyo", "JP", 35.68, 139.69, "Tokyo", ["Tokyo, Saitama", "Saitama"]), C("Osaka", "JP", 34.69, 135.5, "Osaka"), C("Inzai", "JP", 35.83, 140.15, "Chiba"), C("Seoul", "KR", 37.57, 126.98, "Seoul"), C("Busan", "KR", 35.18, 129.08, "Busan"), C("Chuncheon", "KR", 37.88, 127.73, "Gangwon"), C("Hong Kong", "HK", 22.32, 114.17), C("Taipei", "TW", 25.03, 121.57, "Taipei"), C("Changhua County", "TW", 24.08, 120.54, "Changhua", ["Changhua"], "approximate"), C("Singapore", "SG", 1.35, 103.82), C("Jurong West", "SG", 1.34, 103.71), C("Kuala Lumpur", "MY", 3.14, 101.69, "Federal Territory"), C("Johor Bahru", "MY", 1.49, 103.74, "Johor", ["Johorbahru", "Johor"]), C("Kulai", "MY", 1.66, 103.6, "Johor"), C("Selangor", "MY", 3.07, 101.52, "Selangor", undefined, "approximate"), C("Jakarta", "ID", -6.21, 106.85, "Jakarta"), C("Batam", "ID", 1.13, 104.05, "Riau Islands"), C("Bangkok", "TH", 13.76, 100.5, "Bangkok"), C("Chonburi", "TH", 13.36, 100.98, "Chonburi"), C("Manila", "PH", 14.6, 120.98, "Metro Manila"), C("Hanoi", "VN", 21.03, 105.85), C("Mumbai", "IN", 19.08, 72.88, "Maharashtra"), C("Pune", "IN", 18.52, 73.86, "Maharashtra"), C("Hyderabad", "IN", 17.39, 78.49, "Telangana"), C("Chennai", "IN", 13.08, 80.27, "Tamil Nadu"), C("Delhi", "IN", 28.61, 77.21, "Delhi", ["New Delhi"]), C("Kolkata", "IN", 22.57, 88.36, "West Bengal"), C("Andhra Pradesh", "IN", 15.91, 79.74, "Andhra Pradesh", ["Visakhapatnam"], "approximate"), C("Sydney", "AU", -33.87, 151.21, "New South Wales", ["New South Wales"]), C("Melbourne", "AU", -37.81, 144.96, "Victoria", ["Victoria"]), C("Canberra", "AU", -35.28, 149.13, "Australian Capital Territory"), C("Perth", "AU", -31.95, 115.86, "Western Australia"), C("Auckland", "NZ", -36.85, 174.76, "Auckland"), // ---- China ---- C("Beijing", "CN", 39.9, 116.41, "Beijing"), C("Shanghai", "CN", 31.23, 121.47, "Shanghai"), C("Shenzhen", "CN", 22.54, 114.06, "Guangdong"), C("Guangzhou", "CN", 23.13, 113.26, "Guangdong"), C("Heyuan", "CN", 23.74, 114.7, "Guangdong"), C("Hangzhou", "CN", 30.27, 120.15, "Zhejiang"), C("Nanjing", "CN", 32.06, 118.8, "Jiangsu"), C("Qingdao", "CN", 36.07, 120.38, "Shandong"), C("Zhangjiakou", "CN", 40.77, 114.89, "Hebei"), C("Hohhot", "CN", 40.84, 111.75, "Inner Mongolia"), C("Ulanqab", "CN", 41.02, 113.13, "Inner Mongolia"), C("Chengdu", "CN", 30.57, 104.07, "Sichuan"), C("Chongqing", "CN", 29.56, 106.55, "Chongqing"), C("Zhongwei", "CN", 37.5, 105.19, "Ningxia"), C("Wuhan", "CN", 30.59, 114.31, "Hubei"), C("Fuzhou", "CN", 26.07, 119.3, "Fujian"), C("Ningxia", "CN", 37.5, 105.19, "Ningxia", undefined, "approximate"), C("Macao", "MO", 22.2, 113.54, undefined, ["Macau"]), ]; /** US state name → ISO 3166-2 style code (used for regionName and to resolve countryIso2 = US). */ export const US_STATES: Record = { alabama: "AL", alaska: "AK", arizona: "AZ", arkansas: "AR", california: "CA", colorado: "CO", connecticut: "CT", delaware: "DE", florida: "FL", georgia: "GA", hawaii: "HI", idaho: "ID", illinois: "IL", indiana: "IN", iowa: "IA", kansas: "KS", kentucky: "KY", louisiana: "LA", maine: "ME", maryland: "MD", massachusetts: "MA", michigan: "MI", minnesota: "MN", mississippi: "MS", missouri: "MO", montana: "MT", nebraska: "NE", nevada: "NV", "new hampshire": "NH", "new jersey": "NJ", "new mexico": "NM", "new york": "NY", "north carolina": "NC", "north dakota": "ND", ohio: "OH", oklahoma: "OK", oregon: "OR", pennsylvania: "PA", "rhode island": "RI", "south carolina": "SC", "south dakota": "SD", tennessee: "TN", texas: "TX", utah: "UT", vermont: "VT", virginia: "VA", washington: "WA", "west virginia": "WV", wisconsin: "WI", wyoming: "WY", "district of columbia": "DC", "washington dc": "DC", "washington, d.c.": "DC", }; export const CA_PROVINCES: Record = { ontario: "ON", québec: "QC", quebec: "QC", alberta: "AB", "british columbia": "BC", manitoba: "MB", saskatchewan: "SK", "nova scotia": "NS", "new brunswick": "NB", "newfoundland and labrador": "NL", "prince edward island": "PE", }; export function normKey(s: string): string { return s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim(); } const INDEX = new Map(); for (const e of CITIES) { for (const n of [e.city, ...(e.aliases ?? [])]) { const k = normKey(n); const withIso = `${k}|${e.iso}`; if (!INDEX.has(withIso)) INDEX.set(withIso, e); if (!INDEX.has(k)) INDEX.set(k, e); } } /** Look a city up by name (accent/case-insensitive), optionally constrained to a country. */ export function lookupCity(name: string | null | undefined, iso?: string | null): CityEntry | null { if (!name) return null; const k = normKey(name); if (!k) return null; if (iso) return INDEX.get(`${k}|${iso.toUpperCase()}`) ?? null; return INDEX.get(k) ?? null; } /** "Virginia" → "US", "Ontario" → "CA", else null. */ export function countryFromStateName(name: string | null | undefined): string | null { if (!name) return null; const k = name.trim().toLowerCase(); if (US_STATES[k]) return "US"; if (CA_PROVINCES[k]) return "CA"; return null; } /** Title-case a state name key from US_STATES / CA_PROVINCES for display. */ export function stateDisplayName(name: string): string { return name.trim().replace(/\s+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).replace(/\bDc\b/, "DC").replace(/\bOf\b/, "of").replace(/\bAnd\b/, "and"); }