diff --git a/README.md b/README.md index 80d01b8..20cc131 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,64 @@ # CollisionAvoidanceSystem -基于springboot开发机场碰撞管理系统,主要整合无人车、航空器及传统车辆位置信息,实现飞机与无人车的冲突预警与智能调度。 \ No newline at end of file +## 🎯 核心目标 (Core Goal) + +本项目旨在开发一套机场地面碰撞避免系统,通过整合机场场面内各类移动目标(航空器、特勤车辆、无人车)的实时数据,实现潜在运行冲突的智能预警与告警,提升机场地面运行安全与效率。 + +## ✨ 主要功能概述 (Key Features) + +* **多源数据集成**: 融合来自不同系统和传感器的数据。 +* **实时态势构建**: 维护场面目标的实时位置、速度和状态。 +* **冲突风险评估**: 基于目标轨迹和运行规则,预测和识别潜在冲突。 +* **规则符合性检查**: 如根据道路/区域限制进行超速检测。 +* **实时告警与通知**: 通过 WebSocket 等机制发布告警信息。 +* **静态环境建模**: 支持对机场道路网络等静态环境进行配置和查询。 + +## 🏗️ 系统架构与模块 (Architecture & Modules) + +系统采用模块化设计,便于开发、测试和维护。主要模块包括数据采集、数据处理、道路网络管理、API 控制器和 WebSocket 通信等。 + +详细的包和目录结构说明,请参见: +* [**目录结构文档 (directory_structure.md)**](doc/directory_structure.md) + +## 💡 核心概念与数据结构 (Core Concepts & Data Structures) + +系统围绕"移动物体 (MovingObject)"进行建模,并处理其动态状态。同时,引入了对机场静态环境(如道路网络)的建模。 + +核心数据结构设计(包括移动物体模型、地理位置、速度、运动状态以及道路信息模型等)的详细说明,请参见: +* [**数据结构设计文档 (design_document.md)**](doc/design_document.md) + +## ⚙️ 配置概览 (Configuration Overview) + +系统通过多种配置文件进行参数设置和行为调整。 + +* **应用核心配置**: `src/main/resources/application.yml` 文件,用于配置服务器端口、数据库连接、消息队列、外部 API 等基础设置。 +* **道路网络配置**: `src/main/resources/config/airport_roads.yaml` 文件,用于定义机场道路的几何、限速、限制等静态信息。数据格式需遵循约定。 +* **区域配置**: `src/main/resources/config/airport_zones.yaml` 文件 (如果使用),用于定义机场功能分区。 +* **Java 配置类**: 各模块可能包含位于 `config` 包下的 Java 类,用于配置 Spring Beans 或加载特定配置。 + +关于各类配置文件的详细说明、用途以及配置加载机制,请参见: +* [**配置文件说明文档 (configuration_guide.md)**](doc/configuration_guide.md) +* 关于如何从 CAD 生成道路网络配置,请参见 [**CAD 转 YAML 指南 (cad_to_yaml_guide.md)**](doc/cad_to_yaml_guide.md) +* 道路网络配置的集成设计方案,请参见 [**道路网络设计方案 (road_network_design.md)**](doc/road_network_design.md) + +## 🚀 快速开始 (Quick Start) + +1. **环境准备**: 确保已安装 Java (JDK 17+), Maven,并准备好所需的外部服务实例 (如 Redis, MongoDB, Kafka)。 +2. **配置检查**: 根据实际环境修改 `application.yml`,并确保 `airport_roads.yaml` 包含有效的道路数据。 +3. **构建项目**: 在项目根目录下运行 `./mvnw clean package` (或 `mvn clean package`)。 +4. **运行应用**: 执行 `java -jar target/CollisionAvoidance-0.0.1-SNAPSHOT.jar` 或通过 Maven 插件 `spring-boot:run` 启动。 + +## 📚 项目文档索引 (Documentation Index) + +本项目包含以下设计和说明文档,均位于 `doc/` 目录下: + +* [**目录结构说明 (directory_structure.md)**](doc/directory_structure.md) +* [**数据结构设计 (design_document.md)**](doc/design_document.md) +* [**配置文件说明 (configuration_guide.md)**](doc/configuration_guide.md) +* [**CAD 转 YAML 指南 (cad_to_yaml_guide.md)**](doc/cad_to_yaml_guide.md) +* [**道路网络设计方案 (road_network_design.md)**](doc/road_network_design.md) +* [**需求收集与分析 (requirements.md)**](doc/requirements.md) (如果适用) + +## 📄 许可证 (License) + +(请在此处添加项目的许可证信息,例如 MIT, Apache 2.0 等) \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index fe2c07e..60a2d3e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,3 +1 @@ -0.1. - -兼容性 兼容性满足(增加接口等) 业务逻辑 \ No newline at end of file +0.4.0 \ No newline at end of file diff --git a/change_log.md b/change_log.md index 5d10a4a..78f34ae 100644 --- a/change_log.md +++ b/change_log.md @@ -2,6 +2,12 @@ 本文档记录碰撞避免系统的所有重要变更,包括新功能、改进和修复。 +## [0.4.0] - 2025-04-21 + +### 新增 +- 道路网络服务 (`RoadNetworkService`),实现道路网络的配置文件处理和查询功能 +- 道路网络配置文件 (`airport_roads.yaml`),定义机场的道路网络信息 + ## [0.3.0] - 2025-03-31 ### 新增 diff --git a/development_log.md b/development_log.md index e6769e7..c4af532 100644 --- a/development_log.md +++ b/development_log.md @@ -1,101 +1,104 @@ # 碰撞避免系统开发日志 本文档详细记录了碰撞避免系统开发过程中的每日活动、决策、问题和解决方案。 -### 2025-03-31 + +## 2025-04-21 +- 完成道路网络的配置文件的设计和处理 +- 完成道路网络服务的设计和实现 + +## 2025-03-31 - 搭建WebSocket框架 - 完成WebSocket连接测试 -### 2025-03-28 +## 2025-03-28 - 完成坐标系统服务(`CoordinateSystemService`)的基本实现 - 实现WGS84到局部坐标系的转换算法 -### 2025-03-27 +## 2025-03-27 - 继续实现坐标系统服务(`CoordinateSystemService`) - 实现WGS84到局部坐标系的转换算法 -### 2025-03-26 +## 2025-03-26 - 开始实现坐标系统服务 - 研究并确定坐标转换算法 - 设计坐标系统服务的接口 -### 2025-03-25 +## 2025-03-25 - 设计数据处理模块的总体架构 - 创建《坐标转换》技术规范文档 -### 2025-03-24 +## 2025-03-24 - 完成速度计算等运动信息功能 - 解决时间间隔不均匀的处理问题 -### 2025-03-21 +## 2025-03-21 - 继续实现速度计算功能 - 开发运动轨迹预测算法 - 解决时间间隔不均匀的处理问题 -### 2025-03-20 +## 2025-03-20 - 开发速度计算模块 - 设计运动信息存储结构 -### 2025-03-19 +## 2025-03-19 - 开始计算速度等运动信息功能 - 完成实际坐标位置导入功能 -### 2025-03-18 +## 2025-03-18 - 继续实现坐标导入及封装功能 -### 2025-03-17 +## 2025-03-17 - 开始实际坐标位置坐标导入及封装 - 完成数据结构设计与封装 - 实现坐标数据解析和标准化 -### 2025-03-14 +## 2025-03-14 - 继续数据结构设计与封装 -### 2025-03-13 +## 2025-03-13 - 继续数据结构设计与封装 - 实现核心数据模型 - 开发数据转换 -### 2025-03-12 +## 2025-03-12 - 继续数据结构设计与封装 - 开发数据模型类 - 为三种移动物体类型设计不同的数据处理策略 -### 2025-03-11 +## 2025-03-11 - 继续数据结构设计与封装 - 分析系统数据需求和特性 - 设计数据访问接口 -### 2025-03-10 +## 2025-03-10 - 继续数据结构设计与封装 - 定义系统核心数据实体 - 规划数据层架构 -### 2025-03-07 +## 2025-03-07 - 继续数据结构设计与封装 - 设计初步数据模型 -### 2025-03-06 +## 2025-03-06 - 开始数据结构设计与封装 - 分析系统需求和数据特点 - 设计初步数据模型 -### 2025-03-05 +## 2025-03-05 - 完成定时任务接口Mock修改 - 调整Mock数据生成策略 - 准备数据结构设计工作 -### 2025-03-04 +## 2025-03-04 - 继续定时任务接口Mock测试 - 实现Mock服务和测试用例 - 解决测试中发现的问题 -### 2025-03-03 +## 2025-03-03 - 开始定时任务接口Mock测试 - 设计Mock数据结构和生成规则 - 建立测试环境和基础框架 -## 2025年2月 - -### 2025-02-28 +## 2025-02-28 - 进行项目可行性分析 - 制定初步技术方案 \ No newline at end of file diff --git a/doc/collision_detection_design.md b/doc/design/collision_detection_design.md similarity index 100% rename from doc/collision_detection_design.md rename to doc/design/collision_detection_design.md diff --git a/doc/坐标转换.txt b/doc/design/coordinate_transformation.md similarity index 100% rename from doc/坐标转换.txt rename to doc/design/coordinate_transformation.md diff --git a/doc/datacollector_design.md b/doc/design/datacollector_design.md similarity index 100% rename from doc/datacollector_design.md rename to doc/design/datacollector_design.md diff --git a/doc/design_document.md b/doc/design/design_document.md similarity index 79% rename from doc/design_document.md rename to doc/design/design_document.md index 36ee190..42af660 100644 --- a/doc/design_document.md +++ b/doc/design/design_document.md @@ -151,6 +151,32 @@ classDiagram - **SPECIAL_VEHICLE**: 特勤车辆(不可控) - **UNMANNED_VEHICLE**: 无人车(可控) +### 3.4 静态环境数据结构 - 道路网络 + +除了移动物体,系统还需要处理静态环境信息,特别是机场的道路网络。相关数据结构位于 `com.dongni.collisionavoidance.roads.model` 包下。 + +#### 3.4.1 道路信息 - RoadInfo + +`RoadInfo` 类封装了系统运行时使用的单条道路信息。它由 `RoadNetworkService` 在初始化时根据配置文件 (`airport_roads.yaml`) 创建,包含处理后的属性和用于空间计算的 JTS 几何对象。关键属性包括: + +- **id**: 道路的唯一标识符 (String)。 +- **name**: 道路名称 (String)。 +- **speedLimitMetersPerSecond**: 该道路的限速,已统一为米/秒 (Double)。 +- **directionality**: 道路方向性,使用 `RoadDirectionality` 枚举表示。 +- **heightLimitMeters**, **widthLimitMeters**: 限高和限宽,已统一为米 (Double)。 +- **prohibited**: 是否禁止通行 (boolean)。 +- **centerline**: 道路中心线的 JTS `LineString` 对象,用于路径分析和距离计算。 +- **boundary**: 道路边界范围的 JTS `Polygon` 对象,通过对中心线进行缓冲计算得到,用于判断车辆是否在道路上。 +- **relatedZones**: 与该道路关联的区域 ID 列表 (List)。 + +#### 3.4.2 道路方向性 - RoadDirectionality + +`RoadDirectionality` 是一个枚举类型,定义了道路的通行方向: + +- **ONE_WAY**: 单向通行。 +- **TWO_WAY**: 双向通行。 +- **UNKNOWN**: 未知或未指定。 + ## 4. 数据流转机制 ### 4.1 历史状态存储机制 diff --git a/doc/design/road_network_design.md b/doc/design/road_network_design.md new file mode 100644 index 0000000..1c47dd0 --- /dev/null +++ b/doc/design/road_network_design.md @@ -0,0 +1,112 @@ +# 设计方案:机场道路网络配置集成 + +## 1. 引言 + +本文档描述了将机场道路网络配置信息(定义在 `src/main/resources/config/airport_roads.yaml` 文件中)加载到碰撞避免系统中,并提供统一接口供其他模块使用的设计方案。目标是实现配置的结构化加载、地理空间表示和便捷查询。 + +## 2. 设计目标 + +* **自动加载**: 在应用程序启动时自动加载并解析 `airport_roads.yaml` 文件。 +* **类型安全**: 将 YAML 配置映射到强类型的 Java 对象。 +* **空间表示**: 使用标准的地理空间库 (JTS) 将道路中心线和边界表示为内存中的几何对象。 +* **统一访问**: 提供一个中心服务 (`RoadNetworkService`) 来封装道路数据的访问逻辑。 +* **高效查询**: 支持根据 ID 获取道路信息,以及基于地理位置(点)查询所在道路及其属性(如限速)。 +* **模块解耦**: 其他模块(如数据处理)通过依赖注入使用服务,无需关心配置文件的具体加载和解析细节。 + +## 3. 核心组件 + +1. **配置属性 POJOs**: 位于 `com.dongni.collisionavoidance.config.properties` 包下,用于映射 `airport_roads.yaml` 文件结构的 Java 类 (如 `AirportRoadsProperties`, `RoadProperties`, `GeometryProperties`, `DimensionValue`)。 +2. **配置加载器**: 位于 `com.dongni.collisionavoidance.config` 包下的 Spring 配置类 (如 `RoadNetworkConfig`) 和辅助类 (`YamlPropertySourceFactory`),负责指定加载 `airport_roads.yaml` 文件并启用属性绑定到 POJOs。 +3. **运行时数据模型 (`RoadInfo`)**: 位于 `com.dongni.collisionavoidance.roads.model` 包下,包含道路静态属性(ID, name, limits 等,已进行单位转换)以及预处理的 JTS 地理空间对象(`LineString` 类型的 `centerline` 和 `Polygon` 类型的 `boundary`)。 +4. **道路网络服务 (`RoadNetworkService`)**: 位于 `com.dongni.collisionavoidance.roads.service` 包下的 Spring Service Bean,负责初始化和提供道路数据查询。 + +## 4. 依赖库 + +* **Java Topology Suite (JTS)**: `org.locationtech.jts:jts-core:1.19.0` (或最新版) +* **SnakeYAML**: (通常由 Spring Boot 包含) +* **Lombok**: (可选) + +## 5. 实现细节 + +### 5.1 配置加载 + +* 使用 `@PropertySource` 注解配合自定义的 `YamlPropertySourceFactory` 来指定加载 `classpath:config/airport_roads.yaml`。 +* 使用 `@EnableConfigurationProperties` 将加载的配置绑定到 `AirportRoadsProperties` POJO 类及其嵌套类。 +* `AirportRoadsProperties` 类使用 `@ConfigurationProperties` (无前缀) 注解。 +* POJO 类结构需与 `airport_roads.yaml` 文件结构精确匹配(字段名、数据类型、列表、嵌套对象)。 + +### 5.2 运行时数据模型 (`RoadInfo`) + +此类应设计为不可变对象 (e.g., 使用 Lombok `@Value` 和 `@Builder`),包含以下关键信息: + +* `id`: String +* `name`: String +* `speedLimitMetersPerSecond`: Double (单位统一为 m/s) +* `directionality`: Enum (`RoadDirectionality`) +* `heightLimitMeters`: Double (单位统一为 m) +* `widthLimitMeters`: Double (单位统一为 m) +* `prohibited`: boolean +* `centerline`: `org.locationtech.jts.geom.LineString` (JTS 对象) +* `boundary`: `org.locationtech.jts.geom.Polygon` (JTS 对象) +* `relatedZones`: `List` + +### 5.3 道路网络服务 (`RoadNetworkService`) + +* **职责**: + * 注入 `AirportRoadsProperties`。 + * 在初始化阶段 (`@PostConstruct`) 执行以下操作: + 1. 遍历从配置加载的 `RoadProperties` 列表。 + 2. 对每个 `RoadProperties`: + * 使用 JTS `GeometryFactory` 将 `coordinates` 列表转换为 `LineString` 对象 (`centerline`)。 + * 根据 `width` 属性,使用 `LineString.buffer()` 方法计算道路边界 `Polygon` 对象 (`boundary`)。(注意:需要处理米到地理坐标度数的近似转换,或采用投影坐标系进行精确缓冲)。 + * 进行必要的单位转换(如速度 km/h -> m/s,长度单位 -> m)。 + * 解析方向性、禁止通行等属性。 + * 创建 `RoadInfo` 实例。 + 3. 将所有创建的 `RoadInfo` 实例存储在内存中的 Map 中 (e.g., `Map`),以 `roadId` 为键。 + 4. (推荐) 将所有 `RoadInfo` 的 `boundary` (Polygon) 添加到 JTS 空间索引 (如 `STRtree`) 中,以优化空间查询。 + * 提供公共方法用于查询道路信息。 +* **主要接口方法签名示例**: + ```java + public Optional getRoadById(String roadId); + public List findRoadsContainingPoint(GeoPosition geoPosition); + public Optional findDominantRoadAt(GeoPosition geoPosition); // 处理点在多条路重叠区域的情况 + public Optional getSpeedLimitMetersPerSecondAt(GeoPosition geoPosition); + // ... 其他必要的查询方法 ... + ``` +* **内部逻辑**: + * `findRoadsContainingPoint`: 使用空间索引 (`STRtree.query()`) 粗筛候选道路,然后使用 `Polygon.covers(Point)` 进行精确判断。 + * 包含健壮的单位转换和错误处理逻辑。 + +### 5.4 在其他模块中使用 + +* 需要访问道路信息的模块(如 `CollisionDetectionService`)通过 Spring 的依赖注入 (`@Autowired` 或构造函数注入) 获取 `RoadNetworkService` 实例。 +* 调用 `RoadNetworkService` 提供的公共方法获取道路属性或执行空间查询。 + +## 6. 目录结构影响 + +引入以下新的包和类: + +``` +src/main/java/com/dongni/collisionavoidance/ +├── config/ +│ ├── properties/ # 新包:存放配置属性 POJOs +│ │ ├── AirportRoadsProperties.java +│ │ ├── RoadProperties.java +│ │ ├── GeometryProperties.java +│ │ └── DimensionValue.java +│ ├── RoadNetworkConfig.java # 新类:加载道路配置 +│ └── YamlPropertySourceFactory.java # 新类(如果不存在) +├── roads/ # 新包:道路网络相关 +│ ├── model/ # 新包:运行时道路模型 +│ │ ├── RoadInfo.java +│ │ └── RoadDirectionality.java +│ └── service/ # 新包:道路服务 +│ └── RoadNetworkService.java +``` + +## 7. 未来考虑 + +* **精确缓冲**: 实现基于 UTM 或其他合适投影坐标系的缓冲计算。 +* **复杂交叉口**: 可能需要更高级的拓扑模型或规则处理。 +* **动态更新**: 考虑配置热加载机制。 +* **单位转换**: 实现更健壮的单位转换库。 \ No newline at end of file diff --git a/doc/速度计算系统设计.md b/doc/design/speed_calculation.md similarity index 100% rename from doc/速度计算系统设计.md rename to doc/design/speed_calculation.md diff --git a/doc/directory_structure.md b/doc/directory_structure.md deleted file mode 100644 index 2d2e0a7..0000000 --- a/doc/directory_structure.md +++ /dev/null @@ -1,139 +0,0 @@ -# 碰撞避免系统目录结构说明 - -本文档描述了碰撞避免系统项目的目录结构及各个目录的功能和用途。 - -## 1. 根目录结构 - -``` -CollisionAvoidanceSystem/ -├── doc/ # 文档目录,包含系统设计和说明文档 -├── src/ # 源代码目录 -├── target/ # 编译输出目录 -├── .idea/ # IntelliJ IDEA项目配置目录 -├── .mvn/ # Maven包装器配置目录 -├── .git/ # Git版本控制目录 -├── pom.xml # Maven项目配置文件 -├── mvnw # Maven包装器脚本(Unix/Linux) -├── mvnw.cmd # Maven包装器脚本(Windows) -├── README.md # 项目说明文档 -├── VERSION.txt # 版本信息文件 -├── change_log.md # 变更日志 -├── development_log.md # 开发日志 -├── .gitignore # Git忽略文件配置 -└── .gitattributes # Git属性配置 -``` - -## 2. 源代码目录结构 - -源代码目录(`src`)包含了项目的所有Java源代码和资源文件: - -``` -src/ -├── main/ # 主要源代码 -│ ├── java/ # Java源代码 -│ │ └── com/ -│ │ └── dongni/ -│ │ └── collisionavoidance/ # 应用程序主包 -│ └── resources/ # 配置文件和静态资源 -└── test/ # 测试源代码 -``` - -## 3. 应用程序主包结构 - -应用程序主包(`com.dongni.collisionavoidance`)组织如下: - -``` -com.dongni.collisionavoidance/ -├── CollisionAvoidanceApplication.java # 应用程序入口类 -├── common/ # 通用组件目录 -│ ├── model/ # 数据模型定义 -│ └── config/ # 通用配置 -├── config/ # 应用程序配置 -├── controller/ # 控制器层,处理HTTP请求 -├── dataCollector/ # 数据采集模块 -├── dataProcessing/ # 数据处理模块 -└── webSocket/ # WebSocket通信模块 -``` - -## 4. 数据模型目录 - -数据模型目录(`com.dongni.collisionavoidance.common.model`)包含系统的核心数据结构定义: - -``` -common/model/ -├── Aircraft.java # 航空器模型 -├── GeoPosition.java # 地理位置模型 -├── MovementState.java # 运动状态模型 -├── MovingObject.java # 移动物体抽象基类 -├── MovingObjectType.java # 移动物体类型枚举 -├── SpecialVehicle.java # 特勤车辆模型 -├── UnmannedVehicle.java # 无人车模型 -└── Velocity.java # 速度模型 -``` - -## 5. 各模块功能说明 - -### 5.1 数据模型 (common/model) - -数据模型模块定义了系统中使用的所有数据结构: - -- **MovingObject**: 所有移动物体的抽象基类,定义共有属性和行为 -- **Aircraft**: 代表航空器的具体实现类 -- **SpecialVehicle**: 代表特勤车辆的具体实现类 -- **UnmannedVehicle**: 代表无人车的具体实现类 -- **GeoPosition**: 表示地理位置的数据结构 -- **Velocity**: 表示速度和局部坐标系位置信息的数据结构 -- **MovementState**: 封装移动物体在特定时刻的完整状态 -- **MovingObjectType**: 定义了系统支持的移动物体类型的枚举 - -### 5.2 数据采集模块 (dataCollector) - -负责从各种数据源获取移动物体的实时位置和状态信息: - -- 航空器数据采集(ADS-B、雷达等) -- 特勤车辆数据采集(GPS、地面雷达等) -- 无人车数据采集(车载传感器等) - -### 5.3 数据处理模块 (dataProcessing) - -处理和分析采集到的数据: - -- 轨迹计算和预测 -- 碰撞风险评估 -- 数据质量检查和过滤 -- 历史数据管理和分析 - -### 5.4 控制器层 (controller) - -提供RESTful API接口,用于: - -- 数据查询和检索 -- 系统配置和管理 -- 状态报告和监控 - -### 5.5 WebSocket模块 (webSocket) - -提供实时通信功能: - -- 推送实时位置更新 -- 发送碰撞警告 -- 支持客户端实时监控 - -### 5.6 配置模块 (config) - -包含应用程序的配置类: - -- 系统参数配置 -- 服务注册和发现 -- 安全配置 -- 数据库连接配置 - -## 6. 文档目录 (doc) - -包含系统相关的设计文档和说明文档: - -``` -doc/ -├── design_document.md # 系统数据结构设计文档 -└── directory_structure.md # 目录结构说明文档(本文档) -``` \ No newline at end of file diff --git a/doc/guide/cad_to_yaml_guide.md b/doc/guide/cad_to_yaml_guide.md new file mode 100644 index 0000000..fe6cbd5 --- /dev/null +++ b/doc/guide/cad_to_yaml_guide.md @@ -0,0 +1,198 @@ +# 操作指南:从 CAD 图纸生成 airport_roads.yaml 配置文件 + +## 1. 目标与挑战 + +本指南旨在说明如何使用免费开源的 GIS 软件 QGIS,从机场的 CAD 工程图纸(通常使用局部坐标系)生成包含精确地理坐标(经纬度,WGS84)的 `airport_roads.yaml` 配置文件,以供应用程序使用。 + +主要挑战在于将 CAD 的局部坐标系转换为标准的 WGS84 地理坐标系,这需要准确的地理参考信息。 + +## 2. 准备工作 + +在开始之前,请确保你拥有: + +* **QGIS 软件**: 从 [QGIS 官网](https://qgis.org/) 下载并安装最新稳定版。 +* **机场 CAD 图纸**: `.dwg` 或 `.dxf` 格式的文件。最好是包含清晰道路中心线的版本。 +* **地面控制点 (GCPs - Ground Control Points)**: **这是最关键的部分!** 你需要获取 CAD 图上至少 3-4 个(越多越好,分布越均匀越好)易于识别的点(如建筑角点、跑道端点、特定标记)的**精确真实世界地理坐标**。坐标可以是: + * **经纬度 (WGS84)**: 例如 `[经度 113.12345, 纬度 22.54321]` + * **投影坐标**: 例如 UTM 坐标,并清楚知道其对应的区域和基准面。 + * 获取方式:可能来自现场 GPS 测量、官方测绘数据、高精度卫星地图的比对等。**控制点的精度直接决定了最终结果的精度。** + +## 3. QGIS 安装 + +访问 [QGIS 官网](https://qgis.org/),根据你的操作系统下载并安装 QGIS Desktop。 + +## 4. CAD 文件准备(可选但推荐) + +为了简化后续操作,建议在 CAD 软件中: + +* 清理图纸,只保留必要的图层,尤其是**道路中心线**图层和用于**地理配准的参考点**所在图层。 +* 确保道路中心线是连接良好的线段 (Polyline)。 +* 如果可能,将文件另存为较旧版本的 `.dxf` 格式,有时兼容性更好。 + +## 5. 导入 CAD 文件到 QGIS + +1. 打开 QGIS。 +2. 通过菜单 `图层 (Layer)` -> `添加图层 (Add Layer)` -> `添加矢量图层 (Add Vector Layer...)` 打开数据源管理器。 +3. 在 `矢量 (Vector)` 选项卡中,选择 `文件 (File)` 类型。 +4. 点击 `源 (Source)` 旁边的 `...` 按钮,浏览并选择你的 `.dxf` 或 `.dwg` 文件。 +5. 点击 `添加 (Add)`。QGIS 可能会询问要导入哪些图层(如果 CAD 文件包含多个图层),选择包含道路中心线和参考点的图层。 +6. 关闭数据源管理器。你现在应该能在 QGIS 地图中看到 CAD 图纸的内容。此时,它的坐标系还是未知的或局部的。 + +## 6. 地理配准 (Georeferencing) + +这是将 CAD 局部坐标转换为地理坐标的关键步骤。 + +1. **打开地理配准器**: 通过菜单 `图层 (Layer)` -> `地理配准器 (Georeferencer...)` 打开工具。 +2. **加载 CAD 图层**: 在地理配准器窗口中,点击 `打开栅格 (Open Raster)` 按钮(虽然 CAD 是矢量,但通常在此工具中作为背景加载)。选择你刚刚导入到 QGIS 主窗口中的 CAD 图层。(如果无法直接加载矢量,可能需要先将 CAD 图层导出为图像格式如 TIFF,再加载该图像进行配准,但这会损失矢量特性,优先尝试直接加载)。 + * *备选方案*:如果直接加载 CAD 困难,可以在 QGIS 主窗口将 CAD 图层导出为高分辨率图像(`项目` -> `导入/导出` -> `导出地图为图像`),然后在地理配准器中加载该图像。 +3. **添加地面控制点 (GCPs)**: + * 在地理配准器地图窗口中,找到你的第一个已知控制点(例如,某个建筑的角点)。 + * 使用工具栏上的 `添加点 (Add Point)` 工具,在图上精确点击该点。 + * 会弹出一个 `输入地图坐标 (Enter map coordinates)` 的对话框。**不要输入** X/Y 坐标(那是源坐标,让 QGIS 自动获取),而是点击 `来自地图画布 (From map canvas)`(如果你的控制点在另一地图层可见)或**手动输入该点的真实世界坐标**(经度 Longitude 对应 X,纬度 Latitude 对应 Y)。**确保输入的是 WGS84 经纬度坐标!** 点击 `确定 (OK)`。 + * 该控制点会出现在下方的 GCP 表中。 + * 重复此过程,为所有已知的控制点添加映射关系(至少 3 个,推荐 4 个以上,分布均匀)。GCP 表中的 `dX`, `dY` 和 `残差 (Residual)` 列可以帮助判断点的精度,残差值越小越好。 +4. **设置变换参数**: + * 点击工具栏上的 `变换设置 (Transformation settings)` 按钮(黄色齿轮图标)。 + * **变换类型 (Transformation type)**: 根据控制点数量和分布选择。`线性 (Linear)` 适用于只有少数点或简单变换;`Helmert` 或 `多项式1/2/3 (Polynomial 1/2/3)` 更常用,能处理更复杂的形变。`薄板样条 (Thin Plate Spline)` 适用于需要局部精确变形的情况。可以先尝试 `多项式 1` 或 `2`。 + * **重采样方法 (Resampling method)**: 如果是基于栅格配准,选 `最近邻 (Nearest neighbour)`。 + * **目标坐标系 (Target CRS)**: **极其重要!** 点击 `选择 CRS (Select CRS)` 按钮,搜索并选择 `WGS 84` (其 EPSG 代码通常是 **4326**)。 + * **输出栅格 (Output raster)**: 指定配准后文件的保存位置和名称。建议保存为 GeoPackage (`.gpkg`) 或 GeoTIFF (`.tif`) 格式。 + * 勾选 `完成后在 QGIS 中加载 (Load in QGIS when done)`。 + * 点击 `确定 (OK)`。 +5. **执行地理配准**: 点击工具栏上的 `开始地理配准 (Start Georeferencing)` 按钮(绿色播放图标)。 +6. 配准完成后,关闭地理配准器。新的、已地理配准的图层会添加到 QGIS 主窗口。你可以通过添加一个在线地图背景(如 OpenStreetMap)来验证配准效果是否准确。 + +## 7. 数字化道路中心线 + +现在,以地理配准后的图层为底图,绘制道路中心线并添加属性。 + +1. **创建新图层**: + * 菜单 `图层 (Layer)` -> `创建图层 (Create Layer)` -> `新建 GeoPackage 图层 (New GeoPackage Layer...)` (推荐) 或 `新建 Shapefile 图层 (New Shapefile Layer...)`。 + * **文件名**: 指定保存位置和文件名,例如 `airport_roads.gpkg`。 + * **图层名称**: 例如 `roads_centerline`。 + * **几何图形类型**: 选择 `线串 (LineString)` 或 `多段线 (MultiLineString)`。 + * **坐标系**: **必须选择 WGS 84 (EPSG:4326)**。 + * **定义属性字段**: 在 `新建字段 (New Field)` 部分,根据 `airport_roads.yaml` 的结构添加所需的字段。例如: + * `road_id` (类型: 文本 Text/String) + * `name` (类型: 文本 Text/String) + * `width_value` (类型: 十进制数 Decimal/Real) + * `width_unit` (类型: 文本 Text/String, 默认值可设为 'm') + * `speed_limit_value` (类型: 整数 Integer 或 十进制数 Decimal) + * `speed_limit_unit` (类型: 文本 Text/String, 默认值可设为 'km/h') + * `directionality` (类型: 文本 Text/String) + * `prohibited` (类型: 布尔 Boolean 或 整数 Integer 0/1) + * `height_limit_value` (类型: 十进制数 Decimal) + * `height_limit_unit` (类型: 文本 Text/String, 默认值 'm') + * `width_limit_value` (类型: 十进制数 Decimal) + * `width_limit_unit` (类型: 文本 Text/String, 默认值 'm') + * `related_zones` (类型: 文本 Text/String, 用于存储关联区域 ID 列表,可能需要后续处理) + * 点击 `添加到字段列表 (Add to Fields List)` 添加每个字段。 + * 点击 `确定 (OK)` 创建图层。 +2. **开始编辑**: + * 在 `图层 (Layers)` 面板中选中新建的 `roads_centerline` 图层。 + * 点击工具栏上的 `切换编辑模式 (Toggle Editing)` 按钮(铅笔图标)。 + * 点击 `添加线要素 (Add Line Feature)` 按钮(带绿色加号的线图标)。 +3. **绘制道路**: + * 将鼠标移动到地理配准后的底图上,沿着一条道路的中心线开始点击。 + * **左键单击** 添加顶点。对于**曲线**,需要**密集地添加顶点**来近似。 + * 完成一条道路的绘制后,**右键单击** 结束绘制。 +4. **填充属性**: + * 右键单击结束绘制后,会弹出一个属性表单窗口。 + * 为刚刚绘制的道路填入对应的属性值(`road_id`, `name`, `width_value` 等)。 + * 点击 `确定 (OK)`。 + * 重复步骤 3 和 4,绘制并录入所有需要的道路。 +5. **保存编辑**: 完成绘制后,再次点击 `切换编辑模式 (Toggle Editing)` 按钮,并在提示时选择 `保存 (Save)`。 + +## 8. 导出数字化道路为 GeoJSON + +GeoJSON 是易于程序处理的格式。 + +1. 在 `图层 (Layers)` 面板中,右键单击 `roads_centerline` 图层。 +2. 选择 `导出 (Export)` -> `要素另存为... (Save Features As...)`。 +3. **格式**: 选择 `GeoJSON`。 +4. **文件名**: 指定导出的 GeoJSON 文件名和保存位置,例如 `airport_roads.geojson`。 +5. **坐标系 (CRS)**: 确保选择的是 `WGS 84 (EPSG:4326)`。 +6. **导出字段**: 确保所有需要的属性字段都被勾选导出。 +7. **几何图形**: 可以设置坐标精度(小数位数),根据需要调整。 +8. 点击 `确定 (OK)`。 + +## 9. 从 GeoJSON 生成 YAML + +现在你有了一个包含所有道路几何和属性的 GeoJSON 文件。你需要编写一个脚本(例如 Python)来将其转换为 `airport_roads.yaml` 格式。 + +**脚本逻辑概要 (以 Python 为例):** + +```python +import json +import yaml # 需要安装 PyYAML: pip install pyyaml + +geojson_file = 'airport_roads.geojson' +yaml_file = 'src/main/resources/config/airport_roads.yaml' # 目标路径 + +output_data = { + 'airport_code': 'XYZ', # 或者从其他地方获取 + 'roads': [] +} + +with open(geojson_file, 'r', encoding='utf-8') as f: + geojson_data = json.load(f) + +for feature in geojson_data['features']: + props = feature['properties'] + coords = feature['geometry']['coordinates'] + + road_entry = { + 'id': props.get('road_id'), + 'name': props.get('name'), + 'geometry': { + 'type': 'LineString', + 'coordinates': coords # GeoJSON 的坐标列表可以直接用 + }, + 'width': { + 'value': props.get('width_value'), + 'unit': props.get('width_unit', 'm') + }, + 'speed_limit': { + 'value': props.get('speed_limit_value'), + 'unit': props.get('speed_limit_unit', 'km/h') + }, + 'directionality': props.get('directionality'), + # ... 其他字段类似处理 ... + # 注意处理 None 或缺失值,以及数据类型转换 + 'prohibited': bool(props.get('prohibited', False)), # 示例:处理布尔值 + # height_limit, width_limit 需要判断值是否存在再添加 + # related_zones 可能需要特殊处理,例如如果 GeoJSON 里是逗号分隔字符串,这里要转成列表 + } + + # 添加可选字段 + if props.get('height_limit_value') is not None: + road_entry['height_limit'] = { + 'value': props.get('height_limit_value'), + 'unit': props.get('height_limit_unit', 'm') + } + if props.get('width_limit_value') is not None: + road_entry['width_limit'] = { + 'value': props.get('width_limit_value'), + 'unit': props.get('width_limit_unit', 'm') + } + if props.get('related_zones'): + # 假设 related_zones 在 QGIS 中是以逗号分隔的字符串输入的 + related_zones_list = [zone.strip() for zone in props.get('related_zones').split(',')] + road_entry['related_zones'] = related_zones_list + + + output_data['roads'].append(road_entry) + +# 写入 YAML 文件 +with open(yaml_file, 'w', encoding='utf-8') as f: + yaml.dump(output_data, f, allow_unicode=True, default_flow_style=False, sort_keys=False) + +print(f"YAML 文件已生成: {yaml_file}") + +``` + +你需要根据你在 QGIS 中定义的实际字段名和数据类型来调整脚本。 + +## 10. 总结 + +通过以上步骤,你可以将 CAD 图纸中的道路信息转换为包含精确地理坐标的 YAML 配置文件。关键在于获取准确的地面控制点 (GCPs) 以及在 QGIS 中细致地完成地理配准和数字化工作。这个过程可能比较耗时,但能确保配置数据的质量。 \ No newline at end of file diff --git a/doc/configuration_guide.md b/doc/guide/configuration_guide.md similarity index 85% rename from doc/configuration_guide.md rename to doc/guide/configuration_guide.md index 9ff6e81..0a2ce96 100644 --- a/doc/configuration_guide.md +++ b/doc/guide/configuration_guide.md @@ -132,6 +132,29 @@ public class ThreadPoolConfig { - 避免数据处理任务阻塞主线程 - 优化系统资源利用,提高处理效率 +### 3.3 道路网络配置 (RoadNetworkConfig & Properties) + +**功能**: 加载和管理机场道路网络静态配置 + +**配置源**: `src/main/resources/config/airport_roads.yaml` + +**说明**: +- 定义了一套 POJO 类(位于 `com.dongni.collisionavoidance.config.properties` 包下,如 `AirportRoadsProperties`, `RoadProperties` 等)来精确映射 `airport_roads.yaml` 文件的结构。 +- `AirportRoadsProperties` 类使用 `@ConfigurationProperties` 注解(无前缀)来声明其属性来源于配置文件。 +- `RoadNetworkConfig.java` 类(位于 `com.dongni.collisionavoidance.config` 包下)使用 `@Configuration`, `@EnableConfigurationProperties(AirportRoadsProperties.class)` 和 `@PropertySource` 注解。 +- `@PropertySource` 指定加载 `airport_roads.yaml` 文件,并指定 `YamlPropertySourceFactory.java` 作为解析工厂。 +- `@EnableConfigurationProperties` 激活 `AirportRoadsProperties` 成为一个 Spring Bean,其属性值会自动从加载的 YAML 文件中填充。 + +**关键组件**: +- `config/properties/*.java`: 配置属性 POJO 类。 +- `config/RoadNetworkConfig.java`: 启用配置加载的主配置类。 +- `config/YamlPropertySourceFactory.java`: 支持 `@PropertySource` 加载 YAML 的工厂类。 + +**用途**: +- 将静态的道路网络信息(几何、限速、限制等)加载到内存中。 +- 为 `RoadNetworkService` 提供原始配置数据,以便其进行处理和初始化。 +- 实现道路网络配置与应用程序代码的解耦。 + ## 4. 数据采集模块配置 (dataCollector/config) 数据采集模块配置位于`com.dongni.collisionavoidance.dataCollector.config`包下,专注于数据采集相关的配置。 @@ -303,3 +326,6 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 5. **序列化配置** (JacksonConfig) - 确保系统中JSON数据的一致性处理 + +6. **道路网络配置** (RoadNetworkConfig & Properties) + - 加载静态道路几何、限速、限制等信息,为数据处理模块提供关键的环境上下文。 diff --git a/doc/guide/directory_structure.md b/doc/guide/directory_structure.md new file mode 100644 index 0000000..dacf54c --- /dev/null +++ b/doc/guide/directory_structure.md @@ -0,0 +1,164 @@ +# 碰撞避免系统目录结构说明 + +本文档描述了碰撞避免系统项目的目录结构及各个目录的功能和用途。 + +## 1. 根目录结构 + +``` +CollisionAvoidanceSystem/ +├── doc/ # 文档目录 +├── src/ # 源代码目录 +├── target/ # 编译输出目录 +├── .idea/ # IDE配置 +├── .mvn/ # Maven包装器配置 +├── .git/ # Git版本控制 +├── pom.xml # Maven项目配置 +├── mvnw / mvnw.cmd # Maven包装器脚本 +├── README.md # 项目说明 +├── VERSION.txt # 版本信息 +├── change_log.md # 变更日志 +├── development_log.md # 开发日志 +├── .gitignore # Git忽略配置 +└── .gitattributes # Git属性配置 +``` + +## 2. 源代码目录结构 (`src`) + +``` +src/ +├── main/ # 主要源代码 +│ ├── java/ # Java源代码 +│ │ └── com/ +│ │ └── dongni/ +│ │ └── collisionavoidance/ # 应用程序主包 +│ └── resources/ # 配置文件和静态资源 +│ ├── config/ # 特定配置文件 (如 airport_roads.yaml) +│ └── static/ # 静态Web资源 +└── test/ # 测试源代码 +``` + +## 3. 应用程序主包结构 (`com.dongni.collisionavoidance`) + +``` +com.dongni.collisionavoidance/ +├── CollisionAvoidanceApplication.java # 应用程序入口类 +├── common/ # 通用组件目录 +│ ├── model/ # 核心移动对象等数据模型 +│ └── config/ # 通用配置 (较少使用,优先模块内配置) +├── config/ # 应用程序配置 +│ ├── properties/ # 配置属性映射类 (POJOs) +│ ├── RoadNetworkConfig.java # 道路网络配置加载类 +│ └── YamlPropertySourceFactory.java # YAML加载工厂类 +│ └── ... (其他配置类如 RedisConfig) +├── controller/ # 控制器层 (REST API) +├── dataCollector/ # 数据采集模块 +├── dataProcessing/ # 数据处理模块 +├── roads/ # 新增:道路网络模块 +│ ├── model/ # 道路网络运行时模型 +│ └── service/ # 道路网络服务 +└── webSocket/ # WebSocket通信模块 +``` + +## 4. 数据模型目录 + +### 4.1 通用数据模型 (`common/model`) + +包含系统核心的、跨模块共享的数据结构: + +``` +common/model/ +├── Aircraft.java +├── GeoPosition.java +├── MovementState.java +├── MovingObject.java +├── MovingObjectType.java +├── SpecialVehicle.java +├── UnmannedVehicle.java +└── Velocity.java +``` + +### 4.2 道路网络数据模型 (`roads/model`) + +包含道路网络模块内部使用的运行时数据结构: + +``` +roads/model/ +├── RoadInfo.java # 运行时道路信息 (含JTS对象) +└── RoadDirectionality.java # 道路方向枚举 +``` + +## 5. 各模块功能说明 + +### 5.1 通用数据模型 (common/model) + +数据模型模块定义了系统中使用的所有数据结构: + +- **MovingObject**: 所有移动物体的抽象基类,定义共有属性和行为 +- **Aircraft**: 代表航空器的具体实现类 +- **SpecialVehicle**: 代表特勤车辆的具体实现类 +- **UnmannedVehicle**: 代表无人车的具体实现类 +- **GeoPosition**: 表示地理位置的数据结构 +- **Velocity**: 表示速度和局部坐标系位置信息的数据结构 +- **MovementState**: 封装移动物体在特定时刻的完整状态 +- **MovingObjectType**: 定义了系统支持的移动物体类型的枚举 + +### 5.2 数据采集模块 (dataCollector) + +负责从各种数据源获取移动物体的实时位置和状态信息: + +- 航空器数据采集(ADS-B、雷达等) +- 特勤车辆数据采集(GPS、地面雷达等) +- 无人车数据采集(车载传感器等) + +### 5.3 数据处理模块 (dataProcessing) + +处理和分析采集到的数据: + +- 轨迹计算和预测 +- 碰撞风险评估 +- 数据质量检查和过滤 +- 历史数据管理和分析 + +### 5.4 控制器层 (controller) + +提供RESTful API接口,用于: + +- 数据查询和检索 +- 系统配置和管理 +- 状态报告和监控 + +### 5.5 WebSocket模块 (webSocket) + +提供实时通信功能: + +- 推送实时位置更新 +- 发送碰撞警告 +- 支持客户端实时监控 + +### 5.6 配置模块 (config) + +包含应用程序的配置类和配置加载机制: + +- **config/properties**: 存放用于绑定配置文件的 POJO 类 (例如 `AirportRoadsProperties`)。 +- 系统参数配置、Bean 配置 (如 `RedisConfig`, `ThreadPoolConfig`)。 +- 特定配置加载器 (如 `RoadNetworkConfig`, `YamlPropertySourceFactory`)。 +- 服务注册、安全、数据库连接等配置。 + +### 5.7 道路网络模块 (roads) + +新增模块,负责管理和查询机场静态道路网络信息: + +- **roads/model**: 定义运行时的道路数据结构 (`RoadInfo`),包含 JTS 几何对象和处理过的属性。 +- **roads/service**: 提供 `RoadNetworkService`,负责加载 `airport_roads.yaml` 配置,初始化道路数据和空间索引,并提供查询接口(如根据位置查找道路、获取限速等)。 + +## 6. 文档目录 (doc) + +包含系统相关的设计文档和说明文档: + +``` +doc/ +├── design_document.md # 系统数据结构设计文档 +├── directory_structure.md # 目录结构说明文档(本文档) +├── cad_to_yaml_guide.md # CAD转YAML操作指南 +└── road_network_design.md # 道路网络配置集成设计方案 +``` \ No newline at end of file diff --git a/doc/requirement/requirements.md b/doc/requirement/requirements.md new file mode 100644 index 0000000..80381c4 --- /dev/null +++ b/doc/requirement/requirements.md @@ -0,0 +1,18 @@ +# 需求收集和分析 + +## 需求列表(按时间跟踪) + +### 2025-03-12 + +- 需求:超速预警,根据机场划定的区域限速,当车辆超过限速时,进行预警 +- 分析: + - 需要根据机场划定的区域限速,需要获取机场的区域限速数据 + - 需要获取车辆的实时位置数据 + - 需要对车辆进行超速预警 +- 功能模块: + - 数据采集模块:获取车辆的实时位置数据(具备) + - 数据处理模块:根据机场的区域限速数据,对车辆进行超速预警(新增) + - 数据存储模块:存储车辆的实时位置数据和超速预警数据(增加超速预警数据) + - 告警模块:对车辆进行超速预警(增加超速预警类型) + - 配置模块:配置机场的区域限速数据(新增) + - 通信模块:WebSocket通信(增加超速预警事件) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 516e222..d81da44 100644 --- a/pom.xml +++ b/pom.xml @@ -133,6 +133,13 @@ spring-boot-starter-websocket + + + org.locationtech.jts + jts-core + 1.19.0 + + diff --git a/src/main/java/com/dongni/collisionavoidance/config/RoadNetworkConfig.java b/src/main/java/com/dongni/collisionavoidance/config/RoadNetworkConfig.java new file mode 100644 index 0000000..12297f9 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/RoadNetworkConfig.java @@ -0,0 +1,19 @@ +package com.dongni.collisionavoidance.config; + +import com.dongni.collisionavoidance.config.properties.AirportRoadsProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +/** + * Configuration class responsible for loading the airport_roads.yaml file + * and enabling the binding of its properties to the AirportRoadsProperties bean. + */ +@Configuration +@EnableConfigurationProperties(AirportRoadsProperties.class) +@PropertySource(value = "classpath:config/airport_roads.yaml", factory = YamlPropertySourceFactory.class) +public class RoadNetworkConfig { + // This class primarily uses annotations to configure the loading + // of road network properties. No explicit bean definitions are needed here + // for the properties themselves, as @EnableConfigurationProperties handles it. +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/YamlPropertySourceFactory.java b/src/main/java/com/dongni/collisionavoidance/config/YamlPropertySourceFactory.java new file mode 100644 index 0000000..3cf9de0 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/YamlPropertySourceFactory.java @@ -0,0 +1,41 @@ +package com.dongni.collisionavoidance.config; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +import org.springframework.lang.NonNull; + +import java.io.IOException; +import java.util.Properties; + +/** + * Custom PropertySourceFactory to allow loading properties from YAML files + * using the @PropertySource annotation. + */ +public class YamlPropertySourceFactory implements PropertySourceFactory { + + @Override + @NonNull + public PropertySource createPropertySource(String name, @NonNull EncodedResource resource) throws IOException { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(resource.getResource()); + + Properties properties = factory.getObject(); + + // Ensure properties is not null before creating the PropertySource + if (properties == null) { + throw new IllegalStateException("Failed to load YAML properties from resource: " + resource.getResource()); + } + + // Use the filename as the name of the PropertySource if 'name' is null + String sourceName = (name != null) ? name : resource.getResource().getFilename(); + if (sourceName == null) { + // Fallback if filename is also null (should be rare) + sourceName = "yamlPropertySource"; + } + + return new PropertiesPropertySource(sourceName, properties); + } +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/properties/AirportRoadsProperties.java b/src/main/java/com/dongni/collisionavoidance/config/properties/AirportRoadsProperties.java new file mode 100644 index 0000000..dabbe8d --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/AirportRoadsProperties.java @@ -0,0 +1,26 @@ +package com.dongni.collisionavoidance.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * Maps the entire content of the airport_roads.yaml file. + * Activated by @EnableConfigurationProperties in a @Configuration class. + */ +@ConfigurationProperties // No prefix, maps the root of the YAML file +@Data +public class AirportRoadsProperties { + + /** + * Optional airport identifier code. + */ + private String airportCode; + + /** + * List of road definitions. + */ + private List roads; + +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/properties/DimensionValue.java b/src/main/java/com/dongni/collisionavoidance/config/properties/DimensionValue.java new file mode 100644 index 0000000..2d9da86 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/DimensionValue.java @@ -0,0 +1,20 @@ +package com.dongni.collisionavoidance.config.properties; + +import lombok.Data; + +/** + * Represents a dimension with a value and a unit, used in YAML configuration. + */ +@Data +public class DimensionValue { + /** + * The numeric value of the dimension. + * Using Double to accommodate both integer and decimal values from YAML. + */ + private Double value; + + /** + * The unit of the dimension (e.g., "m", "km/h"). + */ + private String unit; +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/properties/GeometryProperties.java b/src/main/java/com/dongni/collisionavoidance/config/properties/GeometryProperties.java new file mode 100644 index 0000000..5a224fd --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/GeometryProperties.java @@ -0,0 +1,22 @@ +package com.dongni.collisionavoidance.config.properties; + +import lombok.Data; + +import java.util.List; + +/** + * Represents the geometry part of a road in the YAML configuration. + */ +@Data +public class GeometryProperties { + /** + * The type of geometry, expected to be "LineString". + */ + private String type; + + /** + * List of coordinates defining the geometry. + * For LineString, it's a list of [longitude, latitude] pairs. + */ + private List> coordinates; +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/properties/RoadProperties.java b/src/main/java/com/dongni/collisionavoidance/config/properties/RoadProperties.java new file mode 100644 index 0000000..ad7489b --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/RoadProperties.java @@ -0,0 +1,61 @@ +package com.dongni.collisionavoidance.config.properties; + +import lombok.Data; + +import java.util.List; + +/** + * Represents a single road definition from the airport_roads.yaml configuration. + */ +@Data +public class RoadProperties { + /** + * Unique identifier for the road. + */ + private String id; + + /** + * Human-readable name of the road. + */ + private String name; + + /** + * Geometric definition of the road's centerline. + */ + private GeometryProperties geometry; + + /** + * Physical width of the road. + */ + private DimensionValue width; + + /** + * Speed limit on the road. + */ + private DimensionValue speedLimit; + + /** + * Directionality constraint (e.g., "ONE_WAY", "TWO_WAY"). + */ + private String directionality; + + /** + * Whether passage is prohibited (true/false). Defaults to false if null/missing in YAML. + */ + private Boolean prohibited; + + /** + * Height restriction for vehicles. + */ + private DimensionValue heightLimit; + + /** + * Width restriction for vehicles. + */ + private DimensionValue widthLimit; + + /** + * List of zone IDs related to this road. + */ + private List relatedZones; +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java new file mode 100644 index 0000000..8fdd996 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java @@ -0,0 +1,21 @@ +package com.dongni.collisionavoidance.roads.model; + +/** + * Enumeration representing the directionality of a road. + */ +public enum RoadDirectionality { + /** + * Traffic flows in only one direction. + */ + ONE_WAY, + + /** + * Traffic flows in both directions. + */ + TWO_WAY, + + /** + * Directionality is unknown or not specified. + */ + UNKNOWN +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/roads/model/RoadInfo.java b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadInfo.java new file mode 100644 index 0000000..ee0dd1a --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadInfo.java @@ -0,0 +1,69 @@ +package com.dongni.collisionavoidance.roads.model; + +import lombok.Builder; +import lombok.Value; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Polygon; + +import java.util.List; + +/** + * Represents the runtime information for a single road, including processed attributes + * and JTS geometry objects. This is typically an immutable object. + */ +@Value +@Builder +public class RoadInfo { + /** + * Unique identifier for the road. + */ + String id; + + /** + * Human-readable name of the road. + */ + String name; + + /** + * Speed limit in meters per second. + * Null if not specified or invalid. + */ + Double speedLimitMetersPerSecond; + + /** + * Directionality of the road. + */ + RoadDirectionality directionality; + + /** + * Height restriction in meters. + * NaN or null if not applicable. + */ + Double heightLimitMeters; + + /** + * Width restriction in meters. + * NaN or null if not applicable. + */ + Double widthLimitMeters; + + /** + * Indicates if the road is prohibited for passage. + */ + boolean prohibited; + + /** + * The JTS LineString representing the road's centerline. + */ + LineString centerline; + + /** + * The JTS Polygon representing the road's boundary (calculated via buffering). + */ + Polygon boundary; + + /** + * List of zone IDs related to this road. + */ + List relatedZones; +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/roads/service/RoadNetworkService.java b/src/main/java/com/dongni/collisionavoidance/roads/service/RoadNetworkService.java new file mode 100644 index 0000000..1212cc4 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/roads/service/RoadNetworkService.java @@ -0,0 +1,297 @@ +package com.dongni.collisionavoidance.roads.service; + +import com.dongni.collisionavoidance.common.model.GeoPosition; // Assuming GeoPosition is in common.model +import com.dongni.collisionavoidance.roads.model.RoadDirectionality; +import com.dongni.collisionavoidance.roads.model.RoadInfo; +import com.dongni.collisionavoidance.config.properties.AirportRoadsProperties; +import com.dongni.collisionavoidance.config.properties.DimensionValue; +import com.dongni.collisionavoidance.config.properties.RoadProperties; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.index.strtree.STRtree; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Service responsible for managing and providing access to the airport road network data. + * It loads road configuration, processes it into runtime models with JTS geometry, + * and provides query capabilities. + */ +@Service +@RequiredArgsConstructor // Lombok: Injects final fields via constructor +@Slf4j +public class RoadNetworkService { + + private final AirportRoadsProperties airportRoadsProperties; + // Use ConcurrentHashMap for thread safety if needed in the future + private final Map roadInfoMap = new ConcurrentHashMap<>(); + // WGS84 SRID is 4326. PrecisionModel can be adjusted if needed. + private final GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326); + // STRtree is a common choice for static spatial indexing in JTS + private STRtree spatialIndex = new STRtree(); // Initialize empty + + @PostConstruct + public void initialize() { + log.info("Initializing Road Network Service..."); + if (airportRoadsProperties == null || airportRoadsProperties.getRoads() == null || airportRoadsProperties.getRoads().isEmpty()) { + log.warn("Airport roads properties not loaded or empty. Road network will be inactive."); + // Ensure spatialIndex is built even if empty, to avoid NullPointerExceptions later + spatialIndex.build(); + return; + } + + List loadedRoads = new ArrayList<>(); + for (RoadProperties props : airportRoadsProperties.getRoads()) { + if (props.getId() == null) { + log.warn("Skipping road definition due to missing ID: {}", props.getName()); + continue; + } + try { + // 1. Create centerline LineString + if (props.getGeometry() == null || props.getGeometry().getCoordinates() == null || props.getGeometry().getCoordinates().size() < 2) { + log.warn("Skipping road ID [{}] due to invalid geometry (less than 2 coordinates)", props.getId()); + continue; + } + Coordinate[] coords = props.getGeometry().getCoordinates().stream() + .filter(coord -> coord != null && coord.size() >= 2) + .map(coord -> new Coordinate(coord.get(0), coord.get(1))) // lon, lat + .toArray(Coordinate[]::new); + // Validate coordinate array length again after filtering nulls + if (coords.length < 2) { + log.warn("Skipping road ID [{}] after filtering invalid coordinates, resulting in less than 2 valid points", props.getId()); + continue; + } + LineString centerline = geometryFactory.createLineString(coords); + + // 2. Calculate boundary Polygon (using approximate buffering for now) + double widthMeters = convertDimensionToMeters(props.getWidth()); + Polygon boundary; + if (!Double.isNaN(widthMeters) && widthMeters > 0) { + // TODO: Implement more accurate buffering (e.g., project to UTM) + double bufferDistanceDegrees = metersToApproximateDegrees(widthMeters / 2.0, centerline.getCentroid().getY()); + // Use BufferParameters for end cap style if needed (default is round) + boundary = (Polygon) centerline.buffer(bufferDistanceDegrees); + } else { + log.warn("Road ID [{}] has invalid or zero width. Boundary will not be calculated accurately.", props.getId()); + // Create a minimal envelope or empty polygon as a fallback? + // For now, let's skip adding roads without a valid boundary to the index, or handle queries carefully. + boundary = geometryFactory.createPolygon(); // Create an empty polygon + // Or alternatively: boundary = (Polygon) centerline.getEnvelope(); - Not really the boundary + } + + // 3. Unit conversions and attribute mapping + Double speedLimitMps = convertSpeedToMps(props.getSpeedLimit()); + Double heightLimitM = convertDimensionToMeters(props.getHeightLimit()); + Double widthLimitM = convertDimensionToMeters(props.getWidthLimit()); + RoadDirectionality directionality = parseDirectionality(props.getDirectionality()); + boolean prohibited = props.getProhibited() != null && props.getProhibited(); + + // 4. Build RoadInfo + RoadInfo roadInfo = RoadInfo.builder() + .id(props.getId()) + .name(props.getName()) + .centerline(centerline) + .boundary(boundary) + .speedLimitMetersPerSecond(speedLimitMps) + .directionality(directionality) + .heightLimitMeters(heightLimitM) + .widthLimitMeters(widthLimitM) + .prohibited(prohibited) + .relatedZones(props.getRelatedZones() != null ? new ArrayList<>(props.getRelatedZones()) : Collections.emptyList()) + .build(); + + roadInfoMap.put(roadInfo.getId(), roadInfo); + // Only add roads with valid boundaries to the spatial index + if (!boundary.isEmpty()) { + loadedRoads.add(roadInfo); + } + + } catch (Exception e) { + log.error("Failed to process road properties with id [{}]: {}", props.getId(), e.getMessage(), e); + } + } + + // 5. Build spatial index + if (!loadedRoads.isEmpty()) { + spatialIndex = new STRtree(loadedRoads.size()); + for (RoadInfo road : loadedRoads) { + spatialIndex.insert(road.getBoundary().getEnvelopeInternal(), road); + } + } + // Build the index even if it remains empty + spatialIndex.build(); + + log.info("Road Network Service initialized. Loaded {} roads into map, {} roads indexed.", roadInfoMap.size(), loadedRoads.size()); + } + + // --- Service Interface Methods --- + + /** + * Gets road information by its unique ID. + * + * @param roadId The ID of the road. + * @return An Optional containing the RoadInfo if found, otherwise empty. + */ + public Optional getRoadById(String roadId) { + return Optional.ofNullable(roadInfoMap.get(roadId)); + } + + /** + * Finds all roads whose boundary polygon contains the given geographic position. + * + * @param geoPosition The geographic position (latitude, longitude). + * @return A list of RoadInfo objects containing the point. May be empty. + */ + public List findRoadsContainingPoint(GeoPosition geoPosition) { + if (geoPosition == null) return Collections.emptyList(); + Point point = geometryFactory.createPoint(new Coordinate(geoPosition.getLongitude(), geoPosition.getLatitude())); + Envelope searchEnvelope = point.getEnvelopeInternal(); + + @SuppressWarnings("unchecked") // JTS STRtree query returns List + List candidates = spatialIndex.query(searchEnvelope); + + // Filter candidates using precise 'covers' check + return candidates.stream() + .filter(road -> road.getBoundary().covers(point)) + .collect(Collectors.toList()); + } + + /** + * Finds the most likely or 'dominant' road at a given geographic position. + * This is useful when a point might fall within the overlapping boundaries of multiple roads + * (e.g., near intersections or due to buffer approximations). + * Current simple implementation returns the first one found. + * // TODO: Implement a more sophisticated selection logic if needed (e.g., based on area, road type). + * + * @param geoPosition The geographic position. + * @return An Optional containing the dominant RoadInfo if found, otherwise empty. + */ + public Optional findDominantRoadAt(GeoPosition geoPosition) { + // Simple strategy: return the first road found containing the point. + return findRoadsContainingPoint(geoPosition).stream().findFirst(); + } + + /** + * Gets the speed limit in meters per second at a specific geographic position. + * It finds the dominant road at the position and returns its speed limit. + * + * @param geoPosition The geographic position. + * @return An Optional containing the speed limit (m/s) if the point is on a road + * with a defined limit, otherwise empty. + */ + public Optional getSpeedLimitMetersPerSecondAt(GeoPosition geoPosition) { + return findDominantRoadAt(geoPosition) + .map(RoadInfo::getSpeedLimitMetersPerSecond) + .filter(Objects::nonNull); // Ensure the speed limit itself is not null + } + + // --- Private Helper Methods --- + + /** + * Converts a DimensionValue (potentially with various units) to meters. + * Returns Double.NaN if conversion is not possible or input is invalid. + * // TODO: Implement comprehensive unit conversion. + */ + private double convertDimensionToMeters(DimensionValue dimension) { + if (dimension == null || dimension.getValue() == null || dimension.getUnit() == null) { + return Double.NaN; // Indicate invalid/missing input + } + double value = dimension.getValue(); + String unit = dimension.getUnit().trim().toLowerCase(); + + switch (unit) { + case "m": + case "meter": + case "meters": + return value; + case "km": + case "kilometer": + case "kilometers": + return value * 1000.0; + case "ft": + case "foot": + case "feet": + return value * 0.3048; + // Add other common units if needed + default: + log.warn("Unsupported dimension unit for meter conversion: '{}' for value {}", unit, value); + return Double.NaN; + } + } + + /** + * Converts a DimensionValue representing speed to meters per second. + * Returns null if conversion is not possible or input is invalid. + * // TODO: Implement more speed units if needed. + */ + private Double convertSpeedToMps(DimensionValue speed) { + if (speed == null || speed.getValue() == null || speed.getUnit() == null) { + return null; // Indicate missing or invalid speed limit + } + double value = speed.getValue(); + String unit = speed.getUnit().trim().toLowerCase(); + + switch (unit) { + case "m/s": + return value; + case "km/h": + case "kph": + return value / 3.6; + case "mph": + return value * 0.44704; + case "knots": + case "kt": + return value * 0.514444; + default: + log.warn("Unsupported speed unit for m/s conversion: '{}' for value {}", unit, value); + return null; + } + } + + /** + * Parses the directionality string from the configuration into the RoadDirectionality enum. + */ + private RoadDirectionality parseDirectionality(String directionalityStr) { + if (directionalityStr == null) { + return RoadDirectionality.UNKNOWN; + } + String upperCaseDir = directionalityStr.trim().toUpperCase(); + switch (upperCaseDir) { + case "ONE_WAY": + case "ONEWAY": + return RoadDirectionality.ONE_WAY; + case "TWO_WAY": + case "TWOWAY": + return RoadDirectionality.TWO_WAY; + default: + log.trace("Unknown directionality string: '{}'. Defaulting to UNKNOWN.", directionalityStr); + return RoadDirectionality.UNKNOWN; + } + } + + /** + * Approximates the conversion from meters to decimal degrees at a given latitude. + * This is a rough estimate suitable mainly for buffering operations near the equator + * or for small distances. For accuracy, projection is needed. + * + * @param meters Distance in meters. + * @param latitude Latitude in decimal degrees. + * @return Approximate distance in decimal degrees. + */ + private double metersToApproximateDegrees(double meters, double latitude) { + // Based on the approximate length of a degree of latitude and longitude. + // Using latitude degree length as a rough average estimate. + // 1 degree latitude is approx 111,132 meters. + return meters / 111132.0; + // A more complex formula considering longitude: + // double latRad = Math.toRadians(latitude); + // double metersPerDegreeLon = 111320.0 * Math.cos(latRad); + // double metersPerDegreeLat = 111132.0; + // return meters / Math.min(metersPerDegreeLat, metersPerDegreeLon); // Use smaller for conservative buffer? + } +} \ No newline at end of file diff --git a/src/main/resources/config/airport_roads.yaml b/src/main/resources/config/airport_roads.yaml new file mode 100644 index 0000000..bb207dc --- /dev/null +++ b/src/main/resources/config/airport_roads.yaml @@ -0,0 +1,80 @@ +# src/main/resources/config/airport_roads.yaml +# 机场道路网络配置信息 + +airport_code: "XYZ" # 机场代码,可选 + +roads: + - id: "road_T1_departure" + name: "T1航站楼出发道路" + geometry: + type: "LineString" + coordinates: # 定义道路中心线的点序列 [经度, 纬度] + - [113.001, 22.001] + - [113.0015, 22.001] + - [113.002, 22.0015] + - [113.002, 22.002] + width: + value: 7 + unit: "m" + speed_limit: + value: 40 + unit: "km/h" + directionality: "ONE_WAY" # 单向行驶 + height_limit: + value: 4.5 + unit: "m" + width_limit: + value: 3.5 + unit: "m" + related_zones: ["zone_T1_departure_area"] # 关联区域 + + - id: "road_taxiway_A1" + name: "滑行道A1" + geometry: + type: "LineString" + coordinates: + - [113.010, 22.012] # 示例点 + - [113.030, 22.012] + - [113.050, 22.013] + width: + value: 23 + unit: "m" + speed_limit: + value: 60 + unit: "km/h" + directionality: "TWO_WAY" # 双向行驶 + prohibited: false # 允许通行 (默认值,可省略) + # 无特定限高限宽 + related_zones: ["zone_runwayA_area"] + + - id: "road_apronB_access" + name: "B停机坪入口通道" + geometry: + type: "LineString" + coordinates: + # ... 道路坐标点 ... + - [113.021, 22.021] # 示例点 + - [113.022, 22.022] + - [113.023, 22.023] + width: + value: 10 + unit: "m" + speed_limit: + value: 25 + unit: "km/h" + directionality: "TWO_WAY" + related_zones: ["zone_apron_B"] + +# 可以根据需要添加更多道路... + +# 默认道路属性(可选):用于未指定属性的道路 +# default_road_properties: +# width: +# value: 8 +# unit: "m" +# speed_limit: +# value: 60 +# unit: "km/h" +# directionality: "TWO_WAY" +# prohibited: false +# ... \ No newline at end of file diff --git a/src/main/resources/config/airport_zones.yaml b/src/main/resources/config/airport_zones.yaml new file mode 100644 index 0000000..ca2831c --- /dev/null +++ b/src/main/resources/config/airport_zones.yaml @@ -0,0 +1,61 @@ +# src/main/resources/config/airport_zones.yaml +# 机场区域基本配置信息 + +airport_code: "XYZ" # 机场代码,可选 + +zones: + - id: "zone_T1_departure_area" # ID调整以反映区域 + name: "T1航站楼出发区域" # 名称对应调整 + type: "TERMINAL_AREA" # 区域类型 + area: + type: "Polygon" + coordinates: # 示例边界,需要替换 + - [113.000, 22.000] # 示例边界,需要替换 + - [113.005, 22.000] + - [113.005, 22.005] + - [113.000, 22.005] + - [113.000, 22.000] + + - id: "zone_runwayA_area" + name: "主跑道A区域" + type: "RUNWAY" + area: + type: "Polygon" + coordinates: + - [113.010, 22.010] + - [113.050, 22.010] + - [113.050, 22.015] + - [113.010, 22.015] + - [113.010, 22.010] + + - id: "zone_apron_B" + name: "B停机坪区域" + type: "APRON" + area: + type: "Polygon" + coordinates: + # ... B停机坪的坐标点 ... + - [113.020, 22.020] # 示例点,需要替换 + - [113.025, 22.020] + - [113.025, 22.025] + - [113.020, 22.025] + - [113.020, 22.020] + + + - id: "zone_maintenance_hangar1_area" + name: "维修机库1区域" + type: "MAINTENANCE" # 区域类型: 维修区 + area: + type: "Polygon" + coordinates: + # ... 维修机库1的坐标点 ... + - [113.030, 22.030] # 示例点,需要替换 + - [113.035, 22.030] + - [113.035, 22.035] + - [113.030, 22.035] + - [113.030, 22.030] + + +# 可以根据需要添加更多区域... + +# Removed default_properties \ No newline at end of file