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

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

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

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

在缩放级别 z,每条轴上的瓦片数为:
n(z)=2zn(z)=2^z n(z)=2z

所以总瓦片数为:
N(z)=n2=4zN(z)=n^2=4^z 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

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

2. 从 Mercator 点得到 XYZ

设 normalized Mercator 点为 (xm,ym) (x_m,y_m) (xm,ym),在层级 z 上:
xf=nxm,yf=nym x_f=n x_m,\qquad y_f=n y_m xf=nxm,yf=nym

浮点数的整数部分表示瓦片索引,小数部分表示瓦片内位置:
xt=⌊nxm⌋ , yt=⌊nym⌋ \boxed{x_t=\lfloor n x_m\rfloor},\qquad \boxed{y_t=\lfloor n y_m\rfloor} xt=⌊nxm⌋,yt=⌊nym⌋
uf=nxm−xt,vf=nym−yt u_f=nx_m-x_t,\qquad v_f=ny_m-y_t uf=nxm−xt,vf=nym−yt

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

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

不要把 XYZ 与 TMS 混淆。TMS 常把 y 轴原点放在左下,二者关系为:
yTMS=n−1−yXYZ y_{\text{TMS}}=n-1-y_{\text{XYZ}} yTMS=n−1−yXYZ

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

3. 半开区间与边界归属

每个规范瓦片覆盖 Mercator 半开区间:
[ xtn , xt+1 n ) × [ ytn , yt+1 n ) \left[\frac{x_t}{n},\frac{x_t+1}{n}\right) \times \left[\frac{y_t}{n},\frac{y_t+1}{n}\right) [nxt,nxt+1)×[nyt,nyt+1)

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

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

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

4. 四叉树父子关系

每个 (z,x,y)(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) child(z,x,y;i,j)=(z+1,2x+i,2y+j)

其中 i,j∈{0,1}i,j \in \{0,1\} 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, ⌊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) parent(z,x,y)=(z−1,⌊2x⌋,⌊2y⌋)

最低有效位决定孩子位于父级的哪一象限:
i=x  mod  2,j=y  mod  2i=x\bmod 2,\qquad j=y\bmod 2 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⌋ \lfloor nx_m \rfloor ⌊nxm⌋ 称为 unwrappedX。它可以小于 0 或大于等于 n。我们将它分解为:
wrap= ⌊ unwrappedXn ⌋ wrap=\left\lfloor\frac{unwrappedX}{n}\right\rfloor wrap=⌊nunwrappedX⌋
x=unwrappedX(modn)x=unwrappedX\pmod n x=unwrappedX(modn)

这里必须使用欧几里得模 ,使余数总在 0,n−10,n-1 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⋅2zunwrappedX=x+wrap\cdot 2^z unwrappedX=x+wrap⋅2z

瓦片的 normalized Mercator 边界为:
west= unwrappedXn ,east= unwrappedX+1n west=\frac{unwrappedX}{n},\qquad east=\frac{unwrappedX+1}{n} west=nunwrappedX,east=nunwrappedX+1
north=yn,south= y+1n north=\frac{y}{n},\qquad south=\frac{y+1}{n} north=ny,south=ny+1

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

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

9. 地址层的工程不变量

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

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


相关推荐
小林ixn2 小时前
WebGPU + Transformers.js:把 DeepSeek-R1 塞进浏览器,真香!
javascript·llm·gpu
mONESY3 小时前
🔥 React Hooks 进阶实战:受控组件 + 性能优化一站式掌握
javascript
Synmbrf3 小时前
flv播放设置hasAudio为true黑屏
javascript
看到我请叫我铁锤3 小时前
vue编写web端在线预览文档
前端·javascript·vue.js
ssshooter3 小时前
为什么明明只有一个 12px 的小元素,父容器却有 63px 高?
前端·javascript·面试
breeze jiang4 小时前
React + WebGPU 在浏览器运行 DeepSeek:从 Worker 通信到流式生成
前端·javascript·react.js
sunly_4 小时前
React 三个重要概念:Ref、Props、State 详解
前端·javascript·react.js
Mh4 小时前
如何使用GSAP实现一个 `pinned` 滚动楼层叙事?
前端·javascript·css
Larcher10 小时前
React Router 不只是页面跳转:从 SPA 路由到权限守卫的完整实践
javascript·后端