GeoJSONcn
GeoJSONcn / 技术专栏 / 村级 GeoJSON 边界藏着碎…

村级 GeoJSON 边界藏着碎块?用 adcode 与拓扑把碎片拼成整面

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

从省到街道的边界一般都很利索,一"下"到村级就开始不对劲。在做全国村级geojson数据下载的整理时,最大的麻烦不是文件大,而是同一片区域被切成好几块、叠了好几层:一个行政村面被分成两三片,中间还有碎缝和小岛,放大看惨不忍睹。这篇聊聊怎么用 adcode 主键加拓扑把村级废料清理成能直接上地图的干净面,顺手补齐全国省市区县geojson数据下载这套体系里最容易烂的一环。

村级 GeoJSON 碎片是怎么来的

村级数据之所以碎,多半是上游来源决定的。你在做 GeoJSON下载 或从专业源拿村级边界时,常常要过这么几道手续:

所以第一步永远是把村级数据当成"半成品"来对待,不能直接画。

用 adcode 做主键先归簇,再谈合并

对付碎片,别一上来就去碰几何。先把所有要素按 properties.adcode(12 位或 9 位村级码)聚成一组,同一个码的若干碎片就是一家人。这一步同时解决两个问题:一是同一村多片能归拢;二是能发现同码异名(旧档案漏删)和同码几何重叠这种脏数据。下面这段 Node 用 Map 按 adcode 归簇,顺便统计每个码的碎片数和面积:

import { readFileSync } from 'node:fs';
const fc = JSON.parse(readFileSync('village.json', 'utf8'));
const groups = new Map();
for (const f of fc.features) {
  const code = String(f.properties.adcode || '');
  if (!groups.has(code)) groups.set(code, []);
  groups.get(code).push(f);
}
let multi = 0, overlap = 0;
for (const [code, list] of groups) {
  if (list.length > 1) { multi++; console.log(code, '碎片x' + list.length); }
}
console.log('多碎片村级要素数:', multi);

跑完你大概率会看到整片区域有一半以上的村"碎片数 > 1",这就是要清理的目标。归簇里还有个坑:村级用 12 位统计用代码还是 9 位民政代码,来源不一。先统一到 12 位(前 9 位 + 3 位村民小组/虚拟层),否则同一个村会因为位数不同被拆成两组,合并就无从谈起。

同一村先合并,再统一环方向与去孔

碎片归到一族后,合并要按"拓扑优先、几何兜底"的顺序,别直接 union 硬拼——会撞出大量畸形顶点。我的顺序是:

1. 清洗几何。每片先过一遍 buffer(0) 或修复自交,把自交、未闭合环的脏面修掉,否则 union 阶段会埋雷。 2. 同码合并。一个村族的若干片用 union 合成一面。片与片若恰好共边,union 会消缝;若只是接近不重合,会留 0.0000x 度的碎缝。 3. 删多余孔。合并后常有细长孔或小岛,多半是旧档案残留。用面积阈值(比如 < 该村面积 1%)直接剔除。 4. 统一环方向。RFC 7946 要求外环逆时针、内环顺时针。合并后务必跑一遍 rewind,不然 PostGIS 和部分前端会报 non-closed rings。

mapshaper 能一把梭做合并+去缝,但村级要素量大(全国几十万),我更倾向脚本化分批跑。下面是按面积阈值清孔的片段:

function dropTinyHoles(g, minArea) {
  if (g.type !== 'Polygon' && g.type !== 'MultiPolygon') return g;
  const polys = g.type === 'Polygon' ? [g.coordinates] : g.coordinates;
  for (const ring of polys) {
    // ring[0] 外环,其余为内环
    let i = 1;
    while (i < ring.length) {
      if (shoelaceArea(ring[i]) < minArea) ring.splice(i, 1);
      else i++;
    }
  }
  return g;
}

shoelaceArea 用鞋带公式把经纬度环转为有符号面积,按 Math.abs 取面积比较。注意这里面积是"经纬度平方"的近似的量,只适合做相对阈值(相对该村面积的比例),跨村横向比没有物理意义。

兜底验证:面积闭环 + 抽样目检

合并完不能觉得"画着顺眼就完事",得有两个硬校验。

校验一:面积闭环。 村级碎片清理完,把每个村合完的面积累加,和乡镇面面积对对数。行政村面之和应约等于乡镇面(差在政府驻地的飞地、水面等)。偏差 > 5% 的乡镇,说明有片没归对或漏删,回炉重查。校验二:抽样目检。 随机抽几个县城周边与省界附近的村,放大 20 倍肉眼扫一遍缝和小岛——自动化清理后机器看着对,人眼常能抓出残留。

这里也提一句合规:村级边界的几何我是在"地图完整性 = 要素不重叠、不缺失的纯技术校验"这个意义上提的。数据展示与导出始终遵循审图与技术服务边界,脚本只做拓扑与去重,不涉及任何领土主张或范围判定。

合并碎块的常见坑

我把踩过的坑列成一张表,跑村级 GeoJSON 合并前先对一眼:

坑表现对策
adcode 位数不统一同村被拆成两组先统一到 12 位
碎片共边未对齐union 后留细缝先 buffer(0) 修边再 union
同码新旧面叠加统计翻倍、面重叠按 area+时间新旧面只留一张
细长孔残留放大全是小洞按面积阈值剔孔
环方向不统一PostGIS 报 non-closed合并后 rewind

小结

村级 GeoJSON 数据下载回来,别当成即开即用的成品。用 adcode 归簇看清碎片分布,按"清洗→合并→剔孔→rewind"的顺序把同一村拼成整面,再用面积闭环加抽样目检兜底,就能得到一套能直接叠到全国省市区县geojson数据下载那套省市区县边界上的干净村级层。碎片清干净,后续做 choropleth、做 point-in-polygon、做村级统计分析才不会被重复面积坑到。

---

需要这条村级 GeoJSON 数据下载链路里的省市区县乡镇各级边界,欢迎到 geojsoncn.com 的全国省市区县区划边界数据下载专区,本站预览与在线可视化免费,导出原始 GeoJSON 为付费能力(每次下载仅需 ¥1.99),可用于把各级边界按 adcode 对齐到村级层上。