教程:接入离线逻辑瓦片剖面并完成 Stage 02
本教程完成第二篇的最小平面地图。你会把固定的逻辑瓦片剖面接入 Stage 02,用它约束 7×7 调试邻域,并在浏览器中证明整个阶段不依赖远端地图数据。
完成后,页面可以平移、缩放、旋转和倾斜;网格、TileKey 与 cursor inspector 会同步更新,但不会加载任何真实瓦片内容。
前置条件
你已经完成:
- 不可变
ViewState; PlanarTransform与MapRuntime;TileKey和TileGridLayer;- Stage 02 的表单、交互与地面 cursor 探针。
启动 yinqing/ 中的示例服务器:
powershell
npm start
打开:
text
http://localhost:3001/vector-tile-handbook/stages/02-minimal-map/
1. 导入离线剖面
在 config.js 中建立唯一事实来源:
js
export const DEBUG_TILE_PROFILE = Object.freeze({
minZoom: 0,
maxZoom: 15,
tileSize: 512,
});
Stage 02 入口只导入逻辑剖面与固定教学视图:
js
import {
DEBUG_TILE_PROFILE,
TEACHING_VIEW_STATES,
} from "../../config.js";
不要在 Stage 文件里再写一份 0、15 和 512。共享对象冻结后,坐标检查器、运行时和下一篇 cover 会读取相同边界。
**立即观察:**重新加载页面,控制区应出现绿色状态卡:
text
OFFLINE DEBUG GRID · NO TILE DATA
2. 只夹取调试网格的整数 zoom
保持 ViewState.zoom 为连续相机状态。只有建立调试邻域时,才派生一个整数层级:
js
function clamp(value, minimum, maximum) {
return Math.min(maximum, Math.max(minimum, value));
}
const displayZ = clamp(
Math.floor(viewState.zoom),
DEBUG_TILE_PROFILE.minZoom,
DEBUG_TILE_PROFILE.maxZoom,
);
表单提交时可以限制本实验允许的交互范围,但规范化 ViewState 模块本身不读取剖面:
js
runtime.setViewState({
center: [longitude, latitude],
zoom: clamp(
zoom,
DEBUG_TILE_PROFILE.minZoom,
DEBUG_TILE_PROFILE.maxZoom,
),
bearing,
pitch,
});
**立即观察:**输入 13.8,inspector 的 ViewState zoom 保留 13.800,而 TileKey 使用整数 displayZ=13。滚轮缩放时,线框只在跨越整数边界后换层。
3. 建立 7×7 中心邻域
先求中心所在的连续世界地址:
js
const center = lngLatToTileAddress(
viewState.center[0],
viewState.center[1],
displayZ,
);
const size = 2 ** displayZ;
const centerUnwrappedX = center.x + center.wrap * size;
再枚举半径 3:
js
const keys = [];
for (let dy = -3; dy <= 3; dy += 1) {
const y = center.y + dy;
if (y < 0 || y >= size) continue;
for (let dx = -3; dx <= 3; dx += 1) {
const address = canonicalizeTileAddress(
displayZ,
centerUnwrappedX + dx,
y,
);
keys.push(createTileKey({
displayZ,
canonicalZ: address.z,
canonicalX: address.x,
canonicalY: address.y,
wrap: address.wrap,
}));
}
}
垂直地址越界时跳过,水平地址则规范化成 canonical x + wrap。不要 clamp 水平 x,否则邻域会在反经线断开。
把同代 snapshot 与 keys 一起交给网格:
js
gridLayer.setKeys(keys, transformSnapshot);
**立即观察:**上海固定视图通常显示 49 个 key。把 longitude 改到 179.99 后向东拖动,标签中的 canonical x 会回到 0,同时 wrap 增加,线框仍连续。
4. 在 inspector 中展示剖面事实
第四组 inspector 不显示数据状态,只显示空间事实:
js
const profileRows = [
row("profile", "global XYZ", "logical tiles"),
row(
"zoom range",
DEBUG_TILE_PROFILE.minZoom + " ... " + DEBUG_TILE_PROFILE.maxZoom,
"display/canonical",
),
row("tile size", String(DEBUG_TILE_PROFILE.tileSize), "CSS pixels"),
row("network", "disabled", "no remote tile data"),
];
HUD 同时终止任何"已经完成视口选片"的误解:
text
CENTER NEIGHBORHOOD · 49 KEYS
13/13/6859/3346@0
OFFLINE DEBUG GRID · NOT VIEWPORT COVER
**立即观察:**侧栏应稳定显示 global XYZ、0 ... 15、512 和 disabled。这些值不会随平移、bearing 或 pitch 改变。
5. 证明 Stage 02 没有网络数据源
先检查入口 import。它应只包含本地模块,不应创建 source、请求器或异步数据 promise。
然后打开浏览器开发者工具:
- 切换到 Network;
- 清空记录;
- 勾选 Preserve log 以外的普通默认选项即可;
- 重新加载 Stage 02;
- 平移、缩放、旋转并切换预设。
**立即观察:**请求列表只有页面、CSS、本地 ES modules、Three.js vendor 文件和浏览器自身资源;交互不会新增远端地图数据请求。页面右下角也没有数据署名,因为本阶段没有使用外部地图内容。
若看到远端地图请求,先检查 gallery、Stage 入口与共享 render 模块是否误导入了传输配置。不要用请求拦截器掩盖错误依赖。
6. 验证完整交互链路
依次完成以下操作:
| 操作 | 可观察结果 |
|---|---|
| 左键向东拖动 | center longitude 改变,线框贴地移动 |
| 滚轮缩放 | continuous zoom 连续变化,整数边界处更换网格层级 |
| Shift+左键或右键水平拖动 | bearing 改变,网格绕视图中心旋转 |
| Shift+左键或右键垂直拖动 | pitch 改变,线框遵循地面透视 |
点击 North ↑ |
bearing 回到 0° |
点击 Top-down |
pitch 回到 0° |
| 移动 cursor | screen、render-local、Mercator、经纬度和 TileKey 同步变化 |
| cursor 指向天空 | 橙色 ring 隐藏,面板显示没有地面交点 |
每次 viewchange 都使用事件携带的 viewState 与 transformSnapshot。不要在一次更新里混读旧 key 和新的 live camera。
**立即观察:**无论 bearing/pitch 如何变化,cursor 越过线框的时刻都应与 TileKey 地址切换一致。
7. 明确 7×7 邻域不是 viewport cover
固定半径邻域回答的是:
中心瓦片附近有哪些地址,足以用来检查坐标和身份吗?
真正的 viewport cover 回答的是:
当前整个屏幕可见地面与哪些瓦片相交?
二者不会恒等:
- 宽屏、低 zoom 或高 pitch 时,7×7 可能漏掉可见地面;
- 窄屏或高 zoom 时,它可能画出大量屏外瓦片;
- bearing 改变不会改变中心邻域,却会改变真实地面 footprint;
- 指向地平线时,四角射线可能无法形成完整地面 polygon。
**立即观察:**把 pitch 提高到 80°。远处地面明显延伸到固定邻域之外,这正是 Stage 03 必须从屏幕射线重新计算 cover 的原因。
8. 完成标准
- Stage 02 只导入冻结的离线逻辑剖面;
- ViewState zoom 保持连续,相机状态不被 source 范围改写;
- 调试层级被限制在 z0--z15;
- 中心附近最多绘制 49 个 TileKey;
- 反经线邻域通过 wrap 连续;
- inspector 明确显示逻辑剖面与网络禁用状态;
- 所有交互与 cursor 转换继续工作;
- 文档、HUD 与 UI 都没有把固定邻域称为完整 cover。
第二篇至此得到一张完全离线、可交互、可检查坐标链路的空地图。第三篇将把固定邻域替换为四角地面 footprint、精确相交测试和可恢复的四叉树遍历。