Web Mercator:把经纬网压进单位正方形
矢量瓦片最常见的平面坐标基础是 Web Mercator(在 GIS 行业标准中对应 EPSG:3857,历史上曾使用过 EPSG:900913、EPSG:3785 等标识)。它把经度方向线性映射,把纬度方向按正割函数逐渐拉伸,使局部角度得以保持。代价是面积和距离在高纬度严重放大,而且南北极永远无法映射到有限坐标。

1. 模型和符号
本书采用球面模型,而不是椭球 Mercator:
λ:经度弧度,范围通常为[−π,π];φ:纬度弧度;R:球体半径;(X_m,Y_m):以米或任意同尺度单位表示的未归一化投影;(x,y):归一化到单位世界的坐标;- 屏幕/瓦片约定让
y向南增大。
角度输入来自用户时通常是度。必须先换为弧度:
θrad=θdeg180π
JavaScript 的 Math.sin、Math.tan、Math.atan 等函数全部接收弧度。
1.1 球面模型与椭球 Mercator 的区别
在地理信息系统中,Mercator 投影有两种主流实现:
- 椭球 Mercator(如 EPSG:3395): 严格按照 WGS84 椭球体长短半轴公式计算。推导积分时需引入第一偏心率 e,其纵坐标公式为: Ym=Rlntan(4π+2φ)⋅(1+esinφ1−esinφ)e/2
- 球面 Web Mercator(EPSG:3857): 假定地球为完美正球体(半径取 WGS84 长半轴 R=6,378,137 m),直接采用前述简化积分公式。
为什么 Web Mercator 放弃了更精确的椭球模型?
- 计算性能极高: 球面公式省去了偏心率修正项,计算量显著降低,极其适合渲染引擎和客户端进行实时坐标变换;
- 完美正方形切割: 球面模型下,南北边界截断后,整个投影世界是一个严格的 2πR×2πR 正方形,非常适合用四叉树(Quadtree)进行逐级切片(XYZ 瓦片);而椭球模型很难直接导出完美的正方形瓦片结构;
- 定位与测绘的区别: Web 电子地图的核心目标是视觉渲染与交互 而非测绘工程。虽然球面模型会导致最大约 20 公里左右的绝对纬度定位偏移(若不经转换直接对比),但由于所有 Web 瓦片与要素都统一使用这套球面规则,因此在地图上叠加矢量数据时,相对位置完全对齐,不存在视觉错位。
2. 经度为什么是线性的
Mercator 圆柱投影把赤道方向展开为直线。经度相差 2π 正好绕球一周,所以最自然的未归一化横坐标为:
Xm=Rλ
将 λ=-π 平移到 0,再除以完整周长 2π,得到单位世界横坐标:
x=2πλ+π
于是:
−180° → x=0;0° → x=0.5;+180° → x=1。
公式本身不必截断经度。λ>π 或 λ<−π 会自然产生 [0,1] 外的 x,后续由 wrap 记录重复世界。这一点对跨反经线的连续平移很重要。
3. 纬度公式从哪里来
3.1 保角条件
球面上一小段纬线在东西方向的实际长度随纬度缩短。纬度为 φ 时,平行圈半径为 R cosφ,因此经度微分 dλ 对应的地表长度为:
dsx=Rcosφdλ
南北方向的纬度微分长度为:
dsy=Rdφ
投影横向微分为 dX_m=R dλ。若希望局部东西、南北使用同一放大比例,纵向投影必须满足:
RdφdYm=RcosφdλdXm=cosφ1
因此:
dYm=Rsecφdφ
对纬度积分:
Ym=R∫secφdφ=Rln∣secφ+tanφ∣+C
取赤道 φ=0 时 Y_m=0,可得 C=0。利用恒等式:
secφ+tanφ=tan(4π+2φ)
得到经典球面 Mercator 纵坐标:
Ym=Rlntan(4π+2φ)
3.2 从向北为正改为向南为正
数学投影的 Y_m 向北增大;XYZ 瓦片和屏幕坐标却从北向南编号。完整世界在横向的跨度为 2πR,将赤道放在 y=1/2 并翻转轴:
y=21−2πlntan(4π+2φ)
赤道上 tan(π/4)=1,ln1=0,所以 y=0.5。
在代码实现(如 JavaScript)中,利用双曲函数恒等式 ln(secφ+tanφ)=atanh(sinφ),该公式可进一步表达为:
y=21−2πatanh(sinφ)
工程优势: 使用 Math.atanh(Math.sin(phi)) 相比传统的 Math.log(Math.tan(...)),减少了一次加法、一次除法与多次三角函数嵌套,在计算机浮点运算中拥有更好的指令性能和数值稳定性。
4. 为什么必须截断纬度
当 φ→π/2 时,tan(π/4+φ/2)→∞,投影纵坐标也趋向无穷。南极同理。因此不可能把两个极点放进有限正方形。
XYZ 瓦片选择的截止纬度,使投影纵坐标刚好达到横向半周,即 normalized y=0 或 1。令北边界 y=0:
lntan(4π+2φmax)=π
由反函数(下一节证明)可得:
φmax=arctan(sinhπ)
换成角度:
φmax=85.0511287798066∘
任何更北或更南的输入都必须先截断到 ±φmax,否则会得到超出有限世界的数值,靠近极点时还可能出现 Infinity。
截断不是"修复错误坐标",而是这套平面瓦片模型的定义域。若产品必须展示极点,应选择地球/极区投影,而不是继续扩大 Web Mercator。
工程截断时机: 在代码实现中,截断必须在传入任何三角函数计算之前(或紧随度转弧度之后)完成:
javascript
// 正确:先做范围约束,再进入三角函数,防止 ±90° 计算抛出 Infinity/NaN
const clampedLat = Math.max(-85.0511287798066, Math.min(85.0511287798066, lat));
const phi = (clampedLat * Math.PI) / 180;
5. 逆投影推导
横坐标直接变形:
λ=2πx−π
对于纬度,令:
a=π(1−2y)
由正向公式可知:
a=lntan(4π+2φ)
指数化后:
ea=tan(4π+2φ)
可写成常见反式:
φ=2arctan(ea)−2π
利用 atan(sinh a) 与上式等价,得到数值实现更直接的形式:
φ=arctan(sinh(π(1−2y)))
最后把 λ/φ 从弧度乘 180/π 转回度。
6. 两组可手算的检查
6.1 原点检查
输入 (0°,0°):
x=2π0+π=0.5
y=21−2πlntan(π/4)=21−2πln1=0.5
所以结果必须精确 为 (0.5,0.5)。若不是,最常见原因是把角度直接传给三角函数,或忘记 y 轴翻转。
6.2 上海检查
输入:
(λdeg,φdeg)=(121.4737,31.2304)
投影结果约为:
(x,y)=(0.8374269444444444,0.40860372471489265)
将这两个值送入逆投影,应恢复:
(121.4737∘,31.2304∘)
双精度往返误差应远小于 10⁻⁹°。这里检验的是公式与实现的一致性,不代表地面测量精度达到纳米级。
7. 失真如何影响瓦片引擎
Mercator 的局部线性放大比例为:
k=secφ=cosφ1
赤道 k=1;纬度 60° 时 k=2。这意味着投影平面上一段固定长度对应的真实地表距离随纬度变化。它会影响:
- "每像素多少米"的计算;
- 以米定义的建筑高度或线宽;
- 相机高度与缩放级别之间的换算;
- 距离标尺。
但瓦片寻址仍在 normalized Mercator 中进行。不要为了修正米制失真而改变 x/y 或 XYZ 规则;应该在需要物理尺度的模块中显式加入纬度比例。
7.1 归一化坐标向像素/瓦片坐标的映射
归一化坐标 (x,y)∈0,1 是跨缩放级别的统一表达。在特定缩放级别(Zoom Level Z)及瓦片像素尺寸 S(通常为 256px 或 512px)下,坐标换算公式为:
-
世界像素坐标: px=x⋅S⋅2Z py=y⋅S⋅2Z
-
瓦片索引 (Tile X / Tile Y): TileX=⌊x⋅2Z⌋ TileY=⌊y⋅2Z⌋
8. 实现契约
数学模块应满足:
projectLngLat接收度,输出无量纲 normalized Mercator;- 纬度在三角函数之前截断;
- 经度不自动折返,以保留连续世界信息;
unprojectMercator接收 normalized 数值,输出度;- 模块不依赖 DOM、Three.js、瓦片缓存或当前相机;
- 返回值不可变,避免检查器或渲染代码意外改写共享结果。
下一篇教程将把这些公式写成一个依赖为零的 ES 模块,并用原点、上海和边界输入逐项核对。