Files
Jyotisha/frontend/scripts/pull-china-locations.mjs
2026-07-16 11:02:33 +08:00

98 lines
2.9 KiB
JavaScript

import { mkdir, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const SOURCE_URL = "https://raw.githubusercontent.com/zhChuXiao/ChinaGeoJson/master/info.json";
const outputPath = resolve(
dirname(fileURLToPath(import.meta.url)),
"../src/data/china-locations.json",
);
const response = await fetch(SOURCE_URL, {
headers: { "user-agent": "Jyotisha location-data refresh" },
});
if (!response.ok) {
throw new Error(`Unable to download China location data: ${response.status} ${response.statusText}`);
}
const source = await response.json();
function childrenOf(code) {
const record = source[String(code)];
return Array.isArray(record?.children) ? record.children : [];
}
function toLocation(node) {
const [longitude, latitude] = Array.isArray(node.center) ? node.center : [];
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
throw new Error(`Missing valid coordinates for ${node.name} (${node.adcode})`);
}
return {
code: String(node.adcode),
name: node.name,
center: [longitude, latitude],
};
}
function toCity(node) {
const location = toLocation(node);
return {
...location,
districts: childrenOf(node.adcode).map(toLocation),
};
}
function toProvince(node) {
const location = toLocation(node);
const directChildren = childrenOf(node.adcode);
const directDistricts = directChildren.filter((child) => child.level === "district");
// Direct-administered municipalities, Hong Kong and Macao have districts
// directly below the province-level node. A virtual city keeps the UI's
// country → province → city → district sequence consistent.
if (directChildren.length === directDistricts.length) {
return {
...location,
cities: [{
code: `${node.adcode}-city`,
name: node.name,
center: location.center,
districts: directDistricts.map(toLocation),
}],
};
}
return {
...location,
cities: directChildren.map(toCity),
};
}
const countryChildren = childrenOf(100000).filter((node) => node.level === "province");
const output = {
source: {
name: "ChinaGeoJson / DataV.GeoAtlas",
url: SOURCE_URL,
license: "MIT",
generatedAt: new Date().toISOString(),
},
country: {
code: "CN",
name: "中国",
timezone: 8,
provinces: countryChildren.map(toProvince),
},
};
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, `${JSON.stringify(output)}\n`, "utf8");
const cityCount = output.country.provinces.reduce((total, province) => total + province.cities.length, 0);
const districtCount = output.country.provinces.reduce(
(total, province) => total + province.cities.reduce((sum, city) => sum + city.districts.length, 0),
0,
);
console.log(`Wrote ${outputPath}: ${output.country.provinces.length} provinces, ${cityCount} cities, ${districtCount} districts.`);