27-cesium-viewer-integration.md 15 KB

27: 接入 Cesium 常驻 Viewer(引擎接入)

What to build: 把中央一张图的宿主接到真实引擎上:装 cesium,按决策地图的 Cesium 调研配方配置静态资源与 CESIUM_BASE_URL,由地图服务层在现有宿主要素上创建唯一 Viewer,并让尺寸变化与销毁也走服务层。外壳与 MapCanvas 组件不改。

(本票来自票 02 验收清单未覆盖的部分:决策地图把「创建 Cesium Viewer」写给票 02,但票 02 的验收只要求常驻/生命周期,故独立成票。归属由维护者 2026-09-16 决定。)

Blocked by: 02

Status: resolved Labels: ready-for-agent Category: enhancement

请勿据标签重复接单:本票已完成并有关闭记录,Labels 保留只是为了让面板不把它退化成 needs-triage。

依赖与事实

  • 决策地图的 Cesium 调研:Cesium integration into Fes and Vite,产物 cesium-fes-vite.md(含冲突清单、静态资源拷贝、CESIUM_BASE_URL 时序、禁止项) —— 注意跨 effort 同号:这里的「06」指决策地图的调研票,不是本 effort 的 issues/06(驾驶舱静态资产)。
  • 环境已核实:npm registry 可达,cesium@1.145.0 可装
  • ADR 0004:Cesium 为主、Three.js 为增强;ADR 0008:相机契约(三键)在票 03 实现

验收

  • 安装 cesium 为运行时依赖(版本固定),不放进 federation 的 shared,并加入 manualChunks 单独分包。
  • 构建期把 node_modules/cesium/Build/Cesium/{Workers,ThirdParty,Assets,Widgets} 拷到产物 cesium/ 目录,且在 yarn dev 与 pre.dev.one(copyPublicDir:false)两种模式下都生效。
  • window.CESIUM_BASE_URL 在 import cesium 之前设置,取值来自 publicPath: './'(qiankun 下用 __INJECTED_PUBLIC_PATH_BY_QIANKUN__),不硬编码 /cesium/。
  • services/mapService.js 在宿主要素上创建唯一 Viewer:mountMap() 仍是幂等的、resizeMap() 转调 viewer.resize()、unmountMap() 转调 viewer.destroy();getLifecycleStats() 语义不变。
  • 不使用 Ion token、Terrain.fromWorldTerrain、createOsmBuildings 等需要账号的能力(票 06 明令禁止)。
  • 关闭 Cesium 自带的默认控件与默认相机输入(导航控件、时间轴等)——默认左键旋转/滚轮缩放会与票 03 的三键契约打架。
  • 底图未配置时不伪造地理配准:显示明确的「底图未配置」状态,而不是把临时影像硬塞进某个假坐标范围。
  • 引擎初始化失败时页面不白屏:给出明确失败状态,中央区域仍有可读兜底(现有临时影像或纯色 + 文案)。
  • 现有的 17 项走查断言全部仍然通过(尤其是 A14 地图宿主实例 id 不变、A12 铺满几何)。
  • 新增可观测证据:走查断言宿主内只有 1 个 Cesium 画布、实例 id 跨模式切换与面板收放保持不变、且 worker 静态资源可访问(HTTP 200)。
  • 逻辑断言脚本中的地图服务生命周期一节继续通过(幂等挂载、只创建一次、销毁语义)。

不在本票范围

  • 相机三键契约(票 03)、3D Tiles/地形/倾斜摄影(票 21)、管线渲染(票 22)、监测点与事件图层(票 08/09)。
  • 真底图来源(天地图/甲方本地瓦片):票 06 调研定性为「底图校验」与正式资源两条路,归票 21。

进度:100%

已完成并关闭(2026-09-17)。 中央一张图现在挂的是真实 Cesium Viewer:引擎、静态资源、CESIUM_BASE_URL 时序都在地图服务层,外壳与 MapCanvas 组件未改。

