石家庄GIS数据怎么转GeoJSON?Shapely与Fiona实战技巧(附:代码示例)

编程与开发
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

《石家庄GIS数据怎么转GeoJSON?Shapely与Fiona实战技巧(附:代码示例)》这篇教程面向正在处理石家庄行政区、道路、POI、管线、地块等矢量数据的 GIS 学生和工程人员,重点解决一个具体问题:如何用 Python 中的 Fiona 读取 GIS 数据、用 Shapely 检查和处理几何,并稳定导出 GeoJSON。

引言:石家庄GIS数据转GeoJSON为什么容易出问题

很多石家庄本地项目会拿到 Shapefile、GPKG、CAD 转出的面线数据,后续又需要在 Leaflet、OpenLayers、Mapbox、Cesium 或接口服务中使用 GeoJSON。看起来只是“格式转换”,实际经常遇到坐标偏移、中文字段乱码、几何无效、文件过大、WebGIS 加载卡顿等问题。

如果只是用软件“另存为 GeoJSON”,小数据通常没问题。但当数据包含复杂多边形、多个图层、中文属性、地方坐标系或历史采集数据时,建议使用 Fiona 与 Shapely 做一次可控转换。这样可以在导出前检查坐标系、修复几何、筛选字段、统一编码,并为 WebGIS 使用做优化。

石家庄GIS数据转GeoJSON Shapely与Fiona实战流程
石家庄GIS数据转GeoJSON的推荐流程:先读数据,再检查几何和坐标系,最后导出并在WebGIS中验证。

背景:哪些石家庄GIS数据适合转成GeoJSON

GeoJSON 是基于 JSON 的地理空间数据格式,常用于 WebGIS 前端展示和轻量级接口传输。对于石家庄 GIS 数据,以下场景比较适合转 GeoJSON:

  • 在 Leaflet 或 OpenLayers 中展示石家庄行政边界、街道、兴趣点。
  • 把小范围道路、地块、网格、采样点作为接口返回给前端。
  • 制作静态专题图,例如石家庄各区县统计图、项目范围图。
  • 将 Shapefile 数据交给不使用 ArcGIS 或 QGIS 的开发人员。

但并不是所有数据都适合直接转成 GeoJSON。如果是全市级别的高精度建筑轮廓、道路中心线、管网、地块宗地图,文件可能非常大。GeoJSON 是文本格式,体积通常比 Shapefile 或 GeoPackage 更大,前端加载也更慢。

常见输入数据格式

  • Shapefile:常见扩展名包括 .shp、.shx、.dbf、.prj,必须保持同名文件在同一目录。
  • GeoPackage:扩展名为 .gpkg,适合保存多个图层,编码和字段支持更稳定。
  • GeoJSON:如果只是清洗和压缩,也可以作为输入重新处理。
  • CAD转换数据:通常需要先在 QGIS 或 ArcGIS Pro 中转为矢量图层,再用 Python 处理。

原理:Fiona负责读写,Shapely负责几何

在 Python GIS 工作流中,Fiona 和 Shapely 的分工很清楚。Fiona 主要负责读取和写入矢量数据文件,Shapely 主要负责几何对象的判断、修复、裁剪、简化和空间关系计算。

工具 主要作用 在石家庄GIS数据转GeoJSON中的用途
Fiona 矢量数据读写 读取 Shapefile、GeoPackage,获取字段、坐标系和要素
Shapely 几何处理 检查无效几何、修复自相交面、简化复杂边界
pyproj 坐标转换 将本地投影坐标转换为 WGS84,经纬度适配 GeoJSON
json JSON 输出 控制 GeoJSON 的输出结构和编码

GeoJSON 的标准坐标通常使用 WGS84 经纬度,也就是 EPSG:4326。很多 WebGIS 库可以直接加载 EPSG:4326 的 GeoJSON,再在地图中投影到 Web Mercator。对于石家庄 GIS 数据,如果源数据是 CGCS2000 投影、高斯克吕格坐标、地方坐标或未知坐标系,就不能直接导出给前端使用。

判断是否需要坐标转换的简单方法:如果坐标值类似 114.5、38.0,多半是经纬度;如果坐标值类似 500000、4200000 或更大的平面坐标,就需要先确认投影坐标系。

步骤:用Shapely与Fiona把石家庄GIS数据转GeoJSON

步骤1:准备Python环境

建议使用 Conda 环境安装 Fiona、Shapely 和 pyproj。Fiona 依赖 GDAL,直接用 pip 在部分 Windows 环境中可能会遇到编译或动态库问题。

