# 项目技术难点说明 ## 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`、生产刷新 |