501 lines
38 KiB
Markdown
501 lines
38 KiB
Markdown
# 智能星球前端结构
|
||
|
||
本文件描述当前智能星球前端的真实结构,重点是帮助后续继续改 HUD、图层、媒体面板、真实地形、BGP 可视化时,不再重复踩结构和状态同步上的坑。
|
||
|
||
相关规则建议一起参考:
|
||
|
||
- 仓库根目录 `rules.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 级别状态同步
|
||
|
||
Earth 收到 `/ws` 的 `earth_updates` 时只把它当作刷新提示,真实数据仍通过 `/api/v1/visualization/...` 接口重新 GET。数据库驱动的刷新由后端 listener 直接清理缓存再广播,不再默认经过 `earth_refresh` 作业队列;前端收到 `database_changed` 后会按 layer 读取 `clear_then_reload`、`reload` 或 `delta` 策略。`clear_then_reload` 必须先清 Three.js 对象再 no-store 重拉,summary 只做一致性校验,不能用 `0` 作为跳过图层重拉的理由。技术链路见 [数据作业与 Outbox 技术架构](/home/ray/dev/linkong/planet/docs/technical/zh/data-job-earth-sync-architecture.md),业务数据流见 [业务架构与数据流转](/home/ray/dev/linkong/planet/docs/technical/zh/platform-data-flows.md)。
|
||
|
||
### 3. 地球控制层
|
||
|
||
- [controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js)
|
||
|
||
职责:
|
||
|
||
- 工具栏交互
|
||
- 图层面板交互
|
||
- 旋转/缩放/布局
|
||
- HUD 面板拖拽
|
||
- 图层开关状态机
|
||
- Earth 设置读取、持久化与重置
|
||
|
||
这份文件是 Earth 前端当前最核心的 UI 控制入口。
|
||
|
||
Earth 设置面板现在按 `data-settings-tab` 和 `data-settings-tab-panel` 分类组织。桌面端和移动端使用同一组分类语义:运行、显示、面板、动捕、快捷键、系统。新增设置项时应先判断它属于哪个分类,再补 DOM、持久化字段和恢复逻辑;不要把所有控件继续堆到一个长面板里。
|
||
|
||
快捷键配置属于设备本地偏好,由 `controls.js` 负责读取、捕获、启用/禁用和重置。它不应写入后端用户设置,也不应影响其它浏览器。后续新增快捷键时,必须同时提供默认键、显示标签、可禁用状态和重置路径,避免只在 keydown handler 中硬编码。
|
||
|
||
### 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. 动作捕捉控制适配层
|
||
|
||
- [motion-control.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/motion-control.js)
|
||
|
||
职责:
|
||
|
||
- 作为 Motion Provider manager,统一接入 `browser_camera` 与 `motion_agent`。
|
||
- 默认使用浏览器 `getUserMedia` + 本地 MediaPipe 识别;高级模式可连接本地 Motion Capture Agent WebSocket。
|
||
- 处理浏览器摄像头权限/安全上下文错误,以及 Agent 断线重连和 `status` / `heartbeat`。
|
||
- 过滤低置信度和过快重复的手势事件。
|
||
- 将 `rotate_left`、`rotate_right`、`rotate_up`、`rotate_down`、`zoom_in`、`zoom_out`、`focus_prev`、`focus_next`、`layer_prev`、`layer_next`、`confirm` 映射到 `main.js` 暴露的动作入口。
|
||
- 解析 `skeleton` 调试事件并派发 `earth:motion-debug-frame`。
|
||
|
||
动作捕捉识别可以在浏览器本地执行,也可以在本地 Agent 中执行,但两者都不会把实时视频帧发给 SaaS 云端。`main.js` 暴露旋转、缩放、目标切换、图层切换和确认入口,并通过 `window.__planetEarth.motion` 提供调试入口。默认只有 URL 参数 `?motion=1`、本地存储 `planet-earth-motion-control-enabled=true`,或 Earth 设置中的“动捕调试模式”打开时才启动当前 provider。
|
||
|
||
动捕调试面板由 [motion-debug-panel.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/motion-debug-panel.js) 负责。它监听 `earth:motion-debug-frame`,用 canvas 绘制归一化骨架点和连线;Browser Camera provider 会额外通过 `earth:motion-debug-video-source` 提供本机 `<video>` 作为调试预览底图,`shared.motionDebugSkeletonOnly` 可切换为只显示骨骼。`停止匹配动作` 通过 `earth:motion-recognition-pause` 暂停 gesture 执行,但继续显示视频和骨架。未匹配动作为红色,匹配后变绿并显示动作名。设置项持久化在 `planet.earth.settings.v2` 的 `shared.motionDebugEnabled`、`shared.motionProvider` 与 `shared.motionDebugSkeletonOnly`,switch 和输入源控件都预留 `data-gatekeeper-permission="earth.motion_debug"`。
|
||
|
||
Browser Camera provider 的手势识别管线在 [motion-browser-provider.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/motion-browser-provider.js) 的 `recognizeGesture()`,按以下顺序匹配,前者命中即返回:
|
||
|
||
1. **`getZoomTrend`(趋势 zoom)**:基于上一帧到当前帧两腕 x 间距的变化量(`Math.abs(rightWrist.x - leftWrist.x)` 的 delta)。两腕都越过噪声地板(`ZOOM_TREND_MIN_WRIST_DELTA = 0.010`)且高度差不超过 `ZOOM_TREND_HEIGHT_TOLERANCE`,spread 增加 → `zoom_in`,spread 减少 → `zoom_out`。趋势优先级最高,避免中间帧被 layer/focus/rotate 抢先误判。
|
||
2. **`getZoomHoldPose`(姿态 zoom)**:动作停止后,只要两腕仍保持在胸口及以上、且 span > `ZOOM_HOLD_SPREAD_FACTOR × shoulderWidth`(默认 1.30)就持续派发 `zoom_in`;两肘明显外展且 span < `ZOOM_HOLD_CLOSE_FACTOR × shoulderWidth`(默认 0.85)就持续派发 `zoom_out`。
|
||
3. **layer / focus**:左手抬起 + 上下移动派发 `layer_prev/next`;头部左右倾斜派发 `focus_prev/next`。
|
||
4. **`getRightArmPattern`(单臂 rotate)**:仅当 `!isZoomCandidatePose(...) && isLeftArmAtRest(...)` 同时成立时才考虑。`isLeftArmAtRest` 要求左腕明显垂在肩下 13% 以下且左肘/左腕都不外伸,把「张臂中间帧」「单手举起」「左手扶在胸前」等所有模糊状态都判为非静止 —— 单臂 rotate 严格要求另一只手处于静止。
|
||
|
||
两个关键设计:
|
||
|
||
- **mirror-safe**:所有 zoom 检测都基于 `Math.abs(rightWrist.x - leftWrist.x)`,不依赖单侧 x 方向。无论摄像头是否做镜像翻转(浏览器默认不翻转,`getUserMedia` 返回的就是原始帧),张臂始终 → zoom_in,合手始终 → zoom_out。早期基于「左腕往左 + 右腕往右」的判定会在非镜像视图里把方向判反。
|
||
- **连续 vs 离散**:rotate / layer / focus / confirm 都过 `applyPoseLatch`,同一手势只发一次,必须先回到中性位才能再发(挥一下转一格)。zoom 故意 bypass latch,每帧匹配都返回 → 下游 `GESTURE_POLICIES.zoom_in/out.cooldownMs = 120` 节流到 ~8 次/秒,张臂保持就一直放大直到姿势变化。这是 zoom 跟其它手势在交互语义上的本质区别,不要复用 latch 给 zoom。
|
||
|
||
[presentation-controller.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/presentation-controller.js) 是新的 Presentation 层。第一阶段只接入 Motion:`motion-cruise-adapter.js` 通过 persistent presentation 复用巡航固定卡片位置和 connector,但不会让鼠标移动触发自动隐藏;connector 每帧重算 source/target anchor,让卡片拖动、地球旋转和目标移动时端点继续跟随。BGP/News 仍保持原有 `CruiseSequencer` 自动轮播路径,避免改变既有巡航体验。
|
||
|
||
### 6. 地球与地形
|
||
|
||
- [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)
|
||
- [country-boundaries.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/country-boundaries.js)
|
||
|
||
职责:
|
||
|
||
- 地球球体、云层、大气
|
||
- 真实地形 mesh
|
||
- terrain tile 拉取、解码、位移、着色
|
||
- 海陆基座与国界底图的整球 overlay
|
||
|
||
智能星球地表是多层近似同心球,不是单一 mesh。`earth.js` 的基座球、高清材质 overlay、云层/大气,以及 `country-boundaries.js` 的海陆基座都需要明确半径间距。远距视图下 GPU 深度精度会下降,相邻 shell 过近会 z-fighting,表现为黑色闪烁块或雪花。当前稳定策略是让海陆基座使用 `landAltitudeOffset = 0.32`,高清材质使用 `EARTH_SURFACE_TEXTURE_ALTITUDE_OFFSET = 0.48`;国界线、coastline、claim 线和国界 hover 线也必须使用同一个高清材质壳半径,避免转动地球时相对高清贴图产生视差漂浮感。后续新增或调整整球地表 overlay 时,必须同步检查 [智能星球渲染图层顺序](/home/ray/dev/linkong/planet/docs/technical/zh/earth-render-layer-order.md),并在 50% 缩放视图验证。
|
||
|
||
### 7. 图层模块
|
||
|
||
- [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 语义
|
||
|
||
`tv.js` 管理 `media-panel` 里的直播 / 态势新闻 tab。toolbar 打开或切换 TV/新闻时,会通过 `earth:tv-visibility-change` 和 `earth:tv-tab-change` 回写 Earth 设置:面板可见性仍按 desktop/mobile viewport 存在 `views.<scope>.panelVisibility.media-panel`,当前 tab 存在 `shared.mediaPanelActiveTab`,因此刷新页面后能恢复用户上次打开的直播或新闻状态。`closeTransientMobileOverlays()` 这类临时收起会带 `persist:false`,不会覆盖用户偏好。
|
||
|
||
`brand.js` 管理智能星球 HUD 品牌资源。默认品牌来自静态资源,运行时覆盖值来自 `/api/v1/earth/brand`,上传的图片通过 `/earth-brand-assets/...` 读取。前端必须把 logo/title 图片和文本 fallback 分开处理:图片加载失败时显示文本标题,文本字段为空时使用后端默认值,避免 HUD 品牌区空白。控制台的智能星球内容页负责保存和重置品牌配置,智能星球前端只消费结果。
|
||
|
||
`about.js` 管理智能星球设置里的“关于”卡片。默认内容仍保留在前端作为兜底,运行时优先读取 `/api/v1/earth/about`。接口失败或字段缺失时必须回退默认值,避免设置页出现空白。控制台的智能星球内容页提供“关于”tab,保存走 `PUT /api/v1/earth/about`,恢复默认走 `DELETE /api/v1/earth/about`。
|
||
|
||
`oobe.js` 管理 Earth 首次初始化引导。是否显示 OOBE 必须由 `/api/v1/earth/oobe-status` 的 `ready` 字段决定,不能依赖 `localStorage` 判断系统是否初始化。`localStorage` 只允许记录“本浏览器暂时跳过”的短时状态;如果后端已经认为 `ready: true`,退出登录、清空本地缓存或换浏览器都不应再次弹出 OOBE。桌面端使用深色星空遮罩和毛玻璃启动面板,移动端改为底部 sheet,并尊重 `prefers-reduced-motion`。
|
||
|
||
控制台的智能星球内容页必须按运行时语义组织这些配置:
|
||
|
||
- `品牌标识`:品牌预览应使用与 Earth HUD 左上角一致的深色星空背景、尺寸、间距、logo/title 渲染和文本 fallback,而不是普通表单预览。
|
||
- `关于`:配置 Earth 设置里的 About 卡片,包括 logo、眉标、标题、版本、描述和元信息条目;Earth 运行时从 `/earth/about` 读取,失败时回退默认内容。
|
||
- `国界精度`:构建边界、刷新状态和恢复默认属于这个分区内部动作,不应放在页面全局工具栏。
|
||
- `电视直播`:列表同时区分内置源、采集源和自定义源;卡片状态表达启用、停用、新建或错误。内置源不能删除,采集源和自定义源可以删除。新增直播源在点击加号后才进入草稿状态,保存后固化到列表,取消则销毁草稿。
|
||
- `底图资源`、`图层资源`、`3D 模型`、`新闻锚点策略`:如果后端能力未接入,控制台应明确显示待接入空态,不应混入 TV 或品牌配置。
|
||
|
||
TV 预览需要尽量复用 Earth 运行时的直播卡片结构和状态标签,这样控制台看到的内置、直播加载中、播放源、地区、语言等信息与 Earth 中实际显示保持一致。
|
||
|
||
其中 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 事件展示”混写在同一个状态机里。
|
||
|
||
新闻巡航摘要的未来计划保存在仓库路径 `docs/plans/earth-news-cruise-summary-plan.md`,不作为公开 Docs 页面入口。
|
||
|
||
## 当前样式分层
|
||
|
||
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/vessels/snapshot` 获取当前视口初始快照;请求必须携带 `bbox`、`zoom`,并传入受控 `limit`
|
||
- 通过 `/ws` 的 `vessels` channel 订阅后续增量;订阅 payload 同样必须携带当前视口 `bbox`、`zoom`、`limit`
|
||
- 将聚合后的 AIS GeoJSON 转为地球局部坐标 marker 数据;后端默认 `limit=1000`,最大 `limit=5000`
|
||
- 通过 `createInteractableLayer()` 注册 Interactable 图标层
|
||
- 用按航向分桶的 `THREE.Points` 批量渲染普通船只 marker
|
||
- 按船型映射颜色;`vessels.js` 会用 `vessel_type_name` 和 AIS `vessel_type` 数字共同归一化船型
|
||
- 根据航行/停泊状态绘制三角形或圆点纹理
|
||
- 用单点 `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 不一致。
|
||
|
||
船型展示也必须复用同一套归一化结果。`buildVesselMarkerData()` 会把后端的 `vessel_type_name` 和 AIS 数字类型码归一化为 `type`,用于 marker 颜色;同时生成 `vessel_type_display`,供详情卡、hover 简述和搜索结果显示。不要让详情卡直接只读原始 `vessel_type_name`,否则会出现 marker 已按 Cargo/Tanker 等颜色显示、卡片仍写 `Other` 的不一致。
|
||
|
||
AISStream 的 `PositionReport` 常带实时位置和 `MetaData.ShipName`,但船型通常来自低频 `ShipStaticData.Type`。后端会把 `MetaData.ShipName` 补进船名,并将类型码映射为 Cargo / Tanker / Passenger / Fishing / Military;仍缺失的船型需要等待静态 AIS 消息或后续船舶资料 enrichment,不能在前端凭颜色之外的信息臆造细分类。
|
||
|
||
旧 `/api/v1/visualization/geo/vessels` 路由已移除。前端打开船只图层时应先按当前视口拉一次 `/api/v1/vessels/snapshot`,再用 WebSocket 接收同一视口内的 upsert 增量;地图拖动或缩放后应重新拉取 snapshot 并重发 vessels 订阅。后端只在当前 raw 窗口为空时受控回退到 legacy `vessel_position` / `vessel_static`,前端可通过 `diagnostics.legacy_fallback_used` 识别该状态。
|
||
|
||
新的图层接口族是 `/api/v1/layers/*`,用于把地图渲染数据和聚合面板统计分开。地图层请求必须带 `bbox`、`zoom` 和受控 `limit`,响应会返回 `visible_count`、`returned_count` 和 `diagnostics`,其中 `degraded/truncated/limit_clamped` 用于前端提示降级。右侧聚合统计不要从图层响应累加,应读取 `/api/v1/data-products` 或 `/api/v1/data-products/{product_id}/status`,因为这些统计保持全量/全局口径,不随当前视口变化。
|
||
|
||
船只 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。算力中心后端在启动链路只渲染源数据自带坐标或 `compute_center_locations` 维表坐标;手动候选采集会调用 ROR 和 Nominatim/OpenStreetMap,并在 GeoJSON 或候选响应中返回位置精度、置信度、来源说明和核验时间;前端详情卡展示这些字段。
|
||
|
||
算力中心图层行左上角的通知气泡显示 GeoJSON `unresolved` 数量。这个数字表示“完全没有可信坐标、不能渲染到地球上”的记录,不等同于地图上带 `?` 的已定位待确认点。点击气泡会在图层面板右侧打开固定信息卡,信息卡内容区内部滚动,不随鼠标 hover 消失。列表中的单条 `采集` 只展示候选;顶部 `一键采用` 会按当前列表顺序逐条采集、保存最高置信候选,成功一条就移除一条、重新编号,并通过 `earth:compute-center-unresolved-count-change` 同步气泡数量。批量结束后再触发 `earth:compute-center-location-saved` 刷新真实图层。
|
||
|
||
详情卡里的坐标候选状态由 [info-card.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/info-card.js) 按 `entityType:entityId` 缓存在模块内存中。用户关闭详情卡或待定位列表后再次打开同一个算力中心 / BGP 观测站,已经采集到的候选和状态文案会恢复;`一键采用` 会优先使用缓存候选,避免重复调用在线地理编码或 LLM factcheck。保存成功后该实体的候选列表会清空为“正在刷新图层”状态,避免旧候选在刷新后继续误导用户。
|
||
|
||
候选行的 `预览 / 保存` 按钮采用单一的事件委托模型:每个候选根(详情卡里的 `[data-collect-cache-key]` 块,或待定位列表里的 `[data-unresolved-item]`)只挂一个 `click` 监听,由 `data-candidate-actions-bound` 幂等标记,不再混用 `pointerup` / `click` 直绑或重复委托。候选对象不再以 JSON 字符串塞进 HTML 属性后再 `JSON.parse`,按钮只携带 `data-candidate-index`,handler 通过 cache-key 在模块内存的 `Map` 里取出原对象,避开 HTML 实体转义对 `&` / `<` / `"` 的破坏。点击 `预览` 会派发 `earth:preview-location-candidate`,由 `main.js` 的 `previewLocationCandidate()` 调用 `showComputeCenterLocationPreview()`:在候选经纬度上挂双层空心呼吸 sprite(视觉参考 BGP 事件 ring),并把视角聚焦到候选坐标;切换到另一个候选会替换为新呼吸圈,保存时立即清除并由 `spawnSavedComputeCenterLocation()` 即时生成正式算力中心交互图标。注意 `main.js` 没有模块级 `earth` 变量,所有 location-save / preview 处理函数必须先 `const earth = getEarth();`,否则会在事件 handler 里抛 `ReferenceError` 被 `.catch` 静默掉,外观上等同于按钮“没有反应”。
|
||
|
||
`earth:compute-center-location-saved` 之后的图层校准链路对后台刷新失败保持沉默:`spawnComputeCenterAfterLocationSave()` 已经把 toast 和 locked 状态都给了用户,`refreshComputeCentersAfterLocationSave()` 只在场景就绪时重新拉取后端数据,本身不再吐 `已保存` toast;`handleComputeCenterLocationSaved()` 在 spawn 成功路径让 refresh 静默后台运行,只在 spawn 返回 `null`(场景未就绪)或抛错时才让 refresh 接管成功 toast,refresh 自身报错只走 `console.warn`,绝不冒泡成 `保存失败` 文案——保存请求本身已经成功,刷新失败属于后续同步问题。
|
||
|
||
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 的同坐标关系也在公共层记录,但真实位置必须始终以 `icon_base_position` 为准。缩放、避让、聚合和后续 spiderfy 展开都只能改变屏幕表现,不能写回 `marker.position` 或 `THREE.Points` 里的业务锚点;巡航定位、详情卡、搜索定位和 picking 返回对象都必须落回真实经纬度。多个图标归入同一个经纬度 key 时,公共层只写 `icon_avoidance_*` 元数据,供业务层弱化 halo 或显示聚合提示;真正的低缩放聚合应通过独立 cluster glyph / screen layout 层实现,而不是把对象沿地表切平面挪开。
|
||
|
||
`Interactable` 的单点显示只由全局地图缩放决定:170% 及以下强制显示小圆点,超过 170% 显示原图标。cluster 判定使用离散 zoom band 推导出的球面邻近半径,而不是当前屏幕投影距离;同一 band 内同一组地理位置不应因为旋转角度或 100% 到 199% 的连续缩放而改变聚合语义,只有跨过 band 边界才允许拆成更小集群。170% 以上会按 band 收紧聚合阈值,轻微擦边直接拆成图标,避免高缩放下仍然到处是圆点。cluster 每帧从当前可见 marker 重新计算,不使用上一帧聚合状态,避免缩放来回后不同地区被粘成一组。cluster 不使用无限连通分量,避免 A 重叠 B、B 重叠 C 一路串成跨区域大组;圆点展示位置使用局部成员中心,但业务坐标仍以成员真实经纬度为准。cluster 圆点大小随包含对象数量增长,数量过多时按稳定地理顺序拆成多个较小圆点;数量默认只在 hover tooltip 中显示。这个过程只设置 `icon_cluster_*` 展示元数据和重建渲染 Points,不改变每个 marker 的真实经纬度。当前默认只对启用同坐标关系记录的图层开启 cluster,船只这类高频动态层继续关闭。
|
||
|
||
接口细节、生命周期和接入示例见:
|
||
|
||
- [智能星球可交互图标接入](/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) 统一维护智能星球缩放状态,所有入口最终都必须调用 `setZoomLevel()` 写相机距离。不要在其他模块直接写 `camera.position.z`,否则缩放百分比、拖拽灵敏度和 Interactable 聚合阈值会再次分叉。
|
||
|
||
滚轮输入分两类处理:传统鼠标滚轮保留 10% 档位和短动画,并用 `wheelZoomTarget` 作为连续滚动的逻辑基准;触控板 / 高精度滚轮按 pixel delta 连续缩放,直接调用 `setZoomLevel()`,不走 10% 档位动画。触控板路径还会过滤反向后的短窗口旧方向小 residual delta,避免惯性尾巴把用户刚反向的缩放又拉回旧方向。
|
||
|
||
该提示每 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`
|
||
|
||
地形不是默认可见图层时,启动期不会立即阻塞加载地形瓦片。`controls.js` 会在图层可见性恢复完成后才调度 `scheduleTerrainPrefetch()`,并且只在高清材质可用、地形尚未 ready、预取未开始时执行。预取使用 `setTimeout` + `requestIdleCallback`,避免和首屏云图、高清材质、图层启动队列抢主线程。
|
||
|
||
地形瓦片请求也不再逐个散发大量单 tile 请求。`terrain.js` 会把需要的 Terrarium tile 去重后按 `TERRAIN_CONFIG.batchRequestSize` 分批请求 `/api/v1/visualization/terrain/terrarium/batch`;后端用 LRU 内存缓存、批次去重和并发限制代理 S3 Terrarium tile。单 tile endpoint 仍保留给回退路径和浏览器缓存语义。
|
||
|
||
以后别的异步图层也可以沿用这套约定。
|
||
|
||
## 当前设置持久化
|
||
|
||
Earth 设置面板当前由 [controls.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/controls.js) 统一负责:
|
||
|
||
- 捕获默认值
|
||
- 从 `localStorage` 读取上次设置
|
||
- 初始化应用当前设置
|
||
- 用户变更后即时持久化
|
||
- 一键重置回默认值
|
||
|
||
当前持久化的范围是:
|
||
|
||
- 旋转模式
|
||
- 地球默认大小(作为重置视角、缩放重置和巡航视图的默认 zoom 真源)
|
||
- HUD 面板显示/隐藏
|
||
- 图层控制开关:`地形 / 卫星 / 海缆 / BGP`
|
||
- 卫星显示偏好:`卫星显示风格 / 卫星呼吸闪烁 / 真实卫星高度 / 轨迹显示`
|
||
- 地表 hover 提示偏好:`国家 / 位置 / 完整`
|
||
- 国界精度偏好:低精 fallback / 高精 PMTiles
|
||
- 地形透明度
|
||
|
||
也就是说,Earth 设置不是一次性 UI 状态了,而是本地设备级偏好。后续如果再加入新的设置项,应优先接入同一条持久化链,而不是各自散着写 `localStorage`。
|
||
|
||
地表 hover 提示由 `controls.js` 持久化为 `shared.surfaceHoverInfoMode`,实际 tooltip 在 `main.js` 的地表 hover 分支组合。`国家` 模式只在命中国家时显示国家信息,海洋区域不显示地表 tooltip;`位置` 模式只显示纬度、经度和地形采样海拔,并清除国家边界 hover;`完整` 模式在陆地显示国家 + 位置,在海洋显示位置。
|
||
|
||
卫星真实高度开关由 `controls.js` 持久化,实际渲染状态在 `satellites.js`。开启时,SGP4 得到的真实半径会按对数压缩到当前 Earth 视觉半径范围;关闭时,卫星点、轨迹和预测轨道都回到旧版同层球面。切换时必须刷新卫星位置并清理轨迹缓存,避免同一条轨迹混入两个高度模型。`maxRealAltitudeOffset = 25` 是当前相机和 `earthRadius = 100` 下的视觉上限:它让 GEO / MEO 比 LEO 明显更高,但把最高轨道控制在地球半径外约 25%,避免选择点、红色轨迹和主体地球之间出现过大的空场。
|
||
|
||
SGP4 传播输出是惯性系位置,不能直接当成 Earth 的经纬度固定坐标使用。`satellites.js` 会用当前时间的 `gstime` 把 ECI/TEME 位置转换到 ECF,再映射到 `latLonToVector3()` 使用的 Three.js 坐标轴。卫星点和短尾迹使用随采样时间变化的地固坐标,表示相对当前地球表面的实际位置;锁定后的预测轨道线使用锁定时刻固定的 `gstime`,把未来一圈惯性轨道投到当前地球姿态上显示,因此会闭合,并且轨道面倾角应与详情卡一致。fallback 预测轨道也必须使用真正的 RAAN + inclination 轨道平面公式,不能把 inclination 当成恒定纬度。
|
||
|
||
国界精度偏好独立存储在 `country-boundaries.js` 的 `planet.earth.boundaries.highPrecisionEnabled`。未开启高精时,即使本机已经有高精 manifest/PMTiles,也继续加载低精 `countries-admin0.min.geojson` fallback;开启高精但高精产物缺失时,智能星球工具栏设置会调用 `/api/v1/earth/boundaries/build` 启动后台构建并轮询进度。构建成功后调用 `reloadCountryBoundaries()` 热切换,不再刷新整个页面。国界 hover 与 tooltip 解耦:只要地表坐标落在国界 polygon 内就保持高亮;如果鼠标同时命中卫星、船只、BGP 等 interactable,tooltip 显示 interactable 信息,但国界高亮不应闪烁。
|
||
|
||
## 当前地形链路
|
||
|
||
真实地形首次启用会慢,原因不只是一个:
|
||
|
||
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)
|