cockpit-deployment.md 39 KB

驾驶舱生产部署操作手册

面向部署人员(运维/实施)。只改配置与静态目录,不需要重新构建前端。 阅读顺序:先看「一页速查」,按第 2 节改配置,按第 3 节挂数据,最后跑第 5 节自检。


一页速查

我要做的事 改哪里 怎么改
打开/关闭运行时配置 产物 index.html 加/删一行 window.__COCKPIT_CONFIG_URL__ = 'auto'
改后端接口地址 dist/cockpit/config.json → api.baseUrl 填网关地址,如 /api/cockpit/v1
改数据源(mock / 真接口) 同上 → dataSource "mock"(默认)或 "real"
配置真接口凭据(已废弃) — 2026-09-28 起免鉴权:驾驶舱 REST 与 /ws/deviceStatus 均不要求凭据,api.token/ws.token 留空即可(旧配置写了也会被前端忽略);安全性由网络层内网隔离保障
换全局底图 同上 → map.basemap.source 默认 natural-earth(离线);可选 xyz / tianditu / arcgis-imagery
挂地形 DEM 同上 → map.terrain.url 指向含 layer.json 的目录 URL
挂本地精细影像 同上 → map.overlays[] url(TMS 用 {reverseY})+ bounds
挂 3D Tiles / 倾斜摄影 同上 → map.tilesets[] 每项一个 tileset.json 的 URL
挂 GIS 静态目录(nginx) 服务器 nginx 见第 3 节的两段 location
改「目标城市」与归位视角 dist/cockpit/config.json → map.home 见 2.6
开关进入引导(自转星球 + 点击进入) 同上 → map.intro.enabled 见 2.6
地面/管线视图的倾角与半透明 同上 → map.viewMode 见 2.7(不配则用默认:管线 pitch -18°、距离 ×0.35、地形 α0.45、模型 α0.4)
框定主场景区(区域限位 + 自建雾气) 同上 → map.primaryArea.bounds 见 2.9;不填就等于关掉这两个能力
主场景区限位的运行表现 — 见 2.10(进区后最远=map.home.range、拖到边界软回弹、返回全球)
区内视距雾(色/距离/进出时长) 同上 → map.fog 见 2.11(默认 18/42 km、#bdcdd6;不配也是这一套)
粗→精影像交接(渐显时机/时长) 同上 → map.handover 见 2.12(默认等雾保护 + 瓦片就绪才渐显,700 ms;不配也是这一套)
现场调试生产包(显示诊断信息) 同上 → map.ui.debug true 临时打开;false 强制干净形态(默认跟随构建)
完全关掉三维引擎 同上 → map.cesium.enabled false → 中央区域退回临时影像

三个文件的分工(先记住这句,后面就不会乱):

  1. dist/cockpit/config.json —— 运行时配置,部署后改这个(环境地址、数据源、图层)。
  2. 产物 index.html —— 加一行开关,告诉前端「去读上面那个文件」。
  3. 服务器上的 GIS 目录 + nginx —— DEM / 影像 / 3D Tiles 的静态数据。

1. 配置是怎么生效的

前端按四层取值,后者覆盖前者(合并是深合并,只写要改的字段即可):

层 来源 谁改 说明
1 内置默认 前端 dataSource: mock、basemap: natural-earth、不挂地形/影像/3D Tiles
2 构建期 VITE_COCKPIT_* 前端构建者 打进产物,换环境要重建,不推荐生产用
3 <base>cockpit/config.json 部署人员 本次部署的主入口,改完刷新页面即生效
4 宿主注入 window.__COCKPIT_CONFIG__ 集成方(qiankun 主应用等) 整份对象注入,优先级最高;给了就不再读第 3 层

打开第 3 层:默认前端不探测配置文件(免得每页刷 404)。二选一:

  • 部署后改产物(推荐,无需重建):编辑 dist/index.html,在 <head> 里加一行 ——

    <script>window.__COCKPIT_CONFIG_URL__ = 'auto';</script>
    

    'auto' = 取应用基址下的 cockpit/config.json(publicPath: './' 与 qiankun 子路径下都对)。 也可以给绝对 URL(例如配置由后端下发):window.__COCKPIT_CONFIG_URL__ = 'https://cfg.example.com/cockpit.json';

  • 构建期:.env 里写 VITE_COCKPIT_CONFIG_URL=auto 再 yarn build。

顶栏能看到状态:.cockpit-shell__config 标签显示 配置就绪 / 配置缺失 / 配置载入失败 与数据源; 引擎浮标显示 底图:… · 地形:… · 叠加影像 N 层。排查问题时先看这两个地方。


2. 改 dist/cockpit/config.json

模板见仓库 public/cockpit/config.example.json(带逐字段注释)。只需写要覆盖的字段:

2.1 后端接口

{
  "dataSource": "real",                    // 真接口;默认 mock
  "api": { "baseUrl": "/api/cockpit/v1" }, // 网关地址;同源可写相对路径
  "ws":  { "url": "wss://内网主机/ws/deviceStatus" }
}
  • api.token / ws.token 已废弃(2026-09-28 免鉴权):按 ADR 0005 修订,驾驶舱 REST 与 /ws/deviceStatus 均不再要求凭据;接口安全由网络层内网隔离保障(网关与可直连的应用端口 都须只在内网可达)。旧配置里写了 token 也会被前端忽略。若个别分区出现 401/403(例如网关层 另行加了策略),仍只把受影响分区标「数据不可用」,不会跳登录页。
  • 想改回 mock 演示:"dataSource": "mock",无需改其它字段。

2.2 全局底图(离线优先)

"map": { "basemap": { "source": "natural-earth" } }   // 推荐:Cesium 自带离线影像,零外网
取值 是否离线 需要什么
natural-earth ✅ 完全离线 无(影像随 dist/cesium/Assets 一起部署)
xyz ✅ 可离线 url 模板,如 /gis/world/{z}/{x}/{y}.png
tianditu ❌ 在线 map.tianditu.token(天地图 tk),开发走 /tianditu.map 代理
arcgis-imagery ❌ 在线 无(免 key,Esri 署名),但需要外网
none — 不配底图,中央区域显示临时影像

内网环境请用 natural-earth 或 xyz。本项目优先本地/内网离线数据。

2.3 地形 DEM

"map": { "terrain": { "url": "/gis/dem", "requestVertexNormals": true } }
  • url 指向含 layer.json 的目录(量化网格 quantized-mesh)。
  • cesiumlab 导出的 layer.json 里 scheme: "tms" —— Cesium 自己会认,服务端不要翻 y。
  • 配错或不存在的现象:浮标显示 地形加载失败(…)· 回退椭球,页面照常(不白屏)。

2.4 本地精细影像

"map": {
  "overlays": [{
    "url": "/gis/pic3/{z}/{x}/{reverseY}.png",
    "minimumLevel": 0, "maximumLevel": 19,
    "bounds": [109.96215820312366, 27.962869019358852, 111.04568213224277, 29.060971407383555],
    "credit": "沅陵本地影像"
  }]
}
  • TMS 数据必须用 {reverseY}(cesiumlab 导出的 meta.json 里 tiletrans: "tms"); 若你的切片是标准 XYZ(slippy map),则写 {y}。
  • bounds = [西, 南, 东, 北],抄 meta.json 的 bounds。填了它,范围外不会发请求(否则整球视角刷一屏 404)。
  • 可以有多个 overlay,按数组顺序从下往上叠。

2.5 3D Tiles / 倾斜摄影

"map": { "tilesets": [{ "url": "/gis/model/tileset.json", "lighting": true }] }

lighting:true 是显式模型图层开关:Cesium 为该 tileset 根据其地理包围球配置固定局部日光与环境光,不跟随相机改变;省略时不修改现有场景光照。运行时 LOD 必须由根 tileset 的空间子节点、geometricError 与 refine: "REPLACE" 提供,单个 GLB 旁边放一个 LOD1 文件不会自动切换。范围较大的程序化街区可以使用“粗父节点 LOD1 → 多个细叶子 LOD0”的两级空间树,让远景只请求粗父节点、近景再替换为局部叶子;根节点应保留有效的空 GLB 锚点,避免 Cesium 跳过无内容根节点。

  • 每项一个 tileset.json 的 URL(根 tileset,不是分块 json),可选 maximumScreenSpaceError。
  • 清单请求或后续内容请求失败只记状态、不影响其它图层:状态行显示 3D Tiles 加载失败(…);内容阶段错误由 tileFailed 事件捕获,不能只用根 fromUrl() 成功判断。
  • geometricError 应按数据层级和实际误差设置。旧版把一次未加载现象直接归因于数值0,未隔离相机/视锥等变量,不能作为格式硬约束。
  • 地面视图需开启 globe.depthTestAgainstTerrain。本轮已修复默认值;地面与管线半透明来回切换由像素回归验证,不能用“模型已加载”替代遮挡验收。
  • 当前模型状态:
    1. 旧程序化体块已被否决,禁止默认启用:D:\GIS\GIS\cityMassing\ 保留作历史资料,map.tilesets 留空。新 Blender 资产库与示范街区在 tools/city-massing/house-library/ 和 tools/city-massing/district/generated/,仅通过本机 serve-review.mjs 提供效果审查,不进入生产包,也未获得完整县城验收。 所有立面/楼层为估计,影像描绘存在视差;非测绘建筑,不得用于量测/统计/应急决策。复现、资产与覆盖清单见 tools/city-massing/README.md。
    2. 甲方原始 3D Tiles:D:\GIS\GIS\yuanlingSHP(cesiumlab model2tiles 导出)缺根 tileset.json、分块是局部坐标;曾补过一版合成根文件,经目测错位且是白模,不采用 (合成文件已删除)。若日后用 cesiumlab 重导并勾选生成根 tileset,直接填 map.tilesets 即可替换示意模型。

2.6 「目标城市」与进入引导

进系统时的落点、以及顶栏「归位」按钮回到的视角,都由 map.home 决定;引导层由 map.intro 控制。

"map": {
  "home": {
    "lon": 110.39028164,   // 目标点经度(CGCS2000/天地图口径,与 WGS-84 本项目视为一致)
    "lat": 28.4436787,     // 目标点纬度
    "heading": 0,          // 朝向(0 = 正北)
    "pitch": -55,          // 俯角(负 = 从上往下看)
    "range": 12000,        // 弹簧臂距离(米):12000 ≈ 县域级;4600 ≈ 街区级
    "label": "沅陵县城"     // 归位按钮的提示文案
  },
  "intro": {
    "enabled": true,       // false = 跳过引导,打开即进系统(不需要引导的部署设 false)
    "title": "点击进入系统",
    "spinSeconds": 48      // 星球自转一圈的秒数;0 = 不自转
  }
}

要点:

  • home.lon/lat 换成别的城市即可;range 决定"进入时看多细",别小于 2000(再近看不到县城轮廓)。
  • 中心高程不用填:地形就绪后系统会用 DEM 实测高程(沅陵实测 111.6 m),填了也会被实测值覆盖 —— 这是 为了避免相机按椭球 0 米定位而钻进山体(拉到 120 m 时曾经整屏黑)。
  • intro.enabled=false 时不显示引导层,但「归位」按钮仍在、home 仍然生效。
  • 需要"进入后记住,下次直接进"的话目前不支持(每次刷新都会显示引导),属已知取舍。

2.7 地面视图 / 管线视图

驾驶舱顶栏筛选条里有「地面视图 / 管线视图」两个按钮(全局条件,切一级模式不重置)。 管线视图会压低相机 + 放宽俯仰,并把地形与 3D Tiles 压成半透明,以便露出管线;地面视图还原。 旋转中心不重置、三键操作完全不变(左键平移、右键旋转、滚轮伸缩)。

默认值一般够用;要调就写 map.viewMode:

"map": {
  "viewMode": {
    "pipeline": {
      "pitch": -18,          // 管线视图倾角(度,越接近 0 越平视;允许到 +5)
      "rangeFactor": 0.35,   // 弹簧臂压到当前距离的几成
      "terrainAlpha": 0.45,  // 地形透明度(0–1;1 = 不透明,0 = 全透)
      "tilesetAlpha": 0.4    // 3D Tiles / 倾斜摄影透明度
    }
  }
}

写错类型/范围外的值会被忽略并退回默认(不会把相机顶到奇怪角度)。


2.8 界面形态:开发诊断 vs 生产干净

驾驶舱有两套界面形态,生产构建默认就是干净形态,不需要配置:

调试形态(开发构建默认) 生产干净形态
中央图上的「中央一张图」标题、图层占位、实例提示 显示 收起
引擎状态(底图/地形/影像/3D Tiles) 顶部药丸,带边框底色 贴底半透明小字(无边框无底色)
顶栏「配置就绪 · 数据源」标签、「当前模式」读数 显示 收起
点位状态图例(颜色语义)、状态灯 显示 保留
范围/行业/视图筛选、归位按钮、只读视图、时钟 显示 保留

判定规则:map.ui.debug 显式给布尔值就听它,否则跟随构建模式(yarn dev → 调试;yarn build 产物 → 干净)。

"map": {
  "ui": {
    "debug": false   // 想在生产包里临时看诊断信息就写 true;不写则跟随构建模式
  }
}

故障时颜色语义仍在:引擎失败时那行小字会转成淡红(生产形态没有边框,靠字色传达)。


2.9 主场景区(区域限位 + 自建雾气)

map.primaryArea.bounds 框出主场景区:进入后相机被限位在区内、远景罩上自建距离雾。 两个能力(限位、雾)都以这一个字段为开关 —— 不填 = 两个能力一起关闭,不是"用了某个默认范围"。

"map": {
  "primaryArea": {
    "bounds": [110.328749941417, 28.3917230659234, 110.465779226663, 28.4913038992907]
  }
}

顺序是硬要求:[西, 南, 东, 北](度),与 map.overlays[].bounds 完全同口径, 必须满足 west < east 且 south < north。

  • 甲方或数据方常按 [W, N, E, S](纬度降序)给范围 —— 请在写配置时就把两个纬度对调, 运行时代码不会替你翻转。
  • 写反了会怎样:不算"差不多能用",而是判为配置非法,与没配一样走关闭路径, 并在引擎浮标与宿主 data-map-primary-area-reason 上写明原因(见下)。
  • 为什么不自动翻转:翻对了没人会发现配置抄错,翻错了就是拿一段假范围去锁相机 —— 两种都让部署问题在运行期隐身。

矩形怎么定:优先按甲方框定的范围。若要以数据覆盖为准,由部署侧离线算 影像 bounds ∩ DEM layer.json 的 valid_bounds,围绕 map.home 经纬度、东西/南北各折半后写入本字段 (不要指望前端在运行时推导,也不要在源码里写死某个县的地理常量)。

怎么确认生效:引擎浮标(调试形态在中央顶部,生产形态在右下角小字)会出现

情况 浮标文字 宿主读数
已配置且顺序正确 主场景区:13.4×11.1 km data-map-primary-area="ready"
没写这个字段 主场景区:未配置 · 限位与雾气关闭 data-map-primary-area="missing"
顺序写反 主场景区:配置无效(顺序应为 [西,南,东,北],不自动翻转) · 限位与雾气关闭 data-map-primary-area="invalid",原因在 data-map-primary-area-reason
数值个数/类型不对 主场景区:配置无效(bounds 应为四个有限数字) · 限位与雾气关闭 同上(invalid)

未配置 与 配置无效 都会让「进区锁定 / 相机区域限位」(票 37)与「区内视距雾气」(票 38)关闭; 配置问题同时会记进配置服务的 issues 清单,便于排查。


2.10 进区锁定与相机区域限位(票 37)

配好主场景区之后,进区即被限位:最远视野=归位默认视角(map.home.range,本部署 12,000 m), 中心点约束在框内,相机不钻地形。运行期表现为:

位置 顶栏 相机 宿主读数
区外(自由) 只有一个「归位/进入」 可拉到全球尺度(上限 3×10⁷ m) data-map-area-state="global"
区内(锁定) 「归位」+「返回全球」 滚轮最远只到 map.home.range;中心被夹在框内 data-map-area-state="locked"、data-map-range-max="12000"
  • 怎么进区:点「进入系统」(引导页)或顶栏「归位」。
  • 怎么出区:只有「返回全球」—— 解除限位并飞回自由全球视角(此后完全自由)。
  • 拖到边界:按紧左键可向外推到框短边 5%(≈550 m)为止,松开自动弹回框内。
  • 防入地:相机绝对高度始终高于其正下方地形 10 m(可微调);不足时只退距离,不改倾角、不动中心。
  • 点选定位是例外:监测点散布全县而主场景区只框了县城,所以点选/定位到框外点位不受中心约束 (镜头照常飞过去);一旦手动按左键拖动,就回到区内规则。
  • 全球态闲置 10 秒 → 回引导页(镜头回初始全景 + 星球自转 + 「点击进入系统」),不重置一级模式与筛选; map.intro.enabled=false 时按自由全球视角启动、不发生这件事。
  • 排障:区内却拉得很远 → 看 data-map-area-state 是否真是 locked(不是 locked 就说明引导 或「归位」没走通,或主场景区配置不可用);data-map-range-max 与 map.home.range 应一致。

2.11 区内视距雾(票 38)

进入主场景区后自动加一层距离雾:近处地物清晰、远处平滑融入雾色,旋转镜头时不露出远景瑕疵。 只在区内生效,全球/引导态关闭。默认参数就是下面这组,不配也是它:

"map": {
  "fog": {
    "enabled": true,
    "startDistance": 18000,   // 世界射线距离:从这里开始起雾(米)
    "endDistance": 42000,     // 到这里完全遮蔽(米);必须大于 startDistance
    "color": "#bdcdd6",       // 基准雾色(必须是 #rrggbb)
    "fadeInMs": 900,          // 进区渐显时长(本部署现场调校值)
    "fadeOutMs": 300,         // 返回全球淡出时长(本部署现场调校值;要更利落就调小)
    "altitudeFullM": 14000,   // 相机高度 ≤ 此值时雾满强度
    "altitudeZeroM": 3000000, // 相机高度 ≥ 此值时雾收到 0
    "altitudeSlewMs": 600     // 上面这层收口的**时间限速**:系数走完全程至少这么久
  }
}

上面这四个值(fadeInMs / fadeOutMs / altitudeFullM / altitudeZeroM / altitudeSlewMs) 是本部署(沅陵)在真机上调定的,不是规格默认。代码默认另有一套 (domain/fog.js 的 FOG_DEFAULTS:800/400 ms、20/42 km、400 ms)。 换部署若没有自己的现场值,删掉 map.fog 里这几项就会退回代码默认。

要点:

  • 只会给有有效几何深度的地物上雾(B 方案);天空等无深度背景保留原色,不会被整片染色。
  • 色相要求:冷白灰、不得发绿。#bdcdd6 是起点不是终值 —— 正式色值请在真实画面上复核后调。 写非 #rrggbb 的值会被判非法并退回默认(同时记一条配置问题,不会静默生效失败)。
  • 相机高度收口(altitudeFullM / altitudeZeroM):雾的距离端点只有 18–42 km, 而相机在高空时画面上每个片元都远在 42 km 之外 —— 不加收口就会整个地球被涂成雾色。 这两个值一压,退出时不再出现"地球变白一段时间"、进入时也不再有"白→突然一团绿"。 代码默认取 20/42 km 有依据:42 km 是雾自己的全遮蔽端点(高过它雾已无保护意义), 20 km 以下恢复满强度,而区内归位视角约 9.8 km 远在下方 → 区内观感完全不受影响。 必须 altitudeZeroM > altitudeFullM:写反会退化为"高度系数恒为 1"(等于把这层保护关掉)。 配置层会报一条 issue(不静默),但不会替你改值 —— 两个数各自都合法, "要不要在高空保留雾"是部署的取舍(本部署有意把上端放到 3000 km,使飞入全程都有雾)。
  • altitudeSlewMs 是必需的,不是可选的润色:返回全球时相机 range 从 12 km 变到 20,000 km(跨 1.6e6 倍),而雾带只有 20–42 km —— 相机头一帧就越过整条带。 所以"瞬时按高度决定雾浓淡"必然跳变,观感是「雾几乎一瞬间就产生或消失」。 这一项给高度系数加了时间限速:无论相机多快,画面上的雾都不可能比它更快地变化。 写成 0 = 不限速(回到会跳变的旧行为),一般不要这么配。
  • 校验:endDistance 必须大于 startDistance,否则两端语义不成立,配置层会退回默认并记问题。
  • 雾盖不住屏幕中心,这是几何决定的(排障时先知道这条能省很多时间):雾是距离雾, 18 km 起步,而区内相机只有约 9.8 km 高 —— 屏幕中心(脚下街区)的视线距离恒 < 18 km, 所以那里恒无雾。中心露出粗底图还是精细影像,只看精细层 alpha(map.handover)。

怎么确认生效:顶栏出现「视距雾 1.00 · 18–42 km」这样的读数(进区后才显示), 宿主读数在 .map-canvas 上:

情况 读数 顶栏文案
区内正常 data-map-fog="ready"、data-fog-phase="locked"、data-fog-strength="1" 视距雾 1.00 · 18–42 km
区外待命 data-fog-phase="global"、强度 0 视距雾待命 · 区内生效
管线视图观察到透明错位(减弱) data-fog-degrade="reduced"、强度被压低 视距雾减弱 · 强度 0.35
不支持深度 / 运行失败 data-map-fog="unsupported" 视距雾不可用 · 无深度支持

ready 不等于「矩形边缘已被遮住」。 它只说明后处理级可执行; 画面是否真的被遮住属观感验收,读数里 data-map-fog-covers-edges 恒为 no, 前端不提供"已遮缝"这类未经证实的承诺。

降级与可读性:如果管线视图下透明对象被错误浓雾盖住,可读性优先 —— 可在控制台 (DEV)__cockpitMap.setFogTranslucentArtifacts(true) 减弱,或 setFogUserDisabled(true) 暂关; false 恢复。降级不影响相机区域限位(票 37 的限位照旧生效)。 正常情况不会预先关闭管线视图的雾。

性能:规格要求同机同视角下开启雾的 P95 帧耗时增幅 ≤15%。 DEV 下可采样:__cockpitMap.startFogPerfSample('with-fog') → 等一会儿 → __cockpitMap.stopFogPerfSample() 读 P95。注意它采的是渲染事件间隔,不是 GPU 时间, 只能作参考,不能单独判定达标。


2.12 粗→精影像交接(票 39)

从全球飞进县城的全过程不出现肉眼可辨的矩形色块跳变。做法是把精细叠加影像的显隐 交给一套交接时序:全球/引导态隐藏精细层、只留粗底图;进区后等雾先形成保护、 且目标视野瓦片可显示,才渐显精细层;返回时反向交接。

"map": {
  "handover": {
    "enabled": true,           // false = 精细层常显(回到票 39 之前的行为)
    "fadeMs": 500,             // 渐显/收起时长(本部署现场调校值;见下方"为什么调这么短")
    "minEdgeOcclusion": 0.7,   // 相机到影像边处,雾的遮蔽率须 ≥ 此值才允许揭示(0–1)
    "requireTilesLoaded": false// 揭示前是否要求瓦片加载队列为空
  }
}

这三个值也是本部署真机调定的(不是规格默认)。代码默认是 domain/imageryHandover.js 的 HANDOVER_DEFAULTS:380 ms / 0.9 / true。 换部署若没有自己的现场值,删掉 map.handover 就退回默认。

要点:

  • 为什么 fadeMs 要调短("进入时会变绿"的修复):雾是距离雾,18 km 起步; 而区内相机只有约 9.8 km 高 —— 屏幕中心(脚下街区)的视线距离恒 < 18 km, 那里恒无雾,雾在结构上盖不住它。所以渐显期间,中心是 「粗糙 Natural Earth II(陆地偏绿)↔ 精细影像」的混合,窗口越长那段绿越明显。 调短它 = 让混合一闪而过(维护者 2026-09-29 选定此方向)。 改成硬切(fadeMs: 0)也能去掉绿,但会丢掉"粗→精"的过渡感,不推荐。
  • minEdgeOcclusion 调小 = 更早揭示、但遮边更弱。它同时是"相机离矩形边太近就不揭示" 的门槛:值越小,允许在雾更薄的地方揭示。本部署取 0.7(默认 0.9); 现网参数下区内最坏点(西南角,距影像边 35.9 km)遮蔽率约 0.97,两种阈值都能过。
  • requireTilesLoaded: false 的含义与代价:不再等"瓦片加载队列为空"就允许揭示。 决策 08 当初要求这一条是为了避免"先空白再跳方块";关掉后牺牲的正是这个保障 (globe.tilesLoaded 本来也只是代理信号,见下)。本部署接受该代价 —— 若现场看到揭示后才有方块补上,把它改回 true 即可。
  • 隐藏期间仍在预取瓦片("看不见 ≠ 停止加载")。实现上是 alpha = 0 且保持 show = true —— 若改成 show = false 会真的停掉预取,等揭示时才开始下载,反而造成"先空白再跳方块"。 可在 DEV 用 __cockpitMap.getHandoverState().layerProbe 核对:应为 [{show: true, alpha: 0}]。
  • 边缘判据与雾参数同源:minEdgeOcclusion 用的是同一条雾衰减曲线,不是另写一套阈值。 所以调 map.fog.startDistance/endDistance 时,边缘判据自动跟着变,不会各调各的。 现网参数下,相机停在家(县城中心)时到影像最近边约 42 km、遮蔽率 1.0;区内四角最坏约 0.97,都能过闸。
  • 降级:雾不可用(无深度支持/创建失败)时业务可用优先 —— 仍会渐显精细层并如实提示, 但不承诺遮住矩形边。瓦片失败则保持隐藏、继续用粗底图。
  • 交接被 enabled: false 关掉时,精细层常显(把 alpha 还到 1),不会停在隐形状态。

怎么确认生效:宿主读数在 .map-canvas 上(data-map-handover*):

情况 读数
全球/引导态(精细层隐藏) data-map-handover="global"、data-map-handover-alpha="0"
进区等待保护 data-map-handover-block="fog" 或 "tiles",alpha 仍为 0
已揭示 data-map-handover="revealed"、data-map-handover-alpha="1"
瓦片加载失败(保持粗底图) data-map-handover-block="tilesFailed"
相机贴近影像边(保护不成立) data-map-handover-block="edge"
无雾降级(已揭示但不承诺遮边) data-map-handover-degrade="yes"

同样不承诺"边缘已被遮住":data-map-handover-tiles-proxy="yes" 明确标注 瓦片信号(globe.tilesLoaded)只是加载队列为空的代理,不证明目标视野逐像素就绪。 「全程无肉眼可辨的方形跳变」属观感验收,请在真实画面上确认。

已知边界:矩形内部随地形 LOD 的逐级换挡不由整层 alpha 修复 —— 若在真实画面上仍可辨,属票 40 评估局部过渡手段的范围,本层不声称已解决。

性能:与雾同一预算(合计 P95 增幅 ≤15%)。交接读数在渲染循环里带 diff 缓存, 稳态帧不重写 DOM;进区/返回是离散动作,会立刻定相位(不被稳态节流吞掉)。


3. 挂 GIS 静态目录(nginx)

DEM / 影像 / 3D Tiles 都是静态文件,直接从磁盘服务,不要放进前端产物(上百 MB 起)。

# 地形 DEM(里面必须有 layer.json)
location /gis/dem/ {
    alias /data/gis/yuanlingDEM/;
    add_header Cache-Control "public, max-age=86400";
    types { application/octet-stream terrain; }
    default_type application/octet-stream;
}

# 本地影像切片
location /gis/pic3/ {
    alias /data/gis/yuanlingPic3/;
    add_header Cache-Control "public, max-age=86400";
}

# 3D Tiles(若启用)
location /gis/model/ {
    alias /data/gis/model/;
    add_header Cache-Control "public, max-age=86400";
    types { model/gltf-binary glb; application/json json; }
}

两条硬要求:

  1. 别让 .terrain / .png / .glb / layer.json 走 SPA 回退。回退成 index.html 时, Cesium 会报 Unexpected token '<'(地形)或整片瓦片加载失败。上面的 location 前缀即是隔离手段。
  2. 允许跨域(前端与数据不同源时):

    add_header Access-Control-Allow-Origin "*";
    

目录搬走/换机器:只改 config.json 里对应的 url,不用改前端、不用重建。


4. 完整的最小可用配置(内网离线)

{
  "dataSource": "mock",
  "map": {
    "basemap": { "source": "natural-earth" },
    "home": { "lon": 110.39028164, "lat": 28.4436787, "heading": 0, "pitch": -55, "range": 12000, "label": "沅陵县城" },
    "intro": { "enabled": true, "title": "点击进入系统", "spinSeconds": 48 },
    "terrain": { "url": "/gis/dem", "requestVertexNormals": true },
    "overlays": [{
      "url": "/gis/pic3/{z}/{x}/{reverseY}.png",
      "minimumLevel": 0,
      "maximumLevel": 19,
      "bounds": [109.96215820312366, 27.962869019358852, 111.04568213224277, 29.060971407383555]
    }],
    "primaryArea": {
      "bounds": [110.328749941417, 28.3917230659234, 110.465779226663, 28.4913038992907]
    },
    "tilesets": []
  }
}

5. 部署后自检(逐条看现象)

检查 期望现象 不通过时看哪里
页面能打开 /#/cockpit 出现驾驶舱外壳,无白屏 控制台报错、index.html 的配置开关
进入引导 先看到自转星球 + 闪烁「点击进入系统」 map.intro.enabled
进入落点 点一下后镜头落在目标城市(不是随机位置) map.home.lon/lat/range
归位按钮 顶栏「归位」可点,平移后点它回到目标视角 map.home、引擎是否就绪
配置生效 顶栏标签「配置就绪 · 数据源 mock/real」 config.json 路径、__COCKPIT_CONFIG_URL__
全局底图 地球有影像(不是黑球) map.basemap.source
地形 浮标含「地形:dem」;拉近后有山体起伏 nginx /gis/dem/、layer.json 能直接下载
本地影像 压到目标区域能看到精细影像 overlay 的 url/{reverseY}/bounds
主场景区 浮标含「主场景区:N×M km」;没配或顺序错则写明「限位与雾气关闭」 map.primaryArea.bounds(顺序 [西,南,东,北])
进区限位 点「进入系统/归位」后顶栏出现「返回全球」,滚轮最远只到 map.home.range data-map-area-state 应为 locked、data-map-range-max 应等于 map.home.range
区内视距雾 进区后顶栏出现「视距雾 1.00 · 18–42 km」;远景平滑融入雾色、近景清晰 data-map-fog="ready"、data-map-fog-phase="locked";区外应为 global 强度 0
雾的高度收口 返回全球时画面不再整片变白;飞入时也不再"白→突然一团绿" 相机升过 42 km 后 data-map-fog-strength 应为 0;区内约 9.8 km 时仍能到 1
雾不"一瞬间"散掉 返回全球时雾看得出是一段淡出,不是一闪就没 返回后连续采样 data-map-fog-strength,应能取到 0 与 1 之间的中间值(若只采到 0/1 说明限速没生效)
粗→精影像交接 全球态只有粗底图;飞入时雾先起、精细影像后亮,全程无肉眼可辨的矩形跳变 data-map-handover:全球 global→进区 entering→revealed;alpha 由 0 升到 1;layerProbe 在隐藏时应为 show:true, alpha:0
交接的"先收后撤" 点「返回全球」:影像先变淡收起,然后雾才淡出(不应反过来) 采样 data-map-handover-alpha 与 data-fog-strength 的时间序:alpha 归 0 之前雾应保持 1
闲置回引导 全球态闲置 10 秒后回到初始态:镜头回 0/−60/2×10⁷、星球重新自转、中央闪烁「点击进入系统」 __cockpitMap.getLifecycleStats():spin 应为 true、spinWanted > 0;getCameraState() 的 hpr 应回到 0/−60/2×10⁷。只在 global 态计时,区内闲置不回
无 404 噪声 浏览器网络面板里 /gis/** 全是 200 多半是 y 轴约定:TMS 未写 {reverseY}
3D Tiles(若启用) 浮标含「3D Tiles N 个」 根 tileset.json 是否存在(见 2.5)
真接口联调 分区出数据(REST 与 WS 均免鉴权,无需任何凭据);若个别分区 401/403 只该分区「数据不可用」 网络层内网隔离是否覆盖所有入口(网关 + 可直连的应用端口)

5.1 关联设备点位与状态特效自检

先记录前端提交、运行页面 URL、配置数据源(real/mock)、后端运行版本及时间。mock 受控样例通过不等于 real 数据通过;HTTP 200 不等于业务成功,业务错误分区应显示加载失败。不得为走查绕过坐标校验或修改面板 pointer-events。

操作链

  1. 进入系统,在「运行监测」的监测点台账打开一个点,再关闭详情,通过地图标记打开同一点。两种入口都应加载点位摘要并定位;点位名称可读,详情连接线指向该标记。
  2. 有有效自身坐标的关联设备应出现;无坐标/结构非法/区外的设备只留清单与具体原因,不借父点坐标。2026-10-12 起坐标准入改为自动判定(结构合法 + 落在 map.primaryArea.bounds 内即可上图,不再要求人工校核来源):真实数据通常大部分可上图,若本项观察到“全部不可上图”,应先核对后端是否已加载含该判定的构建、以及坐标是否确实落在主场景区内,再决定是否记为缺口——不要沿用旧口径直接判成“无可上图样本”。
  3. 聚合点显示“聚合 K 台”。hover 高亮预览,click 固定展开且入口变暗;点子设备进入设备详情,该设备持续可见并接替聚合入口。再次 hover/click 可切换另一设备。K>maxFan 时本屏保留上限内设备,其余仍在清单;选中清单尾部设备也应保持可见。
  4. 设备详情与短线跟随设备,返回摘要恢复监测点锚点。平移/旋转后再次选设备应保留地形高程,不钻入山体;关闭/换点后无旧设备层或旧定位响应复活。
  5. 同一快照翻页不应闪烁设备层;刷新/快照失效应先撤旧层,新范围完整加载后按所选点位重新核对。切四个模式以及引导态,逐项核对暂停/恢复、图例计数和事件回联,不能只检查按钮能点击。
  6. 报警/预警/工单外围环应随帧变化,中心图标保持清晰不透明;正常/离线/未知无动画。静止相机时连续截取同一设备区域,比较实际外围环帧差;全屏变化可能来自时钟、地图瓦片或模型加载,不能单独证明动效。
  7. 关闭动效只停动画,静态状态色/形和清单不消失。异常台数超过 map.effects.abnormalLimit(默认30)时停动画并显示异常数;这与 map.overlap.maxFan(默认10)的展开上限是两种不同降级,不能互代。

可观察字段

字段位于 .cockpit-shell__map .map-canvas,DEV只读句柄为 window.__cockpitMap;生产没有DEV句柄时查看元素属性和真实画面。

检查 字段/句柄 判据
详情身份 data-map-panel-selection、data-map-device-picked point/equipment 与面板当前对象一致;不把聚合符号当虚构设备
聚合 data-map-overlap-state、data-map-overlap-count;getOverlapState() collapsed/expanded/selected,超量为degraded;count为K,selectedCode为真实编码
展开子点 getOverlapTargets()、getAssociatedDevices() 可见子点有真实编码,overlapEntry只标记中心已选设备;位置偏移仅用于屏幕呈现
设备状态动画 data-map-device-effects、data-map-device-effects-animated、data-map-device-effects-abnormal;getDeviceEffects() on/off/degraded,台数真实;tick增长只是驱动证据,还要确认可见外围环跨帧变化
面板锚定 .cockpit-shell__detail 的 data-anchor-code、data-anchor-state 点位摘要指向点位,设备详情指向设备;离屏暂隐不改身份
位置退化 详情/清单的原因、无地图位置列表 坐标缺失(MISSING_)、越界(_OUT_OF_RANGE)、零坐标、区外(OUTSIDE_PRIMARY_AREA)、地形失败/待确认、资产降级分别记录,不冒充成功

证据与排障边界

  • 性能使用同机、同视口、同视角、相同加载条件的开/关动效重复采样,保留样本数、P50/P95及口径。浏览器长任务/请求延迟不是Cesium GPU帧耗时,不能代替该读数。
  • 真实Redis/Kingbase结论需运行实例提交绑定、服务端日志/部署证据与真实接口/WS样本;页面显示“已连接”或只读GET通过不能证明两种存储均联调成功。
  • 当前工作区已修:同编码数据更新、父点门控、固定无位置点详情、定位取消与首次设备档继承;定向回归通过,真实样本仍需回验。仍未完成:刷新/失效撤层重载链与折叠聚合动效。完整真实交互、帧差与性能证据齐备前,不将本节操作链记为全通过。

6. 常见问题

Q:改了 config.json 没生效? 确认产物 index.html 里有 window.__COCKPIT_CONFIG_URL__ 那一行;再硬刷新(配置请求带 cache: 'no-store', 但 index.html 可能被缓存)。

Q:地形/影像请求 404? 先看 URL 能不能直接下载(浏览器地址栏打开 …/gis/dem/layer.json)。全 404 = nginx alias 路径不对; 个别 404 = 瓦片 y 轴约定不对(TMS 要 {reverseY})或 bounds 与数据范围不一致。

Q:能不能用天地图/ArcGIS? 可以但有前提:天地图要免费 tk;ArcGIS(arcgis-imagery,免 key)要外网。 本项目约定优先用本地/内网离线数据,在线源仅作可选补充。

Q:拉很近画面变黑? 已修(票 30):中心点高程现在按 DEM 实测取(不是椭球 0 米),拉到距离下限 120 m 仍能看到地面。 若仍黑,检查地形是否加载成功(浮标应有「地形:dem」)。

Q:不想每次进来都点「点击进入系统」? map.intro.enabled = false。归位按钮不受影响。

Q:进入引导挡住了自动化/走查? 同上,或由宿主页注入 window.__COCKPIT_CONFIG__ = { map: { intro: { enabled: false } } } 只关这一个用例。

Q:要换一批数据(另一个区县)? 把新数据按同样形态准备好 → nginx 挂新目录 → 改 config.json 的三个 url。前端不用动。