Sfoglia il codice sorgente

fix(cockpit): 叠加影像拆分为独立模块并回退层级外请求,资产基址固化绝对 URL

走查发现 ring1 层级外瓦片请求产生 404 风暴:将叠加影像构造拆到 overlayImagery.js,声明级别外的请求回退 1×1 透明画布(不伪造缺失影像,仅节流声明范围外请求);runtimeBase 抽函数并优先以 documentBase 固化绝对 URL,消除 CSS 相对 url() 按样式表目录解析的歧义。
ChenJZ 1 giorno fa
parent
commit
7217e13cdb

+ 11 - 4
src/modules/cockpit/domain/runtimeAssets.js

@@ -43,6 +43,15 @@ function defaultBase() {
     return viteBase || '/'
 }
 
+function runtimeBase(options) {
+    const root = options.base === undefined ? defaultBase() : options.base
+    const base = root.endsWith('/') ? root : `${root}/`
+    // CSS 自定义属性里的相对 url() 按消费样式表解析,生产 CSS 位于 css/。
+    // 先按文档部署基址固定为绝对 URL;无 DOM 的逻辑调用保留既有 base 契约。
+    const documentBase = globalThis.document && globalThis.document.baseURI
+    return documentBase ? new URL(base, documentBase).href : base
+}
+
 /** 路径安全:受控相对路径,禁止绝对路径、`..` 越界与任何 URL scheme。 */
 function isSafePath(path) {
     return (
@@ -119,8 +128,7 @@ export function resolveAsset(key, options = {}) {
     if (!entry) {
         return { key, url: '', status: 'missing', diagnostic: validation.valid ? ASSET_DIAGNOSTIC.ENTRY_MISSING : validation.code }
     }
-    const root = options.base === undefined ? defaultBase() : options.base
-    const base = root.endsWith('/') ? root : `${root}/`
+    const base = runtimeBase(options)
     return {
         key: entry.key,
         url: `${base}cockpit/${entry.path}`,
@@ -255,8 +263,7 @@ export function resolveFont(key, options = {}) {
             missing: evidence.missing,
         }
     }
-    const root = options.base === undefined ? defaultBase() : options.base
-    const base = root.endsWith('/') ? root : `${root}/`
+    const base = runtimeBase(options)
     return {
         key: entry.key,
         available: true,

+ 2 - 11
src/modules/cockpit/services/cesiumEngine.js

@@ -61,6 +61,7 @@ import { createRegionLoadingController, contentAdmission } from './regionLoading
 import { hasVisibleTileCoverage } from '../domain/tileCoverage.js'
 import { clampResolution, sameResolution } from '../domain/renderResolution.js'
 import { removeSceneTileset } from './tilesetCleanup.js'
+import { createOverlayProvider } from './overlayImagery.js'
 const regionControllers = new WeakMap()
 // 票 47:动效的**相位与映射**同样在 domain(纯函数,可跨时间采样断言);
 // 本层只把相位写成 billboard 的 scale/alpha/rotation。
@@ -410,16 +411,6 @@ export function applyMotionResolution(viewer, moving) {
     return resolutionControllerFor(viewer)?.setMoving(moving === true) ?? false
 }
 
-/** 叠加影像(票 21):本地精细切片画在底图之上,URL 模板里可用 `{reverseY}` 表示 TMS 的 y。 */
-function createOverlayProvider(overlay) {
-    const options = { url: overlay.url }
-    if (Number.isInteger(overlay.minimumLevel)) options.minimumLevel = overlay.minimumLevel
-    if (Number.isInteger(overlay.maximumLevel)) options.maximumLevel = overlay.maximumLevel
-    if (overlay.bounds) options.rectangle = Rectangle.fromDegrees(overlay.bounds[0], overlay.bounds[1], overlay.bounds[2], overlay.bounds[3])
-    if (overlay.credit) options.credit = overlay.credit
-    return new UrlTemplateImageryProvider(options)
-}
-
 /**
  * 批量采样真实地表高程(票 34 的建模用途):一次请求把整张网格取回来,
  * 比逐点调 `sampleTerrainHeight()` 快得多(逐点会重复拉同一批瓦片)。
@@ -546,7 +537,7 @@ export function setCameraAltitude(viewer, altitudeM) {
 export function applyOverlays(viewer, overlays) {
     const handles = []
     for (const overlay of overlays || []) {
-        const layer = new ImageryLayer(createOverlayProvider(overlay))
+        const layer = new ImageryLayer(createOverlayProvider(overlay, { UrlTemplateImageryProvider, Rectangle }))
         viewer.imageryLayers.add(layer)
         handles.push(createOverlayHandle(layer))
     }

+ 26 - 0
src/modules/cockpit/services/overlayImagery.js

@@ -0,0 +1,26 @@
+/** 叠加影像:保留 Cesium 注入时序,URL 的 {reverseY} 表示 TMS 的 y。 */
+export function createOverlayProvider(overlay, { UrlTemplateImageryProvider, Rectangle }) {
+    const options = { url: overlay.url }
+    if (Number.isInteger(overlay.minimumLevel)) options.minimumLevel = overlay.minimumLevel
+    if (Number.isInteger(overlay.maximumLevel)) options.maximumLevel = overlay.maximumLevel
+    if (overlay.bounds) options.rectangle = Rectangle.fromDegrees(overlay.bounds[0], overlay.bounds[1], overlay.bounds[2], overlay.bounds[3])
+    if (overlay.credit) options.credit = overlay.credit
+    const provider = new UrlTemplateImageryProvider(options)
+    const requestImage = provider.requestImage
+    let transparentImage
+    // Cesium 的失败回退会请求 minimumLevel 以下的父瓦片;声明级别外只透出底图。
+    // 不捕获级别内的错误,不伪造缺失影像;同步保留 undefined 的请求节流语义。
+    provider.requestImage = function (...args) {
+        const level = args[2]
+        if (level < provider.minimumLevel || level > provider.maximumLevel) {
+            if (!transparentImage) {
+                transparentImage = document.createElement('canvas')
+                transparentImage.width = 1
+                transparentImage.height = 1
+            }
+            return Promise.resolve(transparentImage)
+        }
+        return requestImage.apply(this, args)
+    }
+    return provider
+}