Files
ECAP_Server/arch_doc/TECHNICAL_HARD_POINTS.md
T
2026-07-27 18:28:21 +08:00

960 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 项目技术难点说明
## 1. 技术难点总览
| 编号 | 技术点 | 难度 | 核心模块 | 主要风险 | 关键源码 |
| -- | -- | -- | -- | -- | -- |
| 1 | 多执行器数据源协程和有序解析 | 高 | Local_Server、Asio | 乱序、积压、退出等待 | [`Data_Source::loop_coro()`](../module/Local_Server/server/io_coro.cpp#L206-L299) |
| 2 | ADS-B/CPR 三维位置和基站范围过滤 | 高 | SSR、Data_Source | 坐标/高度错误造成误过滤 | [`CPR.cpp`](../third_party/SSR/SSR/CPR/CPR.cpp#L247-L303) |
| 3 | WebSocket 版本增量与轨迹监控状态 | 高 | Database、Data_Source.tsx | 漏更新、重复轨迹、慢连接 | [`Database.cpp`](../module/Local_Server/Data_Source/Database.cpp#L92-L289) |
| 4 | Cesium 生命周期和大对象增量同步 | 高 | Cesium_Map | 主线程长任务、资源泄漏 | [`Cesium_Map`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L117-L298) |
| 5 | Terrarium 自定义地形和 Worker 解码 | 高 | Terrain Provider/Worker | 层级翻转、主线程阻塞、缓存失控 | [`Terrarium_Terrain_Provider`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx#L22-L61) |
| 6 | ECEF 地形 LOS 与 Worker 池 | 高 | Los_Worker、Worker_Pool | 任务积压、旧结果、基准误差 | [`calculate_los()`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L45-L91) |
| 7 | 统一 2D/3D 业务状态和双渲染适配 | 中高 | Data_Source、Aircraft_Model | 重复状态、显示语义漂移 | [`Aircraft_Model`](../third_party/eacp_webapp/src/Map/Aircraft_Model.tsx#L22-L75) |
| 8 | 基站、设备和飞机的三维坐标/姿态 | 中高 | Cesium_Map、Flight_VTO | 轴向、原点、世界/屏幕尺寸错误 | [`sync_base_station()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L768-L801) |
| 9 | 多目录瓦片异步读取和 HTTP 缓存 | 中高 | tiles.cpp | 阻塞网络线程、重复回调、缓存失效 | [`Tile_Source`](../module/Local_Server/server/tiles.cpp#L202-L324) |
| 10 | Cesium 相机模式和视图持久化 | 中 | Camera Control、Map View | 输入冲突、跟随状态恢复错误 | [`Cesium_Camera_Control`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L12-L177) |
| 11 | 后端/前端/Cesium 静态资源组合构建 | 中 | CMake、Vite | 开发可用而生产资源 404 | [`vite.config.js`](../third_party/eacp_webapp/vite.config.js#L21-L53) |
## 2. 技术难点依赖关系
```mermaid
flowchart LR
AsyncSource[1 多执行器数据源] --> CPR[2 ADS-B/CPR]
CPR --> Stream[3 WebSocket增量]
Stream --> Shared[7 统一业务状态]
Shared --> CesiumSync[4 Cesium增量同步]
Tiles[9 瓦片服务] --> Terrain[5 Terrarium地形]
Terrain --> LOS[6 LOS Worker池]
Shared --> Spatial[8 三维坐标姿态]
Terrain --> Spatial
Spatial --> CesiumSync
Camera[10 相机模式] --> CesiumSync
Build[11 组合构建] --> CesiumSync
Build --> Tiles
```
## 3. 技术难点详情
### 3.1 多执行器数据源协程和有序解析
**难度等级:**
**复杂度来源:** 并发、生命周期、性能
**所属模块:** `module/Local_Server/server``module/Local_Server/Data_Source`
**当前状态:** 已实现
#### 解决的问题
TCP、串口、文件、DLL 和共享内存等输入不能因报文解析阻塞网络循环,同时同一数据源中的报文顺序不能被线程池并发破坏。
#### 为什么困难
读取、解析和输出跨越不同 executor。等待处理模式要求读协程等待解析完成;不等待模式需要独立 strand 保序。停止过程中还必须停止新读取、等待处理线程、关闭底层资源并终止 `io_context`
#### 当前架构
`Coro` 持有一个 Asio `io_context` 和一个 `process_data` 线程池。每个 `Data_Source` 在网络 executor 上读取,按配置切到处理池或 strand,完成后回到网络 executor。
```mermaid
flowchart LR
IO[io_context读取] --> Queue[数据源本地处理路径]
Queue --> Pool[process_data线程池]
Pool --> SSR[SSR解析]
SSR --> IO
```
#### 核心执行流程
1. `Coro::start()` 创建网络线程和处理池。
2. `Data_Source::loop_coro()` 调用输入源 `read_coro()`
3. 数据切包后切换或投递到 `process_data`
4. `Data_Source_Handler` 解析并分发。
5. 协程回到网络 executor,继续读取。
6. `Coro::stop()` 停止源、等待线程池并释放。
#### 核心数据结构
- `Coro::io`
- `Coro::process_data`
- 每个 Data Source 的 `asio::strand`
- `With_Loop_Coro_Data` 状态
#### 并发和异步语义
`co_await` 本身不创建线程;真正的线程切换来自 executor。strand 保证同一数据源投递任务不并行。不同数据源可以在处理池并行。当前没有统一的任务队列上限或背压指标。
#### 算法和数值语义
不涉及数值算法。复杂度主要随报文数线性增长;空间成本取决于待处理消息积压。
#### 关键源码
- [`Coro::start()/stop()`](../module/Local_Server/server/io_coro.cpp#L47-L131)
- `module/Local_Server/server/io_coro.cpp:47-131`
- [`Data_Source::loop_coro()`](../module/Local_Server/server/io_coro.cpp#L206-L299)
- `module/Local_Server/server/io_coro.cpp:206-299`
- [`With_Loop_Coro::run_loop_coro()`](../module/Local_Server/server/With_Loop_Coro.cpp#L34-L73)
- `module/Local_Server/server/With_Loop_Coro.cpp:34-73`
- [`process_mode_acs_data()`](../module/Local_Server/Data_Source/Data_Source_Handler.cpp#L83-L153)
- `module/Local_Server/Data_Source/Data_Source_Handler.cpp:83-153`
#### 性能成本
CPU 成本来自切包和协议解析;并发成本来自 executor 切换、队列与 strand;积压会增加内存和实时延迟。
#### 容易出现的问题
- 不等待模式中的消息顺序被破坏;
- 停止时仍有任务访问已关闭数据源;
- 解析速度低于输入速度;
- 在网络 executor 执行阻塞解析。
#### 修改注意事项
不能改变 `Data_Source::loop_coro()` 的读取顺序语义。新增数据源必须实现相同的打开、读取、关闭生命周期;修改处理池切换时必须同时检查等待和不等待两个分支。
#### 可验证方式
用多数据源高频报文压力测试,记录每源序号、队列等待、处理耗时和退出时间;使用 ThreadSanitizer 可用平台或日志验证顺序与停止边界。
#### 可改进方向
增加每数据源有界队列、队列深度、最大等待时间和按业务允许的丢弃/合并策略。
### 3.2 ADS-B/CPR 三维位置和基站范围过滤
**难度等级:**
**复杂度来源:** 算法、坐标系统、数值精度
**所属模块:** `third_party/SSR/SSR``Data_Source_Handler`
**当前状态:** 已实现,实测精度待验证
#### 解决的问题
从奇偶 CPR 报文或参考位置恢复飞机坐标,并利用有效基站位置、目标高度和宽松系数过滤超出理论无线电视距的异常点。
#### 为什么困难
地面 CPR 有位置象限歧义,需要基站参考位置;距离计算必须使用球面距离;范围同时取决于基站和目标高度。高度单位、负值、地面/空中轨迹和报文时序都会影响结果。
#### 当前架构
宿主通过 `Data_Source_Interface` 将基站三维位置和空中/地面约束传给 SSR。CPR 模块集中实现地面位置、haversine 距离、理论视距和范围判断,其他模块复用而不重复实现距离公式。
#### 核心执行流程
1. 解析 ADS-B 位置报文。
2. 按空中或地面选择轨迹与约束。
3. 用 CPR 全局或局部算法得到候选经纬度。
4. 有基站有效位置时计算球面距离。
5. 根据两端高度计算理论视距并乘过滤系数。
6. 合格位置进入轨迹列表。
#### 核心数据结构
- `CPR::Position`
- `Position_3D`
- `ADS_B_T::Constraint`
- `Aircraft_Info::airborne_pos_track_list`
- `Aircraft_Info::surface_pos_track_list`
#### 并发和异步语义
算法本身同步执行,由数据源处理线程池调用。单飞机状态的并发安全依赖上层数据源保序。
#### 算法和数值语义
- haversine 输入经纬度,输出米;
- 无线电视距使用地球半径与两端高度;
- `distance <= range * factor`
- 地面 CPR 以基站经纬度消除 90 度象限歧义。
#### 关键源码
- [`surface_position()`](../third_party/SSR/SSR/CPR/CPR.cpp#L247-L265)
- `third_party/SSR/SSR/CPR/CPR.cpp:247-265`
- [`haversine()`](../third_party/SSR/SSR/CPR/CPR.cpp#L275-L290)
- `third_party/SSR/SSR/CPR/CPR.cpp:275-290`
- [`radio_line_of_sight_range_meters()`](../third_party/SSR/SSR/CPR/CPR.cpp#L292-L303)
- `third_party/SSR/SSR/CPR/CPR.cpp:292-303`
- [`parse_mode_s_bin()` 调用](../module/Local_Server/Data_Source/Data_Source_Handler.cpp#L155-L189)
- `module/Local_Server/Data_Source/Data_Source_Handler.cpp:155-189`
#### 性能成本
单次计算是常数级;主要成本来自高频位置报文和轨迹容器更新。错误过滤的业务成本远高于 CPU 成本。
#### 容易出现的问题
- 将经纬度欧氏距离当成米;
- 高度单位或基准不一致;
- 地面 CPR 参考位置无效;
- 过滤系数方向理解反了;
- 奇偶报文时间窗口错误。
#### 修改注意事项
距离和视距算法只能保留 CPR 中的一份实现。修改函数签名时必须同步 `export.h`、宿主调用和测试;不能把空中和地面轨迹重新合并成无类型列表。
#### 可验证方式
用已知 ADS-B 报文和参考站位置做对照;测试赤道、日期变更线、高纬度、零高度、负高度和过滤边界;对照地理库距离结果。
#### 可改进方向
明确所有高度的基准和单位,在配置与接口中携带高度类型;为范围过滤增加拒绝原因统计。
### 3.3 WebSocket 版本增量与轨迹监控状态
**难度等级:**
**复杂度来源:** 协议、状态机、并发、缓存一致性
**所属模块:** `Database.cpp``Data_Source.tsx`
**当前状态:** 已实现
#### 解决的问题
用 WebSocket 主动推送飞机变化和被监控轨迹,避免 `/aircraft_change_list` 式周期全量列表。
#### 为什么困难
服务端必须维护每个连接、每个数据源、每个 ICAO 的版本;前端重连时要提交本地版本和轨迹游标;全部监控模式与手动监控集合不能混为一套状态。
#### 当前架构
后端保存连接状态并每秒构造增量。前端 `Aircraft_Stream_Client` 统一维护连接、重连和延迟订阅;每个 `Data_Source` 保留飞机版本、监控模式和手动 ICAO。
#### 核心执行流程
1. WebSocket 建立。
2. 前端发送数据源 key、监控模式、ICAO、飞机版本和轨迹大小。
3. 后端保存订阅状态并立即响应。
4. 周期任务生成变化、删除和轨迹增量。
5. 前端原地更新或删除飞机,再请求当前地图适配器同步。
#### 核心数据结构
- `Aircraft_Stream_Client_State`
- `Aircraft_Stream_Source_State`
- `aircraft_change_versions`
- `manual_track_icao_set`
- `monitor_all_aircraft_mode`
#### 并发和异步语义
后端状态由全局 mutex 保护;当前周期推送在持锁期间遍历并调用发送。浏览器使用事件循环和定时器合并订阅请求。没有 Cookie Session,连接对象就是实时状态边界。
#### 算法和数值语义
按 ICAO 比较版本,生成 `change_list``removed_icaos`;轨迹按最后大小/序号截取增量。时间复杂度与当前可见飞机和客户端订阅规模相关。
#### 关键源码
- [`Aircraft_Stream_Client_State`](../module/Local_Server/Data_Source/Database.cpp#L92-L103)
- `module/Local_Server/Data_Source/Database.cpp:92-103`
- [`aircraft_stream_source_update_json()`](../module/Local_Server/Data_Source/Database.cpp#L156-L207)
- `module/Local_Server/Data_Source/Database.cpp:156-207`
- [`register_aircraft_stream_ws()`](../module/Local_Server/Data_Source/Database.cpp#L283-L289)
- `module/Local_Server/Data_Source/Database.cpp:283-289`
- [`Aircraft_Stream_Client`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L483-L573)
- `third_party/eacp_webapp/src/Data_Source/Data_Source.tsx:483-573`
#### 性能成本
后端周期快照、版本比较、JSON 序列化和多客户端发送;前端增量解析、对象更新和地图同步。
#### 容易出现的问题
- 重连时版本状态与服务端不一致;
- 删除消息遗漏;
- 全部监控和手动监控互相覆盖;
- 慢连接放大锁持有时间;
- 轨迹游标回退导致重复点。
#### 修改注意事项
协议字段必须前后端一起修改。不能把全部监控实现为向手动集合加入所有 ICAO;它们有不同的生命周期语义。飞机删除必须同步清除版本和轨迹显示。
#### 可验证方式
模拟重连、数据源启停、ICAO 删除/重现、模式切换和多慢客户端;抓取 WebSocket 帧确认只有增量。
#### 可改进方向
构造不可变待发送消息后锁外发送;增加 per-client backlog、消息大小和版本重同步机制。
### 3.4 Cesium 生命周期和大对象增量同步
**难度等级:**
**复杂度来源:** 生命周期、性能、GPU
**所属模块:** `Cesium_Map.tsx`
**当前状态:** 已实现,目标规模待性能验证
#### 解决的问题
在进入 3D 时按需创建 Cesium,在实时数据更新时复用对象,并在离开页面时彻底释放 Viewer、Worker、监听器和计时器。
#### 为什么困难
Cesium Viewer、Entity、Primitive、Provider、Worker 和 React 组件有不同生命周期。一次全量创建大量对象会阻塞主线程;异步结果还可能在组件卸载后返回。
#### 当前架构
`App` 动态导入三维页面。`Cesium_Map` 保存按业务 ID 索引的飞机和基站 record。同步先发现存活对象,再生成飞机任务,用数量和耗时双阈值分片处理。Viewer 使用 request-render mode。
#### 核心执行流程
1. `/map3d` 动态加载模块。
2. `load_map()` 加载配置并创建 Viewer。
3. WebSocket 更新调用 `request_sync_data_sources()`
4. 同步请求被定时器合并。
5. 基站立即同步,飞机任务分片消费。
6. 删除不再存活的 records。
7. 卸载时递增 generation、清定时器、销毁 Worker/Provider/Viewer。
#### 核心数据结构
- `aircraft_entity_map`
- `base_station_entity_map`
- `sync_aircraft_tasks`
- `sync_generation`
- `PointPrimitiveCollection`
#### 并发和异步语义
Cesium API 操作都在浏览器主线程。分片不是多线程,而是把工作拆到多个事件循环/渲染时机。地形和 LOS 的 CPU 工作才由 Worker 执行。
#### 算法和数值语义
每轮发现阶段是 O(数据源 + 飞机),记录更新是按 key 查找。轨迹按每架飞机最大点数裁剪最旧点。
#### 关键源码
- [`load_map()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L247-L298)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:247-298`
- [`request_sync_data_sources()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L707-L713)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:707-713`
- [`sync_data_sources()` 和分片消费](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L714-L767)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:714-767`
- [`on_un_mount()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L190-L245)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:190-245`
#### 性能成本
主线程对象同步、标签布局、模型 Draw Call、Polyline 和 PointPrimitive 更新;GPU 成本取决于模型、分辨率和可见标签。
#### 容易出现的问题
- Viewer 重复创建;
- 卸载后异步回调访问旧 Viewer;
- 每个普通轨迹点创建 GLB
- 更新一架飞机却重建全部 Entity;
- 分片生产速度持续高于消费。
#### 修改注意事项
不能把普通 TypeScript 业务模型改成 Cesium 类型。新增 Entity 必须加入 record 并在删除/卸载路径释放;任何异步路径都必须检查 generation 或 destroyed 状态。
#### 可验证方式
Chrome Performance、Memory、React Profiler 和 Cesium Inspector;循环进入退出 3D 100 次;记录 Entity/Primitive/Worker 数量和长任务。
#### 可改进方向
当目标规模达到数千时,将飞机适配层切换为共享模型/Primitive 或 3D Tiles,不改变统一业务模型。
### 3.5 Terrarium 自定义地形和 Worker 解码
**难度等级:**
**复杂度来源:** 算法、Worker、缓存、第三方约束
**所属模块:** `Terrarium_Terrain_Provider.tsx``Terrarium_Terrain_Worker.ts`
**当前状态:** 已实现
#### 解决的问题
把 AWS Terrarium PNG 转为 Cesium 可消费的 Heightmap 数据,并避免在主线程执行 `getImageData()`
#### 为什么困难
需要遵循瓦片 Y 轴、Web Mercator、层级上限和 Cesium 高度数组顺序;请求可能缺失或被取消;缓存必须有限。
#### 当前架构
`CustomHeightmapTerrainProvider``requestTileGeometry` 被适配到 Worker 请求。Worker fetch PNG,使用 `createImageBitmap``OffscreenCanvas` 解码,返回可转移 `Float32Array`
#### 核心执行流程
1. Cesium 请求 z/x/y。
2. Provider 应用配置层级和 Y 轴。
3. Worker fetch 后端地形 URL。
4. PNG 解码为 RGBA。
5. 按 Terrarium 公式生成北到南、从西到东的高度数组。
6. 结果进入有界缓存并返回 Cesium。
7. 404 返回缺失,让 Cesium 使用父级。
#### 核心数据结构
- `CustomHeightmapTerrainProvider`
- pending request Map
- 高度瓦片 LRU/容量缓存
- `Float32Array`
#### 并发和异步语义
一个 Provider 对应一个 Worker。fetch 和解码都不在 React render 或浏览器主线程;`destroy()` 终止 Worker 并拒绝/清理 pending。
#### 算法和数值语义
`height = R * 256 + G + B / 256 - 32768`,单位米。时间和空间复杂度均为 O(tile_size²)。
#### 关键源码
- [`Terrarium_Terrain_Provider`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx#L22-L61)
- `third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx:22-61`
- [`decode_terrarium_png()`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Worker.ts#L53-L72)
- `third_party/eacp_webapp/src/Map/Terrarium_Terrain_Worker.ts:53-72`
- [`url_template_for_y_axis()`](../third_party/eacp_webapp/src/Map/Map_Resources.tsx#L42-L45)
- `third_party/eacp_webapp/src/Map/Map_Resources.tsx:42-45`
#### 性能成本
网络、PNG 解码、Float32 内存和 Cesium 网格生成。缓存越大,回看越快但内存越高。
#### 容易出现的问题
- XYZ/TMS 上下颠倒;
- 高度数组行顺序错误;
- 404 被当成零高;
- Worker 不支持 OffscreenCanvas
- 卸载后 pending 回调泄漏。
#### 修改注意事项
不能把 Terrarium 当 quantized-mesh。修改公开元数据时必须同步后端 `/map/resources` 和两个前端消费者。
#### 可验证方式
对照已知山峰和海平面;检查瓦片接缝、南北方向、层级切换、404 父级回退、Worker 请求不超过最大层级。
#### 可改进方向
补充更准确 availability;对低层级和热点瓦片预取需基于实测,不能无界预取。
### 3.6 ECEF 地形 LOS 与 Worker 池
**难度等级:**
**复杂度来源:** 算法、并发、坐标系统、缓存
**所属模块:** `Los_Worker.ts``Worker_Pool.ts``Cesium_Map.tsx`
**当前状态:** 已实现,精度和压力待验证
#### 解决的问题
判断基站设备到被监控飞机的直线是否被 Terrarium 地形遮挡,并在多个飞机间有限并行。
#### 为什么困难
长距离视线必须在 ECEF 中插值,不能线性插值经纬高。每个采样点需要映射到瓦片和像素;飞机移动会造成旧任务晚于新任务返回。
#### 当前架构
主线程生成纯数字请求,固定 Worker Pool 调度。Worker 执行 WGS84 ECEF 转换、分段采样、Terrarium 缓存和双线性插值。主线程用 generation/request ID 丢弃旧结果。
#### 核心执行流程
1. 检查 LOS 开关、有效基站、监控状态和范围。
2. 生成基站/飞机经纬高请求。
3. 提交 Worker Pool。
4. Worker 沿 ECEF 线采样并查询高程。
5. 返回最小净空和首次遮挡位置。
6. 当前 generation 匹配时才应用。
#### 核心数据结构
- `Los_Request`
- `Los_Result`
- `Worker_Slot`
- FIFO pending queue
- Worker 本地 `tile_cache`
- per-aircraft generation
#### 并发和异步语义
Worker 数量有上限,任务超出后排队。`Promise.all` 不创建线程;并行度来自 Worker 数量。当前没有队列容量、同实体待处理任务替换或强制取消。
#### 算法和数值语义
输入为 WGS84 经纬度和高度;转换 ECEF 后做线性插值,再转回 Cartographic 查询地形。瓦片像素用双线性插值。复杂度约为 O(视线长度/采样间距)。
#### 关键源码
- [`Worker_Pool.run()/dispatch()`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L38-L97)
- `third_party/eacp_webapp/src/Map/Worker_Pool.ts:38-97`
- [`calculate_los()`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L45-L91)
- `third_party/eacp_webapp/src/Map/Los_Worker.ts:45-91`
- [`ECEF 转换和插值`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L93-L138)
- `third_party/eacp_webapp/src/Map/Los_Worker.ts:93-138`
- [`update_aircraft_occlusion()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1320)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:1320`
#### 性能成本
每条 LOS 产生 CPU 采样、瓦片请求、PNG 解码和缓存。多个 Worker 会重复保存相同瓦片高度。
#### 容易出现的问题
- 主线程一次提交全部飞机;
- 旧位置结果覆盖新位置;
- 任务队列无限增长;
- 气压高度和地形高度基准不同;
- 采样过稀漏掉山脊,过密浪费计算。
#### 修改注意事项
不能向 Worker 传 Cesium Viewer、Cartesian3 类实例或 TerrainProvider。协议必须是可结构化克隆的普通数据;结果应用前必须保留 generation 校验。
#### 可验证方式
构造无遮挡、单峰遮挡、瓦片边缘、远距离和缺失瓦片场景;显示 Worker 队列、缓存命中率、任务年龄和旧结果丢弃率。
#### 可改进方向
为同一飞机只保留最新待处理任务;按选中/手动监控/全部监控设置优先级和速率;增加粗到细两阶段采样。
### 3.7 统一 2D/3D 业务状态和双渲染适配
**难度等级:** 中高
**复杂度来源:** 架构、状态一致性、生命周期
**所属模块:** `Data_Source.tsx``Aircraft_Model.tsx`、Leaflet、Cesium
**当前状态:** 已实现
#### 解决的问题
二维和三维显示同一飞机、轨迹、监控和子数据源数据,同时允许各自独立显示样式。
#### 为什么困难
业务状态必须共享,但渲染对象、尺寸语义、点击和生命周期不同。错误抽象会产生两套解析和订阅,或把 Cesium 类型泄漏到通用模型。
#### 当前架构
`Data_Source``Aircraft_Model` 是统一状态;Leaflet `Aircraft` 和 Cesium record 是适配层。`map2d``map3d` 配置隔离,监控状态共享。
#### 核心执行流程
WebSocket 更新统一对象;当前活动地图收到同步请求;切换页面不重新建立另一套业务订阅;各适配器按自身配置更新显示。
#### 核心数据结构
- `Data_Source.aircraftMap`
- `Aircraft_Model.trackPoints`
- `Data_Source_Map_Display_Data` 的 2D/3D 实例
- 共享监控集合
#### 并发和异步语义
业务更新在浏览器事件循环。Cesium 动态模块只在 3D 加载;统一模型没有运行时 Cesium import。
#### 算法和数值语义
不涉及复杂算法;难点是状态所有权和增量语义。
#### 关键源码
- [`Data_Source`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L68-L145)
- `third_party/eacp_webapp/src/Data_Source/Data_Source.tsx:68-145`
- [`Aircraft_Model`](../third_party/eacp_webapp/src/Map/Aircraft_Model.tsx#L22-L75)
- `third_party/eacp_webapp/src/Map/Aircraft_Model.tsx:22-75`
- [`React.lazy(Cesium_Map)`](../third_party/eacp_webapp/src/App.tsx#L23-L26)
- `third_party/eacp_webapp/src/App.tsx:23-26`
#### 性能成本
统一状态降低重复解析,但当前适配器仍需扫描可见对象。React 刷新和 Cesium 同步频率需要保持解耦。
#### 容易出现的问题
- 2D/3D 样式互相覆盖;
- 切换地图重复订阅;
- Cesium 类型进入通用模型导致 2D 下载 Cesium chunk
- 删除飞机只清理一个渲染器。
#### 修改注意事项
新增业务字段先加统一模型,再由适配器读取。只有纯显示字段放 2D/3D 配置。监控模式和轨迹数据不能复制到独立 Cesium 状态。
#### 可验证方式
在同一 WebSocket 数据下往返切换 2D/3D,检查对象数量、监控集合、轨迹长度和 Network chunk。
#### 可改进方向
将 WebSocket delta 协议和统一 Store 再独立为无 UI 模块,增加纯 TypeScript 单元测试。
### 3.8 基站、设备和飞机的三维坐标与姿态
**难度等级:** 中高
**复杂度来源:** 坐标系统、模型资产、显示语义
**所属模块:** `Cesium_Map.tsx``Flight_VTO`、模型配置
**当前状态:** 已实现,替换模型需重新标定
#### 解决的问题
让飞机沿轨迹三维方向显示;让基站从地形地面延伸到配置设备高度,并把设备放在顶部;支持模型自身初始角度和世界/屏幕尺寸。
#### 为什么困难
GLB 的机头轴、上轴、原点和单位不统一;Cesium 使用地固坐标和局部 ENU。固定屏幕尺寸与世界尺寸不能混用。地形异步返回会改变基站底部。
#### 当前架构
后端 VTO 提供轨迹方向,前端结合每类模型初始 heading/pitch/roll 生成四元数。基站模型位置在地形高程,统一 scale 使纵向达到设备高度;横向节点变换应用显示大小。设备位于顶部并承担 picking。
#### 核心执行流程
1. 从最后两个三维轨迹点得到方向。
2. 选择涡轮/机型模型配置。
3. 在飞机位置构造 ENU/HPR 姿态。
4. 异步采样基站地面。
5. 计算 `antenna_height / model_original_height`
6. 基站放地面、设备放顶部,更新标签和范围。
#### 核心数据结构
- `Flight_Orientation`
- `Map_Model_Item_Config`
- `Base_Station_Entity_Record`
- `Aircraft_Entity_Record`
#### 并发和异步语义
地形高度异步采样,结果以位置和地形签名缓存;完成后请求下一次 Cesium 同步。模型加载由 Cesium 管理。
#### 算法和数值语义
位置使用 WGS84 经纬高;姿态使用 heading/pitch/roll 和四元数;基站纵向世界比例由米制高度差与 GLB 原始高度比计算。
#### 关键源码
- [`Flight_Orientation/VTO`](../module/Local_Server/Aircraft/Flight_VTO.h#L3-L22)
- `module/Local_Server/Aircraft/Flight_VTO.h:3-22`
- [`sync_base_station()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L768-L801)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:768-801`
- [`create_base_station_record()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L802-L893)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:802-893`
- [`base_station_heights()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1084-L1092)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:1084-1092`
- [`base_station_node_transformations()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1163-L1169)
- `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:1163-1169`
#### 性能成本
模型 Draw Call、标签、地形采样和每次位置变化的矩阵/四元数更新。
#### 容易出现的问题
- 飞机头朝下或翅膀竖直;
- 模型原点不在底部/挂点;
- 基站和设备高度互换;
- 显示缩放改变物理顶部;
- 辅助范围几何抢占 picking。
#### 修改注意事项
模型颜色保持 GLB 原色,配置颜色只用于标注。替换模型必须设置原始尺寸和初始角度。基站底座不可绑定完整业务交互;设备是选择入口。
#### 可验证方式
从四个方位和俯视检查同一基站;使用已知东西南北和爬升/下降轨迹检查机头;切换固定屏幕尺寸观察近远行为。
#### 可改进方向
上传模型时解析 bounds、根节点、单位和挂点,保存显式 `mount_offset` 与水平尺寸节点,而不是依赖固定节点名。
### 3.9 多目录瓦片异步读取和 HTTP 缓存
**难度等级:** 中高
**复杂度来源:** 异步、文件系统、HTTP 语义
**所属模块:** `tiles.cpp``Ucoro_Drogon_Glue.h`
**当前状态:** 已实现
#### 解决的问题
按配置目录顺序提供本地影像/地形瓦片,且文件系统查找和读取不阻塞 Drogon 网络线程。
#### 为什么困难
查找、stat、打开、长度和读取都可能阻塞;GET/HEAD、304、404、读取失败和客户端生命周期必须只完成一次回调。缓存验证还要随文件变化。
#### 当前架构
`Tile_Path_Resolver` 按目录顺序查找。`Tile_Source::load()` 在专用 Asio executor 完成所有文件操作,返回拥有字节数据和缓存元数据的结果;Drogon 协程桥回事件循环构造响应。
#### 核心执行流程
1. 解析路由 z/x/y。
2. 专用执行器按目录查找。
3. 获取文件大小、修改时间和 ETag。
4. 命中条件请求则返回 304 元数据。
5. HEAD 不读正文,GET 异步读取文件。
6. 回 Drogon 事件循环构造唯一响应。
#### 核心数据结构
- `Tile_Config`
- `Tile_Path_Resolver`
- `Tile_Load_Result`
- `Tile_Cache_Request`
#### 并发和异步语义
文件操作在独立 Asio 池,不在 Drogon 网络线程。当前该桥接池大小为 1,受控但可能成为高并发串行瓶颈。
#### 算法和数值语义
目录查找 O(根目录数),不扫描目录树。单文件上限 32 MB,长度转换前有边界检查。
#### 关键源码
- [`Tile_Path_Resolver`](../module/Local_Server/server/tiles.cpp#L162-L200)
- `module/Local_Server/server/tiles.cpp:162-200`
- [`Tile_Source::load()`](../module/Local_Server/server/tiles.cpp#L202-L295)
- `module/Local_Server/server/tiles.cpp:202-295`
- [`register_tile_handler()`](../module/Local_Server/server/tiles.cpp#L297-L324)
- `module/Local_Server/server/tiles.cpp:297-324`
- [`to_drogon()`](../module/Local_Server/server/Ucoro_Drogon_Glue.h#L102-L110)
- `module/Local_Server/server/Ucoro_Drogon_Glue.h:102-110`
#### 性能成本
每请求若干次文件存在性/stat 检查、一次打开读取和响应内存。依赖操作系统文件缓存和浏览器 HTTP 缓存。
#### 容易出现的问题
- 只把最后的 read 投递线程池,前面的 exists/stat 仍阻塞网络线程;
- 条件请求和 HEAD 仍读文件;
- 多目录顺序错误;
- 回调重复;
- ETag 不随文件变化。
#### 修改注意事项
影像和地形必须复用同一流程。新增格式差异只能放配置/MIME 层;不能为路由复制一套文件读取。
#### 可验证方式
测试两个目录层级、文件哈希、404、HEAD、304 和文件修改后 ETag;并发请求并采样 Drogon 线程。
#### 可改进方向
根据磁盘和并发测试调整专用池大小;保持有限并发,不增加应用层全量预加载。
### 3.10 Cesium 相机模式和视图持久化
**难度等级:**
**复杂度来源:** 状态机、输入、生命周期
**所属模块:** `Cesium_Camera_Control.ts``Map_View.tsx`
**当前状态:** 已实现
#### 解决的问题
提供地表导航、地心轨道和自由观察三种显式操作方式,并保存/恢复 3D 相机位置。
#### 为什么困难
自定义事件处理必须禁用 Cesium 默认输入并在模式退出时完整恢复;跟随飞机、飞行动画、参考坐标系和历史视角会互相影响。
#### 当前架构
一个控制器保存 Cesium 默认输入状态。切换模式先销毁旧 handler;地表模式恢复默认控制,另外两种模式创建独立 `ScreenSpaceEventHandler`
#### 核心执行流程
选择模式 → 取消跟随/飞行 → 配置输入 → 鼠标事件更新相机 → 离开模式销毁 handler → 恢复默认控制。
#### 核心数据结构
- `Camera_Control_Mode`
- 默认 controller state
- free-look position/heading/pitch/roll
- `Map_Camera_View`
#### 并发和异步语义
全部在主线程事件循环。相机保存使用 HTTP 异步接口,但相机操作本身同步。
#### 算法和数值语义
地心轨道以 ECEF 原点为旋转中心;自由观察固定相机位置,只改变 heading/pitch;俯仰角限制在极点以内。
#### 关键源码
- [`Cesium_Camera_Control.set_mode()`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L32-L55)
- `third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts:32-55`
- [`enable_earth_orbit()`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L64-L87)
- `third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts:64-87`
- [`enable_free_look()`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L88-L121)
- `third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts:88-121`
- [`load/save_map_view_config()`](../third_party/eacp_webapp/src/Map/Map_View.tsx#L83-L90)
- `third_party/eacp_webapp/src/Map/Map_View.tsx:83-90`
#### 性能成本
鼠标移动时相机矩阵更新和 requestRender,成本较低;主要风险是 handler 重复。
#### 容易出现的问题
- 多个 handler 同时处理输入;
- 切回地表模式仍禁用缩放;
- trackedEntity 下一帧覆盖手动视角;
- 保存飞行中间状态。
#### 修改注意事项
每个新增模式必须有对称销毁和默认状态恢复。改变相机前要明确是否取消跟随、是否保存上一视角。
#### 可验证方式
反复切换模式、跟随和页面;检查每种鼠标语义、相机位置不变条件、刷新后恢复。
#### 可改进方向
给相机状态增加明确版本和保存节流,避免拖动过程中高频持久化。
### 3.11 后端/前端/Cesium 静态资源组合构建
**难度等级:**
**复杂度来源:** 构建、部署、第三方约束
**所属模块:** CMake、Vite、Drogon 静态服务
**当前状态:** 已实现
#### 解决的问题
让按需拆分的 Cesium JS、Workers、Assets、ThirdParty、Widgets 和本地 GLB 在开发代理和生产后端托管下使用相同 URL。
#### 为什么困难
Cesium runtime 资源不是普通 JS import;开发服务器和生产输出目录不同;SPA 直接刷新还需要服务端 fallback。
#### 当前架构
Vite 定义固定 `CESIUM_BASE_URL`,插件开发期挂载静态中间件、构建期复制 Cesium 和模型目录;React 对三维模块动态 importDrogon 托管生产资源。
#### 核心执行流程
npm build → Vite 拆 chunk → copy Cesium/model → 生成 `wwwroot` → 后端静态路由提供 `/ui/*`
#### 核心数据结构
不涉及业务结构;关键是输出目录和 URL 合同。
#### 并发和异步语义
浏览器按需下载 Cesium chunk 和 Workers2D 路由不执行三维动态模块。
#### 算法和数值语义
不涉及。
#### 关键源码
- [`React.lazy()`](../third_party/eacp_webapp/src/App.tsx#L23-L26)
- `third_party/eacp_webapp/src/App.tsx:23-26`
- [`cesiumAssetsPlugin()`](../third_party/eacp_webapp/vite.config.js#L21-L53)
- `third_party/eacp_webapp/vite.config.js:21-53`
- [`CESIUM_BASE_URL`](../third_party/eacp_webapp/vite.config.js#L99-L101)
- `third_party/eacp_webapp/vite.config.js:99-101`
- [`ecap_server` 构建目标](../main.cmake#L40-L59)
- `main.cmake:40-59`
#### 性能成本
首次进入 3D 下载较大 Cesium chunk;2D 初始加载不承担该成本。
#### 容易出现的问题
- Workers 404
- 开发可用、生产 base URL 错误;
- 2D 公共模块运行时 import Cesium
- 直接刷新 `/ui/map3d` 返回 404。
#### 修改注意事项
Cesium CSS 和渲染适配器不能移到公共入口;修改 `/ui` base 必须同时修改 Vite、后端静态路由和部署路径。
#### 可验证方式
检查生产输出、刷新 2D/3D、Network 中 Cesium chunk 和 Workers;离线运行确认无 ion/CDN 请求。
#### 可改进方向
在 CI 增加生产静态资源链接检查和 Playwright 网络断言。
## 4. 技术难点分级
### P0:系统核心且修改风险极高
- **多执行器数据源协程和有序解析**:决定数据是否能持续、按序、可停止地进入系统。
- **ADS-B/CPR 三维位置和基站范围过滤**:错误会直接删除正确位置或保留异常位置。
- **WebSocket 版本增量与轨迹监控状态**:连接级状态错误会造成前端长期不一致。
- **Cesium 生命周期和大对象增量同步**:错误通常表现为页面卡死、GPU/内存泄漏或交互失效。
- **Terrarium + LOS Worker 管线**:跨坐标、跨线程、跨缓存,结果过期与高度基准都影响正确性。
### P1:重要且需要专门设计
- **统一 2D/3D 业务状态**:决定两套地图是否长期保持同一业务语义。
- **基站、设备和飞机三维坐标与姿态**:需要模型资产、位置和配置同时正确。
- **多目录瓦片异步读取和 HTTP 缓存**:直接影响网络线程稳定性和地图加载。
### P2:局部复杂但边界清晰
- **Cesium 相机模式和视图持久化**:局限在相机控制器和视图配置。
- **组合构建和静态资源部署**:边界是构建输出与 URL 合同,可通过集成测试覆盖。
## 5. 面试表达摘要
### 多执行器数据源协程
系统需要同时接入网络、串口、文件和 DLL 数据源,协议解析不能阻塞网络线程,同一数据源又必须保持消息顺序。项目用 standalone Asio 协程统一输入生命周期,在 `io_context` 上读取,再切到受控处理线程池;不等待模式为每个数据源使用 strand 保序。难点不在 `co_await` 语法,而在 executor 所有权、退出等待和任务积压。当前实现已分离网络与解析,后续重点是增加有界队列和可观测背压。
### ADS-B/CPR 和基站过滤
位置解码同时涉及奇偶 CPR、地面参考站、球面距离、三维高度和理论无线电视距。项目把 haversine、视距和范围判断集中在 CPR 模块,宿主只传有效基站三维位置和约束,避免多份距离算法漂移。取舍是先采用地球几何视距与宽松系数,不把完整传播模型混入位置解码。正确性通过已知报文、边界经纬度和过滤阈值对照验证。
### WebSocket 版本增量
原先轮询全量飞机会随目标数快速增加带宽和前端处理。项目改为连接级 WebSocket 状态:前端提交每架飞机版本和轨迹游标,后端返回变化、删除和被监控轨迹。全部监控是独立模式,不等同于手动集合。主要取舍是后端维护连接状态换取小消息和低重复计算;当前仍需把锁内发送优化为锁外发送,并加入慢客户端背压指标。
### Cesium 增量同步与生命周期
三维页面的难点是不能把实时消息直接等同于 React 重渲染和 Cesium 全量重建。项目动态加载 Cesium,用业务 ID Map 复用 Entity/Primitive,普通轨迹点采用 PointPrimitive,并把飞机同步拆成数量和时间双阈值的片段。卸载路径集中销毁 Viewer、Worker、Provider、监听器和定时器。该方案保持业务模型独立,未来可将渲染器替换为 Primitive/实例化而不改变数据协议。
### Terrarium 与 LOS Worker
地形使用 Terrarium PNG,但 Cesium 需要高度数组;LOS 又要求沿地球曲率下的真实三维直线采样。项目把 PNG fetch、OffscreenCanvas 解码和 ECEF LOS 放入 Worker,主线程只传普通数字协议。固定 Worker Pool 限制并行,实体 generation 丢弃过期结果。取舍是每个 Worker 保留独立 LRU,代码简单但可能重复内存;后续应做同实体任务合并和队列背压。
### 统一 2D/3D 状态
Leaflet 与 Cesium 的显示对象完全不同,但飞机、轨迹和监控是同一业务。项目用普通 TypeScript `Aircraft_Model``Data_Source` 作为唯一状态,二维和三维只在适配层分叉;显示配置分别存储,监控状态共享。这样 2D 页面不会因模型类型导入 Cesium chunk,也避免两套 WebSocket 解析。修改业务字段时必须先进入统一模型,再分别适配显示。
### 三维模型坐标和姿态
飞机 GLB 可能有不同机头轴和初始角,基站还需要地形底部、配置顶部和设备挂点一致。项目用每模型初始 HPR 配置修正姿态,用最后两个三维轨迹点提供方向;基站纵向按真实高度缩放,横向显示大小独立,设备位于顶部并承担交互。关键取舍是保持 GLB 原色,配置颜色只用于标注。替换模型时必须重新标定 bounds、原点和角度。
### 异步瓦片服务
本地瓦片可能分布在多个目录,浏览器还要求 GET、HEAD、304 和缓存语义。项目将目录查找、stat、打开、长度和读取整体放到专用 Asio executor,再回 Drogon 事件循环构造唯一响应;目录按配置顺序短路,不扫描全盘。方案依赖浏览器和操作系统缓存,不引入复杂应用 LRU。后续优化应基于并发吞吐测试调整有限文件 I/O 并行度。
## 6. 源码导航索引
| 技术点 | 核心入口 | 辅助实现 | 调用方 | 测试或验证代码 |
| -- | -- | -- | -- | -- |
| 数据源协程 | [`Data_Source::loop_coro()`](../module/Local_Server/server/io_coro.cpp#L206) | [`With_Loop_Coro`](../module/Local_Server/server/With_Loop_Coro.cpp#L34) | [`Coro::coro_thread()`](../module/Local_Server/server/io_coro.cpp#L133) | 运行压力测试,现无专用单测 |
| CPR/范围 | [`CPR.cpp`](../third_party/SSR/SSR/CPR/CPR.cpp#L247) | [`BaseStation.cpp`](../module/Local_Server/Data_Source/BaseStation.cpp#L5) | [`Data_Source_Handler.cpp`](../module/Local_Server/Data_Source/Data_Source_Handler.cpp#L155) | [`CPR_TEST.cpp`](../third_party/SSR/SSR/CPR/CPR_TEST.cpp#L1) |
| WebSocket 增量 | [`register_aircraft_stream_ws()`](../module/Local_Server/Data_Source/Database.cpp#L283) | [`aircraft_stream_source_update_json()`](../module/Local_Server/Data_Source/Database.cpp#L156) | [`Aircraft_Stream_Client`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L483) | 浏览器 WebSocket 集成测试待补 |
| Cesium 同步 | [`sync_data_sources()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L714) | [`sync_aircraft()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1188) | [`request_sync_data_sources()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L707) | Chrome Performance/Memory |
| Terrarium | [`Terrarium_Terrain_Provider`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx#L22) | [`Terrarium_Terrain_Worker`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Worker.ts#L18) | [`Cesium_Map.load_map()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L247) | 已知高程点对照待自动化 |
| LOS | [`calculate_los()`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L45) | [`Worker_Pool`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L22) | [`update_aircraft_occlusion()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1320) | 遮挡场景集成测试待补 |
| 统一模型 | [`Aircraft_Model`](../third_party/eacp_webapp/src/Map/Aircraft_Model.tsx#L22) | [`Data_Source`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L68) | Leaflet/Cesium | `npm run typecheck/lint:src` |
| 模型姿态 | [`sync_base_station()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L768) | [`Flight_VTO`](../module/Local_Server/Aircraft/Flight_VTO.cpp#L57) | Cesium records | 多方位视觉检查 |
| 瓦片 | [`Tile_Source`](../module/Local_Server/server/tiles.cpp#L202) | [`to_drogon()`](../module/Local_Server/server/Ucoro_Drogon_Glue.h#L102) | [`init_tiles()`](../module/Local_Server/server/tiles.cpp#L589) | HTTP GET/HEAD/304/并发 |
| 相机 | [`Cesium_Camera_Control`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L12) | [`Map_View`](../third_party/eacp_webapp/src/Map/Map_View.tsx#L47) | 子数据源控制面板 | 页面往返和输入检查 |
| 组合构建 | [`vite.config.js`](../third_party/eacp_webapp/vite.config.js#L21) | [`main.cmake`](../main.cmake#L40) | Drogon 静态服务 | `npm run build`、生产刷新 |