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

1. 每升一级,分辨率如何变化
在缩放级别 z,每条轴上的瓦片数为:
n(z)=2z
所以总瓦片数为:
N(z)=n2=4z
| z | 横向瓦片数 | 该层总瓦片数 | 单瓦片世界宽度 |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
| 1 | 2 | 4 | 1/2 |
| 2 | 4 | 16 | 1/4 |
| 3 | 8 | 64 | 1/8 |
| 13 | 8192 | 67,108,864 | 1/8192 |
总数按 4z 增长,解释了为什么地图引擎永远不能"先创建整层瓦片再选视口"。它必须从视口或树关系出发,只触碰一个很小的活动子集。
2. 从 Mercator 点得到 XYZ
设 normalized Mercator 点为 (xm,ym),在层级 z 上:
xf=nxm,yf=nym
浮点数的整数部分表示瓦片索引,小数部分表示瓦片内位置:
xt=⌊nxm⌋,yt=⌊nym⌋
uf=nxm−xt,vf=nym−yt
XYZ 的原点位于世界左上角:
x从西向东增加;y从北向南增加;(0,0)是该层最西北瓦片;(n−1,n−1)是最东南瓦片。
不要把 XYZ 与 TMS 混淆。TMS 常把 y 轴原点放在左下,二者关系为:
yTMS=n−1−yXYZ
本书、MapTiler URL 和教学代码全部使用 XYZ。
3. 半开区间与边界归属
每个规范瓦片覆盖 Mercator 半开区间:
[nxt,nxt+1)×[nyt,nyt+1)
半开区间让共享边界只属于一个瓦片,避免一个点在地址计算中同时落入左右两个瓦片。
世界最东边 xm=1 是特殊情况: ⌊n⌋=n,已经跨入下一份重复世界,而不是当前规范世界的最后一列。这个结果对连续平移是正确的;后面会把它记作 canonical x=0, wrap=1。(在视口裁剪或单世界渲染时,引擎通常会通过极值 Clamp 或闭区间处理,避免边界点被误判出界。)
纬度经过 Mercator 截断后,浮点误差仍可能令边界出现 −ε 或 1+ε。因此教学实现把离散 y 夹到 [0,n−1]。水平方向不能这样夹取,因为它必须保留世界重复。
4. 四叉树父子关系
每个 (z,x,y) 在下一层恰有四个孩子:
child(z,x,y;i,j)=(z+1,2x+i,2y+j)
其中 i,j∈{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,⌊2x⌋,⌊2y⌋)
最低有效位决定孩子位于父级的哪一象限:
i=xmod2,j=ymod2
这不是只用于画金字塔的数学。后续瓦片替换会沿父子关系寻找可呈现的祖先或完整孩子集合;缓存保护也会用它识别"当前画面正在依赖的回退瓦片"。
5. Quadkey:把树路径编码成字符串
Quadkey 从最高有效位到最低有效位读取 x/y。在每一级,把该位组合成一个数字:
| x 位 | y 位 | digit | 方位 |
|---|---|---|---|
| 0 | 0 | 0 | NW |
| 1 | 0 | 1 | NE |
| 0 | 1 | 2 | SW |
| 1 | 1 | 3 | SE |
例如 z=3, x=5(101₂), y=3(011₂):
text
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 会经过:
text
... -0.1, 0.2, 0.9, 1.1, 1.8, 2.1 ...
在 z 级,把 ⌊nxm⌋ 称为 unwrappedX。它可以小于 0 或大于等于 n。我们将它分解为:
wrap=⌊nunwrappedX⌋
x=unwrappedX(modn)
这里必须使用欧几里得模 ,使余数总在 0,n−1:
js
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
瓦片身份还常被三个"缩放级别"混淆:

sourceZ:数据源实际提供内容的层级;displayZ:相机/视口希望观察的层级;overscaledZ:同一 source tile 被继续放大时用于渲染尺度和样式判断的层级。
假设数据源最高只到 z=15,视口希望 z=17:
text
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⋅2z
瓦片的 normalized Mercator 边界为:
west=nunwrappedX,east=nunwrappedX+1
north=ny,south=ny+1
注意:此处 north 与 south 代表归一化 Mercator 坐标值 ym。由于坐标原点位于西北角, ym 向南递增,因此在 ym 数值上 north<south(在地理纬度上则相反, latnorth>latsouth)。
边界函数返回空间范围,不自动逆投影为经纬度;需要显示地理范围时再显式调用逆投影。
9. 地址层的工程不变量
z/x/y是整数;x/y是索引,不是坐标单位;- 规范 x∈0,2z−1,规范 y∈0,2z−1;
- 只有水平方向 wrap,Web Mercator 世界不做垂直重复;
- 请求身份忽略 wrap,呈现位置保留 wrap;
- overzoom 改变显示尺度,不凭空创造源内容;
- 父子替换依赖空间 footprint,不能只比较字符串 key;
- 地址范围使用半开区间,边界归属必须一致。
下一篇教程会实现这组不变量,并用 179.9° 与 −180.1° 检查跨反经线后的规范地址。