全国乡镇街道边界数据下载后的面积口径:内环、被包裹要素与重叠怎么算
一份区县级的乡镇边界文件里,同一个乡的几何可能由两个环组成:外环一圈,内环一圈,内环里又装着别的街道。这种结构在 GIS 里完全合法,画在地图上也是一张严丝合缝的县域图——可一旦拿去做面积汇总,就会凭空多出一块地。全国省市区县区划边界数据下载回来之后,越往乡镇街道这一级走,这种嵌套越密集。
拿阜新市新邱区举例——这是做全国省市区县geojson数据下载时随手抽到的一份。全区 5 个要素,长营子蒙古族镇的几何是 1 个部件、2 个环,外环 1635 个顶点,内环 353 个顶点。外环围出 122.68 平方千米,内环挖去 14.95 平方千米,落进去的是兴隆、新发、中兴、益民 4 个街道,加起来正好 14.95 平方千米。按扣洞口径算,这个镇只剩 107.73 平方千米。整区相加,含洞 137.63、扣洞 122.68,相差一成二。
内环不是装饰,它是被别人占走的那块地
全量扫过站内 2931 个区县文件:42294 个要素里,679 个带内环,内环总数 11595 个,面积相加 6283.4 平方千米。相对要素总面积 9442362 平方千米,占比不到千分之一,可分布极不均匀——绝大多数内环只有几平方米到几百平方米,是几何化简留下的针孔,成规模的只有 155 个(超过 1 平方千米)。
值得动手的,是内环里确实装着同级要素的那一类。判定方式直白:取同文件其它要素的部件质心,看是否落在这个内环里,且该要素面积小于内环面积。严格判定下来有 18 个内环满足,面积相加 2526.0 平方千米,装在里面 46 个要素的净面积相加 2508.6 平方千米,两边吻合。
| 文件 | 外环持有者 | 被包裹要素 | 内环面积 |
|---|---|---|---|
| 巴音郭楞州-尉犁县 | 古勒巴格乡 | 兵团三十四团、三十一团、三十三团 | 2206.3 km² |
| 金昌市-金川区 | 宁远堡镇 | 滨河路、桂林路、金川路、新华路、广州路街道 | 84.0 km² |
| 榆林市-横山区 | 城关街道 | 怀远街道、夏州街道 | 51.3 km² |
| 吉林市-舒兰市 | 环城街道办事处 | 南城、滨河街道办事处 | 29.4 km² |
| 通辽市-霍林郭勒市 | 达来胡硕苏木 | 珠斯花、莫斯台、宝日呼吉尔街道 | 24.0 km² |
| 阜新市-新邱区 | 长营子蒙古族镇 | 兴隆、新发、中兴、益民街道 | 15.0 km² |
| 三门峡市-义马市 | 新区街道 | 千秋路街道、泰山路街道 | 5.5 km² |
这张表的规律值得注意:被包裹的多是街道、兵团团场、开发区、林场这类不占独立连续空间、后来从镇或苏木里划出来的单元。义马市是县级市,7 个要素里新区街道含洞 46.27、扣洞 40.73,挖掉的正是在它腹地的两条街道。金川区的宁远堡镇含洞 1400.46、扣洞 1316.48,腹地装着主城区的 5 条街道。
尉犁县那个 2206.3 平方千米的内环最能说明问题:它和里面三个兵团团场的面积相加差 0.01 平方千米,误差不到百万分之一。两边严丝合缝,说明数据生产方当初就老老实实把团场从乡的外环里挖掉了,只是下游按含洞口径统计时,没人记得这回事。
两个口径差多少:逐级汇总会放大
全量对账:含洞口径 9447743、扣洞口径 9442362、相差万分之五点七。平均数看不出风险,因为大内环只集中在 17 个文件里。这 17 个文件才值得单独过一遍:
| 文件 | 含洞 | 扣洞 | 虚高 |
|---|---|---|---|
| 阜新市-新邱区 | 137.6 | 122.7 | 12.19% |
| 三门峡市-义马市 | 105.9 | 100.3 | 5.64% |
| 通辽市-霍林郭勒市 | 606.7 | 582.8 | 4.11% |
| 巴音郭楞州-尉犁县 | 61340.8 | 59134.5 | 3.73% |
| 襄阳市-樊城区 | 515.3 | 498.4 | 3.38% |
| 金昌市-金川区 | 3147.7 | 3063.7 | 2.74% |
| 赤峰市-元宝山区 | 963.6 | 939.8 | 2.53% |
| 双鸭山市-宝山区 | 437.3 | 428.1 | 2.15% |
| 榆林市-横山区 | 4344.3 | 4293.0 | 1.20% |
| 齐齐哈尔市-昂昂溪区 | 735.3 | 727.2 | 1.12% |
一成二的偏差不算小。乡镇级面积经常被拿去做人口密度、耕地占比、设施半径的分母,分母虚高一成二,结论就偏一成二。这类误差还不会报警:文件能解析、图能画、每个要素都长在正确的位置上。
一种更隐蔽的情况:不是洞,是叠着画
内环至少还遵守"挖掉"的约定。另一种情况是两个要素的几何直接重叠,各自保留完整外环——统计时两边都会把重叠那块算进去,谁也不知道。武汉青山区就有一例:17 个要素里,中国一冶有限责任公司、中国一冶集体企业管理处、武钢实业公司这三个企业单元的质心,分别落在冶金街道、钢花村街道、新沟桥街道的外环里。
判定方式一样,看谁的部件质心落在谁的外环里:
def containment(pairs):
"""pairs: [(name_a, rings_a, area_a_net), ...],rings 为各部件外环"""
out = []
for i, (na, ra, aa) in enumerate(pairs):
for j, (nb, rb, ab) in enumerate(pairs):
if i == j:
continue
small, big = (ra, rb) if aa < ab else (rb, ra)
# 小者的每个部件质心都落在大的某个外环内
if all(any(in_ring(centroid(r), br) for br in big) for r in small):
out.append((na, nb, aa))
return out
重叠面积只占青山区 98.56 平方千米里很小一块,但性质比内环差:内环至少在几何上承认了这块地属于别人,重叠则是两份数据各说各话。做区县内乡镇级加总时,这类重叠会让合计比区县自身的面积还大——青山区的合计和区级口径对不上,源头就在这里。
清理顺序:先判环、再定归属、最后才动几何
处理这类问题最容易把顺序做反:先跑抽稀,再去修嵌套。抽稀会改变顶点数、还可能翻转环向,本来判得清的包含关系就判不准了。可行的一次性流程是这样:
import json, math
def signed_area(ring):
s = 0.0
for i in range(len(ring) - 1):
(x1, y1), (x2, y2) = ring[i], ring[i + 1]
s += (x2 - x1) * (y2 + y1)
return s / 2
def classify(path):
fc = json.load(open(path, encoding="utf-8"))
holes, solids = [], []
for f in fc["features"]:
g = f["geometry"]
polys = [g["coordinates"]] if g["type"] == "Polygon" else g["coordinates"]
for poly in polys:
for idx, ring in enumerate(poly):
# 外环逆时针、内环顺时针;方向反了先依据规范纠正再分类
ccw = signed_area(ring) > 0
(solids if (idx == 0) == ccw else holes).append((f, ring, idx))
return solids, holes
三个顺序上的判断值得说清。环向修正在分类之前:一个被误写成顺时针的外环会被当成内环,整块地凭空消失。面积一律按内环相减的口径算,含洞值只在对账时用。几何合并放到最后,因为要不要合并、合并成谁,取决于归属判定,而不是反过来。
合并到哪一级也要先定。区县内乡镇级这一层,内环挖掉的部分本来归属明确,把洞填平、按扣洞口径统计即可。跨级汇总时(乡镇加总到区县),必须用扣洞值,否则区县合计会大于区县自身面积——这是最容易被数据审核抓出来的一类硬伤。GeoJSON下载回来的文件若做过几何化简,针孔内环还会再增加一批,这条判据同样适用。
这套检查的全部成本在解析。单个区县文件跑一次包含判定在百毫秒级,2931 个文件全扫 5 到 6 分钟,单机单线程。挂到构建脚本里,每次数据更新后自动跑一遍,代价可以忽略。往下一级做全国村级geojson数据下载时,嵌套密度只会更高,这套判环的代码可以原样复用,区别只是内环里包着的从街道变成村民小组。
放行标准
判据建议只看三条:本文件内不存在被同级要素包裹的内环;不存在小要素完全落在大要素外环内的重叠;含洞与扣洞的差低于千分之五。三条都过再去做抽稀、切瓦片、算密度。不通过时不要删几何——把被包裹的要素单独标出来,交给人工确认是历史残留还是现行建制,比自动合并安全得多。
拿这份数据去做汇报底图时,嵌套结构还有一个额外影响:内环里的碎面落到版面上会显得零散,图例和取景都要另作安排。真要用某个市、某个区县单独出图,直接到 地图 PPT 模板选购页 按省市逐级挑一份可编辑底图,先看预览确认范围再决定,单份 ¥4.99,比在脚本里拼接几何省事。至于 GeoJSON 文件本身,站内各省、市、区县、乡镇都能先看预览图核对轮廓,确认无误再按次导出;预览不收费,导出与 PPT 文件是付费项目。
顺带一提,这类嵌套并非数据质量问题,而是行政区划真实形态的忠实记录:乡包围街道、镇包围开发区、苏木包围林场,都是实际存在的建制格局。要改的是我们统计时的口径,不是数据。本文数据仅用于技术讨论,区划口径以国家有关部门发布的为准。