把北京一处 WGS-84 坐标 39.904200, 116.407400 输进这个页面,它算出的 GCJ-02 是 39.905603, 116.413642。
两个数字一减,位置被挪走了 554.85 米,方向 73.66°。
广州更远,620.97 米;乌鲁木齐最近,也有 265.02 米。
这不是算错了,是国内地图的通行规则。

在线案例与源码: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.264400 | 23.126423, 113.269730 | 620.97 m | 118.64° |
| 深圳 | 22.543100, 114.057900 | 22.540383, 114.063014 | 605.91 m | 119.91° |
| 北京 | 39.904200, 116.407400 | 39.905603, 116.413642 | 554.85 m | 73.66° |
| 上海 | 31.230400, 121.473700 | 31.228458, 121.478223 | 481.25 m | 116.66° |
| 西安 | 34.341600, 108.939800 | 34.340044, 108.944456 | 461.12 m | 112.03° |
| 成都 | 30.572800, 104.066800 | 30.570346, 104.069305 | 363.30 m | 138.68° |
| 拉萨 | 29.650000, 91.100000 | 29.647300, 91.101568 | 336.28 m | 153.21° |
| 乌鲁木齐 | 43.825600, 87.616800 | 43.826805, 87.619650 | 265.02 m | 59.62° |
极差 355.95 米,最大和最小差了 1.34 倍。
方向也散得厉害。广州朝东南偏东,乌鲁木齐朝东北,拉萨朝东南。

三、准备环境:进入码道 Web
浏览器打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source...
登录后直接在对话窗口输入需求,不用装软件。
码道有三种使用方式:WebUI 浏览器对话、TUI 终端命令行和桌面 IDE 插件。本文用 WebUI 版演示。
四、需求原文:把要验的东西先写死
给码道的需求里,关键的几条是这些。
- 输入 WGS-84 经纬度,输入即算,实时显示 GCJ-02、偏移距离和方位角。
- 北京、上海、广州、深圳、成都、西安、拉萨、乌鲁木齐 八个预设按钮。
- GCJ-02 转回 WGS-84 用迭代逼近,最多 10 次,显示往返误差。
- 一行一个城市的对照表,按偏移距离从大到小排。
- canvas 画出原始点、偏移点和两点连线,连线旁标注偏移米数。
- 页面上放一个「运行自检」按钮,内置至少 8 条断言,点击后显示通过数和失败原因。
- 输入非法时给中文提示,不抛异常、页面不崩。
- 深色科技风,中文界面。
第 6 条是有意写死的:页面上有可点开的断言,文章里的测试数字才有出处。
五、页面上有哪几块
左侧是输入与结果面板,上面填经纬度,下面列 GCJ-02、偏移距离、方位角、反解结果和往返误差。
右上角是偏移可视化,红点是原始点,蓝点是偏移点,中间那条虚线就是这 500 米。
右侧下方是 8 城对照表和自检断言。

六、代码亮点:椭球、多项式与反向迭代
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 米 |

第二步,我拿 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°。

八、本地复现与亲手点三下
页面是单文件,把 index.html 下载下来双击就能打开,没有依赖也没有后端。
想用本地服务的方式打开,在文件所在目录跑一行命令。
bash
python -m http.server 8080
# 浏览器打开 http://127.0.0.1:8080/index.html
亲手点三下,顺序建议这样。
- 看关键数值:点「北京」,偏移距离应该是 554.85 米,方位角 73.66°。
- 换一个状态:点「广州」,数字跳到 620.97 米和 118.64°,连线方向跟着转。
- 看失败路径:纬度改成 91,应该看到「纬度超出范围,应在 -90 ~ 90 之间。」;改成 abc,看到「输入包含非数字字符,请只输入小数(如 39.9042)。」
九、用码道的体会
这一轮里,需求写清楚比写得漂亮重要。
「页面上要有自检按钮,内置至少 8 条断言」这一句,换来了交付时可以直接点开的 9 条断言。
「反向转换用迭代逼近,显示往返误差」这一句,把最容易糊弄过去的一步变成了可验证的数字。
另一条体会是边界要自己主动试。
境外坐标、极点、失败态画布这三处,都是交付之后我自己试出来的。
如果不主动喂极端输入,页面看上去是完美的。
十、直接下载试玩
- Demo Park 仓库:atomgit.com/deli007/dem...
- 本案例目录:atomgit.com/deli007/dem...
十一、下一步
一个明显的改进是把境外判断补上,让中国以外的坐标原样返回。
再往后可以把偏移场画成一片箭头图,一眼看出全国各地方向的走向。