'use strict'; /** * 归属判定离线单测(不发网络请求)。 * * 覆盖三件事: * 1. 同名县按坐标消歧(城中区:柳州 vs 西宁) * 2. 已知边界点归属正确(环江喀斯特→河池市/环江县,不许跑到荔波县) * 3. 下层地名是**县**,不是村/街道 */ const { attachPlaces } = require('../lib/sources'); const CASES = [ // [名称, lat, lon, 期望地级, 期望下层县] ['环江喀斯特世界自然遗产-核心功能区', 25.1, 108.28, '河池市', '环江毛南族自治县'], ['大化瑶族自治县七百弄', 24.35, 107.99, '河池市', '大化瑶族自治县'], ['都安瑶族自治县', 24.09, 108.10, '河池市', '都安瑶族自治县'], ['荔波小七孔', 25.25, 107.88, '黔南州', '荔波县'], ['柳州钢铁厂旧址', 24.33, 109.42, '柳州市', '城中区'], ['西宁某点', 36.62, 101.78, '西宁市', '城中区'], ['鼓楼区南京点', 32.06, 118.77, '南京市', '鼓楼区'], ['福州鼓楼点', 26.08, 119.30, '福州市', '鼓楼区'], ['徐州鼓楼点', 34.26, 117.19, '徐州市', '鼓楼区'], ]; (async () => { // 构造候选:带 OSM 归属字段,模拟真实场景里 attachPlaces 走的那条分支 const cands = CASES.map(([name, lat, lon]) => ({ name, lat, lon, kind: '景区', osmCounty: '', osmCity: '', osmDistrict: '', })); // 第一轮:喂「OSM 自带的县名字段」 const withNames = [ ['环江喀斯特世界自然遗产-核心功能区', '环江毛南族自治县'], ['大化瑶族自治县七百弄', '大化瑶族自治县'], ['都安瑶族自治县', '都安瑶族自治县'], ['荔波小七孔', '荔波县'], ['柳州钢铁厂旧址', '城中区'], ['西宁某点', '城中区'], ['鼓楼区南京点', '鼓楼区'], ['福州鼓楼点', '鼓楼区'], ['徐州鼓楼点', '鼓楼区'], ]; withNames.forEach((c, i) => { cands[i].osmCounty = c[1]; }); await attachPlaces(cands, {}); let pass = 0; let fail = 0; CASES.forEach(([name, , , expArea, expDetail], i) => { const c = cands[i]; const okArea = c.place === expArea; const okDetail = c.placeDetail === expDetail; if (okArea && okDetail) { pass += 1; console.log(` ✓ ${name} → ${c.place} / ${c.placeDetail}`); } else { fail += 1; console.log(` ✗ ${name} → 实际 ${c.place} / ${c.placeDetail || '(空)'},期望 ${expArea} / ${expDetail}`); } }); // 第二轮:不给任何 OSM 字段,纯靠坐标兜底(应落到最近县,仍不应出现空组名) const bare = CASES.map(([name, lat, lon]) => ({ name, lat, lon, kind: '景区' })); await attachPlaces(bare, {}); console.log(' --- 无 OSM 字段(坐标兜底)'); bare.forEach((c, i) => { const ok = c.place && c.place !== '?' && c.placeDetail !== '?'; if (ok) pass += 1; else fail += 1; console.log(` ${ok ? '✓' : '✗'} ${CASES[i][0]} → ${c.place || '(空)'} / ${c.placeDetail || '(空)'}`); }); // 第三轮:狭长县的远端点。大化县中心点在西北角,东南角的响水关离中心 85km, // 按 80km 一刀切会把**正确的**县名丢掉。精确全称命中要放宽阈值。 console.log(' --- 狭长县远端点'); { const c = { name: '响水关', lat: 24.7217, lon: 107.6123, kind: '景区', osmCounty: '大化瑶族自治县' }; await attachPlaces([c], {}); const ok = c.place === '河池市' && c.placeDetail === '大化瑶族自治县'; if (ok) pass += 1; else fail += 1; console.log(` ${ok ? '✓' : '✗'} 响水关 → ${c.place} / ${c.placeDetail || '(空)'}`); } console.log(`\n通过 ${pass} / 失败 ${fail}`); process.exit(fail ? 1 : 0); })();