GeoJSON 太大加载慢?用 TopoJSON 转换加压缩瘦身
做行政区划边界数据的人,迟早会撞上一个尴尬:一个省的市界县界全画上,GeoJSON 动辄几 MB,网页一打开就转圈。我在 GeoJSONcn 处理全国四级边界时也踩过这个坑。这篇不绕弯子,只聊怎么把体积压下来。
为什么 GeoJSON 这么占地方
GeoJSON 是纯文本,每个坐标都是 [经度, 纬度] 两个数,外加上一堆引号和逗号。更致命的是相邻行政区共边:北京和河北的省界,在 GeoJSON 里会被两个 Polygon 各存一遍,数据量直接翻倍。
数量级感受一下:全国 4.6 万页边界数据,原始 GeoJSON 加起来几百 MB。直接丢给前端,谁都受不了。
TopoJSON:把共享边界只存一次
TopoJSON 是 GeoJSON 的一个变体,核心思路是拓扑编码:
- 先把所有坐标量化成整数网格(比如 1e6 精度);
- 把相邻边界提取成弧段(arc),每条弧段只存一次;
- 每个地块引用这些弧段的编号,而不是重复写坐标。
效果很直接:北京和河北共用的那条省界,从两份坐标变成一份弧段加两个引用。嵌套层级越多省得越多。实测一个省的边界,TopoJSON 比 GeoJSON 小 60% 到 80%。
转出来的结构长这样:
{
"type": "Topology",
"arcs": [[[116, 40], [117, 40], [118, 41]]],
"objects": {
"provinces": { "type": "GeometryCollection", "geometries": [] }
}
}
前端渲染前用 topojson-client 的 feature() 还原成 GeoJSON 即可,接口几乎无感。
压缩:传输层别浪费
TopoJSON 是结构化瘦身,压缩是传输瘦身,两件事要一起做:
- 源文件直接存 gzip 或 Brotli,.geojson.br 能再小一半多;
- 让边缘(ESA / Cloudflare)按客户端 Accept-Encoding 自动发压缩版;
- 别在 Worker 里现压,那是 CPU 黑洞。压缩交给边缘,源站只管存。
我在 GeoJSONcn 的做法是:边界数据在生成期就转成 TopoJSON 并预压缩,边缘按 AE 头协商发版本。前端拿到的是已经很小的包,解码毫秒级。
一个容易忽略的点:精度不是越高越好
坐标小数位越多文件越大,但屏幕像素就那么些。省级边界留 4 到 5 位小数(约 1 米精度)足够,乡镇级留 5 到 6 位。盲目 15 位浮点,体积白白涨几倍,肉眼根本看不出区别。
最后
GeoJSON 体积大的根因是重复存坐标。解法是两套组合拳:结构上用 TopoJSON 去重共享边界,传输上用 Brotli 预压缩加边缘协商。处理好了,一个省的几 MB 能压到几百 KB,前端加载从转圈变秒开。
如果你手上有全国或某省的行政区划边界,想试试 TopoJSON 瘦身,可以去 GeoJSONcn 扒一份对照。下一篇聊个偏产品的技术点:下载要付费,但导出的文件又不能带水印,这矛盾怎么解?