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

44 KiB
Raw Permalink Blame History

项目技术难点说明

1. 技术难点总览

编号 技术点 难度 核心模块 主要风险 关键源码
1 多执行器数据源协程和有序解析 Local_Server、Asio 乱序、积压、退出等待 Data_Source::loop_coro()
2 ADS-B/CPR 三维位置和基站范围过滤 SSR、Data_Source 坐标/高度错误造成误过滤 CPR.cpp
3 WebSocket 版本增量与轨迹监控状态 Database、Data_Source.tsx 漏更新、重复轨迹、慢连接 Database.cpp
4 Cesium 生命周期和大对象增量同步 Cesium_Map 主线程长任务、资源泄漏 Cesium_Map
5 Terrarium 自定义地形和 Worker 解码 Terrain Provider/Worker 层级翻转、主线程阻塞、缓存失控 Terrarium_Terrain_Provider
6 ECEF 地形 LOS 与 Worker 池 Los_Worker、Worker_Pool 任务积压、旧结果、基准误差 calculate_los()
7 统一 2D/3D 业务状态和双渲染适配 中高 Data_Source、Aircraft_Model 重复状态、显示语义漂移 Aircraft_Model
8 基站、设备和飞机的三维坐标/姿态 中高 Cesium_Map、Flight_VTO 轴向、原点、世界/屏幕尺寸错误 sync_base_station()
9 多目录瓦片异步读取和 HTTP 缓存 中高 tiles.cpp 阻塞网络线程、重复回调、缓存失效 Tile_Source
10 Cesium 相机模式和视图持久化 Camera Control、Map View 输入冲突、跟随状态恢复错误 Cesium_Camera_Control
11 后端/前端/Cesium 静态资源组合构建 CMake、Vite 开发可用而生产资源 404 vite.config.js

2. 技术难点依赖关系

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/servermodule/Local_Server/Data_Source
当前状态: 已实现

解决的问题

TCP、串口、文件、DLL 和共享内存等输入不能因报文解析阻塞网络循环,同时同一数据源中的报文顺序不能被线程池并发破坏。

为什么困难

读取、解析和输出跨越不同 executor。等待处理模式要求读协程等待解析完成;不等待模式需要独立 strand 保序。停止过程中还必须停止新读取、等待处理线程、关闭底层资源并终止 io_context

当前架构

Coro 持有一个 Asio io_context 和一个 process_data 线程池。每个 Data_Source 在网络 executor 上读取,按配置切到处理池或 strand,完成后回到网络 executor。

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 保证同一数据源投递任务不并行。不同数据源可以在处理池并行。当前没有统一的任务队列上限或背压指标。

算法和数值语义

不涉及数值算法。复杂度主要随报文数线性增长;空间成本取决于待处理消息积压。

关键源码

性能成本

CPU 成本来自切包和协议解析;并发成本来自 executor 切换、队列与 strand;积压会增加内存和实时延迟。

容易出现的问题

  • 不等待模式中的消息顺序被破坏;
  • 停止时仍有任务访问已关闭数据源;
  • 解析速度低于输入速度;
  • 在网络 executor 执行阻塞解析。

修改注意事项

不能改变 Data_Source::loop_coro() 的读取顺序语义。新增数据源必须实现相同的打开、读取、关闭生命周期;修改处理池切换时必须同时检查等待和不等待两个分支。

可验证方式

用多数据源高频报文压力测试,记录每源序号、队列等待、处理耗时和退出时间;使用 ThreadSanitizer 可用平台或日志验证顺序与停止边界。

可改进方向

增加每数据源有界队列、队列深度、最大等待时间和按业务允许的丢弃/合并策略。

3.2 ADS-B/CPR 三维位置和基站范围过滤

难度等级:
复杂度来源: 算法、坐标系统、数值精度
所属模块: third_party/SSR/SSRData_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 度象限歧义。

关键源码

性能成本

单次计算是常数级;主要成本来自高频位置报文和轨迹容器更新。错误过滤的业务成本远高于 CPU 成本。

容易出现的问题

  • 将经纬度欧氏距离当成米;
  • 高度单位或基准不一致;
  • 地面 CPR 参考位置无效;
  • 过滤系数方向理解反了;
  • 奇偶报文时间窗口错误。

修改注意事项

距离和视距算法只能保留 CPR 中的一份实现。修改函数签名时必须同步 export.h、宿主调用和测试;不能把空中和地面轨迹重新合并成无类型列表。

可验证方式

用已知 ADS-B 报文和参考站位置做对照;测试赤道、日期变更线、高纬度、零高度、负高度和过滤边界;对照地理库距离结果。

可改进方向

明确所有高度的基准和单位,在配置与接口中携带高度类型;为范围过滤增加拒绝原因统计。

