feat(frontend): use offline global birth place hierarchy
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const RELEASE = "v3.2-export.7";
|
||||
const SOURCE_URL = `https://github.com/dr5hn/countries-states-cities-database/releases/download/${RELEASE}/json-countries%2Bstates%2Bcities.json.gz`;
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const outputDir = resolve(root, "public/data/world-locations");
|
||||
const indexPath = resolve(root, "src/data/world-countries.json");
|
||||
|
||||
async function loadSource() {
|
||||
const localPath = process.env.GLOBAL_LOCATION_SOURCE_FILE;
|
||||
if (localPath) return JSON.parse(await readFile(localPath, "utf8"));
|
||||
const response = await fetch(SOURCE_URL, { headers: { "user-agent": "Jyotisha location-data refresh" } });
|
||||
if (!response.ok) throw new Error(`Unable to download global location data: ${response.status} ${response.statusText}`);
|
||||
return JSON.parse(gunzipSync(Buffer.from(await response.arrayBuffer())).toString("utf8"));
|
||||
}
|
||||
|
||||
function coordinate(value, label) {
|
||||
const result = Number(value);
|
||||
if (!Number.isFinite(result)) throw new Error(`Missing valid ${label}: ${value}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
function regionCode(state) {
|
||||
return String(state.iso3166_2 || state.iso2 || state.id);
|
||||
}
|
||||
|
||||
function compactCity(city) {
|
||||
return {
|
||||
code: String(city.id),
|
||||
name: city.name,
|
||||
nativeName: typeof city.native === "string" && city.native.trim() ? city.native : undefined,
|
||||
latitude: coordinate(city.latitude, `latitude for ${city.name}`),
|
||||
longitude: coordinate(city.longitude, `longitude for ${city.name}`),
|
||||
timezone: city.timezone,
|
||||
};
|
||||
}
|
||||
|
||||
function compactRegion(state) {
|
||||
return {
|
||||
code: regionCode(state),
|
||||
name: state.name,
|
||||
nativeName: typeof state.native === "string" && state.native.trim() ? state.native : undefined,
|
||||
latitude: coordinate(state.latitude, `latitude for ${state.name}`),
|
||||
longitude: coordinate(state.longitude, `longitude for ${state.name}`),
|
||||
timezone: state.timezone,
|
||||
cities: (Array.isArray(state.cities) ? state.cities : []).map(compactCity),
|
||||
};
|
||||
}
|
||||
|
||||
const countries = await loadSource();
|
||||
if (!Array.isArray(countries)) throw new Error("Global location source is not an array");
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const index = [];
|
||||
let stateCount = 0;
|
||||
let cityCount = 0;
|
||||
for (const country of countries) {
|
||||
const code = typeof country.iso2 === "string" ? country.iso2.trim().toUpperCase() : "";
|
||||
if (!/^[A-Z]{2}$/.test(code)) continue;
|
||||
const regions = (Array.isArray(country.states) ? country.states : []).map(compactRegion);
|
||||
if (regions.length === 0) continue;
|
||||
const payload = {
|
||||
code,
|
||||
name: country.name,
|
||||
nativeName: typeof country.native === "string" && country.native.trim() ? country.native : undefined,
|
||||
regions,
|
||||
};
|
||||
await writeFile(resolve(outputDir, `${code.toLowerCase()}.json`), `${JSON.stringify(payload)}\n`, "utf8");
|
||||
index.push({ code, name: country.name, nativeName: payload.nativeName, regionCount: regions.length });
|
||||
stateCount += regions.length;
|
||||
cityCount += regions.reduce((total, region) => total + region.cities.length, 0);
|
||||
}
|
||||
|
||||
index.sort((a, b) => a.name.localeCompare(b.name));
|
||||
await writeFile(indexPath, `${JSON.stringify({ source: { repository: "dr5hn/countries-states-cities-database", release: RELEASE, url: SOURCE_URL, license: "ODbL-1.0" }, countries: index })}\n`, "utf8");
|
||||
console.log(`Wrote ${indexPath}: ${index.length} countries, ${stateCount} regions, ${cityCount} cities.`);
|
||||
Reference in New Issue
Block a user