Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

126 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Weather App 的可搜索城市数据生成工具。
* 从 Railway12306 参考城市与车站数据生成 searchableCities.json。
*
* 用法: node apps/Weather/scripts/build_cities.mjs
* 输出: apps/Weather/data/searchableCities.json
*/
import { readFileSync, writeFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const APP_DIR = resolve(__dirname, '..');
const ROOT = resolve(APP_DIR, '../..');
const ADCODE_TO_PROVINCE = {
'11': '北京', '12': '天津', '13': '河北', '14': '山西', '15': '内蒙古',
'21': '辽宁', '22': '吉林', '23': '黑龙江',
'31': '上海', '32': '江苏', '33': '浙江', '34': '安徽', '35': '福建',
'36': '江西', '37': '山东',
'41': '河南', '42': '湖北', '43': '湖南', '44': '广东', '45': '广西',
'46': '海南',
'50': '重庆', '51': '四川', '52': '贵州', '53': '云南', '54': '西藏',
'61': '陕西', '62': '甘肃', '63': '青海', '64': '宁夏', '65': '新疆',
'71': '台湾', '81': '香港', '82': '澳门',
};
// 某些城市名不带"市"后缀的特殊情况
const NO_CITY_SUFFIX = new Set([
'香港', '澳门',
// 自治州/地区/盟等非"市"单位在 cities.json 中已带完整名称
]);
function needsCitySuffix(name) {
if (NO_CITY_SUFFIX.has(name)) return false;
if (name.endsWith('市') || name.endsWith('州') || name.endsWith('地区') ||
name.endsWith('盟') || name.endsWith('林区') || name.endsWith('区') ||
name.endsWith('县')) return false;
return true;
}
// 1. 读取数据
const citiesJson = JSON.parse(readFileSync(resolve(ROOT, 'apps/Railway12306/data/reference/cities.json'), 'utf-8'));
const stationList = JSON.parse(readFileSync(resolve(ROOT, 'apps/Railway12306/data/reference/stationList.json'), 'utf-8'));
// 2. 从 stationList 构建 cityName → 最高等级车站的 {lat, lng} 映射
const cityCoords = new Map();
for (const s of stationList) {
if (s.lat == null || s.lng == null) continue;
const existing = cityCoords.get(s.cityName);
if (!existing || s.stationClass > existing.stationClass) {
cityCoords.set(s.cityName, { lat: s.lat, lng: s.lng, stationClass: s.stationClass });
}
}
// 3. 从 cities.json 提取所有城市(去重 hot 与字母分组的重复)
const seen = new Set();
const allCities = [];
for (const [key, items] of Object.entries(citiesJson)) {
if (!Array.isArray(items)) continue;
for (const city of items) {
if (seen.has(city.adcode)) continue;
seen.add(city.adcode);
allCities.push(city);
}
}
// 4. 合成结果
const results = [];
let noCoords = 0;
for (const city of allCities) {
const adcodePrefix = city.adcode.substring(0, 2);
const adm1 = ADCODE_TO_PROVINCE[adcodePrefix];
if (!adm1) {
console.warn(`未知 adcode 前缀: ${city.adcode} (${city.name})`);
continue;
}
const displayName = needsCitySuffix(city.name) ? city.name + '市' : city.name;
// 尝试匹配坐标:先用原始名(不带市),再用带"市"的名
let coords = cityCoords.get(city.name) || cityCoords.get(displayName);
// 部分城市名在 stationList 中可能有变体
if (!coords) {
// 自治州等名称可能只有前面部分匹配
for (const [stCityName, stCoords] of cityCoords) {
if (city.name.startsWith(stCityName) || stCityName.startsWith(city.name)) {
coords = stCoords;
break;
}
}
}
if (!coords) {
noCoords++;
continue;
}
const id = city.pinyin;
const adm2 = displayName;
results.push({
id,
name: displayName,
adm1,
adm2,
pinyin: city.pinyin,
lon: Math.round(coords.lng * 10000) / 10000,
lat: Math.round(coords.lat * 10000) / 10000,
});
}
// 按拼音排序
results.sort((a, b) => a.pinyin.localeCompare(b.pinyin));
console.log(`总计生成 ${results.length} 个城市(${noCoords} 个因缺少坐标被跳过)`);
// 5. 写入
const outPath = resolve(APP_DIR, 'data/searchableCities.json');
writeFileSync(outPath, JSON.stringify(results, null, 2) + '\n', 'utf-8');
console.log(`已写入: ${outPath}`);