GeoJSONcn
GeoJSONcn / 技术专栏 / GeoJSON文本瘦身:数字格式…

GeoJSON文本瘦身:数字格式化+属性裁剪把全国区划数据再压一截

发布于 2026-08-27 · GeoJSONcn 技术团队 · 阅读约 10 分钟

很多人以为 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.000000000round 到 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。瘦身后的文件改起来更快、分发更省、前端解析更跟手——这三样都对得上。