石家庄GIS数据怎么转GeoJSON?Shapely与Fiona实战技巧(附:代码示例)
《石家庄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
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 中双重验证。这样可以避免最常见的偏移、乱码、空图层和加载卡顿问题。