全国村级geojson数据下载后的聚合质检:无码要素、坏几何与台账对不上的县
村界数据是一堆面,乡镇边界也是一堆面,把村面按空间叠合拼成乡镇,听着像一步操作,实际跑完会收到三份"对不上"的清单。全国村级geojson数据下载回来之后,我拿 32 个省级村界文件做了一轮聚合,输出 41470 个乡镇级单元,中间拦下 35050 个没有村级码的要素、26981 处无效几何,还有 1936 个县在乡镇个数上与正式台账对不上。重点不在算法,在哪些差异必须拦、哪些可以放行。
源头数据的字段情况比预想散。同一个属性容器,各省塞的键都不一样:上海给的是 STREETCODE 加 BLOCK_CODE,压根没有村级码;黑龙江多一个 乡代码;陕西给的是 BSM。全国省市区县geojson数据下载、全国省市区县区划边界数据下载回来的文件若直接按固定字段名做脚本,第一批就会静默丢字段——键名不可假定,得先扫一遍键集合。
无码要素:5.1% 的量,99.8% 的集中度
全国 687711 个村级面里,35050 个的村级码是空的,占 5.1%。这个数字本身不吓人,吓人的是它的分布:
| 省份 | 村级面 | 无码要素 | 无码占比 |
|---|---|---|---|
| 陕西 | 28200 | 28146 | 99.8% |
| 上海 | 5601 | 5493 | 98.1% |
| 湖北 | 32457 | 1367 | 4.2% |
| 云南 | 13594 | 41 | 0.3% |
| 天津 | 5847 | 3 | 0.1% |
| 内蒙古 | 14570 | 0 | 0% |
| 北京 | 8021 | 0 | 0% |
| 山东 | 85879 | 0 | 0% |
陕西 99.8%、上海 98.1% 这类不是"漏填",是该省从源头上就没有用行政村代码这个体系,转而用别的标识(陕西的 BSM、上海的两个 CODE 字段)。剩下 30 个省的村级码是齐的,内蒙古、北京、山东这些是 0 无码。按数量看,35050 个里 28146 个出在陕西,会被误判成"陕西数据质量差";按省看,真相是两种编码体系并存,得给它做一层映射,而不是去"补码"。
做法上,无码要素不要直接丢弃,也不要硬凑一个码。可行的处理是三级回退:先用本省的替代标识(BSM / STREETCODE+BLOCK_CODE)当临时主键;没有替代标识的,用"县码 + 乡镇名 + 村名"拼一个业务键;两种都拿不到的才标为待补。空间归属这一步则完全不依赖村级码——直接用村面的部件质心去命中乡镇面,32 省合计成功归属 35039 个,与 35050 只差 11 个,说明归属靠几何,不靠码。
无效几何:26981 处,修复要放在聚合之前
第二个拦路的是几何本身。26981 处无效几何按省分布同样不均:
| 省份 | 无效几何修复 |
|---|---|
| 黑龙江 | 3565 |
| 广东 | 2360 |
| 浙江 | 2095 |
| 福建 | 2094 |
| 山西 | 2043 |
| 安徽 | 1666 |
| 河南 | 1259 |
"无效几何"在村界数据里主要是三类:环未闭合、自相交、顶点数不足,多半来自上游的格式转换或几何化简,不是原始测绘产物。这类几何直接进聚合会出两种事故:union 报错中断整个乡镇;或者不报错但拼出来的乡镇面缺一块,面积悄悄偏小。
顺序上有一条硬规则:修几何必须早于聚合,聚合必须早于抽稀。抽稀会改变顶点、减少交点,本来能修的自相交可能被抽成更隐蔽的形态。上面那 26981 处如果放到抽稀之后处理,修复率会掉一大截。判断是否无效也别用"能不能解析 JSON"当标准——能解析但环不闭合的文件照样是坏的:
from shapely.geometry import shape
from shapely.validation import explain_validity
def triage(path):
bad = []
for f in load_features(path):
g = shape(f["geometry"])
if not g.is_valid:
bad.append((f.get("properties", {}).get("村名"), explain_validity(g)))
return bad
explain_validity 给出的原因串(如 Self-intersection[120.3 30.1])比布尔值的 is_valid 有用得多,它直接指到出问题的坐标,便于判断是整片环错还是单点毛刺。
台账对照:1936 个县的乡镇个数对不上
这一项最容易被跳过,却最能反映数据集的成熟度。把聚合出的乡镇与正式行政区划名录逐个县比对,1936 个县存在乡镇个数差异,方向两种都有:
- 村界有名录无:村面聚出了这个单元,但名录里查不到。上海 310104、310112 各多出 1 个,属于近年新设或改名的单元尚未进名录。
- 名录有村界无:名录里有、村面里找不到。陕西 610202 生成 4 个、名录 7 个,缺 3 个;黑龙江 230108 生成 4 个、名录 10 个,缺 6 个。这通常意味着该乡镇的地面在原始村界文件里就没被任何村面覆盖。
两种方向的处置完全不同。前者要查的是"名录更新滞后",后者要查的是"覆盖空洞"。把两者混在一起看总数,只会得到一个没用的 1936。
这一层还有两个副产品值得单独记一笔。
一是码县矛盾。黑龙江检出 3 组:230822205000 的村级码落在 230811、230805200000 落在 230826,村级码里的县段与所属乡码的县段不是同一个县。这类错误不会导致解析失败,但会让"按码挂树"的脚本把村挂到错误的县下。
二是同县同名。全国需要拆分合并的 2409 组,按省看:河北 458 组最多,江西 278、浙江 223、贵州 191、山西 185。同一县内出现两个同名乡镇,来源通常是乡镇撤并后新旧两套边界都还在文件里。黑龙江 231281 还检出 1 组无法自动拆分的保留项——同一个镇名对应 231281110000 与 231281111000 两个乡码,需要人工裁定哪个是现行建制。
碎片清理则是必须做但量很小的一步:聚合过程产生的零碎孤块合计 1669 块、面积 2.0574 平方千米。这些是村面之间几米宽的缝,不清理会让乡镇面出现针孔状空洞。
聚合结果与体积
跑完全流程,32 个省级单位共输出 41470 个乡镇级单元。平均每个乡级单元下辖的村面数各省差距很大:山东 48.2 个最高,北京 26.3、河北 25.9、山西 25.1、浙江 25.0 依次排开;另一端新疆只有 4.8 个,黑龙江 7.9、西藏 8.2、青海 10.1。这个比值可以直接当分块渲染的预算依据——山东一个乡镇要画几十个村面,新疆几个就够,同一套抽稀参数套全国必然一头吃力一头浪费。
抽稀之后的体积对比更能说明分层处理的必要:
| 省份 | 源文件解压后 | 抽稀后 | 留存 |
|---|---|---|---|
| 浙江 | 654.4 MB | 32.4 MB | 5% |
| 湖南 | 1155.0 MB | 50.8 MB | 4% |
| 山东 | 735.3 MB | 29.6 MB | 4% |
| 新疆 | 423.0 MB | 34.8 MB | 8% |
| 四川 | 704.1 MB | 148.6 MB | 21% |
32 省抽稀后合计 844.2 MB。四川留存 21% 明显高于其他省,原因不是它有多特殊,而是四川的村界原始精度本来就比别省高一档,同样的抽稀容差砍掉的相对量自然少。要齐平,得按省单独标定容差。
放行标准
这套聚合质检建议固定在三条上:
1. 无码率按省判断,同省内部无码率突高的县单独列,不要去改统计数字凑齐。 2. 无效几何修复必须落在聚合之前,修复后复核为真才能进 union。 3. 台账差异分清方向归档,多出来的查名录时效,缺掉的查覆盖空洞。
三条之外的都属于常规收尾,出了就处理,不影响主流程。
最后提一句落到汇报材料上的情况。乡镇级几何在版面上极密,山东一个乡镇几十个村面的轮廓缩到投影尺寸就是一坨;要按某一级单独成图,模板选购页 里能按省市逐级挑可编辑底图,单份 ¥4.99,先看预览定范围。站内省、市、区县、乡镇各级都有预览图可核对轮廓,看清楚再按次导出,预览环节不收费用。这种 GeoJSON下载 来的原始文件适合继续加工,直接上版面另有一条路。
上面 5.1%、26981、1936 这组数字取自特定一轮数据版本,换一版源会浮动,方法比数字更值得带走。本文数据仅用于技术讨论,区划口径以国家有关部门发布的为准。