3.3 WebSocket 版本增量与轨迹监控状态

难度等级:
复杂度来源: 协议、状态机、并发、缓存一致性
所属模块: Database.cppData_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_listremoved_icaos;轨迹按最后大小/序号截取增量。时间复杂度与当前可见飞机和客户端订阅规模相关。

关键源码

性能成本

后端周期快照、版本比较、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 查找。轨迹按每架飞机最大点数裁剪最旧点。

关键源码

性能成本

主线程对象同步、标签布局、模型 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.tsxTerrarium_Terrain_Worker.ts
当前状态: 已实现

解决的问题

把 AWS Terrarium PNG 转为 Cesium 可消费的 Heightmap 数据,并避免在主线程执行 getImageData()

为什么困难

需要遵循瓦片 Y 轴、Web Mercator、层级上限和 Cesium 高度数组顺序;请求可能缺失或被取消;缓存必须有限。

当前架构

CustomHeightmapTerrainProviderrequestTileGeometry 被适配到 Worker 请求。Worker fetch PNG,使用 createImageBitmapOffscreenCanvas 解码,返回可转移 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²)。

关键源码

性能成本

网络、PNG 解码、Float32 内存和 Cesium 网格生成。缓存越大,回看越快但内存越高。

容易出现的问题

  • XYZ/TMS 上下颠倒;
  • 高度数组行顺序错误;
  • 404 被当成零高;
  • Worker 不支持 OffscreenCanvas
  • 卸载后 pending 回调泄漏。

修改注意事项

不能把 Terrarium 当 quantized-mesh。修改公开元数据时必须同步后端 /map/resources 和两个前端消费者。

可验证方式

对照已知山峰和海平面;检查瓦片接缝、南北方向、层级切换、404 父级回退、Worker 请求不超过最大层级。

可改进方向

补充更准确 availability;对低层级和热点瓦片预取需基于实测,不能无界预取。

3.6 ECEF 地形 LOS 与 Worker 池

难度等级:
复杂度来源: 算法、并发、坐标系统、缓存
所属模块: Los_Worker.tsWorker_Pool.tsCesium_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(视线长度/采样间距)。

关键源码

性能成本

每条 LOS 产生 CPU 采样、瓦片请求、PNG 解码和缓存。多个 Worker 会重复保存相同瓦片高度。

容易出现的问题

  • 主线程一次提交全部飞机;
  • 旧位置结果覆盖新位置;
  • 任务队列无限增长;
  • 气压高度和地形高度基准不同;
  • 采样过稀漏掉山脊,过密浪费计算。

修改注意事项

不能向 Worker 传 Cesium Viewer、Cartesian3 类实例或 TerrainProvider。协议必须是可结构化克隆的普通数据;结果应用前必须保留 generation 校验。

可验证方式

构造无遮挡、单峰遮挡、瓦片边缘、远距离和缺失瓦片场景;显示 Worker 队列、缓存命中率、任务年龄和旧结果丢弃率。

可改进方向

为同一飞机只保留最新待处理任务;按选中/手动监控/全部监控设置优先级和速率;增加粗到细两阶段采样。

3.7 统一 2D/3D 业务状态和双渲染适配

难度等级: 中高
复杂度来源: 架构、状态一致性、生命周期
所属模块: Data_Source.tsxAircraft_Model.tsx、Leaflet、Cesium
当前状态: 已实现

解决的问题

二维和三维显示同一飞机、轨迹、监控和子数据源数据,同时允许各自独立显示样式。

为什么困难

业务状态必须共享,但渲染对象、尺寸语义、点击和生命周期不同。错误抽象会产生两套解析和订阅,或把 Cesium 类型泄漏到通用模型。

当前架构

Data_SourceAircraft_Model 是统一状态;Leaflet Aircraft 和 Cesium record 是适配层。map2dmap3d 配置隔离,监控状态共享。

核心执行流程

WebSocket 更新统一对象;当前活动地图收到同步请求;切换页面不重新建立另一套业务订阅;各适配器按自身配置更新显示。

核心数据结构

  • Data_Source.aircraftMap
  • Aircraft_Model.trackPoints
  • Data_Source_Map_Display_Data 的 2D/3D 实例
  • 共享监控集合

并发和异步语义

业务更新在浏览器事件循环。Cesium 动态模块只在 3D 加载;统一模型没有运行时 Cesium import。

算法和数值语义

不涉及复杂算法;难点是状态所有权和增量语义。

关键源码

性能成本

统一状态降低重复解析,但当前适配器仍需扫描可见对象。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.tsxFlight_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 原始高度比计算。

关键源码

性能成本

模型 Draw Call、标签、地形采样和每次位置变化的矩阵/四元数更新。

