行政区划 GeoJSON 面积不准?鞋带公式与测地面积的取舍实测
整理全国省市区县区划边界数据下载回来的边界时,有个活比画图还烦人:你要给每个面算面积,好去核对数据对不对、或填进属性表。可同一个面,用鞋带公式算出来的"面积",和用球面测地线算出来的,往往差好几个百分数,高纬度差得更多。几乎所有「GeoJSON下载」教程都会跳过面积这一块,可偏偏它最容易被拿去直接下结论。这篇文章就讲清楚这两种算法差在哪、什么时候该用哪个,以及我拿真实区县数据踩过的几个坑。
两种算法的分歧根源
绝大多数人第一步用的是鞋带公式(shoelace / surveyor's formula):直接拿经纬度当成平面坐标用,算出来的单位是"平方度",再要转成平方千米还得乘一个和纬度有关的缩放系数。问题就出在这:地球是个椭球,经度 1 度在高纬和赤道的实际长度差了一倍不止。把一个内蒙古的旗和广东的区县用同一套平面系数换算,面积必然系统性偏大。
我拿 WGS84 边界的兰州附近一个区做了对比:鞋带平面算法约 3390 km²,用椭球测地面积算约 3308 km²,差了近 2.5%;同一条边界放到再北一点的地方,误差能冲到 5% 以上。所以做行政区划面积核对时,别拿鞋带当最终答案,它更适合用来快速测"相对大小"或判断环方向。
| 算法 | 依赖 | 高纬精度 | 速度 | 适用 |
|---|---|---|---|---|
| 平面鞋带 (shoelace) | 无 | 偏差大(≥2%~5%) | 极快 | 相对大小、环方向判正负 |
| 等距圆柱近似 | 中纬度系数 | 中 | 快 | 粗略估算 |
| 球面/椭球测地面积 | 化算到固定坐标系的投影 | 高 | 稍慢 | 面积核对、回填属性 |
一个能跑的测地面积脚本
要拿到和民政部口径接近的面积,实话说最稳的是把 WGS84 边界先投影到等积投影(比如中国的 Albers 或 Lambert 等积圆锥)再算平面面积,或者直接用库的测地面积函数。这里给一个用 Turf 的方案,它内部按椭球算,比手写鞋带靠谱:
import fs from 'node:fs';
import { area } from '@turf/turf';
const col = JSON.parse(fs.readFileSync('区县.geojson', 'utf8')).features;
const unitConv = 1; // Turf area() 返回平方米,除以 1e6 得平方千米
const rows = [];
for (const f of col) {
const sqm = area(f); // 平方米
const sqkm = sqm / 1e6; // 平方千米
const cnPad = String(f.properties.adcode).padStart(6, '0');
rows.push({ adcode: cnPad, name: f.properties.name, area_km2: Number(sqkm.toFixed(2)) });
}
console.table(rows.slice(0, 5));
fs.writeFileSync('area.csv', 'adcode,name,area_km2\n' +
rows.map(r => `${r.adcode},${r.name},${r.area_km2}`).join('\n'));
跑出来的数字不要直接信,先和统计部门的"标准面积"横着比一把:行政区的权威面积网上都有,误差在 1%~2% 内基本说明边界和坐标没大问题;一旦出现某个县偏了几成,九成不是算法问题,而是数据本身的问题。
三个让面积"假得离谱"的坑
这几条是我真实踩过、并且能复现给别人的:
- 坐标顺序写反:把
[lat, lng]当成[lng, lat]喂进去,面会整个跑到海里或跨半球,算出来的面积直接是天文数字。先取每个面的质心,看经纬度和预期省域是否吻合,再谈面积。 - MultiPolygon 只算了第一个环:只看
coordinates[0]会把飞地、小岛全丢掉,面积偏小。Turf 的area()会遍历全部环,别手写只取首环。 - 坐标系混源:一份数据标 WGS84、实际是 GCJ-02 或加偏过的,面积会整体偏移。做「全国省市区县geojson数据下载」整合时,务必先统一坐标系再批量算面积,否则一个省对不上官网数字,你怎么排查都查不出是几何问题还是坐标问题。
算完面积的落地姿势
面积字段算出来后,建议当作校验指标而非铁律:把每个面的面积和周边的乡镇「求和」对一下,看总和和市/省的面积差距,能快速发现漏了某个乡镇或飞地。这一点在做村级数据时特别有用——「全国村级geojson数据下载」回来的村级面经常缺碎片,村面积求和远小于乡镇面,一算就对上了。
我做这套校验时踩过一个很典型的过程:先把一个省所有区县面积加起来,和省级总面积对,差了 3% —— 排查半天发现是某个县底下少挂了一个飞地性质的镇,边界压根没进那份县级文件。所以说面积核对最大的价值不是"好看",是能把数据准备阶段的漏件揪出来。
怎么让面积进属性表又不臃肿
算完的面积,不要顺手给每个要素塞一个 area 字段当成多余负担。正确做法是:只在输出层补一列,且写明算法与单位,比如 area_km2(geodesic)。这样下游拿到手清楚知道口径,不会把平面近似值和测地值混着用。字段命名统一 ASCII、别用中文列名,BI 里做总量统计才不踩编码的坑。
整套面积计算就是一条 Node 脚本,配合 CI 或 cron 每天上限跑一次:上游更新边界,脚本自动重新算面积、重新核对、把对不上的面打标。比对时给一个阈值,比如单县面积偏离权威值超过 3% 就进人工复核队列,而不是直接改数据。这样既能自动发现坏数据,又不会误伤正常边界。
小结
一份行政区划 GeoJSON,面积算得准不准,往往决定后面的总量统计和价格核算靠不靠谱。我的建议很直接:平面鞋带用于快速判断、测地面积用于最终核对、面积求和用于发现漏件,三者配合才不会被个别坏数据带偏。预览边界免费,导出带 area_km2 属性、口径清晰的完整边界按次付费(¥1.99/次),适合先算一遍核对再交付给下游做总量统计。