From 8835607ccd1aff7fbe858705c0e3e8d45f436dde Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Mon, 27 Jul 2026 18:28:21 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arch_doc/ARCHITECTURE.md | 671 ++++++++++++++++++ arch_doc/TECHNICAL_HARD_POINTS.md | 959 ++++++++++++++++++++++++++ config/config.json | 14 +- module/Local_Server/server/Config.cpp | 8 + module/Local_Server/server/Config.h | 6 +- module/Local_Server/server/tiles.cpp | 8 +- 6 files changed, 1662 insertions(+), 4 deletions(-) create mode 100644 arch_doc/ARCHITECTURE.md create mode 100644 arch_doc/TECHNICAL_HARD_POINTS.md diff --git a/arch_doc/ARCHITECTURE.md b/arch_doc/ARCHITECTURE.md new file mode 100644 index 0000000..70fcbeb --- /dev/null +++ b/arch_doc/ARCHITECTURE.md @@ -0,0 +1,671 @@ +# 项目架构说明 + +## 1. 项目概述 + +ECAP_Server 是一个面向 Mode A/C/S、ADS-B 与相关设备数据的数据接入、解析、存储、转发和地图显示组合系统。后端以 C++ 可执行程序运行,接入 TCP、串口、文件、DLL 和共享内存等数据源,将报文交给 SSR 协议模块解析,并通过 HTTP、WebSocket 和数据转发服务对外提供结果。前端提供 Leaflet 二维地图与 Cesium 三维地图,共用飞机、轨迹、基站和子数据源业务状态。 + +当前源码可确认的主要运行平台是 Windows;构建脚本也保留 Linux 分支。最终产物包括: + +- `ecap_server` 后端可执行程序; +- `dll_source` 等动态数据源组件; +- Vite 生产构建生成的静态前端; +- 本地影像、Terrarium 地形和 GLB 模型资源。 + +最核心的子系统是: + +1. 数据源和数据转发协程; +2. SSR/ADS-B/CPR 协议解析与飞机状态; +3. Drogon HTTP/WebSocket 服务; +4. Leaflet/Cesium 地图渲染; +5. 地形解码、LOS 通视分析和 Worker 调度。 + +关键入口: + +- [`psc_main()`](../module/Local_Server_main.cpp#L56-L175) + - `module/Local_Server_main.cpp:56-175` +- [`Global::Global()`](../module/Local_Server/server/Global.cpp#L58-L145) + - `module/Local_Server/server/Global.cpp:58-145` +- [`App`](../third_party/eacp_webapp/src/App.tsx#L36-L293) + - `third_party/eacp_webapp/src/App.tsx:36-293` + +## 2. 技术栈 + +| 层级 | 技术 | 用途 | 项目封装位置 | 关键源码 | +| -- | -- | -- | -- | -- | +| 语言 | C++20、TypeScript/TSX、JavaScript | 后端、算法、前端 | `module`、`third_party/SSR`、`third_party/eacp_webapp/src` | [`CMakeLists.txt`](../CMakeLists.txt#L5), [`package.json`](../third_party/eacp_webapp/package.json#L6) | +| 构建 | CMake、自定义 `_create/_depend/_attach_*` | 后端目标和依赖装配 | `third_party/build_infra`、`main.cmake` | [`main.cmake`](../main.cmake#L40-L59) | +| 后端 Web | Drogon/Trantor | HTTP、WebSocket、静态 UI | `module/Local_Server/server` | [`Global::init_web_server()`](../module/Local_Server/server/server.cpp#L136-L232) | +| 并发 | standalone Asio 1.38、C++ 协程、线程池 | 网络循环、数据处理、文件 I/O | `io_coro.*`、`Ucoro_Drogon_Glue.h` | [`Coro::start()`](../module/Local_Server/server/io_coro.cpp#L47-L80), [`ASIO_VERSION`](../third_party/CPP_Core/3rd/asio/asio-asio-1-38-0/include/asio/version.hpp#L21) | +| 协议 | SSR 自有 Mode A/C/S、ADS-B、CPR 实现 | 报文解析、位置和距离算法 | `third_party/SSR/SSR` | [`parse_mode_s_bin()`](../third_party/SSR/SSR/Aircraft_Info.cpp#L279), [`CPR`](../third_party/SSR/SSR/CPR/CPR.h#L12-L132) | +| 数据格式 | Psc JSON | 配置、接口、业务序列化 | `Config.h`、各模块 `toJson/fromJson` | [`Config::fromJson()`](../module/Local_Server/server/Config.h#L299-L313) | +| 存储 | SQLiteCpp、文件 | 飞机/轨迹数据和配置 | `Database.*`、配置文件 | [`database_server()`](../module/Local_Server/Data_Source/Database.cpp#L516-L522) | +| 前端 | React 18、Ant Design、Vite 6 | SPA、设置和业务面板 | `third_party/eacp_webapp` | [`package.json`](../third_party/eacp_webapp/package.json#L19-L49) | +| 二维地图 | Leaflet 1.9 | 二维地图、飞机和轨迹 | `Leaflet_Map.tsx`、`Aircraft.tsx` | [`Leaflet_Map`](../third_party/eacp_webapp/src/Map/Leaflet_Map.tsx#L42) | +| 三维地图 | CesiumJS 1.143 | 地球、模型、轨迹、地形和相机 | `src/Map/Cesium_*` | [`Cesium_Map`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L117) | +| 地形 | Terrarium PNG、CustomHeightmapTerrainProvider | 本地高程地形 | `Terrarium_Terrain_*` | [`Terrarium_Terrain_Provider`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx#L22-L61) | +| 浏览器并发 | Web Worker、固定 Worker 池 | PNG 解码和 LOS | `Worker_Pool.ts`、`Los_Worker.ts` | [`Worker_Pool`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L22-L97) | +| 日志 | Core 日志封装 | 后端运行和协议日志 | `third_party/CPP_Core`、`Global.cpp` | [`Global::Global()`](../module/Local_Server/server/Global.cpp#L58) | +| 测试 | GoogleTest、TypeScript、ESLint、Vite build | 后端单测与前端静态验证 | SSR 测试、前端 scripts | [`CPR_TEST.cpp`](../third_party/SSR/SSR/CPR/CPR_TEST.cpp#L1), [`package.json`](../third_party/eacp_webapp/package.json#L6-L12) | +| 部署 | Drogon 静态资源、Cesium runtime copy | 后端托管生产 UI | `vite.config.js`、`server.cpp` | [`cesiumAssetsPlugin()`](../third_party/eacp_webapp/vite.config.js#L21-L53) | + +## 3. 仓库目录结构 + +| 目录 | 职责 | 类型与关系 | +| -- | -- | -- | +| `module/Local_Server` | 主服务、配置、数据源、数据转发、飞机 VTO、HTTP/WebSocket | 项目核心源码,依赖 SSR、Core、Drogon、Asio | +| `module/dll_source` | 可由主服务加载的数据源 DLL | 独立构建目标,主程序构建依赖它 | +| `module/mlat_source`、`module/Radarcape_Core` | MLAT 和设备相关能力 | 由主服务按配置调用 | +| `third_party/SSR` | Mode A/C/S、ADS-B、CPR 算法和状态对象 | 深度集成的第三方/项目内算法库 | +| `third_party/CPP_Core` | 日志、网络、协程、JSON 等基础能力 | 多模块基础依赖 | +| `third_party/eacp_webapp` | React、Leaflet、Cesium 前端及模型 | 生产构建由后端静态托管 | +| `third_party/build_infra` | CMake 封装、工具链和依赖装配 | 顶层构建基础设施 | +| `config` | 源配置模板 | 构建后复制到目标目录 | +| `data` | 运行数据和数据库 | 后端运行期资源 | +| `arch_doc` | 架构和技术难点文档 | 本文档输出目录 | + +构建输出、`node_modules`、IDE 缓存和生成文件不属于架构源码;它们只在验证生产资源和实际运行配置时使用。 + +## 4. 构建与启动结构 + +顶层 CMake 先加载构建基础设施、Core 和 SSR,再装配 ECAP 目标。`ecap_server` 关联整个 `Local_Server` 源目录、SSR、Drogon、SQLiteCpp、zlib 和 libarchive,并在构建后复制配置。 + +```mermaid +flowchart LR + CMake[CMakeLists.txt] --> Infra[build_infra] + CMake --> Core[CPP_Core] + CMake --> SSR[SSR interface target] + CMake --> Main[main.cmake] + Main --> Server[ecap_server] + Main --> DLL[dll_source] + SSR --> Core + Server --> SSR + Server --> Drogon[Drogon] + Server --> SQLite[SQLiteCpp] + Server --> Archive[zlib/libarchive] + Server --> DLL + Vite[Vite build] --> WebRoot[wwwroot] + WebRoot --> Server +``` + +证据: + +- [`CMakeLists.txt` 的包含顺序](../CMakeLists.txt#L8-L19) + - `CMakeLists.txt:8-19` +- [`ecap_server` 目标装配](../main.cmake#L40-L59) + - `main.cmake:40-59` +- [`ecap_server` 对 `dll_source` 的依赖](../main.cmake#L132) + - `main.cmake:132` +- [`Vite` 生产输出和 Cesium 资源复制](../third_party/eacp_webapp/vite.config.js#L21-L53) + - `third_party/eacp_webapp/vite.config.js:21-53` + +后端启动顺序是: + +1. `main()` 进入 `psc_main()`; +2. 创建 `Global`,加载配置和模块; +3. 初始化 Drogon 路由; +4. 启动 Web 服务线程; +5. 启动 `Coro` 的 Asio 网络线程和数据处理线程池; +6. 主循环清理超时飞机并维持设备状态; +7. 退出时先停协程和设备,再销毁全局对象。 + +开发模式由 Vite 代理后端 `/api`、`/map`、`/tiles` 和 `/ws`;生产模式由 Drogon 提供 Vite 输出及 `/ui/cesium`、`/ui/model` 静态资源。 + +## 5. 总体架构 + +```mermaid +flowchart LR + Input[TCP/串口/文件/DLL/共享内存] --> Source[Data_Source] + Source --> Parse[Data_Source_Handler] + Parse --> SSR[SSR/ADS-B/CPR] + SSR --> Aircraft[Aircraft_Info/轨迹] + Parse --> Feed[Data_Feed] + Aircraft --> DB[DataBase] + DB --> WS[Aircraft WebSocket] + Config[Config JSON] --> Source + Config --> HTTP[Drogon HTTP] + Tiles[本地影像/地形] --> HTTP + HTTP --> Frontend[React 业务状态] + WS --> Frontend + Frontend --> Leaflet[Leaflet 2D] + Frontend --> Cesium[Cesium 3D] + Tiles --> Terrain[Terrarium Worker] + Terrain --> Cesium + Frontend --> LOS[LOS Worker Pool] + LOS --> Cesium +``` + +依赖方向以数据接入、协议域对象、服务接口、前端业务模型、渲染适配器为主。Leaflet 和 Cesium 不应成为后端业务对象的依赖;统一飞机模型不运行时导入 Cesium。Cesium 专属坐标、Entity、Worker 和相机状态停留在三维适配层。 + +## 6. 核心模块说明 + +### 6.1 程序和全局运行时 + +#### 职责 + +创建全局配置、服务、设备和数据源对象,控制后端启动与退出。 + +#### 核心类型和函数 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `psc_main()` | 函数 | 后端主流程 | 进程全生命周期 | [`psc_main()`](../module/Local_Server_main.cpp#L56-L175) | +| `Global` | 单例类 | 配置和主要模块所有者 | 主流程创建、退出销毁 | [`Global`](../module/Local_Server/server/Global.h#L148-L168) | +| `Global::init_web_server()` | 函数 | 注册路由并启动 Web 配置 | `Global` 初始化期 | [`init_web_server()`](../module/Local_Server/server/server.cpp#L136-L232) | + +输入是配置文件和运行参数,输出是持续运行的服务与模块集合。异常处理包含顶层退出路径和日志;部分初始化失败会阻止 `init_ok`。 + +### 6.2 配置系统 + +#### 职责 + +以一个 JSON 配置对象管理数据源、地图资源、二维/三维视图、Cesium 画质和模型元数据。 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Map_Tile_Config` | 结构体 | 瓦片目录、布局、Y 轴、投影和层级 | 配置生命周期 | [`Map_Tile_Config`](../module/Local_Server/server/Config.h#L114-L135) | +| `Map_View_Config` | 结构体 | 2D/3D 图层、层级和相机 | 配置生命周期 | [`Map_View_Config`](../module/Local_Server/server/Config.h#L164-L172) | +| `Cesium_Graphics_Config` | 结构体 | 画质、地形、日照和 LOS | 配置生命周期 | [`Cesium_Graphics_Config`](../module/Local_Server/server/Config.h#L173-L195) | +| `Map_Model_Config` | 结构体 | 飞机、基站、设备、轨迹点模型 | 配置生命周期 | [`Map_Model_Config`](../module/Local_Server/server/Config.h#L208-L262) | +| `Config::fromJson/toJson` | 函数 | 单一配置解析和序列化 | 启动加载、接口保存 | [`fromJson()`](../module/Local_Server/server/Config.h#L299-L313), [`toJson()`](../module/Local_Server/server/Config.h#L315-L333) | + +地图接口只公开前端所需元数据,不公开本机目录。配置更新由后端接口写回同一配置对象和文件。 + +### 6.3 数据源与数据转发 + +#### 职责 + +抽象不同输入介质,读取原始数据,在线程池解析,再向数据库、飞机状态和数据转发模块发布。 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Data_Source` | 基类 | 输入源、状态和显示配置 | 配置创建到停用/退出 | [`Data_Source`](../module/Local_Server/Data_Source/Data_Source.h#L121) | +| `Data_Source_Handler` | 基类 | 切包、解析、业务分发 | 随 Data Source | [`Data_Source_Handler`](../module/Local_Server/Data_Source/Data_Source_Handler.h#L14-L19) | +| `Data_Feed` | 基类 | 向 TCP/UDP 等客户端转发 | 配置创建到退出 | [`Data_Feed`](../module/Local_Server/Data_Feed/Data_Feed.h#L16) | +| `Coro` | 单例类 | Asio 网络循环和处理线程池 | 服务运行期 | [`Coro`](../module/Local_Server/server/io_coro.h#L17-L26) | + +异步模型见第 10 节。主要失败路径包括输入断开、协议切包失败、解析异常和输出连接失败。`With_Loop_Coro` 状态机负责打开、循环和关闭。 + +### 6.4 SSR、ADS-B、CPR 和飞机状态 + +#### 职责 + +解析 Mode S 报文,维护每架飞机的消息、空中/地面轨迹和派生属性;CPR 负责位置解码、球面距离和无线电视距范围。 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Data_Source_Interface` | 接口 | SSR 对宿主数据源的最小依赖 | 随数据源 | [`Data_Source_Interface`](../third_party/SSR/SSR/export.h#L38-L45) | +| `parse_mode_s_bin()` | 函数 | Mode S 主解析入口 | 每条报文 | [`parse_mode_s_bin()`](../third_party/SSR/SSR/Aircraft_Info.cpp#L279-L303) | +| `Aircraft_Info` | 类 | 飞机状态和轨迹所有者 | ICAO 出现到超时删除 | [`Aircraft_Info`](../third_party/SSR/SSR/Aircraft_Info.h#L44-L101) | +| `CPR::surface_position()` | 函数 | 基于参考站的地面 CPR 解码 | 地面位置报文 | [`surface_position()`](../third_party/SSR/SSR/CPR/CPR.cpp#L247-L265) | +| `CPR::in_radio_line_of_sight_range()` | 函数 | 球面距离与理论视距过滤 | 位置候选校验 | [`in_radio_line_of_sight_range()`](../third_party/SSR/SSR/CPR/CPR.cpp#L299-L303) | + +`Data_Source_Handler` 将数据源中的基站位置和空中/地面约束传入 SSR,而不是让 SSR 反向依赖服务配置。 + +### 6.5 数据库与实时接口 + +#### 职责 + +提供飞机/轨迹查询,并维护按 WebSocket 连接隔离的版本和轨迹订阅状态。 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Aircraft_Stream_Client_State` | 连接状态 | 保存各数据源版本和订阅 | WebSocket 连接期 | [`Aircraft_Stream_Client_State`](../module/Local_Server/Data_Source/Database.cpp#L98-L103) | +| `aircraft_stream_source_update_json()` | 函数 | 构造单数据源增量 | 每次推送 | [`aircraft_stream_source_update_json()`](../module/Local_Server/Data_Source/Database.cpp#L156-L207) | +| `get_visible_aircraft_snapshot()` | 函数 | 点数和基站范围过滤 | 查询/推送时 | [`get_visible_aircraft_snapshot()`](../module/Local_Server/Data_Source/Database.cpp#L360-L407) | +| `register_aircraft_stream_ws()` | 函数 | 注册流和周期推送 | 服务生命周期 | [`register_aircraft_stream_ws()`](../module/Local_Server/Data_Source/Database.cpp#L283-L289) | + +前端按 ICAO 返回 `change_version` 和轨迹位置,后端只发送变化、删除和被监控轨迹。全部监控与手动 ICAO 集合是不同状态。 + +### 6.6 HTTP、瓦片与资源配置 + +#### 职责 + +按有序目录查找影像和地形瓦片,通过异步文件读取返回 GET/HEAD、缓存头和条件响应,同时提供公开资源元数据。 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Tile_Path_Resolver` | 类 | 按布局和目录优先级定位文件 | 服务生命周期 | [`Tile_Path_Resolver`](../module/Local_Server/server/tiles.cpp#L162-L200) | +| `Tile_Source` | 类 | 查找、状态、读取和响应元数据 | 服务生命周期 | [`Tile_Source`](../module/Local_Server/server/tiles.cpp#L202-L295) | +| `register_tile_handler()` | 函数 | GET/HEAD、304、404 和回调桥接 | 路由注册期 | [`register_tile_handler()`](../module/Local_Server/server/tiles.cpp#L297-L324) | +| `Global::init_tiles()` | 函数 | 创建影像、Google 影像、地形源 | 服务初始化期 | [`init_tiles()`](../module/Local_Server/server/tiles.cpp#L589-L599) | + +多目录严格按列表顺序查询,第一个普通文件获胜;不扫描全目录、不预加载瓦片。文件查找、`status`、打开和读取整体通过 `to_drogon()` 运行在专用 Asio 执行器,再回到 Drogon 事件循环完成一次回调。 + +### 6.7 前端统一业务状态 + +#### 职责 + +`Data_Source` 保存子数据源、基站、飞机集合、2D/3D 显示配置和监控模式;`Aircraft_Model` 保存与渲染框架无关的飞机/轨迹状态。 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Data_Source` | 类 | 单数据源前端状态所有者 | 配置加载到页面卸载 | [`Data_Source`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L68-L145) | +| `Aircraft_Model` | 类 | 统一飞机位置、姿态、属性和轨迹 | 飞机出现到删除 | [`Aircraft_Model`](../third_party/eacp_webapp/src/Map/Aircraft_Model.tsx#L22-L75) | +| `Aircraft_Stream_Client` | 类 | WebSocket 重连、订阅和增量应用 | SPA 生命周期 | [`Aircraft_Stream_Client`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L483-L573) | + +Leaflet 的 `Aircraft` 包装统一模型,Cesium 直接读取同一对象。Cesium 依赖只存在于三维适配器。 + +### 6.8 Leaflet 二维渲染 + +#### 职责 + +维持原有二维地图、瓦片层、飞机图标、轨迹线和右键交互。 + +核心入口是 [`Leaflet_Map`](../third_party/eacp_webapp/src/Map/Leaflet_Map.tsx#L42-L252) 和 [`Aircraft`](../third_party/eacp_webapp/src/Map/Aircraft.tsx#L196-L580)。二维对象由 `Data_Source.aircraftMap` 中的同一飞机实例驱动;轨迹监控状态来自共享数据源状态。 + +### 6.9 Cesium 三维渲染 + +#### 职责 + +创建/销毁 Viewer,增量映射数据源、飞机、轨迹、基站和设备,接入影像、地形、模型、相机、LOS 与交互。 + +核心入口: + +- [`Cesium_Map.load_map()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L247-L298) + - `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:247-298` +- [`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` +- [`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` +- [`sync_aircraft()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1188-L1209) + - `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:1188-1209` +- [`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` + +飞机使用 Model Entity,轨迹线使用 Polyline Entity,普通轨迹点使用 `PointPrimitiveCollection`,选中特殊点才使用模型 Entity。基站底座是地形到设备高度的世界空间模型;设备位于顶部,承担选择和完整信息。基站纵向比例由真实架设高度确定,横向显示大小通过模型节点变换控制。 + +### 6.10 地形、LOS 与相机 + +| 名称 | 类型 | 职责 | 生命周期 | 源码 | +| -- | -- | -- | -- | -- | +| `Terrarium_Terrain_Provider` | 适配器 | Cesium Heightmap Provider 和缓存 | Viewer 生命周期 | [`Terrarium_Terrain_Provider`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx#L22-L61) | +| `Terrarium_Terrain_Worker` | Worker | fetch、PNG 解码、RGB 转高度 | Provider 生命周期 | [`decode_terrarium_png()`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Worker.ts#L53-L72) | +| `Worker_Pool` | 调度器 | 固定并行度和 FIFO 任务 | Cesium 生命周期 | [`Worker_Pool`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L22-L97) | +| `Los_Worker` | Worker | ECEF 视线和地形采样 | LOS Pool 生命周期 | [`calculate_los()`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L45-L91) | +| `Cesium_Camera_Control` | 控制器 | 地表、地心轨道、自由观察 | Viewer 生命周期 | [`Cesium_Camera_Control`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L12-L177) | + +## 7. 核心业务对象关系 + +```mermaid +classDiagram + class Data_Source { + Base_Station base_station + Map aircraftMap + Set manual_track_icao_set + bool monitor_all_aircraft_mode + map2d + map3d + } + class Aircraft { + Aircraft_Model data_model + LeafletMarker marker + TrackPath track + } + class Aircraft_Model { + id + longitude + latitude + altitude + orientation + trackPoints + } + class Cesium_Aircraft_Record { + Model Entity + Polyline Entity + PointPrimitive records + LOS Entity + } + class Base_Station { + position + range + maxAircraft + } + class Cesium_Base_Record { + Base world model + Device Entity + Range polylines + } + Data_Source *-- Base_Station + Data_Source *-- Aircraft + Aircraft *-- Aircraft_Model + Aircraft_Model --> Cesium_Aircraft_Record + Base_Station --> Cesium_Base_Record +``` + +业务主体是 `Data_Source`、飞机和基站状态;Cesium Entity、Primitive 和 Leaflet Layer 只是显示对象。一个飞机对应一个统一模型,同时可映射为 Leaflet Marker/Polyline 和 Cesium Model/Polyline/PointPrimitive。设备 Entity 是三维中可点击的基站业务入口,底部基站模型只表现物理支撑,不重复持有业务状态。 + +创建和销毁关系: + +- WebSocket 增量创建或删除 `Data_Source.aircraftMap` 中的飞机; +- Leaflet 和 Cesium 按当前页面适配这些对象; +- Cesium 用 key 到 record 的 Map 复用 Entity; +- 数据源或飞机删除时,渲染适配器同步删除关联轨迹、LOS 和模型; +- Viewer 卸载时统一销毁 Worker、监听器、定时器和 Cesium 资源。 + +## 8. 运行时生命周期 + +```mermaid +sequenceDiagram + participant Main as psc_main + participant Global + participant Coro + participant Source as Data_Source + participant SSR + participant DB + participant WS + participant React + participant Map as Leaflet/Cesium + Main->>Global: 创建并加载配置 + Global->>DB: 注册 HTTP/WS + Main->>Coro: start() + Coro->>Source: 启动输入协程 + Source->>SSR: parse_mode_s_bin + SSR-->>DB: 更新飞机/轨迹 + DB-->>WS: 版本增量 + WS-->>React: source delta + React->>Map: 请求增量同步 + alt 进入 3D + React->>Map: lazy import + load_map + Map->>Map: 创建 Viewer/Worker/Provider + else 离开 3D + React->>Map: on_un_mount + Map->>Map: 清理 Viewer/Worker/监听器 + end + Main->>Coro: stop() + Main->>Global: destroy() +``` + +`/map3d` 使用动态导入,未进入三维页面时不会执行 Viewer 初始化。WebGL 预检查失败会禁用三维入口并返回二维页面。对应源码: + +- [`React.lazy()` 和 WebGL 检查](../third_party/eacp_webapp/src/App.tsx#L23-L26) + - `third_party/eacp_webapp/src/App.tsx:23-26` +- [`/map` 与 `/map3d` 路由](../third_party/eacp_webapp/src/App.tsx#L224-L268) + - `third_party/eacp_webapp/src/App.tsx:224-268` + +## 9. 数据流 + +### 实时数据流 + +```text +输入字节 +→ Data_Source 异步读取 +→ process_data 线程池切包/解析 +→ SSR Aircraft_Info 和轨迹 +→ DataBase 可见性过滤和版本比较 +→ /ws/aircraft_stream 增量 +→ Data_Source.aircraftMap +→ Leaflet/Cesium 渲染记录 +→ 屏幕 +``` + +WebSocket 消息是 JSON;前端保留每架飞机版本和最后轨迹大小。业务状态在前端对象中原地增量更新,Cesium 对象按 ID 复用,不在每次消息后重建 Viewer。 + +### 用户交互流 + +```text +点击飞机/设备/轨迹点 +→ Cesium/Leaflet picking +→ 找到 Data_Source 或 Aircraft_Model +→ 更新 active_aircraft/监控状态 +→ 显示信息、轨迹或相机行为 +``` + +基站范围辅助几何设置为忽略 picking,设备 Entity 才绑定完整基站信息。相关代码见 [`create_base_station_record()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L802-L892)。 + +### 地形与通视分析流 + +```text +基站设备位置 + 飞机位置 +→ LOS generation/request ID +→ Worker Pool +→ ECEF 直线采样 +→ Terrarium 瓦片 fetch/cache/decode +→ 净空和首次遮挡结果 +→ 丢弃过期 generation +→ 更新飞机 LOS 状态和连线 +``` + +LOS Worker 接收普通数字结构,不传递 Cesium Viewer 或类实例。高度缓存位于各 Worker 内,返回结果会检查当前 generation,避免旧位置覆盖新位置。 + +### 配置更新流 + +```text +设置表单临时值 +→ HTTP PUT/POST +→ Global 中单一配置对象 +→ config.json +→ 前端事件通知 +→ 当前渲染器应用 +``` + +地图资源接口只返回 URL、层级、投影、Y 轴和编码。模型上传由后端保存并更新模型 URL 和内置尺寸/初始角度配置。 + +## 10. 并发、异步和调度模型 + +```mermaid +flowchart TB + Main[主线程/进程控制] --> Asio[Asio io_context 单网络线程] + Asio --> Pool[process_data 线程池] + Drogon[Drogon/Trantor 事件循环] --> TilePool[Ucoro Asio 专用线程] + Browser[浏览器主线程] --> CesiumWorkers[Cesium 内置 Workers] + Browser --> TerrainWorker[Terrarium Decode Worker] + Browser --> LosPool[LOS Worker Pool] + WS[WebSocket 事件] --> Browser + Browser --> RAF[Cesium requestRender/postRender] +``` + +后端: + +- `Coro::start()` 创建一个 `io_context` 运行线程和按硬件并发度计算的 `process_data` 线程池; +- 数据源在 `io_context` 读取,随后切换到 `process_data` 解析,再返回网络执行器; +- 无等待模式使用每个数据源自己的 strand 串行化解析; +- 瓦片 HTTP 桥接使用独立 Asio 线程池,整个文件系统查找和读取不在 Drogon 网络线程执行; +- `co_await` 只表达挂起点,实际线程由当前 executor 决定。 + +浏览器: + +- React、WebSocket 消息处理、Cesium API 调用和 UI 在主线程; +- Terrarium PNG 解码在独立 Worker; +- LOS 使用有上限的 Worker 池,`Promise` 只是调用和结果接口; +- Cesium 自身还会使用其 runtime Workers; +- `sync_data_sources()` 先生成任务,再以最多 10 项或约 8 ms 的片段消费; +- `requestRenderMode` 降低静止场景持续渲染成本; +- Worker Pool 队列当前没有最大长度和任务取消,旧结果由实体 generation 淘汰。 + +关键证据: + +- [`Data_Source::loop_coro()`](../module/Local_Server/server/io_coro.cpp#L206-L299) + - `module/Local_Server/server/io_coro.cpp:206-299` +- [`to_drogon()`](../module/Local_Server/server/Ucoro_Drogon_Glue.h#L102-L110) + - `module/Local_Server/server/Ucoro_Drogon_Glue.h:102-110` +- [`Cesium` 分片同步](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L707-L767) + - `third_party/eacp_webapp/src/Map/Cesium_Map.tsx:707-767` +- [`Worker_Pool.dispatch()`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L67-L97) + - `third_party/eacp_webapp/src/Map/Worker_Pool.ts:67-97` + +## 11. Cesium 渲染架构 + +Viewer 在组件挂载后创建,在卸载时销毁。生产构建通过 `CESIUM_BASE_URL=/ui/cesium/` 加载 Workers、Assets、ThirdParty 和 Widgets;模型来自 `/ui/model`。 + +渲染对象选择: + +| 业务对象 | Cesium 对象 | 更新方式 | +| -- | -- | -- | +| 飞机 | Model Entity + Label | ID Map 增量更新 | +| 轨迹线 | Polyline Entity | 更新位置数组 | +| 普通轨迹点 | PointPrimitiveCollection | 按轨迹序号增量增加/裁剪 | +| 选中/特殊轨迹点 | Model Entity | 只为特殊状态创建 | +| 基站底座 | Model Entity | 地形底部、世界空间高度 | +| 设备 | Model Entity + Label | 顶部位置、可点击 | +| 探测范围 | 预创建 Polyline Entity 集合 | 更新位置和 show | +| LOS | Polyline/状态显示 | Worker 结果更新 | + +`sync_data_sources()` 会遍历当前启用数据源和飞机,这是已确认的全量发现阶段;已有记录由 Map 复用,重任务由分片队列消费。是否在目标规模下仍有长任务,需要 Chrome Performance 验证。 + +地形 Provider 使用 Web Mercator、配置层级和 XYZ/TMS 规则。2D 页面不加载 Cesium;3D 页面根据设置启用 Terrarium。关闭地形或卸载 Viewer 时会销毁 Provider 和 Worker。 + +基站/设备当前空间语义: + +- 地形采样值是基站模型底部; +- 配置设备高度是设备模型位置; +- 基站模型纵向比例由两者高度差与模型原始高度计算; +- “基站显示大小”只调整横向尺寸,不破坏顶部高度; +- 设备可按子数据源选择固定屏幕尺寸或普通世界尺寸; +- 完整基站信息绑定到设备,底座忽略 picking。 + +实现见 [`sync_base_station()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L768-L801)、[`create_base_station_record()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L802-L892) 和 [`base_station_heights()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1082-L1092)。 + +## 12. 真正有难度的技术点 + +### 多执行器数据源协程 + +**难度等级:** 高 +**所属模块:** Local_Server 数据源 +**解决的问题:** 网络读取不能被协议解析阻塞,同时单数据源消息顺序必须稳定。 +**为什么困难:** 同一逻辑跨 `io_context`、线程池和 strand,退出时还要等待未完成任务。 +**当前实现:** 输入协程读取后切到 `process_data`;等待模式直接 `co_await`,无等待模式向 strand 投递;再回网络 executor。 +**关键算法或状态机:** `With_Loop_Coro` 的打开、循环、关闭状态。 +**数据流:** 输入字节 → 切包 → SSR → Feed/DB。 +**线程与异步边界:** [`Data_Source::loop_coro()`](../module/Local_Server/server/io_coro.cpp#L206-L299)。 +**性能成本:** executor 切换、任务排队和 JSON/协议解析。 +**边界条件:** 数据源断开、退出、处理速度落后。 +**失败方式:** 顺序破坏、停止等待、线程池积压。 +**现有风险:** 队列长度没有统一可观测指标。 +**可改进方向:** 增加每源队列深度、耗时和丢弃策略指标。 + +### WebSocket 版本增量与轨迹订阅 + +**难度等级:** 高 +**所属模块:** Database、前端 Data_Source +**解决的问题:** 避免周期发送全部飞机和全部轨迹。 +**为什么困难:** 每个连接、数据源、ICAO 都有版本和轨迹游标;还需区分全部监控和手动监控。 +**当前实现:** 连接状态保存版本,前端订阅回传版本和轨迹大小,后端生成变化与删除。 +**关键算法或状态机:** 连接状态 + per-source delta。 +**数据流:** Snapshot → version compare → delta → apply/delete。 +**线程与异步边界:** Drogon 事件循环和浏览器 WebSocket。 +**关键源码:** [`Database.cpp`](../module/Local_Server/Data_Source/Database.cpp#L92-L289)、[`Aircraft_Stream_Client`](../third_party/eacp_webapp/src/Data_Source/Data_Source.tsx#L483-L573)。 +**性能成本:** 周期快照、版本 Map 比较和 JSON 序列化。 +**边界条件:** 重连、飞机删除、监控模式切换。 +**失败方式:** 版本失配造成漏更新或重复轨迹。 +**现有风险:** 后端持锁遍历并发送连接,需要压力测试。 +**可改进方向:** 锁内只构造待发送项,锁外发送;增加队列和消息尺寸指标。 + +### Terrarium 地形与 LOS Worker + +**难度等级:** 高 +**所属模块:** Cesium 地形、LOS +**解决的问题:** 在不阻塞 UI 的情况下解码 PNG 高程并计算基站到飞机的地形通视。 +**为什么困难:** 涉及 Web Mercator、XYZ/TMS、ECEF、地球曲率、异步瓦片、缓存和过期结果。 +**当前实现:** Terrain Worker 解码 Heightmap;LOS Worker 自行 fetch/cache/decode 并沿 ECEF 线采样。 +**关键算法或状态机:** Terrarium `R*256+G+B/256-32768`;generation 淘汰旧结果。 +**数据流:** 普通数字协议 → Worker → Float32Array/LOS 结果。 +**线程与异步边界:** 主线程只调度,解码和 LOS 在 Worker。 +**关键源码:** [`Terrarium_Terrain_Worker`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Worker.ts#L18-L72)、[`Los_Worker`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L36-L234)。 +**性能成本:** 网络请求、PNG 解码、每 Worker 独立缓存和采样次数。 +**边界条件:** 404、缺少 OffscreenCanvas、层级上限、卸载。 +**失败方式:** 队列积压、缓存重复、旧 LOS 覆盖新位置。 +**现有风险:** Worker Pool 队列无硬上限。 +**可改进方向:** 按飞机合并待处理任务并增加队列背压。 + +更多难点详见 [项目技术难点说明](./TECHNICAL_HARD_POINTS.md)。 + +## 13. 性能架构 + +| 热点 | 状态 | 源码证据 | 验证方法 | +| -- | -- | -- | -- | +| `sync_data_sources()` 遍历全部当前对象 | 已确认存在,但对象记录复用且任务分片 | [`sync_data_sources()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L714-L767) | Chrome Performance,记录单次任务数和耗时 | +| 飞机 Entity 每次重建 | 已确认正常更新路径不存在;Map 复用记录 | [`sync_aircraft()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1188-L1209) | 统计 Entity add/remove | +| 普通轨迹点全部使用 GLB | 已确认不存在;使用 PointPrimitive | [`create_aircraft_record()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1211) | Cesium Inspector、Primitive 数量 | +| React 消息导致 Viewer 重建 | 已确认正常增量消息不会重建 Viewer | [`request_sync_data_sources()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L707-L713) | React Profiler | +| WebSocket 全量飞机 | 已确认已改为版本增量 | [`aircraft_stream_source_update_json()`](../module/Local_Server/Data_Source/Database.cpp#L156-L207) | 抓取消息尺寸和 change_list | +| LOS 对所有飞机无条件执行 | 需要结合运行配置和监控模式采样验证 | [`update_aircraft_occlusion()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1320) | Worker 任务计数/秒 | +| 地形 PNG 主线程解码 | 已确认不存在于 Provider/LOS 路径 | [`Terrarium_Terrain_Worker`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Worker.ts#L53) | Performance 主线程长任务 | +| Worker 队列无界 | 已确认 | [`Worker_Pool.run()`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L38-L50) | 暴露 queue length 和等待时间 | +| 瓦片文件读取阻塞 Drogon 线程 | 已确认不存在;整体投递专用 Asio | [`register_tile_handler()`](../module/Local_Server/server/tiles.cpp#L297-L324) | 并发 HTTP 延迟和线程采样 | +| 瓦片读取明显串行 | 源码推断:专用池大小为 1,需要压力测试确认影响 | [`Ucoro_Drogon_Glue`](../module/Local_Server/server/Ucoro_Drogon_Glue.h#L13-L17) | 32/64 并发瓦片吞吐 | +| 后端 WebSocket 锁内发送 | 已确认调用位置,实际阻塞程度待验证 | [`push_aircraft_stream_updates()`](../module/Local_Server/Data_Source/Database.cpp#L227-L230) | 慢客户端压力测试和锁等待 | +| 每帧高成本回调 | 未发现每帧地形解码或全量同步;性能面板 postRender 仍需 Profile | [`postRender` 注册](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L525) | Performance flame chart | + +当前项目重点核查项: + +| 核查项 | 状态 | +| -- | -- | +| 进入三维模式完整调用链 | 已确认 | +| `sync_data_sources()` 全量发现 | 已确认存在,后续分片 | +| 飞机、基站、轨迹和模型重复创建 | 正常更新路径已确认不存在;边界重连需运行验证 | +| 高频位置按实体合并最新状态 | WebSocket 版本增量已确认;Cesium generation 会替换待同步批次 | +| 分片队列生产超过消费 | generation 替换旧批次;LOS 队列仍可能积压 | +| 离开三维后的清理 | 已确认有显式清理 | +| LOS 是否对所有飞机执行 | 需运行配置和任务计数确认 | +| 高程采样线程 | 已确认在自定义 Worker | +| Promise 是否等同多线程 | 已确认没有混淆;真正并行来自 Worker | +| Worker 池并行和旧任务 | 固定并行和 generation 已确认;无队列上限 | +| Entity/Primitive 选择 | 已确认普通轨迹点采用 PointPrimitive | +| 基站与设备信息所有权 | 已确认设备承担交互,基站底座是显示对象 | +| 屏幕尺寸和世界尺寸 | 基站世界高度;设备可选固定屏幕尺寸 | +| React 导致 Cesium 重建 | 正常消息路径已确认不会 | +| 每帧分配/全量遍历 | 未发现 LOS/地形全量每帧执行,仍需 Performance 确认 | + +## 14. 配置与扩展点 + +- 配置来源:`config/config.json`,由 `Config::fromJson()` 加载并由 `toJson()` 保存。 +- 业务配置:数据源协议、基站位置、点数限制、CPR 约束、理论范围过滤。 +- 显示配置:2D/3D 分层样式、瓦片源、相机、Cesium 画质、地形、LOS、日照和模型。 +- 新设备类型:扩展 `Data_Source` 子类和工厂注册,并提供 JSON 配置。 +- 新地图图层:扩展 `Map_Resources_Config` 和 `/map/resources` 公开元数据,再在 2D/3D 适配器接入。 +- 新数据源:实现 `Data_Source` 的打开、读取、关闭协程,复用 `Data_Source_Handler`。 +- 新算法:优先放在 SSR 或独立算法模块,通过小接口接入,避免渲染层反向依赖。 +- 新相机模式:扩展 `Camera_Control_Mode` 和 `Cesium_Camera_Control.set_mode()`,必须实现输入恢复和 `destroy()`。 +- 新 Worker 任务:定义纯数据协议,通过 `Worker_Pool` 调度,明确缓存、过期和退出语义。 + +前端配置入口: + +- [`load_map_resources()`](../third_party/eacp_webapp/src/Map/Map_Resources.tsx#L28-L45) +- [`load_map_view_config()`](../third_party/eacp_webapp/src/Map/Map_View.tsx#L83-L90) +- [`load_map_model_config()`](../third_party/eacp_webapp/src/Map/Map_Models.tsx#L117-L137) + +## 15. 架构风险 + +| 等级 | 风险 | 影响 | 证据 | 建议验证方式 | +| -- | -- | -- | -- | -- | +| 高 | LOS Worker Pool 队列无上限、不能取消执行中任务 | 飞机多且更新快时任务积压和无效请求 | [`Worker_Pool.run()`](../third_party/eacp_webapp/src/Map/Worker_Pool.ts#L38-L50) | 记录 queue length、等待时间、旧结果比例 | +| 高 | WebSocket 周期推送在全局客户端锁内构造并发送 | 慢连接可能扩大锁持有时间 | [`push_aircraft_stream_updates()`](../module/Local_Server/Data_Source/Database.cpp#L227-L230) | 多慢客户端压力测试 | +| 中高 | 三维同步仍有全量发现阶段 | 目标数增大后主线程扫描成本增长 | [`sync_data_sources()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L714-L767) | 1k/10k 目标 Performance | +| 中高 | LOS 每 Worker 各自缓存 Terrarium | 并行度增加会重复占用内存 | [`load_height_tile()`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L186-L234) | Worker heap 和缓存命中率 | +| 中 | 瓦片专用 Asio 池为单线程 | 高并发磁盘瓦片吞吐可能串行 | [`Ucoro_Drogon_Glue`](../module/Local_Server/server/Ucoro_Drogon_Glue.h#L13-L17) | 并发请求吞吐、网络线程采样 | +| 中 | 上传替换基站 GLB 时横向节点名依赖 `Node` | 新模型节点命名不一致会失去横向显示比例 | [`base_station_node_transformations()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1163) | 上传不同节点名 GLB 验证 | +| 中 | 气压高度、Terrarium 正高和 Cesium 椭球高基准可能不同 | LOS 和设备高度产生系统偏差 | [`base_station_heights()`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L1082-L1092) | 使用已知测量点对照 | +| 中 | 前端没有独立单元测试脚本 | 增量、缓存和生命周期回归主要依赖集成测试 | [`package.json`](../third_party/eacp_webapp/package.json#L6-L12) | 增加 Vitest/Playwright 关键路径测试 | + +## 16. 源码索引 + +| 子系统 | 入口类型或函数 | 文件位置 | 说明 | +| -- | -- | -- | -- | +| 进程 | `psc_main()` | [`module/Local_Server_main.cpp`](../module/Local_Server_main.cpp#L56) | 后端启动和退出 | +| 全局对象 | `Global` | [`Global.h`](../module/Local_Server/server/Global.h#L148) | 配置和模块所有者 | +| Web 服务 | `init_web_server()` | [`server.cpp`](../module/Local_Server/server/server.cpp#L136) | HTTP/WS 注册 | +| 配置 | `Config` | [`Config.h`](../module/Local_Server/server/Config.h#L263) | 单一 JSON 配置 | +| 协程 | `Coro` | [`io_coro.cpp`](../module/Local_Server/server/io_coro.cpp#L47) | 网络和处理线程 | +| 数据源 | `Data_Source` | [`Data_Source.h`](../module/Local_Server/Data_Source/Data_Source.h#L121) | 输入抽象 | +| 协议处理 | `Data_Source_Handler` | [`Data_Source_Handler.cpp`](../module/Local_Server/Data_Source/Data_Source_Handler.cpp#L83) | 切包和业务分发 | +| SSR | `parse_mode_s_bin()` | [`Aircraft_Info.cpp`](../third_party/SSR/SSR/Aircraft_Info.cpp#L279) | Mode S 主入口 | +| CPR | `surface_position()` | [`CPR.cpp`](../third_party/SSR/SSR/CPR/CPR.cpp#L247) | 位置和距离 | +| 实时流 | `register_aircraft_stream_ws()` | [`Database.cpp`](../module/Local_Server/Data_Source/Database.cpp#L283) | 版本增量 WebSocket | +| 瓦片 | `init_tiles()` | [`tiles.cpp`](../module/Local_Server/server/tiles.cpp#L589) | 影像/地形 HTTP | +| SPA | `App` | [`App.tsx`](../third_party/eacp_webapp/src/App.tsx#L36) | 路由和延迟加载 | +| 统一模型 | `Aircraft_Model` | [`Aircraft_Model.tsx`](../third_party/eacp_webapp/src/Map/Aircraft_Model.tsx#L22) | 2D/3D 共用 | +| 二维地图 | `Leaflet_Map` | [`Leaflet_Map.tsx`](../third_party/eacp_webapp/src/Map/Leaflet_Map.tsx#L42) | Leaflet 渲染 | +| 三维地图 | `Cesium_Map` | [`Cesium_Map.tsx`](../third_party/eacp_webapp/src/Map/Cesium_Map.tsx#L117) | Cesium 适配器 | +| 地形 | `Terrarium_Terrain_Provider` | [`Terrarium_Terrain_Provider.tsx`](../third_party/eacp_webapp/src/Map/Terrarium_Terrain_Provider.tsx#L22) | Heightmap Provider | +| LOS | `calculate_los()` | [`Los_Worker.ts`](../third_party/eacp_webapp/src/Map/Los_Worker.ts#L45) | Worker 通视计算 | +| 相机 | `Cesium_Camera_Control` | [`Cesium_Camera_Control.ts`](../third_party/eacp_webapp/src/Map/Cesium_Camera_Control.ts#L12) | 三种控制模式 | + +## 17. 无法确认的问题 + +1. 没有运行目标规模压力测试,无法确认 1,000 架飞机、10,000 个轨迹点时的主线程、GPU 和 Worker 队列上限。 +2. 当前源码没有给出所有实际部署设备的高度基准,无法确认气压高度、正高和椭球高是否已经统一。 +3. 未获得生产数据库规模和索引统计,无法确认历史轨迹查询成本。 +4. 未看到浏览器端自动化测试,无法从源码确认重复进入/退出 3D、WebGL 初始化失败和 Worker 销毁的长期稳定性。 +5. GLB 上传接口允许替换模型,但无法从配置确认每个外部模型的根节点命名、坐标轴、原点和单位。 +6. Cesium、WebView2 和 Intel 集成显卡的实际驱动组合只能通过目标机器运行验证,源码不能证明 WebGL 初始化一定成功。 +7. LOS 是地形几何通视,不包含建筑、植被、天线方向图、信号强度、菲涅耳区和大气折射;这些能力当前源码未实现。 diff --git a/arch_doc/TECHNICAL_HARD_POINTS.md b/arch_doc/TECHNICAL_HARD_POINTS.md new file mode 100644 index 0000000..8d17f55 --- /dev/null +++ b/arch_doc/TECHNICAL_HARD_POINTS.md @@ -0,0 +1,959 @@ +# 项目技术难点说明 + +## 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`、生产刷新 | diff --git a/config/config.json b/config/config.json index d805549..ddc8195 100644 --- a/config/config.json +++ b/config/config.json @@ -261,7 +261,14 @@ }, "base_station_model": { "url": "/ui/model/base-station.glb", - "built_in_size": 100.000000, + "built_in_size": 16.635522, + "heading_offset_degrees": 0.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "device_model": { + "url": "/ui/model/device.glb", + "built_in_size": 80.000000, "heading_offset_degrees": 0.000000, "pitch_offset_degrees": 0.000000, "roll_offset_degrees": 0.000000 @@ -602,6 +609,9 @@ "terrain_exaggeration": 5.000000, "terrain_limit_maximum_level": true, "terrain_maximum_level": 15, - "terrain_cache_tiles": 128 + "terrain_cache_tiles": 128, + "terrain_occlusion_enabled": false, + "occlusion_sample_spacing_meters": 250.000000, + "occlusion_clearance_margin_meters": 10.000000 } } diff --git a/module/Local_Server/server/Config.cpp b/module/Local_Server/server/Config.cpp index 97f3e1d..ed1961c 100644 --- a/module/Local_Server/server/Config.cpp +++ b/module/Local_Server/server/Config.cpp @@ -368,6 +368,9 @@ void Cesium_Graphics_Config::from_base_json(const Psc::JSON* that_json) { terrain_limit_maximum_level = read_optional_bool_field(that_json, "terrain_limit_maximum_level", terrain_limit_maximum_level); terrain_maximum_level = std::clamp(read_optional_uint32_field(that_json, "terrain_maximum_level", terrain_maximum_level), 0u, 24u); terrain_cache_tiles = std::clamp(read_optional_uint32_field(that_json, "terrain_cache_tiles", terrain_cache_tiles), 16u, 2048u); + terrain_occlusion_enabled = read_optional_bool_field(that_json, "terrain_occlusion_enabled", terrain_occlusion_enabled); + occlusion_sample_spacing_meters = std::clamp(read_optional_double_field(that_json, "occlusion_sample_spacing_meters", occlusion_sample_spacing_meters), 10.0, 5000.0); + occlusion_clearance_margin_meters = read_optional_double_field(that_json, "occlusion_clearance_margin_meters", occlusion_clearance_margin_meters); } Psc::JSON Cesium_Graphics_Config::to_base_json() const { auto ret = Psc::JSON::object(); @@ -387,6 +390,9 @@ Psc::JSON Cesium_Graphics_Config::to_base_json() const { ret.append({"terrain_limit_maximum_level", terrain_limit_maximum_level}); ret.append({"terrain_maximum_level", terrain_maximum_level}); ret.append({"terrain_cache_tiles", terrain_cache_tiles}); + ret.append({"terrain_occlusion_enabled", terrain_occlusion_enabled}); + ret.append({"occlusion_sample_spacing_meters", occlusion_sample_spacing_meters}); + ret.append({"occlusion_clearance_margin_meters", occlusion_clearance_margin_meters}); return ret; } Map_Model_Item_Config::Map_Model_Item_Config(std::string url) @@ -424,12 +430,14 @@ void Map_Model_Config::from_base_json(const Psc::JSON* that_json) { aircraft_model.from_base_json(that_json->get("aircraft_model")); aircraft_models = read_optional_model_object_field(that_json, "aircraft_models", make_default_aircraft_models()); base_station_model.from_base_json(that_json->get("base_station_model")); + device_model.from_base_json(that_json->get("device_model")); } Psc::JSON Map_Model_Config::to_base_json() const { auto ret = Psc::JSON::object(); ret.append({"aircraft_model", aircraft_model.to_base_json()}); ret.append(write_model_object("aircraft_models", aircraft_models)); ret.append({"base_station_model", base_station_model.to_base_json()}); + ret.append({"device_model", device_model.to_base_json()}); return ret; } diff --git a/module/Local_Server/server/Config.h b/module/Local_Server/server/Config.h index 43e69c2..9f62956 100644 --- a/module/Local_Server/server/Config.h +++ b/module/Local_Server/server/Config.h @@ -187,6 +187,9 @@ struct Cesium_Graphics_Config { bool terrain_limit_maximum_level = true; std::uint32_t terrain_maximum_level = 15; std::uint32_t terrain_cache_tiles = 128; + bool terrain_occlusion_enabled = false; + double occlusion_sample_spacing_meters = 250.0; + double occlusion_clearance_margin_meters = 10.0; void from_base_json(const Psc::JSON* that_json); [[nodiscard]] Psc::JSON to_base_json() const; }; @@ -205,7 +208,8 @@ struct Map_Model_Item_Config { struct Map_Model_Config { Map_Model_Item_Config aircraft_model{"/ui/model/aircraft.glb", 50.0, -90.0, 0.0, 0.0}; std::map aircraft_models; - Map_Model_Item_Config base_station_model{"/ui/model/base-station.glb", 100.0, 0.0, 0.0, 0.0}; + Map_Model_Item_Config base_station_model{"/ui/model/base-station.glb", 16.635522, 0.0, 0.0, 0.0}; + Map_Model_Item_Config device_model{"/ui/model/device.glb", 80.0, 0.0, 0.0, 0.0}; Map_Model_Config(); void from_base_json(const Psc::JSON* that_json); [[nodiscard]] Psc::JSON to_base_json() const; diff --git a/module/Local_Server/server/tiles.cpp b/module/Local_Server/server/tiles.cpp index 0382b36..50cb76a 100644 --- a/module/Local_Server/server/tiles.cpp +++ b/module/Local_Server/server/tiles.cpp @@ -438,6 +438,9 @@ std::string model_file_name(std::string_view model_type, std::string_view aircra if (model_type == "base_station") { return "base-station-custom.glb"; } + if (model_type == "device") { + return "device-custom.glb"; + } throw std::invalid_argument("invalid model_type"); } void save_uploaded_model_file(const drogon::HttpFile& upload, @@ -473,9 +476,12 @@ void save_uploaded_model_file(const drogon::HttpFile& upload, else if (model_type == "aircraft_type") { global->map_model_config.aircraft_models[std::string(aircraft_model_key)].url = url; } - else { + else if (model_type == "base_station") { global->map_model_config.base_station_model.url = url; } + else { + global->map_model_config.device_model.url = url; + } } void handle_map_model_upload(const drogon::HttpRequestPtr& req, Global* global) { drogon::MultiPartParser parser;