容易出现的问题

  • 飞机头朝下或翅膀竖直;
  • 模型原点不在底部/挂点;
  • 基站和设备高度互换;
  • 显示缩放改变物理顶部;
  • 辅助范围几何抢占 picking。

修改注意事项

模型颜色保持 GLB 原色,配置颜色只用于标注。替换模型必须设置原始尺寸和初始角度。基站底座不可绑定完整业务交互;设备是选择入口。

可验证方式

从四个方位和俯视检查同一基站;使用已知东西南北和爬升/下降轨迹检查机头;切换固定屏幕尺寸观察近远行为。

可改进方向

上传模型时解析 bounds、根节点、单位和挂点,保存显式 mount_offset 与水平尺寸节点,而不是依赖固定节点名。

3.9 多目录瓦片异步读取和 HTTP 缓存

难度等级: 中高
复杂度来源: 异步、文件系统、HTTP 语义
所属模块: tiles.cppUcoro_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,长度转换前有边界检查。

关键源码

性能成本

每请求若干次文件存在性/stat 检查、一次打开读取和响应内存。依赖操作系统文件缓存和浏览器 HTTP 缓存。

容易出现的问题

  • 只把最后的 read 投递线程池,前面的 exists/stat 仍阻塞网络线程;
  • 条件请求和 HEAD 仍读文件;
  • 多目录顺序错误;
  • 回调重复;
  • ETag 不随文件变化。

修改注意事项

影像和地形必须复用同一流程。新增格式差异只能放配置/MIME 层;不能为路由复制一套文件读取。

可验证方式

测试两个目录层级、文件哈希、404、HEAD、304 和文件修改后 ETag;并发请求并采样 Drogon 线程。

可改进方向

根据磁盘和并发测试调整专用池大小;保持有限并发,不增加应用层全量预加载。

3.10 Cesium 相机模式和视图持久化

难度等级:
复杂度来源: 状态机、输入、生命周期
所属模块: Cesium_Camera_Control.tsMap_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;俯仰角限制在极点以内。

关键源码

性能成本

鼠标移动时相机矩阵更新和 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 路由不执行三维动态模块。

算法和数值语义

不涉及。

关键源码

性能成本

首次进入 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_ModelData_Source 作为唯一状态,二维和三维只在适配层分叉;显示配置分别存储,监控状态共享。这样 2D 页面不会因模型类型导入 Cesium chunk,也避免两套 WebSocket 解析。修改业务字段时必须先进入统一模型,再分别适配显示。

三维模型坐标和姿态

飞机 GLB 可能有不同机头轴和初始角,基站还需要地形底部、配置顶部和设备挂点一致。项目用每模型初始 HPR 配置修正姿态,用最后两个三维轨迹点提供方向;基站纵向按真实高度缩放,横向显示大小独立,设备位于顶部并承担交互。关键取舍是保持 GLB 原色,配置颜色只用于标注。替换模型时必须重新标定 bounds、原点和角度。

异步瓦片服务

本地瓦片可能分布在多个目录,浏览器还要求 GET、HEAD、304 和缓存语义。项目将目录查找、stat、打开、长度和读取整体放到专用 Asio executor,再回 Drogon 事件循环构造唯一响应;目录按配置顺序短路,不扫描全盘。方案依赖浏览器和操作系统缓存,不引入复杂应用 LRU。后续优化应基于并发吞吐测试调整有限文件 I/O 并行度。

6. 源码导航索引

技术点 核心入口 辅助实现 调用方 测试或验证代码
数据源协程 Data_Source::loop_coro() With_Loop_Coro Coro::coro_thread() 运行压力测试,现无专用单测
CPR/范围 CPR.cpp BaseStation.cpp Data_Source_Handler.cpp CPR_TEST.cpp
WebSocket 增量 register_aircraft_stream_ws() aircraft_stream_source_update_json() Aircraft_Stream_Client 浏览器 WebSocket 集成测试待补
Cesium 同步 sync_data_sources() sync_aircraft() request_sync_data_sources() Chrome Performance/Memory
Terrarium Terrarium_Terrain_Provider Terrarium_Terrain_Worker Cesium_Map.load_map() 已知高程点对照待自动化
LOS calculate_los() Worker_Pool update_aircraft_occlusion() 遮挡场景集成测试待补
统一模型 Aircraft_Model Data_Source Leaflet/Cesium npm run typecheck/lint:src
模型姿态 sync_base_station() Flight_VTO Cesium records 多方位视觉检查
瓦片 Tile_Source to_drogon() init_tiles() HTTP GET/HEAD/304/并发
相机 Cesium_Camera_Control Map_View 子数据源控制面板 页面往返和输入检查
组合构建 vite.config.js main.cmake Drogon 静态服务 npm run build、生产刷新