conda create -n gis_geojson python=3.11
conda activate gis_geojson
conda install -c conda-forge fiona shapely pyproj

如果你已经在项目中使用 GeoPandas,也可以安装 GeoPandas。但本文重点演示 Fiona 与 Shapely 的底层写法,便于理解每一步发生了什么。

conda install -c conda-forge geopandas

步骤2:检查输入数据的坐标系和图层信息

以下代码用于查看石家庄 GIS 数据的驱动、坐标系、字段结构和前几条要素。这里假设输入文件是 Shapefile。

import fiona

input_path = r"data/shijiazhuang_boundary.shp"

with fiona.open(input_path, "r") as src:
    print("Driver:", src.driver)
    print("CRS:", src.crs)
    print("Schema:", src.schema)
    print("Feature count:", len(src))

    for i, feature in enumerate(src):
        print(feature)
        if i >= 2:
            break

重点看两个信息:

  • CRS:是否能识别坐标系。如果为空,需要先从数据提供方确认。
  • Schema:字段名称、字段类型和几何类型是否符合预期。

步骤3:判断是否需要转为EPSG:4326

如果源数据已经是 EPSG:4326,可以直接输出 GeoJSON。如果源数据是投影坐标系,需要使用 pyproj 做坐标转换。下面的例子将源坐标系转换为 EPSG:4326。

如果 Fiona 能读取到 CRS,可以直接使用源 CRS;如果读取不到,需要手动指定,例如 EPSG:4547、EPSG:4527 或项目使用的 CGCS2000 分带坐标系。具体 EPSG 号必须以你的数据说明或 .prj 文件为准,不要凭城市名称猜测。

步骤4:编写完整转换脚本

下面是一个可直接改路径使用的完整脚本。它完成了读取 Shapefile、修复无效几何、坐标转换、字段保留和导出 GeoJSON 的流程。

import json
import fiona
from shapely.geometry import shape, mapping
from shapely.ops import transform
from pyproj import Transformer

input_path = r"data/shijiazhuang_boundary.shp"
output_path = r"output/shijiazhuang_boundary.geojson"

target_crs = "EPSG:4326"

# 如果源数据没有CRS,请在这里手动指定,例如:
# source_crs = "EPSG:4527"
source_crs = None

features = []

with fiona.open(input_path, "r") as src:
    if source_crs is None:
        source_crs = src.crs

    if not source_crs:
        raise ValueError("源数据缺少坐标系信息,请先确认.prj文件或手动指定source_crs。")

    transformer = Transformer.from_crs(source_crs, target_crs, always_xy=True)

    for feature in src:
        geom = shape(feature["geometry"])

        if geom.is_empty:
            continue

        # 修复常见无效面,例如轻微自相交
        if not geom.is_valid:
            geom = geom.buffer(0)

        if geom.is_empty or not geom.is_valid:
            continue

        # 坐标转换到WGS84
        geom_wgs84 = transform(transformer.transform, geom)

        # 根据需要保留字段,避免GeoJSON过大
        props = dict(feature["properties"])
        keep_fields = ["name", "区县", "街道", "code", "type"]
        clean_props = {
            key: props.get(key)
            for key in keep_fields
            if key in props
        }

        features.append({
            "type": "Feature",
            "geometry": mapping(geom_wgs84),
            "properties": clean_props
        })

geojson = {
    "type": "FeatureCollection",
    "name": "shijiazhuang_boundary",
    "crs": {
        "type": "name",
        "properties": {
            "name": target_crs
        }
    },
    "features": features
}

with open(output_path, "w", encoding="utf-8") as f:
    json.dump(geojson, f, ensure_ascii=False)

print(f"转换完成,共输出 {len(features)} 个要素:{output_path}")

这段代码中的关键点有三个:

  • always_xy=True:保证坐标转换时按 x、y 顺序处理,即经度、纬度或东坐标、北坐标。
  • geom.buffer(0):常用于修复轻微自相交面,但不是万能修复方法。
  • keep_fields:只保留 WebGIS 必要字段,避免 GeoJSON 属性过多导致体积变大。

步骤5:如果输入是GeoPackage怎么办

GeoPackage 可能包含多个图层。需要先列出图层名称,再指定 layer 参数读取。

import fiona

gpkg_path = r"data/shijiazhuang.gpkg"

layers = fiona.listlayers(gpkg_path)
print(layers)

with fiona.open(gpkg_path, layer=layers[0]) as src:
    print(src.crs)
    print(src.schema)
    print(len(src))

