GeoJSON文本瘦身:数字格式化+属性裁剪把全国区划数据再压一截
很多人以为 GeoJSON 文件大只是坐标点太多,其实文本层面还有两处"隐形脂肪":一是数字没按 JSON 该有的样子存(一堆 .000000000000 尾零、数字被存成字符串),二是 properties 里塞满了永远不会用到的字段。这两块在字符串层面对下载体积的影响,往往被坐标抽稀(series-13)和压缩(series-07/35)盖住了。这篇用几行 Node 代码说明白,怎么在做全国省市区县geojson数据下载或日常 GeoJSON下载之后、发布之前,把 JSON 本身的体积再压一截。
第一刀:数字尾零与长浮点
行政区划边界坐标常见两种浪费写法:
{"type":"Feature",
"geometry":{"type":"Polygon","coordinates":[[[110.000000000000,36.500000000000]]]},
"properties":{"adcode":"110000000","name_c":"北京市"}}
110.000000000000 这种 6 位以上尾零,就是典型的"经纬度是从 double 强转字符串"留下的。它是合法 JSON,png 预览时也画得出来,但用 gzip/Brotli 压前,文本体积已经被撑大了。JSON 数字字符串化时应做最短表示:去掉无意义尾零、必要时保留小数位。对区划边界的坐标,保留 5~6 位小数(约 1 米级)在数据下载场景下就足够,再短会伤拓扑。
第二刀:数字变字符串 vs 字符串变数字
更隐蔽的是该是数字却存成字符串。比如 adcode 用 "110000000" 是为了保前导零(series-37 讲过),这没问题;但面积、人口这类量化字段若也存成 "316.5",体积白白多两个引号,而且前端 sort 时还得先 parseFloat 才能比较。若一个文件里这种字段成百上千,引号和转义的开销是可观的。反过来,纯数字字段降到 number 后,JSON 更小、解析更快。
属性裁剪:白名单比黑名单可靠
最该动刀的是 properties。原始民政部数据字段往往有 name_c、name_e、fullname、extra_zh 等好几个名字字段外加英文别名,还有可能永远用不到的 source、remark。做法是在生成脚本里定义一个白名单,只留前端真正渲染会用到的键(adcode、name、level、center、area 等),其余全部丢弃。白名单比黑名单安全得多——黑名单漏一个字段,体积就悄悄回流。
一张表对号入座
| 体积因素 | 常见毛病 | 瘦身动作 | 参考收益(文本层) |
|---|---|---|---|
| 坐标尾零 | 110.000000000 | round 到 6 位并去尾零 | 省 15%~25% |
| 量化字段字符串 | "area":"316.5" | 转 number | 省 3%~8% |
| 冗余属性键 | 名字/英文/remark 堆叠 | 白名单裁剪 | 省 10%~30% |
| 换行美化 | 每坐标单独成行 | 生成端保持紧凑但可读 | 视格式而定 |
上面百分比是不压缩的 JSON 文本直接衡量;再叠加 Brotli(series-35 那套内容协商)后,实测单省市级文件能再压掉约四成。
拿一个真实案例对账:某个含 2800 多个乡镇面的省级文件,原始 JSON 文本约 41 MB。只做坐标去尾零,文本降到约 31 MB(-24%);再把 population、area 这类量化字段从字符串转成 number,又省 2 MB 出头;最后按白名单砍掉四个从未被前端读取的属性键,直接落到约 24 MB。全程没动一个坐标点,只是把数字和键名理顺。更划算的是这些收益乘数级传导:文本越小,后续 gzip/Brotli 字典越紧凑、CDN 回源和带宽越省,浏览器 JSON.parse 的时间也跟着降——边缘站卡不卡,很多时候就差在字符串解析这一环。
落地:一个 30 行的 Node 瘦身函数
const re = /0+$/; // 去尾零
function fmtNum(n, digits = 6) {
let r = Number(n).toFixed(digits);
if (r.indexOf('.') > -1) r = r.replace(re, '').replace(/\.$/, '');
return r === '-0' ? '0' : r;
}
function slim(geo, keep = ['adcode', 'name', 'level', 'center', 'area']) {
geo.features.forEach(f => {
const p = {};
keep.forEach(k => { if (f.properties[k] !== undefined) p[k] = f.properties[k]; });
f.properties = p;
});
return geo;
}
先跑一个只测 text 层的冒烟:把某区文件瘦身前后的 Buffer.byteLength 打印出来,直观看到引号和尾零省了多少。再交给 series-13 的抽稀和 series-35 的 Brotli,最后接 series-34 的 sha256 版本化和 CDN 分发,整条全国省市区县区划边界数据下载链路的体积就清爽了。
别忘了 JSON 的坑:NaN 与真 null
坐标里若混入 NaN 或 Infinity,JSON.stringify 会直接归成 null,瘦身后再画图可能缺一块。所以数字瘦身后一定做一次合法性扫描:逐坐标检查 isFinite,命中就记录 source/adcode 回原数据修正,而不是带病发布。这也是为什么瘦身要在生成脚本里做、而不是在下游临时跑一遍——改错了能回滚、能对版本。
小结
村级面数量动辄上万,全国村级geojson数据下载时尤其吃体积,文本瘦身在这类大文件上收益最明显。本文讲的就是 GeoJSON 文本层的两处瘦身——数字最短表示 + 属性白名单裁剪,再叠加合法性扫描兜底。它和坐标抽稀、Brotli 压缩、末尾缓存是同一个"数据下载瘦身组合拳"里的不同环节,谁都不该被跳过。需要全国村级geojson数据下载,或省市区县乡镇各层级 2022+ 区划边界的,可以先在页面上预览免费比较,满意再导出,导出按次 ¥1.99。瘦身后的文件改起来更快、分发更省、前端解析更跟手——这三样都对得上。