three.js地图数学基础(四):瓦片金字塔

149 阅读2分钟

XYZ 瓦片金字塔:从连续平面到离散身份

Web Mercator 给出连续的 (x,y),但网络、缓存和调度器不能用“无限多个点”作为工作单元。XYZ 瓦片金字塔在缩放级别 z 把单位世界均匀切成有限个正方形,并给每个正方形一个整数地址。

屏幕截图 2026-08-10 220722.png

1. 每升一级,分辨率如何变化

在缩放级别 z,每条轴上的瓦片数为:

n(z)=2zn(z)=2^z

所以总瓦片数为:

N(z)=n2=4zN(z)=n^2=4^z
z横向瓦片数该层总瓦片数单瓦片世界宽度
0111
1241/2
24161/4
38641/8
13819267,108,8641/8192

总数按 4z4^z 增长,解释了为什么地图引擎永远不能“先创建整层瓦片再选视口”。它必须从视口或树关系出发,只触碰一个很小的活动子集。

2. 从 Mercator 点得到 XYZ

设 normalized Mercator 点为 (xm,ym)(x_m,y_m),在层级 z 上:

xf=nxm,yf=nymx_f=n x_m,\qquad y_f=n y_m

浮点数的整数部分表示瓦片索引,小数部分表示瓦片内位置:

xt=⌊nxm⌋,yt=⌊nym⌋\boxed{x_t=\lfloor n x_m\rfloor},\qquad \boxed{y_t=\lfloor n y_m\rfloor}
uf=nxm−xt,vf=nym−ytu_f=nx_m-x_t,\qquad v_f=ny_m-y_t

XYZ 的原点位于世界左上角:

  • x 从西向东增加;
  • y 从北向南增加;
  • (0,0) 是该层最西北瓦片;
  • (n−1,n−1) 是最东南瓦片。

不要把 XYZ 与 TMS 混淆。TMS 常把 y 轴原点放在左下,二者关系为:

yTMS=n−1−yXYZy_{\text{TMS}}=n-1-y_{\text{XYZ}}

本书、MapTiler URL 和教学代码全部使用 XYZ。

3. 半开区间与边界归属

每个规范瓦片覆盖 Mercator 半开区间:

[xtn,xt+1n)×[ytn,yt+1n)\left[\frac{x_t}{n},\frac{x_t+1}{n}\right) \times \left[\frac{y_t}{n},\frac{y_t+1}{n}\right)

半开区间让共享边界只属于一个瓦片,避免一个点在地址计算中同时落入左右两个瓦片。

世界最东边 xm=1x_m=1 是特殊情况:⌊n⌋=n\lfloor n \rfloor=n,已经跨入下一份重复世界,而不是当前规范世界的最后一列。这个结果对连续平移是正确的;后面会把它记作 canonical x=0, wrap=1。(在视口裁剪或单世界渲染时,引擎通常会通过极值 Clamp 或闭区间处理,避免边界点被误判出界。)

纬度经过 Mercator 截断后,浮点误差仍可能令边界出现 −ε-\varepsilon 或 1+ε1+\varepsilon。因此教学实现把离散 y 夹到 [0,n−1]。水平方向不能这样夹取,因为它必须保留世界重复。

4. 四叉树父子关系

每个 (z,x,y)(z,x,y) 在下一层恰有四个孩子:

child(z,x,y;i,j)=(z+1,  2x+i,  2y+j)\text{child}(z,x,y;i,j)=(z+1,\;2x+i,\;2y+j)

其中 i,j∈{0,1}i,j \in \{0,1\}:

(i,j)方位child x/y
(0,0)西北 NW(2x, 2y)
(1,0)东北 NE(2x+1, 2y)
(0,1)西南 SW(2x, 2y+1)
(1,1)东南 SE(2x+1, 2y+1)

反过来,非根瓦片的父级为:

parent(z,x,y)=(z−1,⌊x2⌋,⌊y2⌋)\text{parent}(z,x,y)=\left(z-1, \left\lfloor\frac{x}{2}\right\rfloor, \left\lfloor\frac{y}{2}\right\rfloor\right)

最低有效位决定孩子位于父级的哪一象限:

i=x mod 2,j=y mod 2i=x\bmod 2,\qquad j=y\bmod 2

这不是只用于画金字塔的数学。后续瓦片替换会沿父子关系寻找可呈现的祖先或完整孩子集合;缓存保护也会用它识别“当前画面正在依赖的回退瓦片”。

