GeoJSONcn
GeoJSONcn / 技术专栏 / 手里有 Shapefile?用 …

手里有 Shapefile?用 Python 把行政区划数据转成 GeoJSON

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

做空间数据,迟早会遇到同一件事:上游给的行政区数据包是 Shapefile,你的网站、地图组件、边界服务却全要 GeoJSON。Shapefile 至今还是 GIS 交换的事实标准,自然资源部、天地图下发的边界常以 .shp 提供;Web 端(Leaflet、Mapbox)和我们的边界服务只用 GeoJSON。两边不通,就得自己转。

为什么不能直接丢给前端

Shapefile 从来不是单个文件,而是 .shp(几何)、.shx(索引)、.dbf(属性表)一组,外加常见的 .prj 投影文件。它不声明编码,中文属性表经常乱码,浏览器也读不了。GeoJSON 是一段纯文本 JSON,天然适配 JavaScript 和 HTTP,把边界画到网页上省心得多。GeoJSON 的坐标约定,可以回看 GeoJSON 到底是什么。

用 Fiona 读 Shapefile,输出 GeoJSON

Fiona 封装了 GDAL/OGR,能把 .shp 直接读成 GeoJSON 兼容的记录。下面这个函数把整个图层读成一个 FeatureCollection:

import fiona
import json

def shp_to_geojson(shp_path):
    features = []
    with fiona.open(shp_path) as src:
        for rec in src:
            features.append({
                "type": "Feature",
                "geometry": rec["geometry"],   # 已是 GeoJSON 兼容的几何
                "properties": dict(rec["properties"]),
            })
    return {"type": "FeatureCollection", "features": features}

fc = shp_to_geojson("county.shp")
with open("county.geojson", "w", encoding="utf-8") as f:
    json.dump(fc, f, ensure_ascii=False, indent=2)

rec["geometry"] 本身就是 GeoJSON 风格的字典,坐标结构不用自己搬,直接拼进 Feature 就行。

坐标系:Shapefile 多半不是 WGS84

最容易栽跟头的就是这里。Shapefile 的 .prj 可能是 CGCS2000(EPSG:4490),也可能是某个地方独立坐标系;GeoJSON 默认按 WGS84(EPSG:4326,顺序是经度、纬度)约定。不重投影,画到中国地图上会整体偏移。用 pyproj 转一次:

from pyproj import Transformer

transformer = Transformer.from_crs("EPSG:4490", "EPSG:4326", always_xy=True)

def reproj_coords(coords):
    return [list(transformer.transform(x, y)) for x, y in coords]

def reproj_geom(geom):
    t = geom["type"]
    if t == "Point":
        return {"type": t, "coordinates": reproj_coords([geom["coordinates"]])[0]}
    if t in ("LineString", "MultiPoint"):
        return {"type": t, "coordinates": reproj_coords(geom["coordinates"])}
    if t == "Polygon":
        return {"type": t, "coordinates": [reproj_coords(r) for r in geom["coordinates"]]}
    if t == "MultiPolygon":
        return {"type": t, "coordinates": [[reproj_coords(r) for r in p] for p in geom["coordinates"]]}
    return geom

always_xy=True 不能省:它强制 pyproj 按 (经度, 纬度) 顺序走,否则 x/y 和 lat/lon 会反。坐标系的底层原理见 WGS84 与 GCJ-02 坐标系。

几何合法性:自相交与环方向

行政区边界多由多段线拼出来,转完偶尔会有自相交(self-intersection)或环方向不对,渲染时要么出空洞、要么切片失败。用 Shapely 校验再加修:

from shapely.geometry import shape
from shapely.validation import make_valid

def fix_geometry(geom_dict):
    g = shape(geom_dict)
    if not g.is_valid:
        g = make_valid(g)   # 修复自相交、环方向等拓扑错误
    return g.__geo_interface__

make_valid 是 Shapely 2.x 的官方修复函数,比老办法 buffer(0) 稳,不会把尖角磨圆。修完再写回 GeoJSON。

顺手做体积控制

行政区边界动辄上万个顶点。转完 GeoJSON,把坐标量化到 6 位小数(约 0.1 米精度,行政区级别够用),再接 TopoJSON 压缩 继续瘦身。我们就是靠这套流程,把全国省、市、区县边界整理成可服务的 GeoJSON,再据此搭起 行政区划边界数据服务。

Shapefile 与 GeoJSON 怎么选

维度ShapefileGeoJSON
文件形态多文件(.shp/.shx/.dbf)单文件 JSON
编码不声明,中文易乱码UTF-8,中文安全
Web 友好否是
坐标系声明依赖 .prj约定 WGS84
库支持GDAL/Fiona/ArcGIS任意 JSON 解析器

上游交换、桌面 GIS 里 Shapefile 还是主力;要进 Web、进服务,转成 GeoJSON 基本是必选项。三种格式怎么选、怎么转,更全的对比看 GeoJSON 转 KML/SVG/CSV/PNG。