改动

  • package.json / yarn.lock:cesium@1.145.0(固定版本),不进 federation shared。
  • .fes.js:注册 cesiumStatic();manualChunks 增加 cesium: ['cesium']。
  • 新增 src/common/cesiumStaticPlugin.js:dev / pre.dev(.one) 下把 <base>/cesium/* 映射到 node_modules/cesium/Build/Cesium 的四个白名单目录(Workers/ThirdParty/Assets/Widgets,防目录穿越);build 下把同样四个目录逐个拷进产物 cesium/(pre.dev.one 的 copyPublicDir:false 同样生效)。
  • 新增 src/modules/cockpit/services/cesiumAssets.js:window.CESIUM_BASE_URL 必须在 import 'cesium' 之前写入,所以本模块刻意不 import cesium —— 取值顺序为 qiankun 注入路径(有则优先)→ import.meta.env.BASE_URL(即 publicPath: './')→ 相对 document.baseURI 解析成绝对 URL,全程无硬编码 /cesium/。
  • 新增 src/modules/cockpit/services/cesiumEngine.js:本仓库唯一 import 'cesium' 的地方(由服务层动态 import 触发,时序可控)。new Viewer(host, …):自带控件全关、baseLayer: false(不配底图 → 不伪造地理配准)、逐项关掉默认相机输入(enableRotate/Zoom/Tilt/Look/Translate = false,不依赖临时的 enableInputs)、不碰任何需要账号的能力(无 Ion token / Terrain.fromWorldTerrain / createOsmBuildings)。
  • 改 src/modules/cockpit/services/mapService.js:mountMap(host) 仍幂等且只创建一次;宿主是真 DOM 元素时才异步起引擎(加载期间被销毁则不留实例、加载期间的尺寸变化会在就绪后补一次),resizeMap() → viewer.resize()、unmountMap() → viewer.destroy();引擎态(idle/unavailable/booting/ready/failed)与底图态写进宿主 data-map-engine / data-map-basemap,并在宿主内自绘一块状态浮标(这样组件不必改);getLifecycleStats() 原六个字段语义不变,另加 engine / engineError / basemap。
  • 未改 MapCanvas.vue 与外壳:宿主元素、data-map-instance、临时影像兜底都保持原样(本票会话未改这两个文件;同期票 06 会话对它们的改动属票 06)。

验证证据(本机 2026-09-17,服务 http://localhost:8000)

  • 逻辑断言:node .scratch/cockpit-shell-panel-layout/verify/t01-panel-catalog.mjs → 通过。新增「Cesium 资源基址」一节:./ + #/cockpit → http://localhost:8000/cesium/、子目录部署、qiankun 绝对基址、根相对基址都按预期解析;非 DOM 宿主不启动引擎且如实标注 unavailable / not-configured;地图服务生命周期一节(幂等挂载、只创建一次、销毁语义)继续通过。
  • 浏览器走查(yarn dev):node .scratch/cockpit-shell-panel-layout/verify/t01-browser-walkthrough.mjs → 20/20 通过(原 17 项 + 本票新增 3 项):
    • A15:宿主内恰好 1 个 .cesium-viewer / 1 个画布、data-map-engine=ready、data-map-basemap=not-configured、状态文案含「底图未配置」、工具栏内无任何自带控件、CESIUM_BASE_URL=http://localhost:8000/cesium/,且走完四模式切换 + 面板收放后实例 id 仍是 cockpit-map-1。
    • A16:Workers/transferTypedArrayTest.js、Assets/approximateTerrainHeights.json、Widgets/widgets.css 全部 HTTP 200。
    • A17(对抗式):用 CDP 拦掉 cesium 依赖包强制失败 → data-map-engine=failed、文案含「初始化失败」、画布数 0、临时影像兜底仍在、外壳照常渲染(不白屏)。
  • yarn pre.dev.one(copyPublicDir:false):同一份走查 19/20,本票相关的 A14/A15/A16/A17/A11 全绿(静态资源同样 200);唯一红的是 A12「铺满视口」,证据里 pageHeaderH=54 —— pre.dev.* 是 TvT 演示站点布局(navigation:'top' + 固定页头),外壳填的是容器而非视口。该环境差异已由票 06 会话查清并立票 28,与本票无关。
  • 构建期拷贝:yarn pre.build.one 的构建日志里插件逐个拷贝四个目录 —— ✔ Copied: …\Build\Cesium\Workers → dist\cesium\Workers(ThirdParty / Assets / Widgets 同),产物 dist/cesium/ 下只有这四个目录,不再带 Cesium.js / index.js / index.cjs 三个 ESM 打包用不上的入口包(首轮曾整目录拷贝,已按验收收窄)。

遗留 / 已排除为本票回归

  • (已解除,见文末补记)本工作区 yarn build / pre.build.one 曾跑不通(预先存在、与本票无关):两个未纳入版本管理的插件缺依赖 —— src/plugins/useThreeGeospatial → @takram/three-3d-tiles-support、src/plugins/map2BuildingRoad → leaflet;Rollup 在渲染 chunk 之前就失败,因此 manualChunks 的 cesium 分包当时只有配置证据、没有产物证据。本票的运行时证据取自 dev / pre.dev.one(走查也在这两个模式下跑)。
  • 与票 26 同类的新触发源:pre.dev.one 首次启动时 watcher 被 src/modules/cockpit/components/.MapCanvas.vue.<pid>.<guid>.tmpdir/MapCanvas.vue.tmp 击穿(DSH 编辑审阅临时目录这次落在源码目录旁,不匹配当时的忽略清单 **/.scratch/** + **/.dsh-*)。目录是瞬时的,重试即过。已由票 28 会话按本票实测补上 **/*.tmpdir + **/*.tmpdir/**,并已复核(见文末补记)。
  • 相机三键契约与真底图分别归票 03 / 21;本票只保证「默认输入已关」与「不伪造配准」。默认相机仍是全球视角;无底图球体 + 默认星空背景会盖住外壳的全息网格/扫描线浮层(.map-holo__grid 等 z-index: auto,被后插入的画布遮住),观感收口建议随票 03/21 一并定。MapCanvas 底部那句「临时底图 aerial.webp · 票 02 已建常驻宿主」在引擎就绪后已过时,因票面要求「组件不改」未动,需维护者定夺改法。
  • resizeMap() / unmountMap() 的转调是代码级证据:走查不离开 /cockpit,所以 viewer.destroy() 未在运行时直接断言;viewer.resize() 因 Cesium 自带 postUpdate 自动 resize,也没有做「这次 resize 确由本服务发起」的观测。
  • 本票改动已提交:4d83d8a(由票 28 会话在独立复跑 20/20 后提交,含 .fes.js、package.json / yarn.lock、mapService.js、cesiumStaticPlugin.js、cesiumAssets.js、cesiumEngine.js 与走查脚本)。同一提交还带上了为解开本票遗留的 build 阻塞而装的 5 个包(@takram/three-* 四个 + leaflet),其插件目录本身仍未跟踪。

复跑复核(票 28 会话独立复核,2026-09-17)

原先的长跑 dev server 停掉后,用 yarn dev 全新启动再跑同一份走查:20/20 通过,A15 widgetChrome: 0。用 DOM 探针确认这不是「被隐藏」而是真的没有:.cesium-viewer-toolbar 存在但零子元素,.cesium-viewer-animationContainer 与 .cesium-viewer-timelineContainer 都不存在——票面「默认控件全关」的结论成立,A15 的选择器并不过严。

此前同一份代码上出现过一次 19/20(A15 红、widgetChrome: 1)。两次唯一的差别是服务实例:那次打的是长时间运行的旧 dev server,最可能是它给浏览器发到了更早的 cesiumEngine.js 模块(陈旧模块),不是代码或断言问题;事后无法回溯证实,故只作推断记录。教训:涉及引擎/懒加载模块的走查,复跑前重启 dev server(该条已写进走查脚本头部与 map.md 的 Notes)。

结论:票面 20/20 与关闭结论成立,本复核只是另起一份干净服务上的可复现证据;票面正文无需改动。

补记:build 阻塞解除 + 分包产物证据 + 第三触发源验证(票 27 会话,2026-09-17 晚)

1. yarn build 现在跑得通了

三个原因,逐个解掉:

  • map2BuildingRoad 缺 leaflet → 装 leaflet@1.9.4(固定版本)。
  • useThreeGeospatial 的 @takram/three-* 导入按「能不能装」分成两类,判据是 API 对不对得上,不是「有没有包」:
    • 已发布到 npm 的四个 → 装成依赖:@takram/three-geospatial@0.9.1、three-atmosphere@0.19.1、three-clouds@0.7.6、three-geospatial-effects@0.6.4。关键判据:cloudsVanilla.vue 用的是 new DataTextureLoader(THREE.Data3DTexture, parseUint8Array, …),只有发布版的 DataTextureLoader 构造函数是 (textureClass, parser, options?, manager?) 与之相符;插件内置的 lib/core(three-geospatial@0.2.0 快照)里这个类已改名为 createDataTextureLoader* 工厂,直接别名过去会在 Rollup 报 "DataTextureLoader" is not exported。
    • 没有发布到 npm 的四个 → 走插件自带副本:@takram/three-3d-tiles-support(registry 上 404)、three-terrain、three-terrain-core、three-geospatial-worker。新增 src/common/vendoredPluginLibAliases.js,扫描 src/plugins/*/lib/*/package.json,把包名与其 exports 子路径(如 @takram/three-geospatial/shaders)映射成 Vite alias;已被 node_modules 解析到的包名一律不覆盖,于是自然形成「能装的走 npm、装不上的走插件副本」。
  • build 脚本本身在 Node 默认堆(4288MB)上 OOM(15526 个模块 transform 完之后挂)→ 补 NODE_OPTIONS=--max-old-space-size=8088,与既有的 pre.build / pre.build.one / pre.build.icegl 三个脚本一致。

证据:

  • yarn build(不加任何环境前缀)→ exit 0,✓ built in 50.95s,四个目录照旧进产物。
  • 分包产物证据(补上验收第 1 条缺的那半):dist/js/cesium.CGXNeBo4….js = 3.97 MB 独立 chunk,cesiumEngine.*.js 是它的懒加载入口,主入口 chunk 里以 cesium.<hash>.js 的形式引用它。
  • 依赖变化:+ cesium@1.145.0、+ leaflet@1.9.4、+ @takram/three-{geospatial@0.9.1, atmosphere@0.19.1, clouds@0.7.6, geospatial-effects@0.6.4},以及它们的传递依赖(react-merge-refs、@petamoriken/float16、astronomy-engine、url-join、tagged-tag)。

2. watcher 第三触发源:已修 + 已验

.fes.js 的 ignored 现为 ['**/.scratch/**', '**/.dsh-*', '**/*.tmpdir', '**/*.tmpdir/**']。验证两层:

  • 确定性:用 chokidar 自己的匹配器(anymatch(list, undefined, { dot: true }))对这次真实崩溃的路径求值 —— 旧清单 false(确实没盖住),新清单 true;目录本身 true、普通源码 false、.scratch 旧行为仍 true。
  • 活体:yarn dev 运行中按 DSH 的形态建同名临时目录、写两次内容、再删除,GET / 仍 200,服务器没被击穿,期间真实源码的 HMR 照常工作。

3. 提交状态(需要维护者收口)

4d83d8a 里 .fes.js 已经 import … from './src/common/vendoredPluginLibAliases.js',但那个文件当时没被跟踪 —— 该提交的树是缺文件的,干净检出跑不起来 build。本次已把 src/common/vendoredPluginLibAliases.js 与 package.json(build 脚本那一行)git add 进暂存区:下次提交务必带上,否则 HEAD 仍是坏的。