在完整转换脚本中,只需要把打开数据的部分改成:

with fiona.open(gpkg_path, layer="boundary") as src:
    # 后续处理逻辑不变
    pass

步骤6:验证导出的GeoJSON

石家庄 GIS 数据转 GeoJSON 后,不建议只看文件是否生成成功,还要做空间位置和属性验证。

  • 用 QGIS 打开 GeoJSON,检查图层是否落在石家庄市附近。
  • 检查经纬度范围是否大致在东经 113 到 115、北纬 37 到 39 附近。
  • 随机查看几个区县、街道或地块属性是否保留正确。
  • 在 Leaflet 或 OpenLayers 中加载,检查是否出现偏移、反向、空白。
  • 检查 GeoJSON 文件大小,如果超过几十 MB,要考虑切片、简化或改用矢量瓦片。

可以用下面的代码快速查看导出坐标范围:

import json
from shapely.geometry import shape

geojson_path = r"output/shijiazhuang_boundary.geojson"

with open(geojson_path, "r", encoding="utf-8") as f:
    data = json.load(f)

bounds = []

for feature in data["features"]:
    geom = shape(feature["geometry"])
    bounds.append(geom.bounds)

minx = min(b[0] for b in bounds)
miny = min(b[1] for b in bounds)
maxx = max(b[2] for b in bounds)
maxy = max(b[3] for b in bounds)

print("GeoJSON bounds:", minx, miny, maxx, maxy)

常见坑:石家庄GIS数据转GeoJSON失败的高频原因

1. 坐标系不明确,导致地图偏移

这是最常见的问题。很多石家庄项目数据只有 .shp、.dbf、.shx,没有 .prj 文件。此时 Fiona 读不到 CRS,转换脚本也无法判断源坐标系。如果强行按 EPSG:4326 输出,前端地图会出现偏移或完全看不到图层。

处理建议:

  • 向数据提供方确认坐标系、中央经线、带号和单位。
  • 在 QGIS 中加载底图进行叠加检查。
  • 不要仅凭“石家庄数据”猜 EPSG 代码。

2. Shapefile中文字段乱码

Shapefile 的属性表来自 DBF 文件,中文编码可能是 GBK、UTF-8 或其他编码。Fiona 读取时如果出现乱码,可以尝试指定 encoding。

with fiona.open(input_path, "r", encoding="GBK") as src:
    for feature in src:
        print(feature["properties"])

如果 GBK 不对,再尝试 UTF-8。实际项目中,最好先用 QGIS 打开属性表确认字段是否正常。

3. 几何无效导致导出中断

自相交面、重复节点、空几何、多部件异常都可能导致转换失败。Shapely 的 is_valid 可以提前检查。

from shapely.geometry import shape

bad_count = 0

with fiona.open(input_path, "r") as src:
    for feature in src:
        geom = shape(feature["geometry"])
        if not geom.is_valid:
            bad_count += 1

print("无效几何数量:", bad_count)

对于轻微问题,buffer(0) 有时可以修复;对于严重拓扑错误,建议在 QGIS 中使用“修复几何”工具,或在 ArcGIS Pro 中使用 Repair Geometry 工具。

4. GeoJSON太大,WebGIS加载慢

如果石家庄 GIS 数据包含大量道路、建筑物或地块,直接转 GeoJSON 可能产生很大的文本文件。前端加载慢不是代码写错,而是数据组织方式不合适。

优化方向包括:

  • 只保留必要字段。
  • 按区县、业务区域或瓦片范围拆分文件。
  • 对展示型边界做适度简化。
  • 大规模数据改用矢量瓦片、PostGIS 接口或 WFS 分页。

5. 简化几何导致边界失真

Shapely 的 simplify 可以减少节点,但容差设置过大时会让行政边界、河流、道路形状明显失真。

# 注意:这里的容差单位取决于当前坐标系
simple_geom = geom_wgs84.simplify(0.0001, preserve_topology=True)

如果已经转到 EPSG:4326,容差单位是经纬度,不是米。若要按米控制简化程度,建议先在投影坐标系下简化,再转为 WGS84。

方法比较:Fiona与Shapely、QGIS、GeoPandas怎么选

方法 适合场景 优点 注意事项
QGIS另存为GeoJSON 一次性转换、小数据、手工检查 操作直观,适合初学者 批量处理和字段清洗不够灵活
Fiona + Shapely 批量转换、几何检查、可控输出 流程透明,适合工程脚本 需要理解坐标系和几何对象
GeoPandas 表格化分析、空间筛选、快速处理 代码简洁,适合数据分析 大数据量时内存压力较大
GDAL ogr2ogr 命令行批处理、格式转换 性能好,参数丰富 复杂属性清洗和几何逻辑不如Python灵活
PostGIS导出 数据库集中管理、多用户查询 适合生产环境和空间查询 需要数据库部署和SQL能力

