Просмотр исходного кода

feat(cockpit): T27 接入 Cesium 常驻 Viewer(引擎、静态资源、生命周期都收在地图服务层)

票 27 的交付面由该票会话实现,票面已关闭;本次由票 28 会话在全新 `yarn dev` 上独立复跑
20/20 后提交(复核记录见该票面「复跑复核」一节)。

- `package.json` / `yarn.lock`:`cesium@1.145.0` 固定版本,不进 federation `shared`。
  同时带上另一 effort 在装的 5 个包(`@takram/three-atmosphere`、`three-clouds`、
  `three-geospatial`、`three-geospatial-effects`、`leaflet`)——那是为了解开票 27 遗留的
  build 阻塞(两个未跟踪插件缺依赖),其插件目录本身仍未跟踪;此处一并提交以保持依赖
  状态自洽,特此标注。
- `.fes.js`:注册 `cesiumStatic()`、`manualChunks` 增加 `cesium` 单独分包;
  `watch.ignored` 由路径式改为按模式覆盖 `**/*.tmpdir`(票 26 第三触发源,见该票补验)。
- 新增 `src/common/cesiumStaticPlugin.js`:dev / predev 中间件映射 `<base>/cesium/*`,
  build 下把四个白名单目录拷进产物(`pre.dev.one` 的 `copyPublicDir:false` 同样生效)。
- 新增 `services/cesiumAssets.js`:`window.CESIUM_BASE_URL` 在 `import 'cesium'` 之前写入,
  取值 qiankun 注入路径 → `publicPath: './'` → `document.baseURI`,无硬编码 `/cesium/`。
- 新增 `services/cesiumEngine.js`:全仓唯一 `import 'cesium'` 之处;`new Viewer(host, …)`
  自带控件全关、`baseLayer: false` 不伪造配准、默认相机输入逐项关闭,不碰需账号的能力。
- `services/mapService.js`:`mountMap()` 仍幂等且只创建一次;`resizeMap()` → `viewer.resize()`、
  `unmountMap()` → `viewer.destroy()`;引擎/底图状态写进宿主 `data-map-engine`/`data-map-basemap`
  并在宿主内自绘状态浮标,外壳与 `MapCanvas` 组件不改。
- 走查脚本新增 A15(控件/画布/实例 id)、A16(静态资源 200)、A17(强制失败不白屏),
  并补「用 `yarn dev`,复跑前重启 dev server」的前置条件注释。

验证(本机 2026-09-17,`yarn dev` 全新启动):走查 **20/20**、`t01-panel-catalog` 通过、
DOM 探针确认 `.cesium-viewer-toolbar` 零子元素且 animation/timeline 容器不存在。
遗留(票面已记):本工作区 `fes build` 曾被两个未跟踪插件缺依赖挡住,依赖已在本提交补齐,
产物证据待构建方补。
Kazerin 1 месяц назад
Родитель
Сommit
4d83d8a402

+ 17 - 2
.fes.js

@@ -16,6 +16,8 @@ import UnoCSS from 'unocss/vite'
 // eslint-disable-next-line import/no-extraneous-dependencies
 // eslint-disable-next-line import/no-extraneous-dependencies
 import glsl from 'vite-plugin-glsl'
 import glsl from 'vite-plugin-glsl'
 import federation from '@originjs/vite-plugin-federation'
 import federation from '@originjs/vite-plugin-federation'
+import cesiumStatic from './src/common/cesiumStaticPlugin.js'
+import { vendoredPluginLibAliases } from './src/common/vendoredPluginLibAliases.js'
 
 
 const __filename = fileURLToPath(import.meta.url);
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
 const __dirname = dirname(__filename);
@@ -79,7 +81,10 @@ export default defineBuildConfig({
                     // "@tresjs/core": { version: '5.2.0' },
                     // "@tresjs/core": { version: '5.2.0' },
                     // three: { import: false, generate: false }
                     // three: { import: false, generate: false }
                 }
                 }
