'use strict'; /** * 把归属表里的组名归一到**地级**(市/州/盟/地区)。 * * 为什么要这一步:Photon 的 `county` 字段层级并不稳定—— * 「荔波小七孔」→ county=黔南州(地级)、city=荔波县(县级)✅ * 「加榜梯田」 → county=从江县(县级)、city=摆别村(村级)❌ * 直接拿 county 当组名,就会出现「加榜梯田」和「从江县」两个并列的组, * 而它们其实是同一片(黔南州)。 * * 办法:对层级不对的条目,用该景点的坐标在「地级名单」里找最近的(≤120km), * 原来的县级名降为 detail(区县说明)。 * * 用法:node tools/build-admin-normalize.cjs */ const fs = require('fs'); const path = require('path'); const cn = require('../lib/places-cn'); const niche = require('../lib/places-niche').NICHE_SPOTS; const file = path.join(__dirname, '..', 'lib', 'places-admin.js'); function haversine(lat1, lon1, lat2, lon2) { const R = 6371; const dLat = (lat2 - lat1) * Math.PI / 180; const dLon = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2; return 2 * R * Math.asin(Math.min(1, Math.sqrt(a))); } const isPrefecture = (n) => /(市|州|盟|地区)$/.test(String(n || '').trim()); function coordOf(group, name) { if (group === 'scenic') { const s = cn.EXTRA_SCENIC.find((x) => x[0] === name); return s ? [s[1], s[2]] : null; } const s = niche.find((x) => x[0] === name); return s ? [s[1], s[2]] : null; } function main() { const src = fs.readFileSync(file, 'utf8'); const SPOT_AREA = JSON.parse(src.match(/const SPOT_AREA = (\{.*?\});/s)[1]); const AREA_COORD = JSON.parse(src.match(/const AREA_COORD = (\{.*?\});/s)[1]); // 地级名单:城市库 + 归属表里层级正确的地名 const prefectures = new Map(); for (const [n, la, lo] of cn.CN_CITIES) prefectures.set(n, [la, lo]); for (const [n, coord] of Object.entries(AREA_COORD)) { if (isPrefecture(n) && coord) prefectures.set(n, coord); } let fixed = 0; let unresolved = 0; for (const [key, entry] of Object.entries(SPOT_AREA)) { if (isPrefecture(entry.area)) continue; const [group, name] = [key.slice(0, key.indexOf(':')), key.slice(key.indexOf(':') + 1)]; const coord = coordOf(group, name); if (!coord) continue; let best = null; for (const [n, c] of prefectures) { const d = haversine(coord[0], coord[1], c[0], c[1]); if (!best || d < best.d) best = { n, d }; } if (!best || best.d > 120) { unresolved += 1; continue; } entry.detail = entry.detail || entry.area; entry.area = best.n; fixed += 1; } const body = `'use strict'; /** * 内置地点库的归属地名(自动生成,勿手改)。 * * 生成方式: * tools/build-admin.cjs —— 首轮,Photon 按名检索 + 坐标距离消歧 * tools/build-admin-fix.cjs —— 二轮,去后缀/核心词重试,补首轮搜不到的 * tools/build-admin-normalize.cjs —— 三轮,把组名归一到地级(市/州/盟/地区) * * 字段: * SPOT_AREA { 'scenic:荔波小七孔': { area, detail, state, level } } * AREA_COORD { '荔波县': [lat, lon] } 各地名代表坐标,运行时做最近市兜底 */ const SPOT_AREA = ${JSON.stringify(SPOT_AREA)}; const AREA_COORD = ${JSON.stringify(AREA_COORD)}; module.exports = { SPOT_AREA, AREA_COORD }; `; fs.writeFileSync(file, body, 'utf8'); console.log('fixed', fixed, 'unresolved', unresolved, 'entries', Object.keys(SPOT_AREA).length); } main();