地图定位为什么偏了 500 米?GPS 与火星坐标实测

19 阅读7分钟

把北京一处 WGS-84 坐标 39.904200, 116.407400 输进这个页面,它算出的 GCJ-02 是 39.905603, 116.413642。

两个数字一减,位置被挪走了 554.85 米,方向 73.66°。

广州更远,620.97 米;乌鲁木齐最近,也有 265.02 米。

这不是算错了,是国内地图的通行规则。

图 1:首页——左侧坐标输入与反解结果,右侧偏移可视化、8 城对照表与自检按钮

在线案例与源码:atomgit.com/deli007/dem…

一、为什么值得看:那 500 米偏在哪

手机 GPS 拿到的坐标叫 WGS-84,是国际通用的坐标系。

高德、腾讯这些国内地图用的是 GCJ-02,俗称火星坐标。

两者原点不同,中间隔着一层加密偏移。

把 GPS 原始坐标直接丢给国内地图,点会落在几百米外。

反过来,把地图上取到的坐标交给需要 WGS-84 的设备,误差一样存在。

做定位打卡、轨迹回放、无人机航点、跨平台数据对账的人,迟早会撞上这件事。

于是我把这层偏移做成了一个单页网页:输入任意坐标,看它偏多少、偏到哪个方向。

二、先给结论:8 个城市的偏移实测

页面预置了 8 个城市,点一下就把 WGS-84 坐标填进去重算。

下表的偏移距离和方位角都是页面实时算出来的,方位角以正北为 0、顺时针增大。

城市WGS-84 纬度,经度GCJ-02 纬度,经度偏移距离方位角
广州23.129100, 113.26440023.126423, 113.269730620.97 m118.64°
深圳22.543100, 114.05790022.540383, 114.063014605.91 m119.91°
北京39.904200, 116.40740039.905603, 116.413642554.85 m73.66°
上海31.230400, 121.47370031.228458, 121.478223481.25 m116.66°
西安34.341600, 108.93980034.340044, 108.944456461.12 m112.03°
成都30.572800, 104.06680030.570346, 104.069305363.30 m138.68°
拉萨29.650000, 91.10000029.647300, 91.101568336.28 m153.21°
乌鲁木齐43.825600, 87.61680043.826805, 87.619650265.02 m59.62°

极差 355.95 米,最大和最小差了 1.34 倍。

方向也散得厉害。广州朝东南偏东,乌鲁木齐朝东北,拉萨朝东南。

图 2:切到广州,偏移跳到 620.97 米,连线长度与方位角同步改变

三、准备环境:进入码道 Web

浏览器打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source…

登录后直接在对话窗口输入需求,不用装软件。

码道有三种使用方式:WebUI 浏览器对话、TUI 终端命令行和桌面 IDE 插件。本文用 WebUI 版演示。

四、需求原文:把要验的东西先写死

给码道的需求里,关键的几条是这些。

  1. 输入 WGS-84 经纬度,输入即算,实时显示 GCJ-02、偏移距离和方位角。
  2. 北京、上海、广州、深圳、成都、西安、拉萨、乌鲁木齐 八个预设按钮。
  3. GCJ-02 转回 WGS-84 用迭代逼近,最多 10 次,显示往返误差。
  4. 一行一个城市的对照表,按偏移距离从大到小排。
  5. canvas 画出原始点、偏移点和两点连线,连线旁标注偏移米数。
  6. 页面上放一个「运行自检」按钮,内置至少 8 条断言,点击后显示通过数和失败原因。
  7. 输入非法时给中文提示,不抛异常、页面不崩。
  8. 深色科技风,中文界面。

第 6 条是有意写死的:页面上有可点开的断言,文章里的测试数字才有出处。

五、页面上有哪几块

左侧是输入与结果面板,上面填经纬度,下面列 GCJ-02、偏移距离、方位角、反解结果和往返误差。

右上角是偏移可视化,红点是原始点,蓝点是偏移点,中间那条虚线就是这 500 米。

右侧下方是 8 城对照表和自检断言。

图 3:切到乌鲁木齐,偏移降到 265.02 米——同一套算法,纬度越高偏得越少

六、代码亮点:椭球、多项式与反向迭代

WGS-84 转 GCJ-02 的公开实现,核心是 Krasovsky 1940 椭球的参数再加两组正余弦多项式。

