# 项目技术难点说明

## 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 对三维模块动态 import；Drogon 托管生产资源。

#### 核心执行流程

npm build → Vite 拆 chunk → copy Cesium/model → 生成 `wwwroot` → 后端静态路由提供 `/ui/*`。

#### 核心数据结构

不涉及业务结构；关键是输出目录和 URL 合同。

#### 并发和异步语义

浏览器按需下载 Cesium chunk 和 Workers；2D 路由不执行三维动态模块。

#### 算法和数值语义

不涉及。

#### 关键源码

- [`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`、生产刷新 |