如果你只是把一个石家庄区县边界 Shapefile 转成 GeoJSON,QGIS 足够。如果你需要对多个图层反复转换、修复几何、过滤字段并交付给 WebGIS 前端,Fiona 与 Shapely 更适合。

检查清单:导出前后必须确认这些项

  • 输入文件完整:Shapefile 至少应包含 .shp、.shx、.dbf,最好有 .prj。
  • 坐标系明确:确认源 CRS,不确定时不要直接转换。
  • 目标坐标系正确:WebGIS 通常优先输出 EPSG:4326 的 GeoJSON。
  • 中文属性正常:检查编码,必要时指定 GBK 或 UTF-8。
  • 几何有效:统计无效几何数量,必要时用 Shapely 或 QGIS 修复。
  • 字段经过筛选:只保留前端展示、弹窗、筛选需要的字段。
  • 文件大小可接受:大文件不要直接塞给浏览器一次性加载。
  • 地图位置正确:在 QGIS 和 WebGIS 中都验证一次空间位置。
  • 边界没有明显变形:如果做了 simplify,需要人工抽查关键区域。

FAQ:石家庄GIS数据转GeoJSON常见问题

Q1:石家庄GIS数据转GeoJSON一定要用EPSG:4326吗?

不一定,但强烈建议用于通用 WebGIS 的 GeoJSON 输出为 EPSG:4326。很多前端库默认按经纬度解释 GeoJSON 坐标。如果你输出的是投影坐标,前端需要额外处理投影,否则很容易出现图层不显示或位置错误。

Q2:Fiona读取Shapefile时CRS为空怎么办?

说明数据缺少坐标系信息,或者 .prj 文件丢失。此时不要直接转换。应先联系数据提供方确认坐标系,或通过 QGIS 叠加已知底图进行判断。确认后在脚本中手动设置 source_crs。

Q3:Shapely的buffer(0)能修复所有无效几何吗?

不能。buffer(0) 对轻微自相交面有帮助,但可能改变几何形状,也可能修复失败。生产数据建议先统计无效几何,再结合 QGIS“修复几何”、ArcGIS Pro Repair Geometry 或 PostGIS ST_MakeValid 处理。

Q4:为什么导出的GeoJSON在Leaflet中没有显示?

常见原因包括坐标系不是 EPSG:4326、坐标顺序错误、文件路径错误、浏览器跨域限制、GeoJSON 结构不合法、图层范围不在当前地图视野内。建议先用 QGIS 打开 GeoJSON,再检查浏览器控制台报错。

Q5:石家庄道路数据转GeoJSON后很卡怎么办?

道路数据通常要素多、节点多,直接转成单个 GeoJSON 容易卡顿。可以按行政区拆分、按视野请求、简化几何、减少字段,或者改用矢量瓦片。对于生产系统,建议把道路数据放入 PostGIS,由后端按范围查询后返回。

Q6:能不能直接用GeoPandas一行代码转换?

可以。简单数据可以使用 GeoPandas 的 read_file 和 to_file。但本文使用 Fiona 与 Shapely,是为了更清楚地控制读取、几何修复、坐标转换和字段筛选。工程项目中,这些细节往往比“一行转换”更重要。

import geopandas as gpd

gdf = gpd.read_file(r"data/shijiazhuang_boundary.shp")
gdf = gdf.to_crs("EPSG:4326")
gdf.to_file(r"output/shijiazhuang_boundary.geojson", driver="GeoJSON")

结论:用可验证的流程完成石家庄GIS数据转GeoJSON

石家庄GIS数据转GeoJSON的关键不只是“格式转换”,而是把坐标系、几何质量、字段编码和前端加载需求一起处理好。Fiona 负责稳定读写,Shapely 负责几何检查和修复,pyproj 负责坐标转换,这套组合适合做可复用、可审计的 GIS 数据处理脚本。

实际项目中,建议按这条路线执行:先确认源数据坐标系,再用 Fiona 读取图层和字段,用 Shapely 检查无效几何,必要时转换为 EPSG:4326,最后导出 GeoJSON 并在 QGIS 与 WebGIS 中双重验证。这样可以避免最常见的偏移、乱码、空图层和加载卡顿问题。