页面里长半轴取 6378245.0,偏心率平方取 0.00669342162296594323。

多项式的自变量是经度减 105、纬度减 35,这是国内流传最广的一版拟合写法。

反向转换没有解析解,只能迭代。先假设 WGS-84 等于 GCJ-02,正算一次,用误差修正,再算一次。

页面最多迭代 10 次,或者两次误差都小于 1e-9 就停。

北京 3 次就收敛,其余点位 4 次。

距离用 Haversine,地球半径取 6371000 米;方向用初始方位角公式,正北为 0。

canvas 用等距投影,经度方向乘纬度余弦,画面比例才不会失真。

七、验证与踩坑

先说页面自检,9 条断言全部通过。

断言实测值
北京偏移在 300~700 米之间554.85 米
WGS84→GCJ02→WGS84 往返误差 < 0.01 米最大 0.00000006 米
所有城市偏移 > 100 米最小 265.02 米
方位角落在 [0,360)全部在范围内
纬度偏移量 < 0.01°最大 0.002717°
经度偏移量 < 0.01°最大 0.006242°
GCJ02→WGS84→GCJ02 闭合误差 < 1e-6°最大 4.974e-13°
纬度影响经度方向偏移北京 0.006242°、广州 0.005330°、差 0.000913°
城市之间偏移差异显著极差 355.95 米

图 4:点一下「运行自检」,9 条断言逐条列出来,全部 PASS

第二步,我拿 Python 把 8 个城市又算了一遍。

用同一套 Krasovsky 参数和多项式实现正向转换,再用 Haversine 算距离、用初始方位角算方向。

8 个城市的 GCJ-02 经纬度与页面逐位一致,偏移距离差 0.00 米,方位角差 0.00°。

这一步的意义在于,页面的数字不是自己说对就对的,它被另一套代码复算过。

第三步,三处真实边界,我留着没改。

第一处是境外坐标。页面没有中国范围判断,东京 35.6895, 139.6917 也被算出 377.60 米的偏移,方位 85.14°。

赤道上的 0,0 更离谱,给出 1749.86 米;悉尼 354.36 米。

GCJ-02 只在中国境内生效,境外本该原样返回,标准实现会先判断坐标是否落在国内。

第二处是极点。纬度填 90 能通过校验,因为代码拦的是大于 90。

这时候余弦变成 0,经度那一项直接爆炸:页面算出 GCJ-02 经度 101052942616760.53,往返误差变成 NaN,迭代 10 次也没收敛。

第三处是失败态下的画面。输入非法值后,左侧数字面板会退回「等待有效输入…」,但右侧 canvas 还留着上一次有效的图形。

输入纬度 91,报错是「纬度超出范围,应在 -90 ~ 90 之间。」,而画布上仍是上一站的 265.0 m 与 59.6°。

图 5:输入纬度 91,红框给出中文提示,数字面板退回等待有效输入

八、本地复现与亲手点三下

页面是单文件,把 index.html 下载下来双击就能打开,没有依赖也没有后端。

想用本地服务的方式打开,在文件所在目录跑一行命令。

python -m http.server 8080
# 浏览器打开 http://127.0.0.1:8080/index.html

亲手点三下,顺序建议这样。

  1. 看关键数值:点「北京」,偏移距离应该是 554.85 米,方位角 73.66°。
  2. 换一个状态:点「广州」,数字跳到 620.97 米和 118.64°,连线方向跟着转。
  3. 看失败路径:纬度改成 91,应该看到「纬度超出范围,应在 -90 ~ 90 之间。」;改成 abc,看到「输入包含非数字字符,请只输入小数(如 39.9042)。」

九、用码道的体会

这一轮里,需求写清楚比写得漂亮重要。

「页面上要有自检按钮,内置至少 8 条断言」这一句,换来了交付时可以直接点开的 9 条断言。

「反向转换用迭代逼近,显示往返误差」这一句,把最容易糊弄过去的一步变成了可验证的数字。

另一条体会是边界要自己主动试。

境外坐标、极点、失败态画布这三处,都是交付之后我自己试出来的。

如果不主动喂极端输入,页面看上去是完美的。

十、直接下载试玩

十一、下一步

一个明显的改进是把境外判断补上,让中国以外的坐标原样返回。

再往后可以把偏移场画成一片箭头图,一眼看出全国各地方向的走向。