GeoJSONcn
GeoJSONcn / 技术专栏 / 没有后端也能做行政区划代码查询:…

没有后端也能做行政区划代码查询:一份离线索引搞定全省级地名联想

发布于 2026-09-07 · GeoJSONcn 技术团队 · 阅读约 8 分钟

给全国几万个区划做地名联想,第一反应是"接个后端搜索服务"。可我是做纯静态下载站的,接口每多一个就多一份维护、多一层被打的风险。后来我把这套联想做成一个不到 1MB 的离线资源,浏览器本地就能"秒出"候选:输入"萧山"立刻补全到 330109,输入"xinjian"也能靠拼音前缀命中。这篇把做法摊开讲——没有后端,照样能做能用的行政区划代码查询。

先想清楚要查什么:名字、代码、拼音,三样缺一不可

做全国省市区县geojson数据下载配套的查询,用户真实输入往往是这几种:直接打汉字"西湖",打省份简称"浙",或者更刁钻——企业背景音拼"guangzhou"。而行政区划代码(adcode)是 9~12 位数字,靠"背"不现实,用户几乎都是靠名字先定位再点选代码。

所以我的索引不是"一条记录存个名",而是把每一级单位拆成可检索的多份 key,存成扁平表:

输入类型例子命中字段期望结果
全称杭州市西湖区name(逐级拼接)精确一条
简称浙 / 杭short_name该省/市入口
拼音全拼zhejiangpinyin.name精确+同级兄弟
拼音首字母zjpinyin.acronym该省
代码片段3301adcode杭州市级起的所有后代

每份 key 我都存成稳定的小写,查询时再把用户输入转小写后拿去比对——很多"查不到"其实是大小写和全半角没归一。

数据怎么装进一份浏览器能啃的文件

直接拿 division_codes.json(几万条)当索引不是不行,可 JSON.parse 一次要吃掉不少时间,天天地图页还要在主页轻量加载,梭进去不合适。我的做法是在构建期烘焙成一棵自定义的数组索引,而不是让前端解析通用对象。

具体说,我用一个三层结构压缩存储:一个扁平数组按深度排(省在前、市次之、区县更深、乡镇街道和村最后),每条只存 [id, parent, name, rank] 四个定长字段;另开一个 Map 把名字/拼音前缀映射到"这批 id 的起止区间"。浏览器端不用管父子树有多深,查出一个 [start,end] 区间后,直接在数组里截一段就是候选集。

// 构建期烘焙:扁平 + 前缀区间,前端零树遍历
const flat = [], prefix = new Map();
codes.forEach((c, i) => {
  flat.push([c.adcode, c.parent || 0, c.name, c.rank]);
  for (let n = 0; n < c.pinyin.length; n++) {         // zhè jiāng 逐段
    const seg = c.pinyin.slice(0, n + 1).join('');
    (prefix.get(seg) || prefix.set(seg, []).get(seg)).push(i);
  }
});

用户输入每敲一个字符,就取一次用户输入的前缀去 prefix 里拿到 id 集合,再回 flat 取名字排版。输入"zhe"命中浙江全省几条可选项,补上"jiang"候选立刻收敛到省一级,体验上跟"有后端"几乎没差。这里有个工程细节:拼音分词要按多音字给多组拼音都建 key,"重"字在"重庆""重合"发音不同,漏了其中一组,口语用户就查不到了。

村一级那么碎,区间裁剪照样管得住

有人担心:省市区县少说几千条,加上乡镇街道和村动不动就破十万,区间会不会崩?不会——因为联想走的是"前缀收敛",用户输入越长命中的 id 越少。真正的坑在另一处:全国村级geojson数据下载之后,很多村名高度重复,光一个"新村"可能对应几十上百个 id。

我的取舍是村一级不留作首屏唯一命中,而是给候选列表按 adcode 排序并带上父级路径,让用户一眼看到"新村(浙江省杭州市西湖区转塘街道)"区别于同名兄弟。选型代码用 rank<=3 只在省市区县层收窄,要盘到村的时候把展示切到"带路径列表"而非"唯一命中"。这样既尊重村级数据的真实存在,又不会让一个烂大街的村名把列表刷屏。

兜底再叠一层:查不到就按父级给"模糊还能用"的候序

就算上面全做完,输入里混了错别字("萧"打成"肖")还是会空手。稳妥兜底是做个轻量编辑距离:空集的候序里挑前 N 个名字里编辑距离最小的,按距离升序塞回去。这套对全国省市区县区划边界数据下载回来后的地名校验尤其好用——我从线上运维里拿真实日志喂进去,靠它纠出过一批手滑少打一个字的查询,准确率够用一个下载站的前端补全。

唯一没法绕的还是钱和带宽:这套离线索引本身是站点免费预览的一部分,浏览器里查多少次都不收钱;只有当你搜到某个省市区县、点进详情页要导出 GeoJSON/SHP/SVG 时才走按次付费(站上 ¥1.99/次),前端输入联想始终不碰订单——职责分开,省得每次敲字都打扰支付链路。

说到底,一个没有后端的状态也能 handle 住上万级地名检索,靠的不是玄学,是"把查询做成前缀+区间"这种老派但可靠的结构。你要真嫌弃手写 trie 麻烦,把这份扁平索引原样丢给地图底图的搜索插件也通——GeoJSON 和它的名字本来就是成对长的,索引里留一份 adcode 路标,前端顺着就能一路点击钻到乡镇、钻到村。