GeoJSON 坐标精度该留几位?行政区划边界数据下载前的点位瘦身
拿到一份 GeoJSON 边界,很多人第一反应是看坐标对不对,第二反应是「怎么这么大」。一份省级文件十几 MB,坐标占了九成体积。可你有没有想过,这些经纬度里小数点后面那么多位,到底有几个是真实有效的?
坐标精度不是越高越好。留多了顶多体积变大,留少了边界会「锯齿化」,甚至切到相邻区县。今天这篇就把小数点后该留几位、过度保留的代价、以及怎么安全地砍位数讲清楚。
有效精度:小数点后 6 位已经是极限
WGS84 经纬度里,1 度大约对应赤道上 111 公里。把这个当作参考,不同小数位对应的实际距离大约是这样:
| 小数位 | 对应空间分辨率 | 适用场景 |
|---|---|---|
| 0 位 | 约 111 km | 只分省份的概览图 |
| 2 位 | 约 1.1 km | 分市的分界草图 |
| 4 位 | 约 11 m | 区县边界够用 |
| 6 位 | 约 0.11 m | 乡镇、村界、甚至建筑级 |
| 7 位+ | 亚厘米 | 几乎没有真实需求 |
也就是说,全国省市区县geojson数据下载回来,绝大多数省级、市级边界留 5~6 位(亚米到厘米级)就完全够了。村界这种更细的数据,6 位同样覆盖。往上再多,比如 8 位、10 位,空间上已经是亚毫米,对你画图毫无帮助,只是白占字节。做 GeoJSON下载 项目时,我第一件事往往就是确认源头留了几位,超了就先统一砍到 6。
过度保留的真正代价:不止体积
很多人觉得「多几位没坏处」,实际踩过坑才明白没那么简单。按经验,把经纬度从 10 位砍到 6 位,一份区县文件往往能瘦掉 20%~40%——这还是没压缩前。再叠加 Brotli 预压缩,下载体验天壤之别。
更隐蔽的是精度不一致带来的拓扑错位。假如两份边界文件,一份坐标留 6 位、另一份留 4 位,叠到同一张地图上做 dissolve 或求差集时,相邻边界会因这个小差异产生细如发丝的重叠或缝隙,GIS 报 "non-closed rings" 就是这么来的。做过全国省市区县区划边界数据下载的人都懂:宁可全表统一精度,也别一份一个样。
最后,别忽略坐标里的「多余抖动」。很多源数据用 float 型存,116.40400000000002 这种尾巴毫无意义,纯属浮点运算残留。这种位数砍掉,文件更干净,也更好 diff。
用 Node 批量砍位数:round 而不是 truncate
砍位数最忌讳「直接截断」。Math.trunc(116.40465) 得 116,会向零取整产生系统性偏移。正确做法是四舍五入到目标小数位,然后还原成数字——注意,JSON 序列化时 toFixed(6) 会保留 6 位,但把字符串 Number() 回去,多余的零才会被丢干净:
const fs = require('fs');
const gj = JSON.parse(fs.readFileSync('santai.geojson', 'utf8'));
// 只动经纬度坐标,depth 对应 GeoJSON 坐标嵌套层数(点/线/面/多点…)
function roundCoords(coords, depth = 0, digits = 6) {
if (depth === 2) { // 到 [lng, lat] 这一层就收手
coords[0] = round1(coords[0]);
coords[1] = round1(coords[1]);
return;
}
for (const c of coords) roundCoords(c, depth + 1, digits);
}
function round1(v) { return Math.round(v * 1e6) / 1e6; }
for (const f of gj.features) roundCoords(f.geometry.coordinates);
fs.writeFileSync('santai.clean.geojson', JSON.stringify(gj));
这段对 Polygon 的 coordinates[0](外环)和 coordinates[n](内环)都生效,因为它按嵌套深度切,到 [lng, lat] 就停。改完先 JSON.stringify 再落盘,顺带跑一遍几何校验,确认没把环切坏。
跑完后验证一下体积,通常省出来的字节数很可观。想查看前后数据一致性,最稳的办法是只比较顶点数量,顶点没少、面积基本不变,就没问题。真担心被动过手脚,就给砍完位数的文件算个 SHA-256 校验和留底,跟原文对照,跟之前写过的那篇校验文章是一个套路。经历过太多次 GeoJSON下载 回来一用就出幺蛾子的情况,现在我对精度和校验都特别上心。
砍位数要放到整个下发链路里看
单独把坐标精度调对只是第一步。很多做全国省市区县区划边界数据下载分发的站,文件是「切块 + 压缩 + 取摘要」一路下来的:区县级切成小块、按 adcode 前缀分片、每片再走 Brotli 预压缩。精度砍位这个动作,必须排在压缩之前做——压缩只是把同样的内容压得更小,而砍位数是从根源减少要表达的信息量,两者叠加效果最好。顺序反过来,先压缩再砍位,等于白压了一遍。
我踩过的一个真实场景是这样:一份村级数据原始 42 MB,坐标留了 9 位。先砍到 6 位,体积掉到 26 MB;再跑一遍 Brotli,最后线上分发只有 7 MB 出头。几何完全没变,边界看起来跟原来一模一样,但传输时间和流量成本明显下降,边缘节点缓存的字节也更少。
顺带说,精度定好之后,文件名里最好带版本或日期锚点(如 santai_202608_v2.geojson)。因为静态站有覆盖写和边缘缓存问题,不带版本号,做全国省市区县geojson数据下载分发的用户很容易拿到缓存里的旧版,回头又来反馈「数据没更新」。
定精度前先想清楚几个前提
- 统一优先级最高:整个数据集用同一个位数,别让省界 6 位、区县 4 位混着来;
- 保留原生坐标,别用投影后坐标:有些工具直接对 EPSG:3857 的米制坐标砍位,几何合法但一回到经纬度就错位,务必在原始 WGS84 上做;
- 6 位是安全默认:绝大多数边界 5~6 位足够,等你真正需要像素级精度再加位数也不迟。
至于全国村级geojson数据下载,元素动辄成千上万,一份文件常几十 MB,精度砍一档省下的体积比任何压缩都直接,排序、面积、跨文件 join 也都跟着受益。
需要验证精度的玩家,可以在 geojsoncn.com 上先在预览页看边界贴不贴合底图(预览免费),精度够了再导出正式文件去用(导出 ¥1.99/次)。坐标位数这处细节,源头一刀切干净,比事后在十个下游里各补一次省心得多。