three.js最小地图运行时(十):接入离线逻辑瓦片剖面

教程:接入离线逻辑瓦片剖面并完成 Stage 02

本教程完成第二篇的最小平面地图。你会把固定的逻辑瓦片剖面接入 Stage 02,用它约束 7×7 调试邻域,并在浏览器中证明整个阶段不依赖远端地图数据。

完成后,页面可以平移、缩放、旋转和倾斜;网格、TileKey 与 cursor inspector 会同步更新,但不会加载任何真实瓦片内容。

前置条件

你已经完成:

  • 不可变 ViewState
  • PlanarTransformMapRuntime
  • TileKeyTileGridLayer
  • 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 文件里再写一份 015512。共享对象冻结后,坐标检查器、运行时和下一篇 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 XYZ0 ... 15512disabled。这些值不会随平移、bearing 或 pitch 改变。

5. 证明 Stage 02 没有网络数据源

先检查入口 import。它应只包含本地模块,不应创建 source、请求器或异步数据 promise。

然后打开浏览器开发者工具:

  1. 切换到 Network;
  2. 清空记录;
  3. 勾选 Preserve log 以外的普通默认选项即可;
  4. 重新加载 Stage 02;
  5. 平移、缩放、旋转并切换预设。

**立即观察:**请求列表只有页面、CSS、本地 ES modules、Three.js vendor 文件和浏览器自身资源;交互不会新增远端地图数据请求。页面右下角也没有数据署名,因为本阶段没有使用外部地图内容。

若看到远端地图请求,先检查 gallery、Stage 入口与共享 render 模块是否误导入了传输配置。不要用请求拦截器掩盖错误依赖。

6. 验证完整交互链路

依次完成以下操作:

操作 可观察结果
左键向东拖动 center longitude 改变,线框贴地移动
滚轮缩放 continuous zoom 连续变化,整数边界处更换网格层级
Shift+左键或右键水平拖动 bearing 改变,网格绕视图中心旋转
Shift+左键或右键垂直拖动 pitch 改变,线框遵循地面透视
点击 North ↑ bearing 回到
点击 Top-down pitch 回到
移动 cursor screen、render-local、Mercator、经纬度和 TileKey 同步变化
cursor 指向天空 橙色 ring 隐藏,面板显示没有地面交点

每次 viewchange 都使用事件携带的 viewStatetransformSnapshot。不要在一次更新里混读旧 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、精确相交测试和可恢复的四叉树遍历。


相关推荐
独立开发之道1 小时前
【Three.js教程】 图元速查:官方内置的立体图形
开发语言·javascript·ecmascript
单线程_0110 小时前
从案例分析 Vue3 Tokenizer 源码一
前端·javascript·vue.js
大模型码小白11 小时前
AI 对话流性能调优:万级消息的虚拟滚动落地
java·大数据·前端·javascript·人工智能·算法·机器学习
m0_5474866611 小时前
《JavaScript核心原理 》全套PPT课件2026
开发语言·javascript·ecmascript
xm_xm_xm_112 小时前
react17版本以前类组件常用操作
前端·javascript·react.js
三维地图技术社区12 小时前
一张实景三维地图是怎么来的?从无人机航测到倾斜摄影建模的完整技术链路
gis·实景三维·三维可视化大屏·bim·无人机航测·倾斜摄影建模·空三解算
lzhdim13 小时前
13、JavaScript事件循环机制 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
liangshanbo121513 小时前
Vue 3 <script setup> 的本质原理是什么?它与普通 setup()有何区别?
前端·javascript·vue.js
七牛开发者15 小时前
实测推荐 3 个 Skill,轻松上手 Codex 网页设计与交付流程
前端·javascript·后端