GeoJSONcn
GeoJSONcn / 技术专栏 / 12 位统计用区划代码怎么拆成四…

12 位统计用区划代码怎么拆成四级行政区划树

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

全国行政区划 GeoJSON 的 properties 里,adcode 是主键。可 6 位代码和 12 位代码是两套东西,混着用,两边的表就是对不上。

两套代码的关系

GB/T 2260 规定的行政区划代码是 6 位数字,前 2 位表示省级,中间 2 位表示地级,后 2 位表示县级。国家统计局另外发布一套统计用区划代码,长度是 12 位:前 6 位与行政区划代码保持一致,第 7 到 9 位表示街道或乡镇,第 10 到 12 位表示居委会或村委会。

统计局的村级清单里还有一个独立的 3 位城乡分类代码,例如 111 表示主城区、220 表示村庄。它和区划代码是并列的两个字段,不是区划代码的一部分,别拼到后面去。

代码位数覆盖层级来源更新频率
行政区划代码6省 / 地 / 县GB/T 2260有区划调整时变
统计用区划代码(乡级)9增加乡镇街道国家统计局每年一版
统计用区划代码(村级)12增加居委会村委会国家统计局每年一版

从 6 位代码判定层级

判定规则全看尾部的 00,不用查表。

function levelOf(code) {
  if (!/^\d{6}$/.test(code)) return null;
  if (code.slice(2) === '0000') return 'province';  // 后 4 位为 0,省级
  if (code.slice(4) === '00') return 'city';        // 后 2 位为 0,地级
  return 'district';                                // 县级
}

县级那 2 位还有一套沿用已久的分段约定:01 到 20 给市辖区,21 到 80 给县和自治县、旗,81 到 99 给县级市。这是编码惯例,不是强制约束。拿它做粗分类没问题,当成判据去过滤数据就会出错。

构树

拿到 12 位清单以后,父子关系全部由前缀推导,不需要额外的 parent 字段。

// rows: [{ code: '110105001001', name: '××居委会' }, ...] 12 位原始清单
function buildTree(rows) {
  const cut = [2, 4, 6, 9, 12];          // 省 / 市 / 县 / 乡 / 村 的前缀长度
  const pad = ['0000', '00', '', '', ''];  // 补齐到 6 位
  const index = new Map();

  for (const row of rows) {
    let parent = null;
    for (let i = 0; i < cut.length; i++) {
      const code = row.code.slice(0, cut[i]) + pad[i];
      let node = index.get(code);
      if (!node) {
        node = { code, level: i, name: '', children: [], seen: new Set() };
        index.set(code, node);
      }
      if (parent && !parent.seen.has(code)) {
        parent.seen.add(code);
        parent.children.push(node);
      }
      parent = node;
    }
    parent.name = row.name;
  }
  return [...index.values()].filter(n => n.level === 0);
}

seen 那个 Set 别省。村级清单几十万行,改用 children.includes 去重,复杂度直接退化成平方级。

跟 GeoJSON 对齐

区县面按 adcode 前缀上卷到地市,是最常用的一步:

const groups = new Map();
for (const f of fc.features) {
  const adcode = String(f.properties.adcode);
  const cityCode = adcode.slice(0, 4) + '00';
  if (!groups.has(cityCode)) groups.set(cityCode, []);
  groups.get(cityCode).push(f);
}

分完组再做几何合并,具体做法在 边界上卷合并 里讲过。把统计数据 join 到面上着色,可以看 分级设色地图。

层级完整性顺手校验一遍,把没有上级的孤儿节点挑出来:

const known = new Set(fc.features.map(f => String(f.properties.adcode)));
const orphans = [...known].filter(
  c => c.slice(4) !== '00' && !known.has(c.slice(0, 4) + '00')
);

三个坑

前缀上卷不是到处都成立。省直辖县级行政区的地级位固定为 90,河南济源是 419001,湖北仙桃是 429004,神农架林区是 429021,它们的 4190 和 4290 只是一个占位分组,现实中并没有对应的地级市实体。直辖市也一样,北京是 110000,下面挂着一个 110100 市辖区,再往下才是 110105 朝阳区,这个 110100 在 GeoJSON 里通常没有独立几何。上卷脚本得把这类虚拟层单独拎出来处理,不然会凭空造出一批空壳面。

adcode 必须按字符串存。12 位代码小于 2 的 53 次方,也就是 9007199254740991,JavaScript 的 Number 表示得下,精度不丢。真正出事的是 Excel:单元格宽度不够时它显示成 1.10105E+11,另存为 CSV 就按显示格式写出去,代码当场损坏。字段定成字符串,这一整类问题就绕开了,slice 取前缀也更顺手。属性字段的处理链路在 Shapefile 转 GeoJSON 实战 里更完整。

代码是带版本的。统计用区划代码每年发布一版,标准时点是当年 10 月 31 日,撤县设区之类的调整会让代码从 21 到 80 段跳到 01 到 20 段。拿 2020 年的边界去 join 2024 年的统计表,肯定漏一批,而且漏得毫无声息。我的做法是在 properties 里记一个版本年份,join 之前先比对两边年份对不对得上。字段命名相关的规范问题,RFC 7946 的隐蔽坑 里也提过。

小结

adcode 不是一串普通字符,它是一棵编码进字符串里的树。前缀结构吃透了,四级联动、层级上卷、跨表 join 都不用另建关系表,一个 slice 就够。全国四级行政区划的 GeoJSON 数据可以在 geojsoncn.com 获取,2800 多个区县的 adcode 字段均按字符串存储。