412 lines
18 KiB
Markdown
412 lines
18 KiB
Markdown
# Earth 前端结构
|
||
|
||
本文件描述当前 Earth 大屏前端的真实结构,重点是帮助后续继续改 HUD、图层、媒体面板、真实地形、BGP 可视化时,不再重复踩结构和状态同步上的坑。
|
||
|
||
相关规则建议一起参考:
|
||
|
||
- [rules.md](/home/ray/dev/linkong/planet/rules.md)
|
||
- [frontend-layout-guidelines.md](/home/ray/dev/linkong/planet/docs/technical/zh/frontend-layout-guidelines.md)
|
||
|
||
## 当前目标
|
||
|
||
Earth 前端不是普通管理页,它是独立的大屏展示前端。当前产品目标是:
|
||
|
||
- 维持地球视图的空间感和可读性
|
||
- 让 HUD、图层、媒体面板、BGP、卫星、海缆等保持统一交互
|
||
- 把加载中、已启用、已隐藏、锁定中这类状态做清楚
|
||
|
||
## 当前入口
|
||
|
||
React 路由入口:
|
||
|
||
- [Earth.tsx](/home/ray/dev/linkong/planet/frontend/src/pages/Earth/Earth.tsx)
|
||
|
||
当前做法很简单:
|
||
|
||
- React 页面只负责提供一个全屏 `iframe`
|
||
- 真正的 Earth 应用运行在:
|
||
- [index.html](/home/ray/dev/linkong/planet/frontend/public/earth/index.html)
|
||
|
||
所以 Earth 前端本质上是 `public/earth` 下的一套独立静态应用。
|
||
|
||
## 当前文件分层
|
||
|
||
### 1. 页面入口与结构
|
||
|
||
- [index.html](/home/ray/dev/linkong/planet/frontend/public/earth/index.html)
|
||
|
||
职责:
|
||
|
||
- HUD 基础 DOM
|
||
- 图层面板
|
||
- 媒体面板
|
||
- 工具栏
|
||
- 设置弹窗
|
||
- 兼容旧元素 id
|
||
|
||
### 2. 主运行时
|
||
|
||
- [main.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/main.js)
|
||
|
||
职责:
|
||
|
||
- 地球初始化
|
||
- Three.js 场景组装
|
||
- 数据加载与刷新
|
||
- 各图层集成
|
||
- Earth 级别状态同步
|
||
|
||
### 3. 地球控制层
|
||
|
||
- [controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js)
|
||
|
||
职责:
|
||
|
||
- 工具栏交互
|
||
- 图层面板交互
|
||
- 旋转/缩放/布局
|
||
- HUD 面板拖拽
|
||
- 图层开关状态机
|
||
- Earth 设置读取、持久化与重置
|
||
|
||
这份文件是 Earth 前端当前最核心的 UI 控制入口。
|
||
|
||
### 4. UI 与状态消息
|
||
|
||
- [ui.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/ui.js)
|
||
|
||
职责:
|
||
|
||
- loading 面板
|
||
- status message
|
||
- tooltip / error / 清理逻辑
|
||
|
||
当前 status message 有两类短提示:
|
||
|
||
- 普通业务提示:通过 `showStatusMessage()` 入队显示。
|
||
- 手势提示:通过 `showGestureStatusMessage()` 直接短暂显示,用于缩放视角时的 `缩放 N%`。
|
||
|
||
手势提示不会抢占 loading 状态。对应样式是 [hud.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/hud.css) 中的 `.earth-status-message.gesture`。
|
||
|
||
### 5. 地球与地形
|
||
|
||
- [earth.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/earth.js)
|
||
- [terrain.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/terrain.js)
|
||
|
||
职责:
|
||
|
||
- 地球球体、云层、大气
|
||
- 真实地形 mesh
|
||
- terrain tile 拉取、解码、位移、着色
|
||
|
||
### 6. 图层模块
|
||
|
||
- [satellites.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/satellites.js)
|
||
- [cables.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/cables.js)
|
||
- [bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js)
|
||
- [bgp-cruise-adapter.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp-cruise-adapter.js)
|
||
- [vessels.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/vessels.js)
|
||
- [news.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/news.js)
|
||
- [tv.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/tv.js)
|
||
- [layer-startup-tasks.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/layer-startup-tasks.js)
|
||
- [cruise-sequencer.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/cruise-sequencer.js)
|
||
- [callout-connector.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/callout-connector.js)
|
||
|
||
职责:
|
||
|
||
- 各自的数据层
|
||
- 开关行为
|
||
- 面板内容
|
||
- hover/lock/selection 语义
|
||
|
||
其中 Earth 启动加载链现在也拆成了两层:
|
||
|
||
- `controls.js`
|
||
- 提供图层注册表与启动元信息
|
||
- `layer-startup-tasks.js`
|
||
- 提供图层启动任务注册表
|
||
- 通过 `registerLayerStartupTask(id, taskFactory)` 扩展启动任务
|
||
- `main.js`
|
||
- 只负责读取排序后的启动图层,再按映射执行队列
|
||
|
||
其中巡航模式现在已经拆成两层:
|
||
|
||
- `cruise-sequencer.js`
|
||
- 负责目标队列顺序、停留时长、切换节奏、打断与恢复
|
||
- `callout-connector.js`
|
||
- 负责卡片连线 SVG、路径计算与绘制动画
|
||
- `bgp-cruise-adapter.js`
|
||
- 负责 BGP 巡航展示适配:目标排序、卡片落点、连线路径、focus/overlay/info-card 时序
|
||
|
||
当前 BGP 巡航只是这套能力的一个调用方,不应再把“按队列巡航”和“BGP 事件展示”混写在同一个状态机里。
|
||
|
||
新闻巡航摘要计划见:
|
||
|
||
- [earth-news-cruise-summary-plan.md](/home/ray/dev/linkong/planet/docs/plans/earth-news-cruise-summary-plan.md)
|
||
|
||
## 当前样式分层
|
||
|
||
Earth 的 CSS 不是一份大样式表,而是分层管理:
|
||
|
||
- [base.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/base.css)
|
||
- [hud.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/hud.css)
|
||
- [toolbar.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/toolbar.css)
|
||
- [layer-panel.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/layer-panel.css)
|
||
- [info-panel.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/info-panel.css)
|
||
- [legend.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/legend.css)
|
||
- [earth-stats.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/earth-stats.css)
|
||
- [coordinates-display.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/coordinates-display.css)
|
||
- [tv-panel.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/tv-panel.css)
|
||
|
||
当前建议:
|
||
|
||
- 通用 HUD 壳层写进 `hud.css`
|
||
- 单一面板特性写进各自子文件
|
||
- 不要把业务状态样式再散回 `index.html`
|
||
|
||
## 当前图层开关状态语义
|
||
|
||
Earth 图层按钮现在不应再只有“开/关”两态,而应支持:
|
||
|
||
- `inactive`
|
||
- `active`
|
||
- `loading`
|
||
|
||
当前入口在:
|
||
|
||
- [controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js)
|
||
- [layer-button-state.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/layer-button-state.js)
|
||
|
||
关键函数:
|
||
|
||
- `updateLayerButtonState(button, isActive)`
|
||
- `setLayerButtonState(button, options)`
|
||
|
||
`setLayerButtonState` 负责:
|
||
|
||
- `loading` 样式
|
||
- `aria-busy`
|
||
- 按钮禁用
|
||
- tooltip 更新
|
||
- 绑定状态文本更新
|
||
- 可选同步 `active`
|
||
|
||
因此后续如果别的图层也需要异步启用,应该直接走这套状态机,而不是再手写一套临时 loading class。
|
||
|
||
另外,Earth 图层控制现在已经收成“注册表驱动”:
|
||
|
||
- 图层元数据
|
||
- `id`
|
||
- `icon`
|
||
- `label`
|
||
- `meta`
|
||
- `buttonId`
|
||
- `persist`
|
||
- `startupPriority`
|
||
- `startupMode`
|
||
- `startupLabel`
|
||
- `startupMessage`
|
||
- 图层行为
|
||
- `getVisible()`
|
||
- `setVisible(next, options)`
|
||
|
||
当前入口仍在 [controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js)。
|
||
|
||
这意味着后续新增图层时,优先应补一条图层注册定义,而不是同时去改:
|
||
|
||
- 图层面板 HTML
|
||
- 持久化快照
|
||
- 初始化恢复
|
||
- click 绑定
|
||
|
||
这四处现在都应该由注册表派生。
|
||
|
||
其中:
|
||
|
||
- `startupPriority`
|
||
- 描述图层参与启动加载时的顺序
|
||
- `startupMode`
|
||
- `visible`
|
||
- 仅当前图层处于启用/可见状态时,才加入启动加载队列
|
||
- `preload`
|
||
- 即使当前图层未显示,也会参与启动预加载
|
||
|
||
当前 `main.js` 会通过注册表读取排序后的启动图层列表,再动态拼装启动加载队列,而不是手写一串固定步骤。像 BGP 这类需要尽早准备数据、但不一定默认显示的图层,应该优先走 `startupMode: "preload"`,而不是在启动流程里写隐式特判。
|
||
|
||
此外,启动阶段给用户看的提示文案也应尽量从注册表派生:
|
||
|
||
- `startupLabel`
|
||
- 用于描述当前启动任务的业务名称
|
||
- `startupMessage`
|
||
- 用于描述启动中的提示文案
|
||
- 可以是字符串
|
||
- 也可以是对象,用于像海缆这种“准备阶段 / 主加载阶段”两段式文案
|
||
|
||
这样后续新增会参与启动加载的图层时,顺序、模式和提示文案都在同一处定义,不需要再去 `main.js` 里补第二套常量。
|
||
|
||
### 船只图层与图例
|
||
|
||
AIS 船只图层入口:
|
||
|
||
- [vessels.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/vessels.js)
|
||
- [interactable.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/interactable.js)
|
||
|
||
船只图层当前负责:
|
||
|
||
- 请求 `/api/v1/visualization/geo/vessels`
|
||
- 将 BarentsWatch AIS GeoJSON 转为地球局部坐标 marker 数据
|
||
- 通过 `createInteractableLayer()` 注册 Interactable 图标层
|
||
- 用按航向分桶的 `THREE.Points` 批量渲染普通船只 marker
|
||
- 按船型映射颜色
|
||
- 根据航行/停泊状态绘制三角形或圆点纹理
|
||
- 用单点 `THREE.Points` overlay 承载 hover / locked glow
|
||
- 支持 hover、lock、轨迹加载和视觉聚焦
|
||
|
||
船只图层不再是“每艘船一个 `THREE.Sprite`”。原始 Sprite 方案在拖动地球时会把透明对象排序、draw call 和对象级 raycast 成本全部放到主交互路径上;即使 BarentsWatch 免费 AIS 当前只覆盖挪威周边,也会让地球拖动明显不跟手。
|
||
|
||
当前设计把普通船只拆成少量批次:
|
||
|
||
- moving / anchored 分开。
|
||
- moving 船只按 `VESSEL_COURSE_BINS` 做航向分桶。
|
||
- 每个批次是一组 `THREE.PointsMaterial`,位置和颜色写入 `BufferGeometry` attribute。
|
||
- 普通态不带 glow;hover / locked 时才在相同点位叠加带 glow 的单点 overlay。
|
||
|
||
方向标准以 AIS `course / cog` 为准:从正北开始顺时针。普通态和交互态都通过同一套 canvas 旋转规则生成纹理,避免 hover 后箭头方向和原 marker 不一致。
|
||
|
||
船只 hover / click 也不再对渲染对象做 `raycaster.intersectObjects()`。`main.js` 只负责传入当前 Earth、camera、pointer 和命中半径,实际命中计算由 `interactable.js` 的图标层接口完成:
|
||
|
||
1. 拖动地球或惯性旋转时跳过 hover picking。
|
||
2. 对 hover picking 做轻量节流。
|
||
3. 只保留正面船只作为候选。
|
||
4. 将候选船只投影到屏幕坐标。
|
||
5. 用 `VESSEL_POINTER_RADIUS_PX` 做像素距离命中,并取最近船只。
|
||
|
||
这样 picking 位置和用户看到的屏幕 marker 对齐,也避免 `Points` 自带 raycaster 在固定屏幕尺寸图标上的命中半径错位。
|
||
|
||
图例系统已经注册 `vessels` 模式:
|
||
|
||
- [legend.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/legend.js)
|
||
- [legend.css](/home/ray/dev/linkong/planet/frontend/public/earth/css/legend.css)
|
||
|
||
`getVesselLegendItems()` 返回带 `shape` 的图例项:
|
||
|
||
- `shape: "vessel"`:三角形,表示航行船只。
|
||
- `shape: "dot"`:圆点,表示停泊或低速状态。
|
||
|
||
图例项颜色来自 `VESSEL_CONFIG.colors`,不要在 `legend.css` 里重新定义业务颜色。新增船型时,应优先改 `vessels.js` 和 `constants.js` 的船型映射,再同步图例项。
|
||
|
||
`interactable.js` 是后续地表图标类图层的共用入口。它当前已经承载船只、BGP 事件、BGP 观测站和算力中心图层的批量 `Points`、texture cache、hover / locked overlay、默认 glow、状态增量更新和屏幕空间 picking;BGP 事件的向外扩散圈、BGP 观测站的 halo / 覆盖扇形仍由 `bgp.js` 保留业务动画,但图标本体和 pointer 命中已经接入通用层。新增小型、中心对齐、可以参与深度测试的图标类元素时,应优先复用这个接口,而不是再次复制船只渲染逻辑。
|
||
|
||
登陆点是当前明确保留的例外:它曾接入 `Interactable`,但 pin 类 SVG 在地球边缘会被 `THREE.Points` 的深度测试裁切成碎片;关闭 depthTest 又会破坏背面遮挡语义。因此登陆点退回 `cables.js` 内的专用 `THREE.Sprite` 路径,并改为 canvas 生成的黄色扁平球纹理。它的 `altitudeOffset` 和 `renderOrder` 与海缆线一致,避免漂在海缆之上;Sprite 本体关闭 `depthTest` 保持球完整,背面可见性由 `isFacingCamera()` 的球体遮挡判断控制。
|
||
|
||
图标资源可以继续用 canvas draw,也可以放到 `frontend/public/earth/assets/icons/` 后由 `Interactable` 预加载。asset 路径不会在每帧读取;图层加载阶段通过 `preloadAssets()` 只加载一次 SVG / 图片,之后按 `icon source + state + bucket + color` 生成 `CanvasTexture` 并复用。当前算力中心已经从 `assets/icons/compute-supercomputer.svg`、`assets/icons/compute-gpu-cluster.svg` 和备用 `assets/icons/compute-hdd-network.svg` 读取图标,再在 canvas 上叠加估算位置的 `?` badge。
|
||
|
||
asset 图标大小由 `Interactable` 的 `icon.fitSize` 控制。SVG / 图片文件应尽量保持原始 viewBox 和路径,不要为了在地球上显示成 60x60 而手写 `transform`;`drawAssetIcon()` 会把资源等比 contain 到指定尺寸并居中绘制到 atlas canvas。
|
||
|
||
`Interactable` 默认使用固定屏幕像素尺寸,适合船只、BGP 事件、BGP 观测站、算力中心这类需要稳定识别的图标。如果某类图标需要跟随相机距离缩放,可以把 `sizeMode` 设为非 `"fixed"`,并用 `sizeScale.min / max / referenceFov` 控制缩放范围;单个 marker 的业务尺寸差异可以通过 `getPointSizeMultiplier()` 表达,例如 BGP 事件按严重级别调整点大小,BGP 观测站按活跃度调整点大小。
|
||
|
||
`Interactable` 不再把图标本体额外抬离业务高度。`altitudeOffset` 就是 marker、hover glow、locked glow 和 picking 共同使用的地表高度;这样船只图标会继续贴着船只轨迹线,不会因为单独抬高显示位置而显得漂浮。后续如果要解决边缘 glow 裁切,应优先考虑 glow 纹理、overlay 尺寸或图层专属特效,而不是把通用图标层整体抬高。
|
||
|
||
跨 Interactable 的同坐标避让也在公共层处理。每个 marker 会保留 `icon_base_position` 作为业务原始位置;当多个 Interactable marker 归入同一个经纬度 key 时,公共层会把它们沿地表切平面排成小圈,并刷新已创建的 `THREE.Points` geometry。这样视觉位置和屏幕空间 picking 位置一致,不需要业务层再单独判断“算力中心和 BGP 事件重叠”这类场景。
|
||
|
||
接口细节、生命周期和接入示例见:
|
||
|
||
- [earth-interactable-usage.md](/home/ray/dev/linkong/planet/docs/technical/zh/earth-interactable-usage.md)
|
||
|
||
### 视角控制反馈
|
||
|
||
[controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js) 统一维护 Earth 缩放状态。滚轮缩放、缩放按钮和触屏双指捏合最终都会更新 `zoomLevel`,并通过 `showZoomStatusCapsule()` 显示当前缩放比例:
|
||
|
||
```javascript
|
||
showGestureStatusMessage(`缩放 ${Math.round(zoomLevel * 100)}%`, "info");
|
||
```
|
||
|
||
该提示每 90ms 最多更新一次,显示 760ms 后淡出。它是视角反馈,不是数据加载进度,也不应该写进图层 loading 状态。
|
||
|
||
[main.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/main.js) 只负责在双指捏合缩放时调用 `setZoomLevel()` 和 `showZoomStatusCapsule()`。鼠标滚轮与缩放按钮的胶囊提示应继续放在 `controls.js`,避免同一种缩放反馈散落在多个模块。
|
||
|
||
拖拽地球的旋转灵敏度会根据当前缩放连续衰减,而不是按某个缩放阈值分段:
|
||
|
||
```javascript
|
||
const scale = THREE.MathUtils.clamp(
|
||
Math.pow(zoom, -CONFIG.dragRotationZoomExponent),
|
||
CONFIG.dragRotationScaleMin,
|
||
CONFIG.dragRotationScaleMax,
|
||
);
|
||
```
|
||
|
||
调参入口在 [constants.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/constants.js):`dragRotationFactorBase` 控制基础速度,`dragRotationZoomExponent` 控制放大后的衰减曲线,`dragRotationScaleMin` / `dragRotationScaleMax` 控制上下限。
|
||
|
||
### `data-status-target`
|
||
|
||
图层按钮可以通过:
|
||
|
||
- `data-status-target`
|
||
|
||
指向一个状态文本节点。当前 terrain 已接入:
|
||
|
||
- 按钮:`#toggle-terrain`
|
||
- 状态节点:`#terrain-status`
|
||
|
||
以后别的异步图层也可以沿用这套约定。
|
||
|
||
## 当前设置持久化
|
||
|
||
Earth 设置面板当前由 [controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js) 统一负责:
|
||
|
||
- 捕获默认值
|
||
- 从 `localStorage` 读取上次设置
|
||
- 初始化应用当前设置
|
||
- 用户变更后即时持久化
|
||
- 一键重置回默认值
|
||
|
||
当前持久化的范围是:
|
||
|
||
- 旋转模式
|
||
- 地球默认大小(作为重置视角、缩放重置和巡航视图的默认 zoom 真源)
|
||
- HUD 面板显示/隐藏
|
||
- 图层控制开关:`地形 / 卫星 / 轨迹 / 海缆 / BGP`
|
||
- 地形透明度
|
||
|
||
也就是说,Earth 设置不是一次性 UI 状态了,而是本地设备级偏好。后续如果再加入新的设置项,应优先接入同一条持久化链,而不是各自散着写 `localStorage`。
|
||
|
||
## 当前地形链路
|
||
|
||
真实地形首次启用会慢,原因不只是一个:
|
||
|
||
1. 需要拉取 Terrarium 瓦片
|
||
2. 需要解码图片
|
||
3. 需要按顶点采样高程
|
||
4. 需要重新写入 geometry 和 color
|
||
5. 需要重新计算法线与包围体
|
||
|
||
当前入口在:
|
||
|
||
- [terrain.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/terrain.js)
|
||
|
||
当前已经做了两层体验优化:
|
||
|
||
1. 图层开关 loading 状态持续可见
|
||
2. 页面空闲时会预热 `ensureTerrainReady()`
|
||
|
||
## 当前巡航链路
|
||
|
||
当前巡航边界:
|
||
|
||
- 通用巡航层包含:
|
||
- 当前目标
|
||
- 队列顺序
|
||
- 相机 focus
|
||
- 停留 / 隐藏 / 切换
|
||
- 业务模块提供:
|
||
- 提供目标队列
|
||
- 提供 focus 坐标
|
||
- 提供卡片内容
|
||
- 提供高亮/图层副作用
|
||
|
||
巡航模块的结构文件:
|
||
|
||
- [cruise-sequencer.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/cruise-sequencer.js)
|
||
- [callout-connector.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/callout-connector.js)
|
||
- [bgp-cruise-adapter.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp-cruise-adapter.js)
|