GeoJSONcn
GeoJSONcn / 技术专栏 / 全国乡镇街道边界数据下载后的面积…

全国乡镇街道边界数据下载后的面积口径:内环、被包裹要素与重叠怎么算

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

一份区县级的乡镇边界文件里,同一个乡的几何可能由两个环组成:外环一圈,内环一圈,内环里又装着别的街道。这种结构在 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.6122.712.19%
三门峡市-义马市105.9100.35.64%
通辽市-霍林郭勒市606.7582.84.11%
巴音郭楞州-尉犁县61340.859134.53.73%
襄阳市-樊城区515.3498.43.38%
金昌市-金川区3147.73063.72.74%
赤峰市-元宝山区963.6939.82.53%
双鸭山市-宝山区437.3428.12.15%
榆林市-横山区4344.34293.01.20%
齐齐哈尔市-昂昂溪区735.3727.21.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 文件是付费项目。

顺带一提,这类嵌套并非数据质量问题,而是行政区划真实形态的忠实记录:乡包围街道、镇包围开发区、苏木包围林场,都是实际存在的建制格局。要改的是我们统计时的口径,不是数据。本文数据仅用于技术讨论,区划口径以国家有关部门发布的为准。