diff --git a/VERSION.txt b/VERSION.txt index 60a2d3e..79a2734 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.4.0 \ No newline at end of file +0.5.0 \ No newline at end of file diff --git a/change_log.md b/change_log.md index bfbd486..1cefa8a 100644 --- a/change_log.md +++ b/change_log.md @@ -12,6 +12,8 @@ ### 新增 - 道路网络服务 (`RoadNetworkService`),实现道路网络的配置文件处理和查询功能 - 道路网络配置文件 (`airport_roads.yaml`),定义机场的道路网络信息 +- 机场区域服务 (`AirportAreaService`),实现机场区域的配置文件处理和查询功能 +- 机场区域配置文件 (`airport_areas.yaml`),定义机场的区域信息 ## [0.3.0] - 2025-03-31 diff --git a/development_log.md b/development_log.md index c4af532..0181fbf 100644 --- a/development_log.md +++ b/development_log.md @@ -2,9 +2,17 @@ 本文档详细记录了碰撞避免系统开发过程中的每日活动、决策、问题和解决方案。 +## 2025-04-29 +- 在QGIS中完成机场道路网络的绘制 +- 导出道路网络的矢量数据 +- 使用 `geojson_to_yaml.py` 脚本将矢量数据转换为 YAML 格式 +- 完成了机场区域服务的设计和实现 +- 完成了机场区域服务的测试,并通过了测试 + ## 2025-04-21 - 完成道路网络的配置文件的设计和处理 - 完成道路网络服务的设计和实现 +- 完成了道路网络服务的测试,并通过了测试 ## 2025-03-31 - 搭建WebSocket框架 diff --git a/doc/design/airport_area_design.md b/doc/design/airport_area_design.md new file mode 100644 index 0000000..90995f7 --- /dev/null +++ b/doc/design/airport_area_design.md @@ -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 allowedVehicleTypes; // 允许的车辆类型 + List 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 getAreaById(String areaId); + List findAreasContainingPoint(GeoPosition position); + Optional findDominantAreaAt(GeoPosition position); + Optional getSpeedLimitKphAt(GeoPosition position); + List findAreasByType(AreaType type); + boolean isPositionInRestrictedArea(GeoPosition position); + List getAllowedVehicleTypesAt(GeoPosition position); + List 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 文档 +- 添加使用示例 +- 完善设计文档 +- 编写部署指南 \ No newline at end of file diff --git a/doc/design/road_network_design.md b/doc/design/road_network_design.md index 1c47dd0..0d310f8 100644 --- a/doc/design/road_network_design.md +++ b/doc/design/road_network_design.md @@ -109,4 +109,106 @@ src/main/java/com/dongni/collisionavoidance/ * **精确缓冲**: 实现基于 UTM 或其他合适投影坐标系的缓冲计算。 * **复杂交叉口**: 可能需要更高级的拓扑模型或规则处理。 * **动态更新**: 考虑配置热加载机制。 -* **单位转换**: 实现更健壮的单位转换库。 \ No newline at end of file +* **单位转换**: 实现更健壮的单位转换库。 + +## 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. 第四阶段(扩展功能): + * 道路属性扩展 + * 可视化功能 + * 高级分析功能 \ No newline at end of file diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 diff --git a/src/main/java/com/dongni/collisionavoidance/area/model/AreaType.java b/src/main/java/com/dongni/collisionavoidance/area/model/AreaType.java new file mode 100644 index 0000000..4aed532 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/area/model/AreaType.java @@ -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 // 保护区 +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/area/service/AirportAreaService.java b/src/main/java/com/dongni/collisionavoidance/area/service/AirportAreaService.java new file mode 100644 index 0000000..0c9eb1a --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/area/service/AirportAreaService.java @@ -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 areas; + + public AirportAreaService(AirportAreasProperties properties) { + this.areas = properties.getAreas(); + } + + public List getAllAreas() { + return areas; + } + + public Optional getAreaById(String id) { + return areas.stream() + .filter(area -> area.getId().equals(id)) + .findFirst(); + } + + public List getAreasByType(String type) { + return areas.stream() + .filter(area -> area.getType().equals(type)) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/AirportAreaConfig.java b/src/main/java/com/dongni/collisionavoidance/config/AirportAreaConfig.java new file mode 100644 index 0000000..93938c9 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/AirportAreaConfig.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/properties/AirportAreasProperties.java b/src/main/java/com/dongni/collisionavoidance/config/properties/AirportAreasProperties.java new file mode 100644 index 0000000..25c5cfd --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/AirportAreasProperties.java @@ -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 areas; + + public List getAreas() { + return areas; + } + + public void setAreas(List areas) { + this.areas = areas; + } +} \ No newline at end of file diff --git a/src/main/java/com/dongni/collisionavoidance/config/properties/AreaProperties.java b/src/main/java/com/dongni/collisionavoidance/config/properties/AreaProperties.java new file mode 100644 index 0000000..64445d4 --- /dev/null +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/AreaProperties.java @@ -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 restrictions; + private List allowedVehicleTypes; + private List allowedAircraftTypes; + private Double maxHeight; + private Double maxWeight; + private GeometryProperties geometry; + private String activeTime; + private String expiryTime; +} \ 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 index ad7489b..b020040 100644 --- a/src/main/java/com/dongni/collisionavoidance/config/properties/RoadProperties.java +++ b/src/main/java/com/dongni/collisionavoidance/config/properties/RoadProperties.java @@ -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. diff --git a/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java index 8fdd996..5ed1804 100644 --- a/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java +++ b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadDirectionality.java @@ -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; + } } \ 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 index ee0dd1a..98d7fe8 100644 --- a/src/main/java/com/dongni/collisionavoidance/roads/model/RoadInfo.java +++ b/src/main/java/com/dongni/collisionavoidance/roads/model/RoadInfo.java @@ -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 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 index cbbe648..6020c1c 100644 --- a/src/main/java/com/dongni/collisionavoidance/roads/service/RoadNetworkService.java +++ b/src/main/java/com/dongni/collisionavoidance/roads/service/RoadNetworkService.java @@ -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 getSpeedLimitMetersPerSecondAt(GeoPosition geoPosition) { + public Optional 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. */ diff --git a/src/main/resources/config/airport_areas.yaml b/src/main/resources/config/airport_areas.yaml new file mode 100644 index 0000000..2b56f14 --- /dev/null +++ b/src/main/resources/config/airport_areas.yaml @@ -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" \ No newline at end of file diff --git a/src/test/java/com/dongni/collisionavoidance/areas/service/AirportAreaServiceIntegrationTest.java b/src/test/java/com/dongni/collisionavoidance/areas/service/AirportAreaServiceIntegrationTest.java new file mode 100644 index 0000000..fa9e581 --- /dev/null +++ b/src/test/java/com/dongni/collisionavoidance/areas/service/AirportAreaServiceIntegrationTest.java @@ -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 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 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 areas = airportAreaService.getAreasByType(nonExistentType); + + assertThat(areas) + .isNotNull() + .isEmpty(); + } +} \ No newline at end of file diff --git a/src/test/java/com/dongni/collisionavoidance/roads/service/RoadNetworkServiceIntegrationTest.java b/src/test/java/com/dongni/collisionavoidance/roads/service/RoadNetworkServiceIntegrationTest.java index f12f65e..f6ea27e 100644 --- a/src/test/java/com/dongni/collisionavoidance/roads/service/RoadNetworkServiceIntegrationTest.java +++ b/src/test/java/com/dongni/collisionavoidance/roads/service/RoadNetworkServiceIntegrationTest.java @@ -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 speedOpt = roadNetworkService.getSpeedLimitMetersPerSecondAt(pointOnRoad); + Optional 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 speedOpt = roadNetworkService.getSpeedLimitMetersPerSecondAt(pointOutside); + Optional speedOpt = roadNetworkService.getSpeedLimitKilometersPerHourAt(pointOutside); assertThat(speedOpt) .as("Should not find speed limit for point outside all roads: %s", pointOutside) diff --git a/src/test/resources/config/airport_areas.yaml b/src/test/resources/config/airport_areas.yaml new file mode 100644 index 0000000..2e24330 --- /dev/null +++ b/src/test/resources/config/airport_areas.yaml @@ -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] + ] \ No newline at end of file diff --git a/src/test/resources/config/airport_roads.yaml b/src/test/resources/config/airport_roads.yaml index 8d2b3d3..924ec16 100644 --- a/src/test/resources/config/airport_roads.yaml +++ b/src/test/resources/config/airport_roads.yaml @@ -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 \ No newline at end of file + width_limit: 3.5 + height_limit: 4.5 \ No newline at end of file