增加了机场区域服务设计方案和基本功能框架
This commit is contained in:
parent
8357a38156
commit
a0d3564f84
@ -1 +1 @@
|
||||
0.4.0
|
||||
0.5.0
|
||||
@ -12,6 +12,8 @@
|
||||
### 新增
|
||||
- 道路网络服务 (`RoadNetworkService`),实现道路网络的配置文件处理和查询功能
|
||||
- 道路网络配置文件 (`airport_roads.yaml`),定义机场的道路网络信息
|
||||
- 机场区域服务 (`AirportAreaService`),实现机场区域的配置文件处理和查询功能
|
||||
- 机场区域配置文件 (`airport_areas.yaml`),定义机场的区域信息
|
||||
|
||||
## [0.3.0] - 2025-03-31
|
||||
|
||||
|
||||
@ -2,9 +2,17 @@
|
||||
|
||||
本文档详细记录了碰撞避免系统开发过程中的每日活动、决策、问题和解决方案。
|
||||
|
||||
## 2025-04-29
|
||||
- 在QGIS中完成机场道路网络的绘制
|
||||
- 导出道路网络的矢量数据
|
||||
- 使用 `geojson_to_yaml.py` 脚本将矢量数据转换为 YAML 格式
|
||||
- 完成了机场区域服务的设计和实现
|
||||
- 完成了机场区域服务的测试,并通过了测试
|
||||
|
||||
## 2025-04-21
|
||||
- 完成道路网络的配置文件的设计和处理
|
||||
- 完成道路网络服务的设计和实现
|
||||
- 完成了道路网络服务的测试,并通过了测试
|
||||
|
||||
## 2025-03-31
|
||||
- 搭建WebSocket框架
|
||||
|
||||
195
doc/design/airport_area_design.md
Normal file
195
doc/design/airport_area_design.md
Normal file
@ -0,0 +1,195 @@
|
||||
# 设计方案:机场区域配置集成
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
* **自动加载**: 在应用程序启动时自动加载并解析机场区域配置文件
|
||||
* **类型安全**: 将配置映射到强类型的 Java 对象
|
||||
* **空间表示**: 使用 JTS 将区域表示为内存中的几何对象
|
||||
* **统一访问**: 提供中心服务 (`AirportAreaService`) 封装区域数据访问
|
||||
* **高效查询**: 支持基于 ID 和地理位置的区域查询
|
||||
* **模块解耦**: 其他模块通过依赖注入使用服务
|
||||
|
||||
## 2. 核心组件
|
||||
|
||||
1. **配置属性 POJOs**:
|
||||
- `AirportAreasProperties`: 顶层配置类
|
||||
- `AreaProperties`: 区域属性类
|
||||
- `GeometryProperties`: 几何属性类
|
||||
|
||||
2. **运行时数据模型 (`AreaInfo`)**:
|
||||
```java
|
||||
@Value
|
||||
@Builder
|
||||
public class AreaInfo {
|
||||
String id; // 区域唯一标识
|
||||
String name; // 区域名称
|
||||
AreaType type; // 区域类型(跑道、机坪等)
|
||||
Double speedLimitKph; // 限速(公里/小时)
|
||||
String description; // 区域用途描述
|
||||
boolean restricted; // 是否限制进入
|
||||
List<String> allowedVehicleTypes; // 允许的车辆类型
|
||||
List<String> allowedAircraftTypes; // 允许的航空器类型
|
||||
Double maxHeight; // 最大高度限制(米)
|
||||
Double maxWeight; // 最大重量限制(吨)
|
||||
Polygon boundary; // JTS 多边形边界
|
||||
ZonedDateTime activeTime; // 生效时间(用于临时区域)
|
||||
ZonedDateTime expiryTime; // 失效时间(用于临时区域)
|
||||
}
|
||||
```
|
||||
|
||||
3. **区域类型枚举 (`AreaType`)**:
|
||||
```java
|
||||
public enum AreaType {
|
||||
RUNWAY, // 跑道
|
||||
TAXIWAY, // 滑行道
|
||||
APRON, // 机坪
|
||||
SERVICE_AREA, // 服务区
|
||||
CARGO_AREA, // 货库区
|
||||
TEMPORARY_AREA, // 临时区域
|
||||
PROTECTION_ZONE, // 地面保护区
|
||||
RESTRICTED_AREA, // 限制区
|
||||
PARKING_AREA, // 停放区
|
||||
MAINTENANCE_AREA // 维修区
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 配置示例 (YAML)
|
||||
|
||||
```yaml
|
||||
areas:
|
||||
- id: "runway-01"
|
||||
name: "主跑道"
|
||||
type: "RUNWAY"
|
||||
speedLimitKph: 0 # 跑道不允许车辆行驶
|
||||
description: "用于航空器起降的主要跑道"
|
||||
restricted: true
|
||||
allowedAircraftTypes: ["A320", "B737", "A330"]
|
||||
maxHeight: 0
|
||||
maxWeight: 0
|
||||
geometry:
|
||||
type: "Polygon"
|
||||
coordinates: [[[x1,y1], [x2,y2], ...]]
|
||||
|
||||
- id: "apron-01"
|
||||
name: "1号机坪"
|
||||
type: "APRON"
|
||||
speedLimitKph: 25
|
||||
description: "用于航空器停放的区域"
|
||||
restricted: true
|
||||
allowedVehicleTypes: ["FOLLOW_ME", "TUG", "FUEL_TRUCK"]
|
||||
allowedAircraftTypes: ["A320", "B737"]
|
||||
maxHeight: 15
|
||||
maxWeight: 100
|
||||
geometry:
|
||||
type: "Polygon"
|
||||
coordinates: [[[x1,y1], [x2,y2], ...]]
|
||||
```
|
||||
|
||||
## 4. 区域服务接口
|
||||
|
||||
```java
|
||||
public interface AirportAreaService {
|
||||
Optional<AreaInfo> getAreaById(String areaId);
|
||||
List<AreaInfo> findAreasContainingPoint(GeoPosition position);
|
||||
Optional<AreaInfo> findDominantAreaAt(GeoPosition position);
|
||||
Optional<Double> getSpeedLimitKphAt(GeoPosition position);
|
||||
List<AreaInfo> findAreasByType(AreaType type);
|
||||
boolean isPositionInRestrictedArea(GeoPosition position);
|
||||
List<String> getAllowedVehicleTypesAt(GeoPosition position);
|
||||
List<String> getAllowedAircraftTypesAt(GeoPosition position);
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 实现细节
|
||||
|
||||
1. **空间索引**:
|
||||
- 使用 JTS 的 `STRtree` 实现空间索引
|
||||
- 支持快速查询包含某点的区域
|
||||
|
||||
2. **时间管理**:
|
||||
- 对临时区域实现时间有效性检查
|
||||
- 支持区域的时间段限制
|
||||
|
||||
3. **访问控制**:
|
||||
- 实现基于车辆/航空器类型的访问控制
|
||||
- 支持区域限制检查
|
||||
|
||||
## 6. 目录结构
|
||||
|
||||
```
|
||||
src/main/java/com/dongni/collisionavoidance/
|
||||
├── config/
|
||||
│ ├── properties/
|
||||
│ │ ├── AirportAreasProperties.java
|
||||
│ │ ├── AreaProperties.java
|
||||
│ │ └── GeometryProperties.java
|
||||
│ └── AirportAreaConfig.java
|
||||
├── areas/
|
||||
│ ├── model/
|
||||
│ │ ├── AreaInfo.java
|
||||
│ │ └── AreaType.java
|
||||
│ └── service/
|
||||
│ └── AirportAreaService.java
|
||||
```
|
||||
|
||||
## 7. 依赖库
|
||||
|
||||
* **Java Topology Suite (JTS)**: `org.locationtech.jts:jts-core:1.19.0`
|
||||
* **SnakeYAML**: (由 Spring Boot 包含)
|
||||
* **Lombok**: (用于简化代码)
|
||||
|
||||
## 8. 未来考虑
|
||||
|
||||
* **精确缓冲**: 实现基于 UTM 或其他合适投影坐标系的缓冲计算
|
||||
* **动态更新**: 考虑配置热加载机制
|
||||
* **单位转换**: 实现更健壮的单位转换库
|
||||
* **区域关系**: 在需要时添加区域之间的关系管理
|
||||
|
||||
## 9. 改进计划
|
||||
|
||||
### 9.1 区域空间查询功能增强
|
||||
- 实现根据坐标点查询包含该点的区域
|
||||
- 添加区域重叠检测功能
|
||||
- 实现最近区域查询功能
|
||||
- 优化空间索引性能
|
||||
|
||||
### 9.2 区域时间有效性验证
|
||||
- 实现区域生效时间和失效时间的检查机制
|
||||
- 添加区域时间有效性过滤功能
|
||||
- 支持临时区域的动态激活和失效
|
||||
|
||||
### 9.3 区域限制条件验证
|
||||
- 实现车辆类型准入验证
|
||||
- 实现航空器类型准入验证
|
||||
- 添加高度和重量限制检查
|
||||
- 支持自定义限制条件
|
||||
|
||||
### 9.4 区域关系管理
|
||||
- 定义区域之间的关系(相邻、包含等)
|
||||
- 实现区域组合查询功能
|
||||
- 支持区域层级结构
|
||||
- 添加区域依赖关系管理
|
||||
|
||||
### 9.5 空间索引优化
|
||||
- 使用 JTS 的 `STRtree` 实现高效的空间查询
|
||||
- 优化区域边界缓冲计算
|
||||
- 实现空间查询缓存机制
|
||||
- 支持大规模区域数据的高效处理
|
||||
|
||||
### 9.6 区域变更通知机制
|
||||
- 实现区域属性动态更新功能
|
||||
- 添加区域变更事件通知机制
|
||||
- 支持区域配置热加载
|
||||
- 实现区域变更日志记录
|
||||
|
||||
### 9.7 测试覆盖
|
||||
- 增加单元测试覆盖率
|
||||
- 添加集成测试场景
|
||||
- 实现性能测试基准
|
||||
- 支持自动化测试流程
|
||||
|
||||
### 9.8 文档完善
|
||||
- 更新 API 文档
|
||||
- 添加使用示例
|
||||
- 完善设计文档
|
||||
- 编写部署指南
|
||||
@ -109,4 +109,106 @@ src/main/java/com/dongni/collisionavoidance/
|
||||
* **精确缓冲**: 实现基于 UTM 或其他合适投影坐标系的缓冲计算。
|
||||
* **复杂交叉口**: 可能需要更高级的拓扑模型或规则处理。
|
||||
* **动态更新**: 考虑配置热加载机制。
|
||||
* **单位转换**: 实现更健壮的单位转换库。
|
||||
* **单位转换**: 实现更健壮的单位转换库。
|
||||
|
||||
## 8. 改进计划
|
||||
|
||||
### 8.1 道路网络功能增强
|
||||
|
||||
#### 8.1.1 道路拓扑关系管理
|
||||
* 实现道路连接点(节点)的识别和管理
|
||||
* 建立道路之间的拓扑关系(相邻、连接等)
|
||||
* 支持道路网络的路径规划功能
|
||||
* 实现道路交叉口的特殊处理逻辑
|
||||
|
||||
#### 8.1.2 道路属性扩展
|
||||
* 添加道路类型(跑道、滑行道、服务道路等)
|
||||
* 增加道路优先级属性
|
||||
* 支持道路状态(开放、关闭、维护等)
|
||||
* 添加道路使用时间限制
|
||||
* 实现道路使用权限管理
|
||||
|
||||
#### 8.1.3 空间查询优化
|
||||
* 改进空间索引实现,使用更高效的索引结构
|
||||
* 优化缓冲区计算,使用投影坐标系提高精度
|
||||
* 添加道路相交检测功能
|
||||
* 实现道路网络的可视化查询
|
||||
* 支持复杂的空间关系查询
|
||||
|
||||
### 8.2 配置管理优化
|
||||
|
||||
#### 8.2.1 配置验证增强
|
||||
* 添加道路配置的完整性检查
|
||||
* 实现道路几何数据的有效性验证
|
||||
* 增加配置项的类型检查和范围验证
|
||||
* 添加道路网络拓扑完整性验证
|
||||
|
||||
#### 8.2.2 配置热更新
|
||||
* 实现配置文件的动态加载
|
||||
* 支持配置变更的实时生效
|
||||
* 添加配置变更的事件通知机制
|
||||
* 实现配置回滚机制
|
||||
|
||||
### 8.3 性能优化
|
||||
|
||||
#### 8.3.1 空间计算优化
|
||||
* 优化坐标转换和投影计算
|
||||
* 改进空间索引的构建和查询性能
|
||||
* 实现空间计算的缓存机制
|
||||
* 添加并行计算支持
|
||||
|
||||
#### 8.3.2 内存管理优化
|
||||
* 优化道路数据的存储结构
|
||||
* 实现数据的懒加载机制
|
||||
* 添加内存使用监控
|
||||
* 实现大规模道路网络的分片处理
|
||||
|
||||
### 8.4 测试覆盖
|
||||
|
||||
#### 8.4.1 单元测试
|
||||
* 增加道路拓扑关系的测试用例
|
||||
* 添加配置验证的测试场景
|
||||
* 完善空间计算的测试覆盖
|
||||
* 添加边界条件测试
|
||||
|
||||
#### 8.4.2 集成测试
|
||||
* 添加道路网络完整功能的测试
|
||||
* 实现性能测试场景
|
||||
* 增加配置热更新的测试用例
|
||||
* 添加负载测试
|
||||
|
||||
### 8.5 文档完善
|
||||
|
||||
#### 8.5.1 技术文档
|
||||
* 更新道路网络设计文档
|
||||
* 添加配置管理说明
|
||||
* 完善API文档
|
||||
* 添加性能优化指南
|
||||
|
||||
#### 8.5.2 使用文档
|
||||
* 添加配置示例
|
||||
* 提供最佳实践指南
|
||||
* 编写故障排除指南
|
||||
* 添加常见问题解答
|
||||
|
||||
### 8.6 实施优先级建议
|
||||
|
||||
1. 第一阶段(核心功能增强):
|
||||
* 道路拓扑关系管理
|
||||
* 空间查询优化
|
||||
* 配置验证增强
|
||||
|
||||
2. 第二阶段(性能与可靠性):
|
||||
* 空间计算优化
|
||||
* 内存管理优化
|
||||
* 单元测试覆盖
|
||||
|
||||
3. 第三阶段(可维护性):
|
||||
* 配置热更新
|
||||
* 文档完善
|
||||
* 集成测试
|
||||
|
||||
4. 第四阶段(扩展功能):
|
||||
* 道路属性扩展
|
||||
* 可视化功能
|
||||
* 高级分析功能
|
||||
@ -0,0 +1,16 @@
|
||||
package com.dongni.collisionavoidance.area.model;
|
||||
|
||||
/**
|
||||
* 机场区域类型枚举
|
||||
*/
|
||||
public enum AreaType {
|
||||
RUNWAY, // 跑道
|
||||
TAXIWAY, // 滑行道
|
||||
APRON, // 停机坪
|
||||
SERVICE_AREA, // 服务区
|
||||
CARGO_AREA, // 货运区
|
||||
TERMINAL_AREA, // 航站楼区域
|
||||
MAINTENANCE, // 维修区
|
||||
RESTRICTED, // 限制区
|
||||
PROTECTION // 保护区
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.dongni.collisionavoidance.area.service;
|
||||
|
||||
import com.dongni.collisionavoidance.config.properties.AirportAreasProperties;
|
||||
import com.dongni.collisionavoidance.config.properties.AreaProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class AirportAreaService {
|
||||
private final List<AreaProperties> areas;
|
||||
|
||||
public AirportAreaService(AirportAreasProperties properties) {
|
||||
this.areas = properties.getAreas();
|
||||
}
|
||||
|
||||
public List<AreaProperties> getAllAreas() {
|
||||
return areas;
|
||||
}
|
||||
|
||||
public Optional<AreaProperties> getAreaById(String id) {
|
||||
return areas.stream()
|
||||
.filter(area -> area.getId().equals(id))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public List<AreaProperties> getAreasByType(String type) {
|
||||
return areas.stream()
|
||||
.filter(area -> area.getType().equals(type))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.dongni.collisionavoidance.config;
|
||||
|
||||
import com.dongni.collisionavoidance.config.properties.AirportAreasProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AirportAreasProperties.class)
|
||||
@PropertySource(value = "classpath:config/airport_areas.yaml", factory = YamlPropertySourceFactory.class)
|
||||
public class AirportAreaConfig {
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.dongni.collisionavoidance.config.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationProperties(prefix = "airport")
|
||||
public class AirportAreasProperties {
|
||||
private List<AreaProperties> areas;
|
||||
|
||||
public List<AreaProperties> getAreas() {
|
||||
return areas;
|
||||
}
|
||||
|
||||
public void setAreas(List<AreaProperties> areas) {
|
||||
this.areas = areas;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.dongni.collisionavoidance.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class AreaProperties {
|
||||
private String id;
|
||||
private String name;
|
||||
private String type;
|
||||
private Double speedLimit;
|
||||
private String purpose;
|
||||
private List<String> restrictions;
|
||||
private List<String> allowedVehicleTypes;
|
||||
private List<String> allowedAircraftTypes;
|
||||
private Double maxHeight;
|
||||
private Double maxWeight;
|
||||
private GeometryProperties geometry;
|
||||
private String activeTime;
|
||||
private String expiryTime;
|
||||
}
|
||||
@ -27,12 +27,12 @@ public class RoadProperties {
|
||||
/**
|
||||
* Physical width of the road.
|
||||
*/
|
||||
private DimensionValue width;
|
||||
private Double width;
|
||||
|
||||
/**
|
||||
* Speed limit on the road.
|
||||
*/
|
||||
private DimensionValue speedLimit;
|
||||
private Double speedLimit;
|
||||
|
||||
/**
|
||||
* Directionality constraint (e.g., "ONE_WAY", "TWO_WAY").
|
||||
@ -47,12 +47,12 @@ public class RoadProperties {
|
||||
/**
|
||||
* Height restriction for vehicles.
|
||||
*/
|
||||
private DimensionValue heightLimit;
|
||||
private Double heightLimit;
|
||||
|
||||
/**
|
||||
* Width restriction for vehicles.
|
||||
*/
|
||||
private DimensionValue widthLimit;
|
||||
private Double widthLimit;
|
||||
|
||||
/**
|
||||
* List of zone IDs related to this road.
|
||||
|
||||
@ -4,18 +4,37 @@ 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,
|
||||
TWO_WAY("双向"),
|
||||
|
||||
/**
|
||||
* Traffic flows in only one direction.
|
||||
*/
|
||||
ONE_WAY("单向"),
|
||||
|
||||
/**
|
||||
* Directionality is unknown or not specified.
|
||||
*/
|
||||
UNKNOWN
|
||||
UNKNOWN("未知");
|
||||
|
||||
private final String chineseName;
|
||||
|
||||
RoadDirectionality(String chineseName) {
|
||||
this.chineseName = chineseName;
|
||||
}
|
||||
|
||||
public String getChineseName() {
|
||||
return chineseName;
|
||||
}
|
||||
|
||||
public static RoadDirectionality fromChineseName(String chineseName) {
|
||||
for (RoadDirectionality value : values()) {
|
||||
if (value.chineseName.equals(chineseName)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,6 @@ 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.
|
||||
@ -25,10 +23,15 @@ public class RoadInfo {
|
||||
String name;
|
||||
|
||||
/**
|
||||
* Speed limit in meters per second.
|
||||
* Width of the road in meters.
|
||||
*/
|
||||
Double width;
|
||||
|
||||
/**
|
||||
* Speed limit in kilometers per hour.
|
||||
* Null if not specified or invalid.
|
||||
*/
|
||||
Double speedLimitMetersPerSecond;
|
||||
Double speedLimitKilometersPerHour;
|
||||
|
||||
/**
|
||||
* Directionality of the road.
|
||||
@ -61,9 +64,4 @@ public class RoadInfo {
|
||||
* The JTS Polygon representing the road's boundary (calculated via buffering).
|
||||
*/
|
||||
Polygon boundary;
|
||||
|
||||
/**
|
||||
* List of zone IDs related to this road.
|
||||
*/
|
||||
List<String> relatedZones;
|
||||
}
|
||||
@ -4,7 +4,6 @@ import com.dongni.collisionavoidance.common.model.GeoPosition; // Assuming GeoPo
|
||||
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;
|
||||
@ -69,9 +68,9 @@ public class RoadNetworkService {
|
||||
LineString centerline = geometryFactory.createLineString(coords);
|
||||
|
||||
// 2. Calculate boundary Polygon (using approximate buffering for now)
|
||||
double widthMeters = convertDimensionToMeters(props.getWidth());
|
||||
double widthMeters = props.getWidth();
|
||||
Polygon boundary;
|
||||
if (!Double.isNaN(widthMeters) && widthMeters > 0) {
|
||||
if (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)
|
||||
@ -85,9 +84,9 @@ public class RoadNetworkService {
|
||||
}
|
||||
|
||||
// 3. Unit conversions and attribute mapping
|
||||
Double speedLimitMps = convertSpeedToMps(props.getSpeedLimit());
|
||||
Double heightLimitM = convertDimensionToMeters(props.getHeightLimit());
|
||||
Double widthLimitM = convertDimensionToMeters(props.getWidthLimit());
|
||||
Double speedLimitKph = props.getSpeedLimit();
|
||||
Double heightLimitM = props.getHeightLimit();
|
||||
Double widthLimitM = props.getWidthLimit();
|
||||
RoadDirectionality directionality = parseDirectionality(props.getDirectionality());
|
||||
boolean prohibited = props.getProhibited() != null && props.getProhibited();
|
||||
|
||||
@ -95,14 +94,14 @@ public class RoadNetworkService {
|
||||
RoadInfo roadInfo = RoadInfo.builder()
|
||||
.id(props.getId())
|
||||
.name(props.getName())
|
||||
.centerline(centerline)
|
||||
.boundary(boundary)
|
||||
.speedLimitMetersPerSecond(speedLimitMps)
|
||||
.width(widthMeters)
|
||||
.speedLimitKilometersPerHour(speedLimitKph)
|
||||
.directionality(directionality)
|
||||
.heightLimitMeters(heightLimitM)
|
||||
.widthLimitMeters(widthLimitM)
|
||||
.prohibited(prohibited)
|
||||
.relatedZones(props.getRelatedZones() != null ? new ArrayList<>(props.getRelatedZones()) : Collections.emptyList())
|
||||
.centerline(centerline)
|
||||
.boundary(boundary)
|
||||
.build();
|
||||
|
||||
roadInfoMap.put(roadInfo.getId(), roadInfo);
|
||||
@ -182,82 +181,21 @@ public class RoadNetworkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the speed limit in meters per second at a specific geographic position.
|
||||
* Gets the speed limit in kilometers per hour 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
|
||||
* @return An Optional containing the speed limit (km/h) if the point is on a road
|
||||
* with a defined limit, otherwise empty.
|
||||
*/
|
||||
public Optional<Double> getSpeedLimitMetersPerSecondAt(GeoPosition geoPosition) {
|
||||
public Optional<Double> getSpeedLimitKilometersPerHourAt(GeoPosition geoPosition) {
|
||||
return findDominantRoadAt(geoPosition)
|
||||
.map(RoadInfo::getSpeedLimitMetersPerSecond)
|
||||
.map(RoadInfo::getSpeedLimitKilometersPerHour)
|
||||
.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.
|
||||
*/
|
||||
|
||||
55
src/main/resources/config/airport_areas.yaml
Normal file
55
src/main/resources/config/airport_areas.yaml
Normal file
@ -0,0 +1,55 @@
|
||||
airport:
|
||||
areas:
|
||||
- id: "1"
|
||||
name: "跑道区域"
|
||||
type: "RUNWAY"
|
||||
speedLimit: 0 # 跑道不允许车辆行驶
|
||||
purpose: "用于航空器起降的主要跑道"
|
||||
restrictions:
|
||||
- "禁止停车"
|
||||
- "禁止通行"
|
||||
allowedVehicleTypes:
|
||||
- "AIRCRAFT"
|
||||
allowedAircraftTypes:
|
||||
- "A320"
|
||||
- "B737"
|
||||
- "A330"
|
||||
maxHeight: 45.0 # 单位:米
|
||||
maxWeight: 400.0 # 单位:吨
|
||||
geometry:
|
||||
type: "Polygon"
|
||||
coordinates: [
|
||||
[120.0834104, 36.35406879],
|
||||
[120.0844104, 36.35506879],
|
||||
[120.0854104, 36.35606879]
|
||||
]
|
||||
activeTime: "2024-01-01T00:00:00"
|
||||
expiryTime: "2024-12-31T23:59:59"
|
||||
|
||||
- id: "2"
|
||||
name: "停机坪区域"
|
||||
type: "APRON"
|
||||
speedLimit: 30 # 单位:km/h
|
||||
purpose: "用于航空器停放和地面服务"
|
||||
restrictions:
|
||||
- "限速30公里/小时"
|
||||
allowedVehicleTypes:
|
||||
- "AIRCRAFT"
|
||||
- "TUG"
|
||||
- "FUEL_TRUCK"
|
||||
- "BAGGAGE_CART"
|
||||
allowedAircraftTypes:
|
||||
- "A320"
|
||||
- "B737"
|
||||
- "A330"
|
||||
maxHeight: 15.0 # 单位:米
|
||||
maxWeight: 200.0 # 单位:吨
|
||||
geometry:
|
||||
type: "Polygon"
|
||||
coordinates: [
|
||||
[120.0864104, 36.35706879],
|
||||
[120.0874104, 36.35806879],
|
||||
[120.0884104, 36.35906879]
|
||||
]
|
||||
activeTime: "2024-01-01T00:00:00"
|
||||
expiryTime: "2024-12-31T23:59:59"
|
||||
@ -0,0 +1,109 @@
|
||||
package com.dongni.collisionavoidance.areas.service;
|
||||
|
||||
import com.dongni.collisionavoidance.config.AirportAreaConfig;
|
||||
import com.dongni.collisionavoidance.config.properties.AreaProperties;
|
||||
import com.dongni.collisionavoidance.area.service.AirportAreaService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* 机场区域服务的集成测试类
|
||||
* 确保区域配置正确加载,并且服务方法按预期工作
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@Import(AirportAreaConfig.class)
|
||||
class AirportAreaServiceIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private AirportAreaService airportAreaService;
|
||||
|
||||
@Test
|
||||
void contextLoadsAndServiceIsInjected() {
|
||||
assertThat(airportAreaService).isNotNull();
|
||||
System.out.println("AirportAreaService 注入成功");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllAreas_shouldReturnAllAreas() {
|
||||
List<AreaProperties> areas = airportAreaService.getAllAreas();
|
||||
|
||||
assertThat(areas)
|
||||
.isNotNull()
|
||||
.isNotEmpty()
|
||||
.hasSize(2); // 配置文件中有两个区域:跑道区域和停机坪区域
|
||||
|
||||
System.out.println("获取到 " + areas.size() + " 个区域");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAreaById_shouldReturnArea_whenIdExists() {
|
||||
String areaId = "1"; // 跑道区域的ID
|
||||
AreaProperties area = airportAreaService.getAreaById(areaId).orElse(null);
|
||||
|
||||
assertThat(area)
|
||||
.isNotNull()
|
||||
.satisfies(a -> {
|
||||
assertThat(a.getId()).isEqualTo("1");
|
||||
assertThat(a.getName()).isEqualTo("跑道区域");
|
||||
assertThat(a.getType()).isEqualTo("RUNWAY");
|
||||
assertThat(a.getSpeedLimit()).isEqualTo(0.0);
|
||||
assertThat(a.getPurpose()).isEqualTo("用于航空器起降的主要跑道");
|
||||
assertThat(a.getRestrictions())
|
||||
.containsExactly("禁止停车", "禁止通行");
|
||||
assertThat(a.getAllowedVehicleTypes())
|
||||
.containsExactly("AIRCRAFT");
|
||||
assertThat(a.getAllowedAircraftTypes())
|
||||
.containsExactly("A320", "B737", "A330");
|
||||
assertThat(a.getMaxHeight()).isEqualTo(45.0);
|
||||
assertThat(a.getMaxWeight()).isEqualTo(400.0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAreaById_shouldReturnNull_whenIdDoesNotExist() {
|
||||
String nonExistentId = "non-existent-area-id";
|
||||
AreaProperties area = airportAreaService.getAreaById(nonExistentId).orElse(null);
|
||||
|
||||
assertThat(area).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAreasByType_shouldReturnAreas_whenTypeExists() {
|
||||
String type = "RUNWAY";
|
||||
List<AreaProperties> areas = airportAreaService.getAreasByType(type);
|
||||
|
||||
assertThat(areas)
|
||||
.isNotNull()
|
||||
.isNotEmpty()
|
||||
.hasSize(1)
|
||||
.allMatch(area -> type.equals(area.getType()));
|
||||
|
||||
// 验证返回的是跑道区域
|
||||
AreaProperties runwayArea = areas.get(0);
|
||||
assertThat(runwayArea)
|
||||
.satisfies(area -> {
|
||||
assertThat(area.getName()).isEqualTo("跑道区域");
|
||||
assertThat(area.getSpeedLimit()).isEqualTo(0.0);
|
||||
assertThat(area.getPurpose()).isEqualTo("用于航空器起降的主要跑道");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAreasByType_shouldReturnEmpty_whenTypeDoesNotExist() {
|
||||
String nonExistentType = "non-existent-type";
|
||||
List<AreaProperties> areas = airportAreaService.getAreasByType(nonExistentType);
|
||||
|
||||
assertThat(areas)
|
||||
.isNotNull()
|
||||
.isEmpty();
|
||||
}
|
||||
}
|
||||
@ -5,15 +5,18 @@ import com.dongni.collisionavoidance.dataCollector.service.DataCollectorService;
|
||||
import com.dongni.collisionavoidance.dataProcessing.service.DataProcessor;
|
||||
import com.dongni.collisionavoidance.roads.model.RoadInfo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.ActiveProfiles; // Optional: if you need specific test profile
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatObject;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
@ -22,16 +25,17 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
* service methods behave as expected.
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) // Load context without web server
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
// @ActiveProfiles("test") // Activate a specific test profile if needed (e.g., for application-test.yml)
|
||||
class RoadNetworkServiceIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private RoadNetworkService roadNetworkService;
|
||||
|
||||
@MockBean
|
||||
@Mock
|
||||
private DataCollectorService dataCollectorService;
|
||||
|
||||
@MockBean
|
||||
@Mock
|
||||
private DataProcessor dataProcessor;
|
||||
|
||||
@Test
|
||||
@ -66,10 +70,10 @@ class RoadNetworkServiceIntegrationTest {
|
||||
|
||||
// 3. Assert numeric properties from test YAML
|
||||
// Speed: 60 km/h
|
||||
assertThat(road.getSpeedLimitMetersPerSecond())
|
||||
assertThat(road.getSpeedLimitKilometersPerHour())
|
||||
.as("Speed limit should be present and correct for %s", roadId)
|
||||
.isNotNull()
|
||||
.isCloseTo(60.0 / 3.6, org.assertj.core.data.Offset.offset(0.01));
|
||||
.isCloseTo(60.0, org.assertj.core.data.Offset.offset(0.01));
|
||||
|
||||
// 4. Assert enum properties from test YAML
|
||||
// Directionality: TWO_WAY
|
||||
@ -89,9 +93,9 @@ class RoadNetworkServiceIntegrationTest {
|
||||
.isEqualTo(15.0);
|
||||
|
||||
// 6. Assert geometry (basic check on centerline)
|
||||
assertThat(road.getCenterline()).isNotNull();
|
||||
assertThatObject(road.getCenterline()).isNotNull();
|
||||
assertThat(road.getCenterline().getCoordinates()).hasSize(3); // Check number of points from test yaml
|
||||
assertThat(road.getBoundary()).isNotNull();
|
||||
assertThatObject(road.getBoundary()).isNotNull();
|
||||
|
||||
// 7. Assert prohibited status
|
||||
assertThat(road.isProhibited())
|
||||
@ -142,33 +146,32 @@ class RoadNetworkServiceIntegrationTest {
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
// --- Test Cases for getSpeedLimitMetersPerSecondAt ---
|
||||
// --- Test Cases for getSpeedLimitKilometersPerHourAt ---
|
||||
|
||||
@Test
|
||||
void getSpeedLimitMetersPerSecondAt_shouldReturnCorrectLimit_whenPointIsInRoadWithLimit() {
|
||||
void getSpeedLimitKilometersPerHourAt_shouldReturnCorrectLimit_whenPointIsInRoadWithLimit() {
|
||||
// Use the same point as in the updated findRoadsContainingPoint test
|
||||
GeoPosition pointOnRoad = new GeoPosition(1.5, 1.0, 0.0); // Using coordinates relevant to test yaml road-1
|
||||
// road-1 speed limit: 60 km/h from test yaml
|
||||
double expectedSpeedMps = 60.0 / 3.6;
|
||||
double expectedSpeedKph = 60.0;
|
||||
|
||||
Optional<Double> speedOpt = roadNetworkService.getSpeedLimitMetersPerSecondAt(pointOnRoad);
|
||||
Optional<Double> speedOpt = roadNetworkService.getSpeedLimitKilometersPerHourAt(pointOnRoad);
|
||||
|
||||
assertThat(speedOpt)
|
||||
.as("Should find speed limit for point on road 'road-1' %s", pointOnRoad) // Updated description
|
||||
.isPresent(); // Assert Optional is not empty first
|
||||
.as("Should find speed limit for point on road 'road-1' %s", pointOnRoad)
|
||||
.isPresent();
|
||||
|
||||
// Then, extract the value and assert it
|
||||
assertThat(speedOpt.get()) // Get the Double value
|
||||
.isCloseTo(expectedSpeedMps, org.assertj.core.data.Offset.offset(0.01));
|
||||
assertThat(speedOpt.get())
|
||||
.isCloseTo(expectedSpeedKph, org.assertj.core.data.Offset.offset(0.01));
|
||||
}
|
||||
|
||||
@Test
|
||||
// Renamed test slightly to reflect the actual check
|
||||
void getSpeedLimitMetersPerSecondAt_shouldReturnEmpty_whenPointIsOutsideAllBoundaries() {
|
||||
void getSpeedLimitKilometersPerHourAt_shouldReturnEmpty_whenPointIsOutsideAllBoundaries() {
|
||||
// Use the same point as in findRoadsContainingPoint test
|
||||
GeoPosition pointOutside = new GeoPosition(20.0, 110.0, 0.0);
|
||||
|
||||
Optional<Double> speedOpt = roadNetworkService.getSpeedLimitMetersPerSecondAt(pointOutside);
|
||||
Optional<Double> speedOpt = roadNetworkService.getSpeedLimitKilometersPerHourAt(pointOutside);
|
||||
|
||||
assertThat(speedOpt)
|
||||
.as("Should not find speed limit for point outside all roads: %s", pointOutside)
|
||||
|
||||
51
src/test/resources/config/airport_areas.yaml
Normal file
51
src/test/resources/config/airport_areas.yaml
Normal file
@ -0,0 +1,51 @@
|
||||
airport:
|
||||
areas:
|
||||
- id: "1"
|
||||
name: "跑道区域"
|
||||
type: "RUNWAY"
|
||||
speedLimit: 0 # 跑道不允许车辆行驶
|
||||
purpose: "用于航空器起降的主要跑道"
|
||||
restrictions:
|
||||
- "禁止停车"
|
||||
- "禁止通行"
|
||||
allowedVehicleTypes:
|
||||
- "AIRCRAFT"
|
||||
allowedAircraftTypes:
|
||||
- "A320"
|
||||
- "B737"
|
||||
- "A330"
|
||||
maxHeight: 45.0 # 单位:米
|
||||
maxWeight: 400.0 # 单位:吨
|
||||
geometry:
|
||||
type: "Polygon"
|
||||
coordinates: [
|
||||
[1.0, 1.0],
|
||||
[1.0, 2.0],
|
||||
[2.0, 2.0]
|
||||
]
|
||||
|
||||
- id: "2"
|
||||
name: "停机坪区域"
|
||||
type: "APRON"
|
||||
speedLimit: 30 # 单位:km/h
|
||||
purpose: "用于航空器停放和地面服务"
|
||||
restrictions:
|
||||
- "限速30公里/小时"
|
||||
allowedVehicleTypes:
|
||||
- "AIRCRAFT"
|
||||
- "TUG"
|
||||
- "FUEL_TRUCK"
|
||||
- "BAGGAGE_CART"
|
||||
allowedAircraftTypes:
|
||||
- "A320"
|
||||
- "B737"
|
||||
- "A330"
|
||||
maxHeight: 15.0 # 单位:米
|
||||
maxWeight: 200.0 # 单位:吨
|
||||
geometry:
|
||||
type: "Polygon"
|
||||
coordinates: [
|
||||
[2.0, 2.0],
|
||||
[2.0, 3.0],
|
||||
[3.0, 3.0]
|
||||
]
|
||||
@ -1,87 +1,30 @@
|
||||
# Test configuration for airport roads
|
||||
airport_code: "TEST"
|
||||
|
||||
roads:
|
||||
- id: "road-1"
|
||||
name: "Main Runway Access"
|
||||
- id: road-1
|
||||
name: Main Runway Access
|
||||
geometry:
|
||||
type: "LineString"
|
||||
type: LineString
|
||||
coordinates:
|
||||
- [1.0, 1.0]
|
||||
- [1.0, 2.0]
|
||||
- [2.0, 2.0]
|
||||
width:
|
||||
value: 20
|
||||
unit: "m"
|
||||
speed_limit:
|
||||
value: 60
|
||||
unit: "km/h"
|
||||
directionality: "TWO_WAY"
|
||||
height_limit:
|
||||
value: 10
|
||||
unit: "m"
|
||||
width_limit:
|
||||
value: 15
|
||||
unit: "m"
|
||||
width: 20.0
|
||||
speed_limit: 60.0
|
||||
directionality: TWO_WAY
|
||||
prohibited: false
|
||||
related_zones: ["zone-A", "zone-B"]
|
||||
width_limit: 15.0
|
||||
height_limit: 10.0
|
||||
|
||||
- id: "road-2"
|
||||
name: "Service Road Alpha"
|
||||
- id: road-3
|
||||
name: Prohibited Service Road
|
||||
geometry:
|
||||
type: "LineString"
|
||||
type: LineString
|
||||
coordinates:
|
||||
- [0.0, 0.0]
|
||||
- [1.0, 0.0]
|
||||
width:
|
||||
value: 5
|
||||
unit: "m"
|
||||
# No speed limit, directionality, etc.
|
||||
|
||||
- id: "road-3"
|
||||
name: "Taxiway Charlie (Closed)"
|
||||
geometry:
|
||||
type: "LineString"
|
||||
coordinates:
|
||||
- [2.0, 0.0]
|
||||
- [2.0, 1.0]
|
||||
width:
|
||||
value: 15
|
||||
unit: "m"
|
||||
speed_limit:
|
||||
value: 30
|
||||
unit: "kph" # Test alternative unit
|
||||
directionality: "ONE_WAY"
|
||||
- [2.0, 2.0]
|
||||
- [2.0, 3.0]
|
||||
- [3.0, 3.0]
|
||||
width: 10.0
|
||||
speed_limit: 30.0
|
||||
directionality: ONE_WAY
|
||||
prohibited: true
|
||||
|
||||
- name: "Road Without ID" # Missing ID - Should be logged and skipped
|
||||
geometry:
|
||||
type: "LineString"
|
||||
coordinates:
|
||||
- [3.0, 0.0]
|
||||
- [3.0, 1.0]
|
||||
width:
|
||||
value: 10
|
||||
unit: "m"
|
||||
|
||||
- id: "road-invalid-geom"
|
||||
name: "Road with Invalid Geometry"
|
||||
geometry:
|
||||
type: "LineString"
|
||||
coordinates:
|
||||
- [4.0, 0.0] # Only one point - Should be logged and skipped
|
||||
width:
|
||||
value: 8
|
||||
unit: "m"
|
||||
|
||||
- id: "road-zero-width"
|
||||
name: "Road with Zero Width"
|
||||
geometry:
|
||||
type: "LineString"
|
||||
coordinates:
|
||||
- [5.0, 0.0]
|
||||
- [5.0, 1.0]
|
||||
width:
|
||||
value: 0
|
||||
unit: "m"
|
||||
# Should be logged, boundary might be empty/invalid
|
||||
width_limit: 3.5
|
||||
height_limit: 4.5
|
||||
Loading…
Reference in New Issue
Block a user