5. Quadkey:把树路径编码成字符串

Quadkey 从最高有效位到最低有效位读取 x/y。在每一级,把该位组合成一个数字:

x 位y 位digit方位
000NW
101NE
012SW
113SE

例如 z=3, x=5(101₂), y=3(011₂):

level bit:  2 1 0
x:          1 0 1
y:          0 1 1
digit:      1 2 3
quadkey:   "123"

Quadkey 的前缀就是祖先路径,适合说明四叉树;但本书运行时以结构化 (z,x,y,wrap) 为准,不用字符串代替热路径里的整数身份。

6. 规范地址与重复世界

平面地图允许水平连续滚动。把经度持续向东增加,Mercator x 会经过:

... -0.1, 0.2, 0.9, 1.1, 1.8, 2.1 ...

在 z 级,把 ⌊nxm⌋\lfloor nx_m \rfloor 称为 unwrappedX。它可以小于 0 或大于等于 n。我们将它分解为:

wrap=⌊unwrappedXn⌋wrap=\left\lfloor\frac{unwrappedX}{n}\right\rfloor
x=unwrappedX(modn)x=unwrappedX\pmod n

这里必须使用欧几里得模,使余数总在 [0,n−1][0,n-1]:

const mod = (a, n) => ((a % n) + n) % n;

JavaScript 的 % 是余数运算,-1 % 8 得到 -1,所以不能直接作为规范 x。

分解后:

  • canonical (z,x,y):请求和共享内容的身份;
  • wrap:这份内容出现在哪个水平世界;
  • unwrappedX=x+wrap·n:恢复连续空间位置。

同一个规范瓦片可在多个 wrap 中显示。网络数据通常只需请求一次,但每个可见实例拥有不同的世界变换。这是“内容身份”和“呈现实例”第一次明确分离。

7. sourceZ、displayZ 与 overscaledZ

瓦片身份还常被三个“缩放级别”混淆:

屏幕截图 2026-08-10 220825-1.png

  • sourceZ:数据源实际提供内容的层级;
  • displayZ:相机/视口希望观察的层级;
  • overscaledZ:同一 source tile 被继续放大时用于渲染尺度和样式判断的层级。

假设数据源最高只到 z=15,视口希望 z=17:

source identity:   15/x/y
spatial footprint: 完整 canonical z15 tile 范围
display scale:     displayZ 17

overzoom 不会产生新的道路、建筑、更细几何或 z17 空间碎片。它只是把完整的 canonical z15 空间 footprint 按 displayZ=17 的显示尺度继续放大。若把 displayZ=17 错当成新的 source/spatial identity,缓存会制造多份内容副本,cover 也会虚构源并不存在的子瓦片。

8. 从地址恢复空间范围

给定 {z,x,y,wrap}:

unwrappedX=x+wrap⋅2zunwrappedX=x+wrap\cdot 2^z

瓦片的 normalized Mercator 边界为:

west=unwrappedXn,east=unwrappedX+1nwest=\frac{unwrappedX}{n},\qquad east=\frac{unwrappedX+1}{n}
north=yn,south=y+1nnorth=\frac{y}{n},\qquad south=\frac{y+1}{n}

注意:此处 north 与 south 代表归一化 Mercator 坐标值 ymy_m。由于坐标原点位于西北角,ymy_m 向南递增,因此在 ymy_m 数值上 north<southnorth < south(在地理纬度上则相反,latnorth>latsouth\text{lat}_{north} > \text{lat}_{south})。

边界函数返回空间范围,不自动逆投影为经纬度;需要显示地理范围时再显式调用逆投影。

9. 地址层的工程不变量

  1. z/x/y 是整数;x/y 是索引,不是坐标单位;
  2. 规范 x∈[0,2z−1]x\in[0,2^z-1],规范 y∈[0,2z−1]y\in[0,2^z-1];
  3. 只有水平方向 wrap,Web Mercator 世界不做垂直重复;
  4. 请求身份忽略 wrap,呈现位置保留 wrap;
  5. overzoom 改变显示尺度,不凭空创造源内容;
  6. 父子替换依赖空间 footprint,不能只比较字符串 key;
  7. 地址范围使用半开区间,边界归属必须一致。

下一篇教程会实现这组不变量,并用 179.9° 与 −180.1° 检查跨反经线后的规范地址。