GeoJSON 转 GeoPackage?把全国省市区县乡镇边界装进一个 SQLite 离线包
> 写稿用正文(下)。
做全国省市区县乡镇街道村geojson数据下载的整理时,我一开始也是老老实实一个省一个 .geojson 往上传。等省级下钻到乡镇,才发现问题:一份全国乡镇边界拆开就有几十个文件,浏览器里点来点去全靠一个个 fetch,边缘缓存一多,依赖关系乱成一锅粥。后来我把思路换成「先转 GeoPackage,再按需切片发布」,整个站点的静态文件数量和更新成本都降了一大截。
GeoPackage(.gpkg)本质就是一个 SQLite 文件,表结构里塞了空间字段和扩展元数据。它到底好在哪,为什么适合做行政区划数据的离线分发?这篇就讲讲我的取舍、一张表对比,以及一段能把 GeoJSON 转成 .gpkg 的落地代码。
为什么用 GeoPackage 装行政区划,而不是抱着 .geojson 不放
先说结论:GeoJSON 是交换格式,GeoPackage 是离线存储格式。两者不是替代关系,是链路里的一前一后。
| 维度 | GeoJSON | GeoPackage (.gpkg) |
|---|---|---|
| 本质 | 纯 JSON 文本,一个 FeatureCollection | SQLite 单文件,含空间表 + 几何插件 |
| 多图层 | 一个文件一个图层,要拆一堆 | 一个文件可放省市区县乡镇村多组图层 |
| 属性查询 | 得自己遍历 features | SQL WHERE 随手筛,还能建空间索引 |
| 读大文件 | 全部 JSON.parse 进内存 | 流式/分页读,范围查询只取需要的 |
| 编辑 | 整文件重写 | SQL 事务,改一条不影响其他 |
| 工具链 | 浏览器/前端友好 | desktop 端(QGIS/ArcGIS/POSTGIS)友好 |
对「全国村级geojson数据下载」这类低频、量大、以离线分析为主的场景,GeoPackage 的单文件 + SQL 能力优势非常明显。对浏览器在线预览,反过来还是切片后的轻量 GeoJSON 更划算。
用 Python 把 GeoJSON 批量转成 GeoPackage
GeoPackage 官方推荐用 GDAL/OGR,但如果你只想在本地把一套省市区县边界转进一个 .gpkg,用 fiona + shapely 就够了。下面这段把多份 GeoJSON 合并进同一个 .gpkg 的不同图层:
import fiona
GPKG='/data/adcode/全国边界.gpkg'
layers=[
('province','省','E:/geo/省/*.geojson'),
('city','市','E:/geo/市/*.geojson'),
('district','区县','E:/geo/区县/*.geojson'),
]
for layer_name,layer_title,pattern in layers:
rows=[]
for path in util.glob.iglob(pattern):
with fiona.open(path,'r') as src:
# 用中段 id 做图层外键,避免跨层属性重复
for f in src:
f['properties']['_layer']=layer_name
rows.append(f)
# 'GPKG' 驱动 + 同一文件多图层,force 覆盖旧层
with fiona.open(GPKG,'w',driver='GPKG',
schema=rows[0]['schema'],crs=rows[0]['crs']) as dst:
for f in rows: dst.write(f)
几个踩过的坑提醒你:
- 坐标系必须先统一成 EPSG:4326。
fiona写 .gpkg 默认带 EPSG,混了投影就会在 QGIS 里整体错位——转之前先src.crs看一眼,非 4326 的用 pyproj 提前transform。 - 字段名别用中文做数据库列名。SQLite 列名建议英文 + 下划线(
adcode、town_name),中文留在properties里当值,SQL 查询才不踩编码坑。 - 几何必须合法、单层。写进空间表前先
shapely.make_valid兜底,否则 PostGIS 或 QGIS 直接读会报non-closed ring。
GeoPackage 拿回去怎么用,前端又能白嫖什么
后端我每天把清洗好的边区转一次 .gpkg 归档,前端喂给浏览器的仍是按需切片的轻量 GeoJSON——两者并行,单一真相源只在生成脚本里。QGIS 打开 .gpkg 直接点开图层树,不用再拼一堆文件;要查某个乡级单位,SQL 面板一句 SELECT * FROM town WHERE adcode LIKE '510722%' 就出来了,这在纯 GeoJSON 里得手写一堆遍历。
如果你也想让浏览器能读 .gpkg,别急着上大库——@ngageoint/geopackage 这个纯 JS 包就能读空间表,配合 Leaflet 的 L.geoJSON 把拿回来的 FeatureCollection 画出来。离线场景(船舶、野外、内网)特别吃这一套:一个 .gpkg 拷进去,整套边界都在手上,不依赖任何 CDN。
顺带一提,把 .gpkg 归档文件丢进对象存储做版本化,配合之前讲的 SHA-256 校验和,更新边界就是「换一个文件」的事,边缘节点按文件名缓存也不会串旧版。想让省市区县边界数据下载后能长期离线使用,这个组合值得一试。
小结
GeoJSON 负责在浏览器里「活」,GeoPackage 负责在本地「存」。两步走:构建期用 fiona 把省市区县乡镇村各级 GeoJSON 合并进一个 .gpkg,发布期切片成前端要的轻量文件。这样静态站的文件数可控、更新原子化,桌面端拿到 .gpkg 又能直接开分析。
> 本文属于 GeoJSONcn 技术专栏,GeoJSON 数据在线预览暂免费,导出(含 .gpkg 转换服务)按次计费 ¥1.99/次。站点为静态生成 + 边缘加速架构,覆盖全国省市区县乡镇街道村各级边界数据下载。