-            })
+                // 票27:cesium 不进 shared —— 远程不需要它,且体积大、CESIUM_BASE_URL 是进程级单例。
+            }),
+            // 票27:把 cesium 预构建静态资源接到 <base>/cesium/(dev 中间件 + build 拷贝)。
+            cesiumStatic()
         ],
         ],
         build: {
         build: {
             target: 'esnext', // 或者 'es2020' 以支持 BigInt
             target: 'esnext', // 或者 'es2020' 以支持 BigInt
@@ -90,6 +95,7 @@ export default defineBuildConfig({
                     manualChunks: {
                     manualChunks: {
                         three: ['three'],
                         three: ['three'],
                         '3d-tiles-renderer': ['3d-tiles-renderer'],
                         '3d-tiles-renderer': ['3d-tiles-renderer'],
+                        cesium: ['cesium'], // 票27:引擎单独分包
                     },
                     },
                     // manualChunks (id) {
                     // manualChunks (id) {
                     //     // 自定义拆分策略,例如将特定的第三方库拆分为单独的 chunk
                     //     // 自定义拆分策略,例如将特定的第三方库拆分为单独的 chunk
@@ -134,9 +140,14 @@ export default defineBuildConfig({
             // 票26:面板与 DSH 的原子写临时文件在 Windows 上都会让 chokidar 抛 EBUSY 击穿 dev server。
             // 票26:面板与 DSH 的原子写临时文件在 Windows 上都会让 chokidar 抛 EBUSY 击穿 dev server。
             //   `**/.scratch/**` → issue tracker 面板保存单据时的 .tmpdir 原子写
             //   `**/.scratch/**` → issue tracker 面板保存单据时的 .tmpdir 原子写
             //   `**/.dsh-*`      → DSH 每次编辑文件时在项目根生成的 .dsh-edit-review.json.<pid>.<guid>.tmpdir
             //   `**/.dsh-*`      → DSH 每次编辑文件时在项目根生成的 .dsh-edit-review.json.<pid>.<guid>.tmpdir
+            //   `**/*.tmpdir/**` → 票27 实测:同一类原子写临时目录这次落在**源码目录旁**
+            //                      (src/modules/cockpit/components/.MapCanvas.vue.<pid>.<guid>.tmpdir),
+            //                      前两条都盖不住,pre.dev.one 首次启动被击穿。按模式排除,别只堵已知的那一个路径。
             // Vite 内部默认 ignore(node_modules/.git/cacheDir 等)与此合并而非覆盖,见 vite resolveChokidarOptions。
             // Vite 内部默认 ignore(node_modules/.git/cacheDir 等)与此合并而非覆盖,见 vite resolveChokidarOptions。
             watch: {
             watch: {
-                ignored: ['**/.scratch/**', '**/.dsh-*'],
+                // 目录本身与目录内容要分别盖住:chokidar 对目录条目的匹配和对其内容的匹配是两条判断,
+                // 只写 `**/*.tmpdir/**` 时那个目录本身仍会被进入,读它时照样 EBUSY。
+                ignored: ['**/.scratch/**', '**/.dsh-*', '**/*.tmpdir', '**/*.tmpdir/**'],
             },
             },
             proxy: {
             proxy: {
                 '/resource.cos': {
                 '/resource.cos': {
@@ -158,6 +169,10 @@ export default defineBuildConfig({
         },
         },
     },
     },
     alias: {
     alias: {
+        // 插件自带的依赖副本(src/plugins/<插件>/lib/<包>)指回插件内部:
+        // useThreeGeospatial 用的 @takram/three-3d-tiles-support 等包没有发布到 npm,
+        // 不映射的话 yarn build 会直接在这些 import 上失败。
+        ...vendoredPluginLibAliases(join(__dirname, './src/plugins'), join(__dirname, './node_modules')),
         PLS: join(__dirname, './src/plugins'),
         PLS: join(__dirname, './src/plugins'),
     },
     },
     // { find: 'pls', replacement: resolve(__dirname, './src/plugins') },
     // { find: 'pls', replacement: resolve(__dirname, './src/plugins') },

+ 127 - 7
.scratch/cockpit-shell-panel-layout/verify/t01-browser-walkthrough.mjs

@@ -7,15 +7,16 @@
  * npmmirror 全部不可达,只有内网 GitLab 通)。改用已安装的 Chrome + puppeteer-core
  * npmmirror 全部不可达,只有内网 GitLab 通)。改用已安装的 Chrome + puppeteer-core
  * 直接走查同一份页面,验证目标与票 01 的四点完全一致。
  * 直接走查同一份页面,验证目标与票 01 的四点完全一致。
  *
  *
- * 运行(需先起 npm run dev,服务在 http://localhost:8000/):
+ * 运行(需先起 `yarn dev`,服务在 http://localhost:8000/):
  *   node .scratch/cockpit-shell-panel-layout/verify/t01-browser-walkthrough.mjs
  *   node .scratch/cockpit-shell-panel-layout/verify/t01-browser-walkthrough.mjs
  *
  *
- * ⚠ 必须用 `npm run dev`(FES_ENV 默认 → .fes.js:layout.navigation = null,
- *   BaseLayout 走 v-else,只渲染 <FMain>,没有页头)。
- *   别用 `npm run pre.dev` / `pre.dev.one`:那两个环境是 TvT 的演示站点布局
- *   (.fes.predev*.js:navigation='top' + isFixedHeader + 演示菜单),会在页面顶部
- *   压出 54px 固定页头,外壳再按 100vh 铺就会被挤出容器底部 54px —— A12 会红,
- *   但那是环境差异而非驾驶舱回归(A12 的证据里 pageHeaderH 会显示这个页头高度)。
+ * ⚠ 用 `yarn dev`(= `fes dev` → .fes.js:layout.navigation = null,BaseLayout 走 v-else,
+ *   只渲染 <FMain>,页面没有页头)。`yarn pre.dev` / `pre.dev.one` 是 TvT 预演站布局
+ *   (.fes.predev*.js:navigation='top' + isFixedHeader + 演示菜单,顶部压出 54px 固定页头)。
+ *   维护者已明确:**真实应用环境不使用 predev 启动服务** —— 那里出来的红(A12)是环境差异,
+ *   不是驾驶舱回归;A12 证据里的 pageHeaderH 就是用来分辨这一点的(见票 28)。
+ *   **复跑前先重启 dev server**:长跑实例可能仍给浏览器发旧模块,曾把 A15(Cesium 默认控件)
+ *   误判为红一次;重启后同一份代码 20/20。
  */
  */
 import { mkdirSync, writeFileSync } from 'node:fs'
 import { mkdirSync, writeFileSync } from 'node:fs'
 import { join } from 'node:path'
 import { join } from 'node:path'
@@ -131,6 +132,17 @@ try {
     check('A1-entry', '/cockpit 可打开并渲染外壳', ok, `entry=${entry}`)
     check('A1-entry', '/cockpit 可打开并渲染外壳', ok, `entry=${entry}`)
     if (!ok) throw new Error('cockpit shell never rendered')
     if (!ok) throw new Error('cockpit shell never rendered')
 
 
+    // 票 27:引擎是在地图服务里动态 import 的,先等它就绪;等不到就交给 A15 报失败,
+    // 不在这里抛错中断整轮走查(引擎起不来也该看到其余断言的真实结果)。
+    const engineReady = await page
+        .waitForFunction(
+            () => document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine === 'ready',
+            { timeout: 60000 },
+        )
+        .then(() => true)
+        .catch(() => false)
+    console.log(`Cesium 引擎就绪:${engineReady}`)
+
     // A2 顶栏、左右两栏、中央一张图同时出现,且各只有一份。
     // A2 顶栏、左右两栏、中央一张图同时出现,且各只有一份。
     const structure = await page.evaluate(() => {
     const structure = await page.evaluate(() => {
         const q = (s) => document.querySelector(s)
         const q = (s) => document.querySelector(s)
@@ -508,6 +520,114 @@ try {
         `after=${JSON.stringify(mapAfterAll)} 初始 instance=${mapInstanceAtStart}`,
         `after=${JSON.stringify(mapAfterAll)} 初始 instance=${mapInstanceAtStart}`,
     )
     )
 
 
+    // A15 票 27:宿主内恰好一个 Cesium Viewer / 画布,引擎与底图状态如实,
+    // 且走完「四模式切换 + 面板收放」后实例 id 仍是初始值。
+    const engine = await page.evaluate(() => {
+        const host = document.querySelector('.cockpit-shell__map .map-canvas')
+        return {
+            baseUrl: window.CESIUM_BASE_URL || '',
+            viewers: host ? host.querySelectorAll('.cesium-viewer').length : 0,
+            canvases: host ? host.querySelectorAll('.cesium-widget canvas').length : 0,
+            state: (host && host.dataset.mapEngine) || '',
+            basemap: (host && host.dataset.mapBasemap) || '',
+            status: host?.querySelector('[data-map-engine-status]')?.textContent?.trim() || '',
+            instanceId: (host && host.dataset.mapInstance) || '',
+            // 自带控件必须关掉:工具栏容器 Cesium 总会建,但里面不该有任何控件;
+            // 时间轴 / 动画容器则只在开启时才建。
+            widgetChrome: host
+                ? host.querySelectorAll(
+                      '.cesium-viewer-toolbar > *, .cesium-viewer-animationContainer, .cesium-viewer-timelineContainer',
+                  ).length
+                : -1,
+        }
+    })
+    check(
+        'A15-cesium-viewer',
+        '宿主内恰好 1 个 Cesium Viewer/画布,引擎状态 ready 且如实标注「底图未配置」,实例 id 不变',
+        engine.viewers === 1 &&
+            engine.canvases === 1 &&
+            engine.state === 'ready' &&
+            engine.basemap === 'not-configured' &&
+            engine.status.includes('底图未配置') &&
+            engine.instanceId !== '' &&
+            engine.instanceId === mapInstanceAtStart &&
+            engine.widgetChrome === 0 &&
+            /\/cesium\/$/.test(engine.baseUrl),
+        JSON.stringify(engine),
+    )
+
+    // A16 票 27:Cesium 运行时依赖的静态资源确实可访问(Worker / Assets / Widgets 各取一个代表)。
+    const cesiumAssets = await page.evaluate(async () => {
+        const base = window.CESIUM_BASE_URL || ''
+        const paths = [
+            'Workers/transferTypedArrayTest.js',
+            'Assets/approximateTerrainHeights.json',
+            'Widgets/widgets.css',
+        ]
+        const out = []
+        for (const item of paths) {
+            try {
+                const res = await fetch(base + item)
+                out.push({ path: item, status: res.status, type: res.headers.get('content-type') || '' })
+            } catch (error) {
+                out.push({ path: item, status: 0, type: String(error) })
+            }
+        }
+        return out
+    })
+    check(
+        'A16-cesium-static-200',
+        'Cesium 静态资源可访问(Workers / Assets / Widgets 全部 HTTP 200)',
+        cesiumAssets.length === 3 && cesiumAssets.every((a) => a.status === 200),
+        JSON.stringify(cesiumAssets),
+    )
+
+    // A17 票 27:引擎初始化失败时页面不白屏。用 CDP 拦掉 cesium 依赖包,强制走失败分支,
+    // 断言「失败状态 + 失败文案 + 仍保留可读兜底(临时影像)」三者同时成立。
+    const failedPage = await browser.newPage()
+    try {
+        const cdp = await failedPage.createCDPSession()
+        await cdp.send('Network.enable')
+        // dev 下 cesium 由 Vite 预打包成 deps 分块;build 下是 cesium 单独分包。
+        await cdp.send('Network.setBlockedURLs', { urls: ['*deps/cesium*', '*js/cesium.*'] })
+
+        await failedPage.goto(entry, { waitUntil: 'networkidle2', timeout: 60000 })
+        await failedPage.waitForSelector('.cockpit-shell', { timeout: 20000 })
+        const fellBack = await failedPage
+            .waitForFunction(
+                () => document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine === 'failed',
+                { timeout: 30000 },
+            )
+            .then(() => true)
+            .catch(() => false)
+
+        const fallback = await failedPage.evaluate(() => {
+            const host = document.querySelector('.cockpit-shell__map .map-canvas')
+            return {
+                shell: !!document.querySelector('.cockpit-shell'),
+                host: !!host,
+                canvases: host ? host.querySelectorAll('canvas').length : -1,
+                photoLayers: host ? host.querySelectorAll('.map-canvas__photo').length : -1,
+                state: (host && host.dataset.mapEngine) || '',
+                status: host?.querySelector('[data-map-engine-status]')?.textContent?.trim() || '',
+            }
+        })
+        check(
+            'A17-engine-failure-fallback',
+            '引擎初始化失败时不白屏:状态如实为 failed、给出失败文案、中央区域保留可读兜底',
+            fellBack &&
+                fallback.shell &&
+                fallback.host &&
+                fallback.state === 'failed' &&
+                fallback.status.includes('初始化失败') &&
+                fallback.photoLayers === 1 &&
+                fallback.canvases === 0,
+            JSON.stringify(fallback),
+        )
+    } finally {
+        await failedPage.close()
+    }
+
     // A11 运行期无错误。
     // A11 运行期无错误。
     check('A11-no-page-errors', '走查期间无页面级报错', pageErrors.length === 0, JSON.stringify(pageErrors.slice(0, 5)))
     check('A11-no-page-errors', '走查期间无页面级报错', pageErrors.length === 0, JSON.stringify(pageErrors.slice(0, 5)))
 
 

+ 32 - 1
.scratch/cockpit-shell-panel-layout/verify/t01-panel-catalog.mjs

@@ -27,6 +27,7 @@ import {
     resizeMap,
     resizeMap,
     unmountMap,
     unmountMap,
 } from '../../../src/modules/cockpit/services/mapService.js'
 } from '../../../src/modules/cockpit/services/mapService.js'
+import { resolveCesiumBaseUrl } from '../../../src/modules/cockpit/services/cesiumAssets.js'
 
 
 const EXPECTED_MODES = ['overview', 'infrastructure', 'monitoring', 'warningResponse']
 const EXPECTED_MODES = ['overview', 'infrastructure', 'monitoring', 'warningResponse']
 
 
@@ -115,9 +116,39 @@ const third = mountMap(hostA)
 assert.notEqual(third.id, first.id, '销毁后重新挂载应是新实例(id 不复用)')
 assert.notEqual(third.id, first.id, '销毁后重新挂载应是新实例(id 不复用)')
 assert.equal(getLifecycleStats().created, 2, '重新挂载后创建数为 2')
 assert.equal(getLifecycleStats().created, 2, '重新挂载后创建数为 2')
 
 
+// ---- Cesium 资源基址(票 27):跟随 publicPath './' 与 qiankun 注入路径,不硬编码 /cesium/ ----
+// hash 路由下文档 URL 是 index.html(#/cockpit 不参与解析)。
+assert.equal(
+    resolveCesiumBaseUrl('./', 'http://localhost:8000/#/cockpit'),
+    'http://localhost:8000/cesium/',
+    '相对 publicPath 应相对文档解析成绝对 URL(hash 路由不影响)',
+)
+assert.equal(
+    resolveCesiumBaseUrl('./', 'http://host/app/index.html'),
+    'http://host/app/cesium/',
+    '子目录部署下应落在文档同级目录',
+)
+assert.equal(
+    resolveCesiumBaseUrl('https://cos.icegl.cn/qiankun/tvt/', 'http://host/'),
+    'https://cos.icegl.cn/qiankun/tvt/cesium/',
+    'qiankun 注入的绝对基址应被采用',
+)
+assert.equal(resolveCesiumBaseUrl('/base/', 'http://host/'), 'http://host/base/cesium/', '根相对基址应被采用')
+assert.equal(resolveCesiumBaseUrl().endsWith('/cesium/'), true, '默认基址也必须以 cesium/ 结尾')
+
+// ---- 非浏览器宿主:不加载引擎、不抛错,且如实标注(票 27)----
+resetLifecycleStatsForTest()
+const domlessHost = { id: 'not-a-dom-node' }
+mountMap(domlessHost)
+assert.equal(getLifecycleStats().engine, 'unavailable', '非 DOM 宿主不应启动引擎')
+assert.equal(getLifecycleStats().basemap, 'not-configured', '本票不配置底图,状态应如实为未配置')
+assert.equal(getLifecycleStats().created, 1, '非 DOM 宿主仍应正常记一次创建')
+unmountMap()
+
 resetLifecycleStatsForTest()
 resetLifecycleStatsForTest()
 assert.equal(getLifecycleStats().created, 0, '重置后计数应归零')
 assert.equal(getLifecycleStats().created, 0, '重置后计数应归零')
+assert.equal(getLifecycleStats().engine, 'idle', '重置后引擎状态应回到 idle')
 
 
 console.log(
 console.log(
-    `T01 检查通过:${EXPECTED_MODES.length} 个模式 / ${allPanelIds.length} 个面板 / 展开收放与默认展开一致 / 筛选选项(${SCOPES.length} 范围 · ${INDUSTRIES.length} 行业)正确 / 地图服务生命周期幂等且只创建一次。`,
+    `T01 检查通过:${EXPECTED_MODES.length} 个模式 / ${allPanelIds.length} 个面板 / 展开收放与默认展开一致 / 筛选选项(${SCOPES.length} 范围 · ${INDUSTRIES.length} 行业)正确 / 地图服务生命周期幂等且只创建一次 / Cesium 资源基址相对 publicPath 解析正确。`,
 )
 )

+ 6 - 0
package.json

@@ -76,6 +76,10 @@
     "@pangenerator/tweakpane-textarea-plugin": "^2.0.0",
     "@pangenerator/tweakpane-textarea-plugin": "^2.0.0",
     "@pmndrs/vanilla": "^1.25.0",
     "@pmndrs/vanilla": "^1.25.0",
     "@sparkjsdev/spark": "2.0.0",
     "@sparkjsdev/spark": "2.0.0",
+    "@takram/three-atmosphere": "0.19.1",
+    "@takram/three-clouds": "0.7.6",
+    "@takram/three-geospatial": "0.9.1",
+    "@takram/three-geospatial-effects": "0.6.4",
     "@tresjs/cientos": "5.2.1",
     "@tresjs/cientos": "5.2.1",
     "@tresjs/core": "5.2.1",
     "@tresjs/core": "5.2.1",
     "@tresjs/leches": "0.14.1",
     "@tresjs/leches": "0.14.1",
@@ -89,6 +93,7 @@
     "animate.css": "^4.1.1",
     "animate.css": "^4.1.1",
     "axios": "^1.12.2",
     "axios": "^1.12.2",
     "cannon-es": "^0.20.0",
     "cannon-es": "^0.20.0",
+    "cesium": "1.145.0",
     "chalk": "^5.6.2",
     "chalk": "^5.6.2",
     "color": "^5.0.2",
     "color": "^5.0.2",
     "compressing": "^2.0.0",
     "compressing": "^2.0.0",
@@ -108,6 +113,7 @@
     "json5": "^2.2.3",
     "json5": "^2.2.3",
     "jszip": "^3.10.1",
     "jszip": "^3.10.1",
     "lamina": "1.2.2",
     "lamina": "1.2.2",
+    "leaflet": "1.9.4",
     "lodash": "^4.17.21",
     "lodash": "^4.17.21",
     "lygia": "^1.3.3",
     "lygia": "^1.3.3",
     "mqtt": "^5.14.1",
     "mqtt": "^5.14.1",

+ 111 - 0
src/common/cesiumStaticPlugin.js

@@ -0,0 +1,111 @@
+import fs from 'node:fs'
+import path from 'node:path'
+
+/**
+ * Vite 插件:把 Cesium 的预构建静态资源挂到 `<base>/cesium/`(票 27)。
+ *
+ * 为什么不用 `vite-plugin-cesium`(决策地图票 06 的调研结论):社区封装会改 index.html 并注入
+ * 脚本,与本仓库的 Fes HTML 注入、`@originjs/vite-plugin-federation`、qiankun 注入冲突。
+ * 这里只做官方 npm 配方必需的两件事:
+ *   · dev / pre.dev / pre.dev.one → 中间件把 `cesium/*` 映射到 node_modules 里的预构建目录
+ *     (`pre.dev.one` 关掉了 `copyPublicDir`,走 public 目录那条路不通);
+ *   · build → 把 Workers / ThirdParty / Assets / Widgets 四个目录拷到产物 `cesium/`。
+ *
+ * 运行时用 `CESIUM_BASE_URL`(见 services/cesiumAssets.js)拼这些目录的地址;
+ * Worker 也是从这里按 `Workers/<模块>.js` 加载的,不能省。
+ */
+// 只搬这四个目录:Build/Cesium 根下还有 Cesium.js / index.js / index.cjs 三个入口包,
+// 我们是 ESM 打包引入,不需要它们,搬过去只是白占体积。
+const STATIC_DIRS = ['Workers', 'ThirdParty', 'Assets', 'Widgets']
+
+const MIME = {
+    '.js': 'text/javascript; charset=utf-8',
+    '.mjs': 'text/javascript; charset=utf-8',
+    '.css': 'text/css; charset=utf-8',
+    '.json': 'application/json; charset=utf-8',
+    '.wasm': 'application/wasm',
+    '.xml': 'application/xml',
+    '.png': 'image/png',
+    '.jpg': 'image/jpeg',
+    '.jpeg': 'image/jpeg',
+    '.gif': 'image/gif',
+    '.svg': 'image/svg+xml',
+    '.ktx2': 'image/ktx2',
+    '.glb': 'model/gltf-binary',
+    '.bin': 'application/octet-stream',
+}
+
+/** 把请求路径解析成源目录内的真实文件;越界、非白名单目录、目录、不存在一律返回 null。 */
+function resolveStaticFile(sourceDir, requestUrl) {
+    const clean = decodeURIComponent((requestUrl || '').split('?')[0].split('#')[0])
+    const relative = path.posix.normalize(clean).replace(/^\/+/, '')
+    if (!relative || relative.startsWith('..')) return null
+
+    const [top] = relative.split('/')
+    if (!STATIC_DIRS.includes(top)) return null
+
+    const file = path.join(sourceDir, relative)
+    if (!file.startsWith(sourceDir + path.sep)) return null
+
+    try {
+        return fs.statSync(file).isFile() ? file : null
+    } catch {
+        return null
+    }
+}
+
+/**
+ * @param {object} [options]
+ * @param {string} [options.sourceDir] 相对项目根的 Cesium 预构建目录
+ * @param {string} [options.targetDir] 产物与请求路径下的目录名
+ */
+export default function cesiumStatic({ sourceDir = 'node_modules/cesium/Build/Cesium', targetDir = 'cesium' } = {}) {
+    let root = process.cwd()
+    let outDir = ''
+    let command = 'serve'
+    const absoluteSource = () => path.resolve(root, sourceDir)
+
+    return {
+        name: 'tvt:cesium-static',
+
+        configResolved(config) {
+            root = config.root || root
+            command = config.command
+            const resolved = (config.build && config.build.outDir) || 'dist'
+            outDir = path.isAbsolute(resolved) ? resolved : path.resolve(root, resolved)
+        },
+
+        configureServer(server) {
+            const dir = absoluteSource()
+            server.middlewares.use(`/${targetDir}`, (req, res, next) => {
+                const file = resolveStaticFile(dir, req.url)
+                if (!file) return next()
+                res.setHeader('Content-Type', MIME[path.extname(file).toLowerCase()] || 'application/octet-stream')
+                res.setHeader('Cache-Control', 'no-cache')
+                fs.createReadStream(file).pipe(res)
+            })
+        },
+
+        // dev 关闭服务器时 Vite 也会调这个钩子,只认构建。
+        closeBundle() {
+            if (command !== 'build') return
+
+            const from = absoluteSource()
+            if (!fs.existsSync(from)) {
+                this.warn(`Cesium 预构建静态资源不存在:${from}(先安装 cesium 依赖)`)
+                return
+            }
+
+            const to = path.join(outDir || path.resolve(root, 'dist'), targetDir)
+            for (const dir of STATIC_DIRS) {
+                const source = path.join(from, dir)
+                if (!fs.existsSync(source)) {
+                    this.warn(`Cesium 静态资源目录缺失:${source}`)
+                    continue
+                }
+                fs.cpSync(source, path.join(to, dir), { recursive: true })
+                console.log(`✔ Copied: ${source} → ${path.join(to, dir)}`)
+            }
+        },
+    }
+}

+ 44 - 0
src/modules/cockpit/services/cesiumAssets.js

@@ -0,0 +1,44 @@
+/**
+ * Cesium 资源基址(票 27 / 决策地图票 06 调研)。
+ *
+ * 硬约束:`window.CESIUM_BASE_URL` 必须在 `import ... from 'cesium'` **之前**赋值。
+ * ES module 的静态 import 会被提升,所以这里刻意不 import cesium —— 由地图服务层
+ * 先调 `ensureCesiumBaseUrl()`,再动态 `import('./cesiumEngine.js')`。
+ *
+ * 取值不硬编码 `/cesium/`:本仓库 `publicPath: './'`(相对 base),qiankun 微前端下
+ * 主应用还会注入 `__INJECTED_PUBLIC_PATH_BY_QIANKUN__`;两者都相对 HTML 文档解析成绝对 URL,
+ * 否则 Worker 会 404。静态资源本体见 `src/common/cesiumStaticPlugin.js`。
+ */
+const CESIUM_DIR = 'cesium/'
+
+/**
+ * 纯函数:把 base(Fes 的 publicPath / qiankun 注入路径)解析成 Cesium 资源基址。
+ * @param {string} base 相对(`./`、`/app/`)或绝对(`https://host/app/`)基址
+ * @param {string} documentBase 文档基址(`document.baseURI`),用于消解相对路径
+ * @returns {string} 绝对 URL,带尾斜杠
+ */
+export function resolveCesiumBaseUrl(base = './', documentBase = 'http://localhost/') {
+    return new URL(CESIUM_DIR, new URL(base, documentBase)).href
+}
+
+function pickBase() {
+    const scope = globalThis
+    if (scope.__POWERED_BY_QIANKUN__ && scope.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
+        return scope.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
+    }
+    // Vite 静态替换 import.meta.env.BASE_URL;非 Vite 环境(单测脚本)取 process.env.BASE_URL。
+    const viteBase = import.meta.env && import.meta.env.BASE_URL
+    if (viteBase) return viteBase
+    if (typeof process !== 'undefined' && process.env && process.env.BASE_URL) return process.env.BASE_URL
+    return './'
+}
+
+/** 幂等写入全局基址并返回它;非浏览器环境只返回计算值,不抛错。 */
+export function ensureCesiumBaseUrl() {
+    const documentBase = typeof document !== 'undefined' && document.baseURI ? document.baseURI : 'http://localhost/'
+    const url = resolveCesiumBaseUrl(pickBase(), documentBase)
+    if (typeof window !== 'undefined') {
+        window.CESIUM_BASE_URL = url
+    }
+    return url
+}

+ 63 - 0
src/modules/cockpit/services/cesiumEngine.js

@@ -0,0 +1,63 @@
+/**
+ * Cesium 引擎适配层(票 27):本仓库里唯一 `import 'cesium'` 的地方。
+ *
+ * 只在 `mapService.mountMap()` 完成 `window.CESIUM_BASE_URL` 注入之后被动态 import,
+ * 因此静态 import 提升不会踩到时序(见 services/cesiumAssets.js)。
+ *
+ * 本票只负责「把引擎接到宿主上」:
+ *   · 唯一 `Viewer`,建在宿主要素内;
+ *   · 关掉自带控件与默认相机输入 —— 默认左键旋转 / 滚轮缩放会与票 03 的三键契约打架
+ *     (`enableInputs` 是临时开关,必须逐项关);
+ *   · 不碰任何需要账号的能力:Ion token、`Terrain.fromWorldTerrain`、`createOsmBuildings`
+ *     都在票 06 明令禁止;
+ *   · 不配置影像底图(`baseLayer: false`):底图未配置就不伪造地理配准,
+ *     正式底图来源见票 21。
+ */
+import { Color, Viewer } from 'cesium'
+import 'cesium/Build/Cesium/Widgets/widgets.css'
+
+const VIEWER_OPTIONS = {
+    // 无底图 → 不往某个假坐标范围里塞临时影像。
+    baseLayer: false,
+    // 自带控件全关(导航/时间轴/拾取框等)。
+    animation: false,
+    timeline: false,
+    geocoder: false,
+    homeButton: false,
+    sceneModePicker: false,
+    baseLayerPicker: false,
+    navigationHelpButton: false,
+    navigationInstructionsInitiallyVisible: false,
+    fullscreenButton: false,
+    infoBox: false,
+    selectionIndicator: false,
+    vrButton: false,
+}
+
+/**
+ * 在宿主要素上创建 Viewer。
+ * @param {Element} host 宿主要素(`.map-canvas`)
+ * @returns {Viewer}
+ */
+export function createViewer(host) {
+    const viewer = new Viewer(host, VIEWER_OPTIONS)
+
+    const controller = viewer.scene.screenSpaceCameraController
+    controller.enableRotate = false
+    controller.enableZoom = false
+    controller.enableTilt = false
+    controller.enableLook = false
+    controller.enableTranslate = false
+
+    // 无底图时用驾驶舱底色画球体,避免 Cesium 默认亮蓝球穿帮。
+    viewer.scene.globe.baseColor = Color.fromCssColorString('#04121f')
+    return viewer
+}
+
+export function resizeViewer(viewer) {
+    viewer.resize()
+}
+
+export function destroyViewer(viewer) {
+    viewer.destroy()
+}

+ 160 - 8
src/modules/cockpit/services/mapService.js

@@ -1,17 +1,53 @@
 /**
 /**
  * 地图服务:驾驶舱「中央一张图」实例生命周期的唯一所有者。
  * 地图服务:驾驶舱「中央一张图」实例生命周期的唯一所有者。
  *
  *
- * 为什么要有这一层(ADR 0004 / 0005、票 02):
+ * 为什么要有这一层(ADR 0004 / 0005、票 02、票 27):
  * 页面组件不得直接持有底层地图实现,地图实例的创建/尺寸/销毁必须集中在一处,
  * 页面组件不得直接持有底层地图实现,地图实例的创建/尺寸/销毁必须集中在一处,
  * 才能保证「切换一级模式或收放面板时不销毁、不重建」。
  * 才能保证「切换一级模式或收放面板时不销毁、不重建」。
  *
  *
- * 当前内部没有引擎(渲染仍是组件里的临时影像占位);接入 Cesium 时只替换本文件内部:
- * 用宿主要素 `new Viewer(host, …)`、`resizeMap()` 转调 `viewer.resize()`、
- * `unmountMap()` 转调 `viewer.destroy()`,外壳与 MapCanvas 无需改动。
- * 接入细节见票 06 的调研:`.scratch/web-cockpit-decision-map/research/cesium-fes-vite.md`。
+ * 票 27 起内部接了真实引擎(Cesium):
+ *   · `mountMap(host)` 幂等,且只对**真 DOM 宿主要素**异步加载引擎(`viewer` 建在宿主内);
+ *   · `resizeMap()` 转调 `viewer.resize()`、`unmountMap()` 转调 `viewer.destroy()`;
+ *   · 引擎没加载完就销毁 / 销毁后才加载完,都不会泄漏实例;
+ *   · 底图未配置时如实标注「底图未配置」,引擎起不来时标注失败 —— 组件里的临时影像继续兜底,
+ *     页面不白屏。
+ * 接入配方见决策地图调研:`.scratch/web-cockpit-decision-map/research/cesium-fes-vite.md`。
  *
  *
- * 本模块不依赖 DOM 与 Vue,可脱离浏览器直接断言(见 verify/t01-panel-catalog.mjs)。
+ * 本模块不依赖 Vue;对宿主元素的 DOM 操作都有 `isDomHost` 守卫,
+ * 因此仍可脱离浏览器直接断言(见 verify/t01-panel-catalog.mjs)。
  */
  */
+import { ensureCesiumBaseUrl } from './cesiumAssets.js'
+
+/** 本票不做真底图(票 21 负责天地图/甲方瓦片);这里只保证「不伪造」。 */
+const BASEMAP_STATE = 'not-configured'
+
+const ENGINE_TEXT = {
+    unavailable: 'Cesium 引擎未启动(无宿主要素)',
+    booting: 'Cesium 引擎加载中…',
+    ready: 'Cesium 引擎已就绪 · 底图未配置(正式底图见票 21)',
+    failed: 'Cesium 引擎初始化失败 · 中央区域回退临时底图',
+}
+
+// 状态浮标由服务层自绘并挂在宿主内:外壳与 MapCanvas 组件按票面要求不改。
+const STATUS_STYLE = [
+    'position:absolute',
+    'z-index:3',
+    'top:74px',
+    'left:50%',
+    'transform:translateX(-50%)',
+    'max-width:min(78%, 620px)',
+    'padding:3px 10px',
+    'border:1px solid rgba(58,170,226,.42)',
+    'background:rgba(4,20,38,.72)',
+    'color:#bad4e0',
+    'font-size:11px',
+    'line-height:1.4',
+    'letter-spacing:.04em',
+    'white-space:nowrap',
+    'overflow:hidden',
+    'text-overflow:ellipsis',
+    'pointer-events:none',
+].join(';')
 
 
 let instance = null
 let instance = null
 let sequence = 0
 let sequence = 0
@@ -19,6 +55,78 @@ let createdCount = 0
 let destroyedCount = 0
 let destroyedCount = 0
 let resizeCount = 0
 let resizeCount = 0
 let lastResizeAt = ''
 let lastResizeAt = ''
+let engineState = 'idle' // idle | unavailable | booting | ready | failed
+let engineError = ''
+
+/** 只有真元素才做 DOM / 引擎操作;单测传普通对象时整条引擎链路都不启动。 */
+function isDomHost(host) {
+    return !!host && typeof host === 'object' && host.nodeType === 1 && typeof host.appendChild === 'function'
+}
+
+/** 把引擎状态写进宿主(dataset 给走查读,浮标给人读)。 */
+function paintEngineState(record) {
+    if (!isDomHost(record.host)) return
+
+    record.host.dataset.mapEngine = engineState
+    record.host.dataset.mapBasemap = BASEMAP_STATE
+
+    if (!record.statusNode) {
+        const node = record.host.ownerDocument.createElement('div')
+        node.className = 'map-engine-status'
+        node.setAttribute('data-map-engine-status', '')
+        node.style.cssText = STATUS_STYLE
+        record.host.appendChild(node)
+        record.statusNode = node
+    }
+
+    const failed = engineState === 'failed'
+    record.statusNode.textContent = `${ENGINE_TEXT[engineState] || engineState}${engineError ? `(${engineError})` : ''}`
+    record.statusNode.style.borderColor = failed ? 'rgba(255,77,94,.55)' : 'rgba(58,170,226,.42)'
+    record.statusNode.style.color = failed ? '#ffb3bb' : '#bad4e0'
+}
+
+function setEngineState(record, state, error = '') {
+    engineState = state
+    engineError = error
+    paintEngineState(record)
+}
+
+/**
+ * 异步加载引擎。全部逻辑都在 try 内,避免出现未处理的 Promise 拒绝(那会算页面级报错)。
+ * @param {object} record mountMap 建立的实例记录
+ */
+async function bootEngine(record) {
+    try {
+        setEngineState(record, 'booting')
+        ensureCesiumBaseUrl()
+        const engine = await import('./cesiumEngine.js')
+
+        // 加载期间已经离开 /cockpit:不留下实例。
+        if (instance !== record) return
+
+        const viewer = engine.createViewer(record.host)
+
+        // 建 Viewer 是同步的,但状态可能在这一刻被销毁过 —— 补一次销毁。
+        if (instance !== record) {
+            engine.destroyViewer(viewer)
+            return
+        }
+
+        record.engine = engine
+        record.viewer = viewer
+
+        // 加载期间的尺寸变化不能丢,补一次 resize。
+        if (record.resizePending) {
+            record.resizePending = false
+            engine.resizeViewer(viewer)
+        }
+
+        setEngineState(record, 'ready')
+    } catch (error) {
+        if (instance !== record) return
+        setEngineState(record, 'failed', String((error && error.message) || error))
+    }
+}
 
 
 /**
 /**
  * 挂载地图实例。幂等:已挂载时直接返回既有实例,绝不重建。
  * 挂载地图实例。幂等:已挂载时直接返回既有实例,绝不重建。
@@ -28,27 +136,51 @@ export function mountMap(host) {
     if (instance) {
     if (instance) {
         return instance
         return instance
     }
     }
+
     sequence += 1
     sequence += 1
     createdCount += 1
     createdCount += 1
     instance = {
     instance = {
         id: `cockpit-map-${sequence}`,
         id: `cockpit-map-${sequence}`,
         host: host || null,
         host: host || null,
         createdAt: new Date().toISOString(),
         createdAt: new Date().toISOString(),
+        engine: null,
+        viewer: null,
+        statusNode: null,
+        resizePending: false,
     }
     }
-    return instance
+
+    const record = instance
+    engineState = 'idle'
+    engineError = ''
+
+    if (isDomHost(record.host)) {
+        bootEngine(record)
+    } else {
+        setEngineState(record, 'unavailable')
+    }
+
+    return record
 }
 }
 
 
 export function getMapInstance() {
 export function getMapInstance() {
     return instance
     return instance
 }
 }
 
 
-/** 宿主尺寸变化时调用;当前只记录,接入 Cesium 后转调 viewer.resize()。 */
+/** 宿主尺寸变化时调用:引擎就绪后转调 `viewer.resize()`。 */
 export function resizeMap() {
 export function resizeMap() {
     if (!instance) {
     if (!instance) {
         return
         return
     }
     }
+
     resizeCount += 1
     resizeCount += 1
     lastResizeAt = new Date().toISOString()
     lastResizeAt = new Date().toISOString()
+
+    if (instance.viewer) {
+        instance.engine.resizeViewer(instance.viewer)
+    } else {
+        // 引擎还在加载:记下来,等 boot 完成后补一次。
+        instance.resizePending = true
+    }
 }
 }
 
 
 /** 只在离开 /cockpit 时销毁。 */
 /** 只在离开 /cockpit 时销毁。 */
@@ -56,8 +188,22 @@ export function unmountMap() {
     if (!instance) {
     if (!instance) {
         return
         return
     }
     }
+
+    const record = instance
     instance = null
     instance = null
     destroyedCount += 1
     destroyedCount += 1
+
+    if (record.statusNode) {
+        record.statusNode.remove()
+        record.statusNode = null
+    }
+    if (record.viewer) {
+        record.engine.destroyViewer(record.viewer)
+        record.viewer = null
+    }
+
+    engineState = 'idle'
+    engineError = ''
 }
 }
 
 
 /** 可观测的生命周期计数:走查与单测据此断言「只创建一次、未重建」。 */
 /** 可观测的生命周期计数:走查与单测据此断言「只创建一次、未重建」。 */
@@ -69,6 +215,10 @@ export function getLifecycleStats() {
         lastResizeAt,
         lastResizeAt,
         mounted: !!instance,
         mounted: !!instance,
         instanceId: instance ? instance.id : '',
         instanceId: instance ? instance.id : '',
+        // 票 27 追加:引擎状态与底图状态(上面 6 个字段语义不变)。
+        engine: engineState,
+        engineError,
+        basemap: BASEMAP_STATE,
     }
     }
 }
 }
 
 
@@ -80,4 +230,6 @@ export function resetLifecycleStatsForTest() {
     destroyedCount = 0
     destroyedCount = 0
     resizeCount = 0
     resizeCount = 0
     lastResizeAt = ''
     lastResizeAt = ''
+    engineState = 'idle'
+    engineError = ''
 }
 }

+ 232 - 1
yarn.lock

@@ -841,6 +841,46 @@
     "@babel/helper-string-parser" "^7.27.1"
     "@babel/helper-string-parser" "^7.27.1"
     "@babel/helper-validator-identifier" "^7.28.5"
     "@babel/helper-validator-identifier" "^7.28.5"
 
 
+"@cesium/engine@^26.3.0":
+  version "26.3.0"
+  resolved "https://registry.yarnpkg.com/@cesium/engine/-/engine-26.3.0.tgz#66e4c3261f938984cc26e19fb5a3d75407fb148c"
+  integrity sha512-jtgOewm/IZoypmqpbeTlYhMOKhQg+XPwUXoYat84vhIU8l8SfVSEat4Lc/Y4Fb7ypi3z1YmElKjVYvoI5CH6Pw==
+  dependencies:
+    "@cesium/wasm-splats" "^0.1.0-alpha.2"
+    "@spz-loader/core" "0.3.1"
+    "@tweenjs/tween.js" "^25.0.0"
+    "@zip.js/zip.js" "^2.9.0"
+    autolinker "^4.0.0"
+    bitmap-sdf "^1.0.3"
+    dompurify "^3.4.14"
+    draco3d "^1.5.1"
+    earcut "3.0.2"
+    grapheme-splitter "^1.0.4"
+    jsep "^1.3.8"
+    kdbush "^4.1.0"
+    ktx-parse "^1.0.0"
+    lerc "^2.0.0"
+    mersenne-twister "^1.1.0"
+    meshoptimizer "^1.2.0"
+    pako "^3.0.0"
+    protobufjs "^8.8.0"
+    rbush "^4.0.1"
+    topojson-client "^3.1.0"
+    urijs "^1.19.7"
+
+"@cesium/wasm-splats@^0.1.0-alpha.2":
+  version "0.1.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/@cesium/wasm-splats/-/wasm-splats-0.1.0-alpha.2.tgz#f2d0d717d46f5f3d6d923549e90fb5ab4d429cab"
+  integrity sha512-t9pMkknv31hhIbLpMa8yPvmqfpvs5UkUjgqlQv9SeO8VerCXOYnyP8/486BDaFrztM0A7FMbRjsXtNeKvqQghA==
+
+"@cesium/widgets@^16.2.0":
+  version "16.2.0"
+  resolved "https://registry.yarnpkg.com/@cesium/widgets/-/widgets-16.2.0.tgz#56127b34db469e765f90b941ecdc1c0abcade44a"
+  integrity sha512-cxYw8t4GVJUVSNCN+yDXqB7MqxLme1mGWT4W3pYQXrlswdIBey9Q+rf5cVZysd0PUGcrLXFtNRfbScOqQe78BQ==
+  dependencies:
+    "@cesium/engine" "^26.3.0"
+    nosleep.js "^0.12.0"
+
 "@css-render/plugin-bem@^0.15.14":
 "@css-render/plugin-bem@^0.15.14":
   version "0.15.14"
   version "0.15.14"
   resolved "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz"
   resolved "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz"
@@ -2040,6 +2080,11 @@
     "@parcel/watcher-win32-ia32" "2.5.6"
     "@parcel/watcher-win32-ia32" "2.5.6"
     "@parcel/watcher-win32-x64" "2.5.6"
     "@parcel/watcher-win32-x64" "2.5.6"
 
 
+"@petamoriken/float16@^3.9.3":
+  version "3.9.3"
+  resolved "https://registry.yarnpkg.com/@petamoriken/float16/-/float16-3.9.3.tgz#84acef4816db7e4c2fe1c4e8cf902bcbc0440ac3"
+  integrity sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==
+
 "@pkgr/core@^0.2.0":
 "@pkgr/core@^0.2.0":
   version "0.2.1"
   version "0.2.1"
   resolved "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.1.tgz"
   resolved "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.1.tgz"
@@ -2472,11 +2517,53 @@
   dependencies:
   dependencies:
     fflate "^0.8.2"
     fflate "^0.8.2"
 
 
+"@spz-loader/core@0.3.1":
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/@spz-loader/core/-/core-0.3.1.tgz#3337d4753f60908c30ae94b5599020f18056a18e"
+  integrity sha512-8qJ1WIBXaJu8HjnJAjYniE0kYcr0kCe5Hp7kDzYiGVvvd7zyrOBwbF5imoW5mvwx1Qba0hxGEK5R9jEoaHKJFA==
+
 "@stitches/react@^1.2.8":
 "@stitches/react@^1.2.8":
   version "1.2.8"
   version "1.2.8"
   resolved "https://registry.npmmirror.com/@stitches/react/-/react-1.2.8.tgz"
   resolved "https://registry.npmmirror.com/@stitches/react/-/react-1.2.8.tgz"
   integrity sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==
   integrity sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==
 
 
+"@takram/three-atmosphere@0.19.1":
+  version "0.19.1"
+  resolved "https://registry.yarnpkg.com/@takram/three-atmosphere/-/three-atmosphere-0.19.1.tgz#4be33fcd2353f7e2eb7d425c138b1822a4555104"
+  integrity sha512-k/QAgED7bMkjVzHiBu2QD1Cqg5lcgHLc8vxVeiTTzUE+glZcZOXTjjvi74yjb+eKQHW5wh1WL/DjxVvluAQU0Q==
+  dependencies:
+    "@takram/three-geospatial" "0.9.1"
+    astronomy-engine "^2.1.19"
+    react-merge-refs "^3.0.2"
+    tiny-invariant "^1.3.3"
+    url-join "^5.0.0"
+
+"@takram/three-clouds@0.7.6":
+  version "0.7.6"
+  resolved "https://registry.yarnpkg.com/@takram/three-clouds/-/three-clouds-0.7.6.tgz#83f7640d91168ac6c6c8d867084ecd9e290fbe8c"
+  integrity sha512-cfoEI3W5NJuYUL1Pd4k5UXLlb7rLThh3dNIOO+/EPw8cbuRUgiIVEVtNPmA+v5KRJf87gPI+rjuxgK0huAajDw==
+  dependencies:
+    "@takram/three-atmosphere" "0.19.1"
+    "@takram/three-geospatial" "0.9.1"
+    tiny-invariant "^1.3.3"
+    type-fest "^5.4.4"
+
+"@takram/three-geospatial-effects@0.6.4":
+  version "0.6.4"
+  resolved "https://registry.yarnpkg.com/@takram/three-geospatial-effects/-/three-geospatial-effects-0.6.4.tgz#10a515a10a28c9bfce11f6ad167ea9cfb92a44e3"
+  integrity sha512-Ufgap/N30j4Go5fyI9qdax9RiVK69OY+UuGCsYeuvGyihbfN5zh8ChHfu1c8vjIkz1Czz8zP8+6OvjD9+QxkMw==
+  dependencies:
+    "@takram/three-geospatial" "0.9.1"
+
+"@takram/three-geospatial@0.9.1":
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/@takram/three-geospatial/-/three-geospatial-0.9.1.tgz#e36cd7f56480aa9a13af7f19635561fd3f29f7ed"
+  integrity sha512-fB7DAW0afN87e0/CcgZYVkF76yI0SXaRcVuT7M6+5vLZ8ySfJ7br2ceni37GVyY7zO1sLMBtoaMCH1mal/Zf9Q==
+  dependencies:
+    "@petamoriken/float16" "^3.9.3"
+    tiny-invariant "^1.3.3"
+    type-fest "^5.4.4"
+
 "@tokenizer/token@^0.3.0":
 "@tokenizer/token@^0.3.0":
   version "0.3.0"
   version "0.3.0"
   resolved "https://registry.npmmirror.com/@tokenizer/token/-/token-0.3.0.tgz"
   resolved "https://registry.npmmirror.com/@tokenizer/token/-/token-0.3.0.tgz"
@@ -4922,6 +5009,11 @@
   resolved "https://registry.npmmirror.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz"
   resolved "https://registry.npmmirror.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz"
   integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
   integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
 
 
+"@zip.js/zip.js@^2.9.0":
+  version "2.15.0"
+  resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.15.0.tgz#ff7bd1cb1ac209ec1014c4007be8c55e16289dd1"
+  integrity sha512-hYAuHAaWjt0axbofaDL5XUlmrQPsBDcK3f45ApZuh6N+8UGVk26GoGdtGDvcvzahsHytTQ6DtBVXwG9IcCMjjQ==
+
 abort-controller@^3.0.0:
 abort-controller@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz"
   resolved "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz"
@@ -5097,6 +5189,11 @@ assign-symbols@^1.0.0:
   resolved "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz"
   resolved "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz"
   integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==
   integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==
 
 
+astronomy-engine@^2.1.19:
+  version "2.1.19"
+  resolved "https://registry.yarnpkg.com/astronomy-engine/-/astronomy-engine-2.1.19.tgz#41b9fd2afb7eba3485e3803cbd9033011f8557af"
+  integrity sha512-8yWKNf7UeNbH458h3sAJ6ZgAjE5jTXp/mNNRFoC20j2SHwZIjAQeEsBB2Q3uCFRaTCCJRv33K2XhkhZQMXoX6w==
+
 async-function@^1.0.0:
 async-function@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz"
   resolved "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz"
@@ -5127,6 +5224,13 @@ attr-accept@^2.2.2:
   resolved "https://registry.npmmirror.com/attr-accept/-/attr-accept-2.2.5.tgz"
   resolved "https://registry.npmmirror.com/attr-accept/-/attr-accept-2.2.5.tgz"
   integrity sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==
   integrity sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==
 
 
+autolinker@^4.0.0:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-4.1.5.tgz#e0d45f04c41d62598bf80b809c422f732d3aafde"
+  integrity sha512-vEfYZPmvVOIuE567XBVCsx8SBgOYtjB2+S1iAaJ+HgH+DNjAcrHem2hmAeC9yaNGWayicv4yR+9UaJlkF3pvtw==
+  dependencies:
+    tslib "^2.8.1"
+
 autoprefixer@^10.4.4:
 autoprefixer@^10.4.4:
   version "10.4.21"
   version "10.4.21"
   resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz"
   resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz"
@@ -5219,6 +5323,11 @@ bit-twiddle@^1.0.0:
   resolved "https://registry.npmmirror.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz"
   resolved "https://registry.npmmirror.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz"
   integrity sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==
   integrity sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==
 
 
+bitmap-sdf@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/bitmap-sdf/-/bitmap-sdf-1.0.4.tgz#e87b8b1d84ee846567cfbb29d60eedd34bca5b6f"
+  integrity sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg==
+
 bl@^1.0.0:
 bl@^1.0.0:
   version "1.2.3"
   version "1.2.3"
   resolved "https://registry.npmmirror.com/bl/-/bl-1.2.3.tgz"
   resolved "https://registry.npmmirror.com/bl/-/bl-1.2.3.tgz"
@@ -5442,6 +5551,15 @@ centra@^2.7.0:
   dependencies:
   dependencies:
     follow-redirects "^1.15.6"
     follow-redirects "^1.15.6"
 
 
+cesium@1.145.0:
+  version "1.145.0"
+  resolved "https://registry.yarnpkg.com/cesium/-/cesium-1.145.0.tgz#2bcf05ba0eff0da0c637a9aebb4c5c754c8c9773"
+  integrity sha512-6Azix8b5LPpoVSx8XQ6zPztpluJVmq+CEO3W2rOWxtc6bri6Nc9MvCYhKmTW1LAEwfisV7yzNgfulCXw9842+g==
+  dependencies:
+    "@cesium/engine" "^26.3.0"
+    "@cesium/widgets" "^16.2.0"
+    protobufjs "^8.8.0"
+
 chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2:
 chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2:
   version "4.1.2"
   version "4.1.2"
   resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
   resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
@@ -6172,6 +6290,13 @@ dompurify@^3.1.7:
   optionalDependencies:
   optionalDependencies:
     "@types/trusted-types" "^2.0.7"
     "@types/trusted-types" "^2.0.7"
 
 
+dompurify@^3.4.14:
+  version "3.4.15"
+  resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.15.tgz#30351b34513894f428c13bddb28bff0a027ae804"
+  integrity sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==
+  optionalDependencies:
+    "@types/trusted-types" "^2.0.7"
+
 domutils@^2.8.0:
 domutils@^2.8.0:
   version "2.8.0"
   version "2.8.0"
   resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz"
   resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz"
@@ -6213,7 +6338,7 @@ dotenv@^16.0.0:
   resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz"
   resolved "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz"
   integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
   integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
 
 
-draco3d@^1.4.1:
+draco3d@^1.4.1, draco3d@^1.5.1:
   version "1.5.7"
   version "1.5.7"
   resolved "https://registry.npmmirror.com/draco3d/-/draco3d-1.5.7.tgz"
   resolved "https://registry.npmmirror.com/draco3d/-/draco3d-1.5.7.tgz"
   integrity sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==
   integrity sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==
@@ -6232,6 +6357,11 @@ duplexer@^0.1.2:
   resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz"
   resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz"
   integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
   integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
 
 
+earcut@3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.2.tgz#d478a29aaf99acf418151493048aa197d0512248"
+  integrity sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==
+
 earcut@^2.0.0, earcut@^2.2.4:
 earcut@^2.0.0, earcut@^2.2.4:
   version "2.2.4"
   version "2.2.4"
   resolved "https://registry.npmmirror.com/earcut/-/earcut-2.2.4.tgz"
   resolved "https://registry.npmmirror.com/earcut/-/earcut-2.2.4.tgz"
@@ -7478,6 +7608,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
   resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz"
   resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz"
   integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
   integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
 
 
+grapheme-splitter@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
+  integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
+
 graphemer@^1.4.0:
 graphemer@^1.4.0:
   version "1.4.0"
   version "1.4.0"
   resolved "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz"
   resolved "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz"
@@ -8146,6 +8281,11 @@ js-yaml@^4.1.0, js-yaml@^4.1.1:
   dependencies:
   dependencies:
     argparse "^2.0.1"
     argparse "^2.0.1"
 
 
+jsep@^1.3.8:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.4.0.tgz#19feccbfa51d8a79f72480b4b8e40ce2e17152f0"
+  integrity sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==
+
 jsesc@^3.0.2, jsesc@~3.1.0:
 jsesc@^3.0.2, jsesc@~3.1.0:
   version "3.1.0"
   version "3.1.0"
   resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz"
   resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz"
@@ -8233,6 +8373,11 @@ jszip@^3.10.1:
     readable-stream "~2.3.6"
     readable-stream "~2.3.6"
     setimmediate "^1.0.5"
     setimmediate "^1.0.5"
 
 
+kdbush@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.1.0.tgz#d504bc0447a59be4fb75533a5c25e316b3ed8859"
+  integrity sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==
+
 keyv@^4.5.3, keyv@^4.5.4:
 keyv@^4.5.3, keyv@^4.5.4:
   version "4.5.4"
   version "4.5.4"
   resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz"
   resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz"
@@ -8247,6 +8392,11 @@ klaw-sync@^6.0.0:
   dependencies:
   dependencies:
     graceful-fs "^4.1.11"
     graceful-fs "^4.1.11"
 
 
+ktx-parse@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/ktx-parse/-/ktx-parse-1.1.0.tgz#1077c87776ddabfc32fc01f644f6ad8f8890abf8"
+  integrity sha512-mKp3y+FaYgR7mXWAbyyzpa/r1zDWeaunH+INJO4fou3hb45XuNSwar+7llrRyvpMWafxSIi99RNFJ05MHedaJQ==
+
 lamina@1.2.2:
 lamina@1.2.2:
   version "1.2.2"
   version "1.2.2"
   resolved "https://registry.npmmirror.com/lamina/-/lamina-1.2.2.tgz"
   resolved "https://registry.npmmirror.com/lamina/-/lamina-1.2.2.tgz"
@@ -8259,6 +8409,16 @@ lamina@1.2.2:
     leva "^0.10.0"
     leva "^0.10.0"
     three-custom-shader-material "^6.3.7"
     three-custom-shader-material "^6.3.7"
 
 
+leaflet@1.9.4:
+  version "1.9.4"
+  resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d"
+  integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
+
+lerc@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lerc/-/lerc-2.0.0.tgz#82feca29ea6202799a815ca38f7b66a370e8cf9b"
+  integrity sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg==
+
 less@^4.2.0:
 less@^4.2.0:
   version "4.3.0"
   version "4.3.0"
   resolved "https://registry.npmmirror.com/less/-/less-4.3.0.tgz"
   resolved "https://registry.npmmirror.com/less/-/less-4.3.0.tgz"
@@ -8370,6 +8530,11 @@ lodash@^4.17.11, lodash@^4.17.21:
   resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz"
   resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 
+long@^5.3.2:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83"
+  integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
+
 loose-envify@^1.4.0:
 loose-envify@^1.4.0:
   version "1.4.0"
   version "1.4.0"
   resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz"
   resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz"
@@ -8500,11 +8665,21 @@ merge2@^1.3.0:
   resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz"
   resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
 
+mersenne-twister@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a"
+  integrity sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==
+
 meshline@^3.3.1:
 meshline@^3.3.1:
   version "3.3.1"
   version "3.3.1"
   resolved "https://registry.npmmirror.com/meshline/-/meshline-3.3.1.tgz"
   resolved "https://registry.npmmirror.com/meshline/-/meshline-3.3.1.tgz"
   integrity sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==
   integrity sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==
 
 
+meshoptimizer@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-1.2.0.tgz#3dcbf6a0f78a721adbb5869818c31518a89fd2df"
+  integrity sha512-davRZeIJbxJrE24cwQle7ZDsxjdk/OphNOV83oX+efQinyoHY9Jcyz3MHbaoG0qySZajldGztNZ1RN/T19PZsg==
+
 meshoptimizer@~0.22.0:
 meshoptimizer@~0.22.0:
   version "0.22.0"
   version "0.22.0"
   resolved "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-0.22.0.tgz"
   resolved "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-0.22.0.tgz"
@@ -8815,6 +8990,11 @@ normalize-range@^0.1.2:
   resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz"
   resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz"
   integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
   integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
 
 
+nosleep.js@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/nosleep.js/-/nosleep.js-0.12.0.tgz#a01fddab2c13af357d673928b1f40a9013a4dc08"
+  integrity sha512-9d1HbpKLh3sdWlhXMhU6MMH+wQzKkrgfRkYV0EBdvt99YJfj0ilCJrWRDYG2130Tm4GXbEoTCx5b34JSaP+HhA==
+
 nth-check@^2.0.1, nth-check@^2.1.1:
 nth-check@^2.0.1, nth-check@^2.1.1:
   version "2.1.1"
   version "2.1.1"
   resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz"
   resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz"
@@ -9058,6 +9238,11 @@ pako@^1.0.5, pako@~1.0.2:
   resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz"
   resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz"
   integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
   integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
 
 
+pako@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-3.0.2.tgz#b983206a36914f169ca9ea0f6bf702fc5ba1ac71"
+  integrity sha512-uBv6IT2aT1A78iU6dpNEbf6+CyhlV/6g9JlJs9kpgjFGFhruIICVRysF/W0SLzXg5+hCl+KroH7e4YUyfEmgLg==
+
 param-case@^3.0.4:
 param-case@^3.0.4:
   version "3.0.4"
   version "3.0.4"
   resolved "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz"
   resolved "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz"
@@ -9472,6 +9657,13 @@ prop-types@^15.8.1:
     object-assign "^4.1.1"
     object-assign "^4.1.1"
     react-is "^16.13.1"
     react-is "^16.13.1"
 
 
+protobufjs@^8.8.0:
+  version "8.8.0"
+  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-8.8.0.tgz#7ed071d94b6755f63cc9936ecd82e4deac7cf624"
+  integrity sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==
+  dependencies:
+    long "^5.3.2"
+
 protocol-buffers-schema@^3.3.1:
 protocol-buffers-schema@^3.3.1:
   version "3.6.1"
   version "3.6.1"
   resolved "https://registry.npmmirror.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz"
   resolved "https://registry.npmmirror.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz"
@@ -9542,6 +9734,11 @@ quickselect@^2.0.0:
   resolved "https://registry.npmmirror.com/quickselect/-/quickselect-2.0.0.tgz"
   resolved "https://registry.npmmirror.com/quickselect/-/quickselect-2.0.0.tgz"
   integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
   integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
 
 
+quickselect@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
+  integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==
+
 radashi@^12.6.2:
 radashi@^12.6.2:
   version "12.7.2"
   version "12.7.2"
   resolved "https://registry.npmmirror.com/radashi/-/radashi-12.7.2.tgz"
   resolved "https://registry.npmmirror.com/radashi/-/radashi-12.7.2.tgz"
@@ -9576,6 +9773,13 @@ rbush@^3.0.1:
   dependencies:
   dependencies:
     quickselect "^2.0.0"
     quickselect "^2.0.0"
 
 
+rbush@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/rbush/-/rbush-4.0.1.tgz#1f55afa64a978f71bf9e9a99bc14ff84f3cb0d6d"
+  integrity sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==
+  dependencies:
+    quickselect "^3.0.0"
+
 react-colorful@^5.5.1:
 react-colorful@^5.5.1:
   version "5.6.1"
   version "5.6.1"
   resolved "https://registry.npmmirror.com/react-colorful/-/react-colorful-5.6.1.tgz"
   resolved "https://registry.npmmirror.com/react-colorful/-/react-colorful-5.6.1.tgz"
@@ -9595,6 +9799,11 @@ react-is@^16.13.1:
   resolved "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz"
   resolved "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
 
+react-merge-refs@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-3.0.2.tgz#483b4e8029f89d805c4e55c8d22e9b8f77e3b58e"
+  integrity sha512-MSZAfwFfdbEvwkKWP5EI5chuLYnNUxNS7vyS0i1Jp+wtd8J4Ga2ddzhaE68aMol2Z4vCnRM/oGOo1a3V75UPlw==
+
 "readable-stream@>=1.0.33-1 <1.1.0-0":
 "readable-stream@>=1.0.33-1 <1.1.0-0":
   version "1.0.34"
   version "1.0.34"
   resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.0.34.tgz"
   resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.0.34.tgz"
@@ -10495,6 +10704,11 @@ systemjs@^6.15.1:
   resolved "https://registry.npmmirror.com/systemjs/-/systemjs-6.15.1.tgz"
   resolved "https://registry.npmmirror.com/systemjs/-/systemjs-6.15.1.tgz"
   integrity sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==
   integrity sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==
 
 
+tagged-tag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/tagged-tag/-/tagged-tag-1.0.0.tgz#a0b5917c2864cba54841495abfa3f6b13edcf4d6"
+  integrity sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==
+
 tapable@^2.2.0:
 tapable@^2.2.0:
   version "2.2.1"
   version "2.2.1"
   resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz"
   resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz"
@@ -11207,6 +11421,13 @@ type-fest@^0.20.2:
   resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz"
   resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz"
   integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
   integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
 
 
+type-fest@^5.4.4:
+  version "5.9.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.9.0.tgz#0a4dd554d397f1a44447477eaeb3d1f4d8c7680f"
+  integrity sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==
+  dependencies:
+    tagged-tag "^1.0.0"
+
 type-is@~1.6.18:
 type-is@~1.6.18:
   version "1.6.18"
   version "1.6.18"
   resolved "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz"
   resolved "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz"
@@ -11413,6 +11634,16 @@ uri-js@^4.2.2:
   dependencies:
   dependencies:
     punycode "^2.1.0"
     punycode "^2.1.0"
 
 
+urijs@^1.19.7:
+  version "1.19.11"
+  resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc"
+  integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==
+
+url-join@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/url-join/-/url-join-5.0.0.tgz#c2f1e5cbd95fa91082a93b58a1f42fecb4bdbcf1"
+  integrity sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==
+
 utif@^2.0.1:
 utif@^2.0.1:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.npmmirror.com/utif/-/utif-2.0.1.tgz"
   resolved "https://registry.npmmirror.com/utif/-/utif-2.0.1.tgz"