From 3f323361ef8061f9a6cfa48b7988ff79ea70cae0 Mon Sep 17 00:00:00 2001 From: Tian jianyong <11429339@qq.com> Date: Fri, 11 Jul 2025 18:15:39 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96=E4=BA=86=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E6=A3=80=E6=B5=8B=E7=9A=84=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=A1=A8=E7=BB=93=E6=9E=84=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BA=86=E5=91=8A=E8=AD=A6=E9=80=BB=E8=BE=91=E5=92=8C=E5=91=8A?= =?UTF-8?q?=E8=AD=A6=E7=B1=BB=E5=9E=8B=E5=8F=8A=E6=B6=88=E6=81=AF=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.md | 2 +- changelog.md | 49 ++++ .../collision/common/model/MovingObject.java | 3 +- .../common/model/MovingObjectType.java | 2 + .../service/DataCollectorService.java | 15 +- .../VehicleDataPersistenceService.java | 16 +- .../event/ConflictAlertEventListener.java | 90 ++++--- .../model/dto/ConflictAlertEvent.java | 144 +++--------- .../model/entity/ConflictAlertLog.java | 29 +-- .../model/entity/ObjectRouteAssignment.java | 19 +- .../model/entity/TransportRoute.java | 12 +- .../ConflictAlertLogRepository.java | 10 - .../ObjectRouteAssignmentRepository.java | 13 +- .../repository/TransportRouteRepository.java | 9 +- .../service/PathConflictDetectionService.java | 222 +++++++++++------- .../PathConflictAlertWebSocketEvent.java | 8 +- .../message/PathConflictAlertMessage.java | 17 +- sql/create_path_conflict_detection_tables.sql | 74 +++--- 18 files changed, 387 insertions(+), 347 deletions(-) diff --git a/VERSION.md b/VERSION.md index 448a0fa1..09e91570 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -0.3.4 \ No newline at end of file +0.3.5 \ No newline at end of file diff --git a/changelog.md b/changelog.md index e02065a4..3d25f45f 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,55 @@ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。 版本规范基于 [Semantic Versioning](https://semver.org/lang/zh-CN/)。 +## [0.3.5] - 2025-07-12 + +### ✨ **新功能与优化:路径冲突检测增强与告警体系精细化** + +- **数据库与Java实体类同步**: + - 将所有引用其他表的 `id` 字段类型改为 `Long`,确保数据一致性。 + - 删除了 `transport_routes`、`object_route_assignments` 和 `conflict_alert_logs` 表中的特定冗余 `id` 字段。 + - 根据需求,`object_name` 字段设置为 `NOT NULL`。 + +- **新增车辆运动类型**: + - 在 `route_type` 和 `object_type` 字段的 `CHECK` 约束中添加了 `'NORMAL_VEHICLE'`(普通车)类型,并在示例数据中更新。 + +- **告警类型与级别精简**: + - `ConflictAlertLog.AlertType` 枚举值精简为 `CONFLICT_WARNING`(冲突预警)和 `CONFLICT_ALERT`(冲突告警)。 + - `AlertLevel` 枚举中彻底移除了 `INFO` 级别,所有相关代码逻辑已同步移除,确保只处理 `WARNING`、`CRITICAL` 和 `EMERGENCY` 级别的告警。 + - `alertLevel` 为 `CRITICAL` 和 `EMERGENCY` 的告警统一映射为 `CONFLICT_ALERT` 类型。 + +- **距离计算与消息精度**: + - 确认 `PathConflictDetectionService.java` 中的距离计算单位为“度”,并注入 `CoordinateSystemService` 将坐标转换为局部米制坐标进行计算。 + - 前端消息中 `object1Distance` 和 `object2Distance` 的值已四舍五入保留两位小数。 + - `PositionUpdatePayload` 中的 `speed` 字段已进行 `BigDecimal` 四舍五入处理,保留两位小数。 + +- **冲突告警逻辑优化**: + - `detectConflictBetweenObjects` 方法修改为**只从无人车 (`UNMANNED_VEHICLE`) 角度检测冲突**,其他类型 (`AIRCRAFT`, `SPECIAL_VEHICLE`, `NORMAL_VEHICLE`) 之间不检测冲突。 + - `evaluateAlertLevel` 调整逻辑,在判断 `WARNING` 或 `CRITICAL` 级别时,只关注 `UNMANNED_VEHICLE` 距离冲突点的距离。如果双方都不是无人车,则不生成告警(`Optional.empty()`)。 + - `ConflictAlertEventListener.java` 中的 `handleConflictAlert` 方法添加过滤逻辑,确保只有 `WARNING`、`CRITICAL` 或 `EMERGENCY` 级别的告警才发送 WebSocket 消息和车辆指令。 + +- **代码健壮性与可维护性提升**: + - 修复了 `PathConflictDetectionService.java` 在处理重叠线段时可能生成空交点导致的 `NullPointerException`。 + - 解决了 `PathConflictAlertWebSocketEvent.java` 中 `getAlertType()` 和 `getAlertLevel()` 方法的类型不兼容问题,统一返回 `String` 类型。 + - 修正了 `ConflictAlertLog.java` 中 `alertType` 字段的注释不一致问题。 + +### 📋 影响文件 + +- `sql/create_path_conflict_detection_tables.sql`:数据库表结构、约束、示例数据更新。 +- `qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java`:新增枚举类型。 +- `qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java`:速度四舍五入、调用 `convertToVehicleLocation`。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/event/ConflictAlertEventListener.java`:告警过滤、距离四舍五入。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java`:字段类型更新。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ConflictAlertLog.java`:告警类型/级别定义、字段注释更新。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ObjectRouteAssignment.java`:字段类型、非空约束、枚举更新。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/TransportRoute.java`:字段删除、枚举更新。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ObjectRouteAssignmentRepository.java`:查询方法更新。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/TransportRouteRepository.java`:查询方法更新。 +- `qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java`:核心逻辑、距离计算、告警评估更新。 +- `qaup-collision/src/main/java/com/qaup/collision/websocket/message/PathConflictAlertMessage.java`:字段类型更新。 +- `qaup-collision/src/main/java/com/qaup/collision/websocket/event/PathConflictAlertWebSocketEvent.java`:getter 返回类型更新。 +- `VERSION.md`:版本号更新为 `0.3.5`。 + ## [0.3.4] - 2025-07-11 ### 🚀 **核心功能增强:路径冲突与违规检测全面优化** diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java index 6dfe65c1..fb9efe15 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java @@ -53,7 +53,8 @@ public class MovingObject { public enum ObjectType { UNMANNED_VEHICLE, // 无人车 AIRCRAFT, // 航空器 - SPECIAL_VEHICLE // 特种车辆(如机场车辆) + SPECIAL_VEHICLE, // 特种车辆(如机场车辆) + NORMAL_VEHICLE // 普通车辆 } /** diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java index 067a0ae3..bb63c355 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java @@ -7,6 +7,8 @@ public enum MovingObjectType { AIRPORT_VEHICLE, // 特勤车辆(具有特殊权限)- 特勤车辆如警车、消防车、救护车等 SPECIAL_VEHICLE, + // 普通车(普通车辆)- 新增的普通车辆类型 + NORMAL_VEHICLE, // 无人车(可控)- 无人车控制接口 UNMANNED_VEHICLE, // 未知类型 diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java index 39168165..eac74fa7 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java @@ -34,6 +34,9 @@ import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.PrecisionModel; +import java.math.BigDecimal; +import java.math.RoundingMode; + /** * 数据采集服务 - 重构版本 * @@ -171,7 +174,7 @@ public class DataCollectorService { .objectType(MovingObject.ObjectType.AIRCRAFT.name()) .position(positionPayload) .heading(movingObject.getCurrentHeading()) - .speed(movingObject.getCurrentSpeed()) + .speed(new BigDecimal(movingObject.getCurrentSpeed()).setScale(2, RoundingMode.HALF_UP).doubleValue()) // 对速度进行四舍五入 .timestamp(currentTime) // 更新为Long类型时间戳 .build(); @@ -294,7 +297,7 @@ public class DataCollectorService { .objectType(MovingObject.ObjectType.SPECIAL_VEHICLE.name()) .position(positionPayload) .heading(movingObject.getCurrentHeading()) - .speed(movingObject.getCurrentSpeed()) + .speed(new BigDecimal(movingObject.getCurrentSpeed()).setScale(2, RoundingMode.HALF_UP).doubleValue()) // 对速度进行四舍五入 .timestamp(currentTime) .build(); @@ -409,6 +412,12 @@ public class DataCollectorService { // unmannedVehicleLocations.add(createTemporaryVehicleLocationForDetection(movingObject)); // 移除此行 // 将最新数据更新到缓存 activeMovingObjectsCache.put(movingObject.getObjectId(), movingObject); + + // 将UnmannedVehicle转换为VehicleLocation并保存到数据库 + VehicleLocation convertedLocation = convertToVehicleLocation(unmannedVehicle); + if (convertedLocation != null) { + vehicleDataPersistenceService.saveUnmannedVehicleLocation(convertedLocation); + } // 创建位置更新消息负载(WebSocket推送) PositionUpdatePayload.Position positionPayload = PositionUpdatePayload.Position.builder() @@ -421,7 +430,7 @@ public class DataCollectorService { .objectType(MovingObject.ObjectType.UNMANNED_VEHICLE.name()) .position(positionPayload) .heading(movingObject.getCurrentHeading()) - .speed(movingObject.getCurrentSpeed()) + .speed(new BigDecimal(movingObject.getCurrentSpeed()).setScale(2, RoundingMode.HALF_UP).doubleValue()) // 对速度进行四舍五入 .timestamp(currentTime) .build(); diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java index 380324ca..24c27b1a 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java @@ -6,6 +6,7 @@ import com.qaup.collision.common.service.VehicleLocationService; import com.qaup.collision.datacollector.model.entity.VehicleCommandEntity; import com.qaup.collision.datacollector.repository.VehicleCommandRepository; import com.qaup.collision.websocket.event.PositionUpdateEvent; +import com.qaup.collision.websocket.message.PositionUpdatePayload; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -67,7 +68,20 @@ public class VehicleDataPersistenceService { // 数据保存完成后发布WebSocket位置更新事件 try { - eventPublisher.publishEvent(new PositionUpdateEvent(savedLocation)); + // 将VehicleLocation转换为PositionUpdatePayload + PositionUpdatePayload positionPayload = PositionUpdatePayload.builder() + .objectId(String.valueOf(savedLocation.getVehicleId())) // 使用vehicleId作为objectId + .objectType(savedLocation.getVehicleType().name()) + .position(PositionUpdatePayload.Position.builder() + .latitude(savedLocation.getLocation().getY()) + .longitude(savedLocation.getLocation().getX()) + .build()) + .heading(savedLocation.getHeading()) + .speed(savedLocation.getSpeed()) + .timestamp(System.currentTimeMillis() * 1000L) // 微秒级时间戳 + .build(); + + eventPublisher.publishEvent(new PositionUpdateEvent(positionPayload)); log.debug("发布无人车位置更新事件: vehicleId={}", savedLocation.getVehicleId()); } catch (Exception eventException) { log.error("发布位置更新事件失败: vehicleId={}", savedLocation.getVehicleId(), eventException); diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/event/ConflictAlertEventListener.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/event/ConflictAlertEventListener.java index 1f3e14f6..3d75cff4 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/event/ConflictAlertEventListener.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/event/ConflictAlertEventListener.java @@ -6,6 +6,7 @@ import com.qaup.collision.websocket.broadcaster.RuleEventWebSocketPublisher; import com.qaup.collision.websocket.message.PathConflictAlertMessage; import com.qaup.collision.pathconflict.model.entity.ObjectRouteAssignment; import com.qaup.collision.pathconflict.repository.ObjectRouteAssignmentRepository; +import com.qaup.collision.pathconflict.model.entity.ConflictAlertLog; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,6 +14,9 @@ import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import java.math.BigDecimal; +import java.math.RoundingMode; + /** * 冲突告警事件监听器 * 负责处理路径冲突告警事件,发送WebSocket消息和车辆控制指令 @@ -36,18 +40,28 @@ public class ConflictAlertEventListener { @Async @EventListener public void handleConflictAlert(ConflictAlertEvent event) { - log.info("处理路径冲突告警事件: conflictId={}, alertType={}", - event.getConflictId(), event.getAlertType()); + log.info("处理路径冲突告警事件: conflictId={}, alertType={}, alertLevel={}", + event.getConflictId().map(String::valueOf).orElse("N/A"), + event.getAlertType().map(Enum::name).orElse("N/A"), + event.getAlertLevel().map(Enum::name).orElse("N/A")); - try { - // 发送WebSocket消息到控制台 - sendWebSocketAlert(event); - - // 向涉及的无人车发送控制指令 - sendVehicleCommands(event); - - } catch (Exception e) { - log.error("处理冲突告警事件失败: conflictId={}", event.getConflictId(), e); + // 只有当告警级别为 WARNING, CRITICAL, EMERGENCY 时才发送给前端和车辆指令 + if (event.getAlertLevel().isPresent() && + (event.getAlertLevel().get() == ConflictAlertLog.AlertLevel.WARNING || + event.getAlertLevel().get() == ConflictAlertLog.AlertLevel.CRITICAL || + event.getAlertLevel().get() == ConflictAlertLog.AlertLevel.EMERGENCY)) { + try { + // 发送WebSocket消息到控制台 + sendWebSocketAlert(event); + + // 向涉及的无人车发送控制指令 + sendVehicleCommands(event); + + } catch (Exception e) { + log.error("处理冲突告警事件失败: conflictId={}", event.getConflictId().orElse(null), e); + } + } else { + log.debug("告警级别不符合发送条件,不发送WebSocket告警和车辆指令: conflictId={}, alertLevel={}", event.getConflictId().orElse(null), event.getAlertLevel().orElse(null)); } } @@ -57,23 +71,25 @@ public class ConflictAlertEventListener { private void sendWebSocketAlert(ConflictAlertEvent event) { try { PathConflictAlertMessage message = PathConflictAlertMessage.builder() - .conflictId(event.getConflictId()) - .alertType(event.getAlertType().name()) // 使用枚举的name() - .alertLevel(event.getAlertLevel().name()) // 使用枚举的name() + .conflictId(event.getConflictId().orElse(null)) + .alertType(event.getAlertType().orElse(null)) // 直接传递 Optional 里的枚举对象 + .alertLevel(event.getAlertLevel().orElse(null)) // 直接传递 Optional 里的枚举对象 .message(event.getMessage()) .object1(PathConflictAlertMessage.ConflictObject.builder() - .objectId(event.getObject1Id()) - .build()) // 移除 objectType + .objectName(event.getObject1Name()) + .objectType(event.getObject1Type().name()) + .build()) .object2(PathConflictAlertMessage.ConflictObject.builder() - .objectId(event.getObject2Id()) - .build()) // 移除 objectType + .objectName(event.getObject2Name()) + .objectType(event.getObject2Type().name()) + .build()) .position(event.getConflictPointLatitude() != null && event.getConflictPointLongitude() != null ? PathConflictAlertMessage.Position.builder() .latitude(event.getConflictPointLatitude()) .longitude(event.getConflictPointLongitude()) .build() : null) - .object1Distance(event.getObject1Distance()) // 新增 - .object2Distance(event.getObject2Distance()) // 新增 + .object1Distance(new BigDecimal(event.getObject1Distance()).setScale(2, RoundingMode.HALF_UP).doubleValue()) // 内联四舍五入 + .object2Distance(new BigDecimal(event.getObject2Distance()).setScale(2, RoundingMode.HALF_UP).doubleValue()) // 内联四舍五入 .timeToConflict1(event.getEstimatedTimeToConflictObj1()) // 新增 .timeToConflict2(event.getEstimatedTimeToConflictObj2()) // 新增 .timeGap(event.getTimeGapSeconds()) // 新增 @@ -83,10 +99,10 @@ public class ConflictAlertEventListener { // 发送WebSocket消息 webSocketPublisher.publishPathConflictAlert(message); - log.info("WebSocket告警消息已发送: conflictId={}", event.getConflictId()); + log.info("WebSocket告警消息已发送: conflictId={}", event.getConflictId().map(String::valueOf).orElse(null)); } catch (Exception e) { - log.error("发送WebSocket告警消息失败: conflictId={}", event.getConflictId(), e); + log.error("发送WebSocket告警消息失败: conflictId={}", event.getConflictId().map(String::valueOf).orElse(null), e); } } @@ -96,17 +112,17 @@ public class ConflictAlertEventListener { private void sendVehicleCommands(ConflictAlertEvent event) { try { // 向对象1发送指令(如果是无人车) - objectRouteAssignmentRepository.findFirstByObjectIdAndObjectTypeOrderByAssignedAtDesc( - event.getObject1Id(), ObjectRouteAssignment.ObjectType.UNMANNED_VEHICLE) - .ifPresent(assignment -> sendVehicleCommand(event.getObject1Id(), event)); + objectRouteAssignmentRepository.findFirstByObjectNameAndObjectTypeOrderByAssignedAtDesc( + event.getObject1Name(), ObjectRouteAssignment.ObjectType.UNMANNED_VEHICLE) + .ifPresent(assignment -> sendVehicleCommand(event.getObject1Name(), event)); // 向对象2发送指令(如果是无人车) - objectRouteAssignmentRepository.findFirstByObjectIdAndObjectTypeOrderByAssignedAtDesc( - event.getObject2Id(), ObjectRouteAssignment.ObjectType.UNMANNED_VEHICLE) - .ifPresent(assignment -> sendVehicleCommand(event.getObject2Id(), event)); + objectRouteAssignmentRepository.findFirstByObjectNameAndObjectTypeOrderByAssignedAtDesc( + event.getObject2Name(), ObjectRouteAssignment.ObjectType.UNMANNED_VEHICLE) + .ifPresent(assignment -> sendVehicleCommand(event.getObject2Name(), event)); } catch (Exception e) { - log.error("发送车辆控制指令失败: conflictId={}", event.getConflictId(), e); + log.error("发送车辆控制指令失败: conflictId={}", event.getConflictId().orElse(null), e); } } @@ -132,25 +148,29 @@ public class ConflictAlertEventListener { commandMessage = String.format("路径冲突预警:前方检测到潜在冲突,请注意观察。对象1距离冲突点:%.1f米,对象2距离冲突点:%.1f米", event.getObject1Distance(), event.getObject2Distance()); } else { - commandType = "INFO"; - commandMessage = "路径冲突信息提醒"; + // 如果是非警告、非关键、非紧急的告警(即旧的INFO级别),则不发送指令。 + // 在PathConflictDetectionService中已经过滤了INFO级别的事件生成, + // 所以理论上此处不会进入此分支,但作为防御性编程,避免意外情况。 + log.warn("收到非WARNING/CRITICAL/EMERGENCY级别的告警事件,不发送车辆指令: conflictId={}, alertLevel={}", + event.getConflictId().orElse(null), event.getAlertLevel().orElse(null)); + return; // 不发送指令 } // 发送指令到无人车 boolean success = vehicleCommandService.sendCommand( - vehicleId, commandType, commandMessage, event.getConflictId()); + vehicleId, commandType, commandMessage, event.getConflictId().map(String::valueOf).orElse(null)); if (success) { log.info("车辆控制指令已发送: vehicleId={}, commandType={}, conflictId={}", - vehicleId, commandType, event.getConflictId()); + vehicleId, commandType, event.getConflictId().map(String::valueOf).orElse(null)); } else { log.warn("车辆控制指令发送失败: vehicleId={}, conflictId={}", - vehicleId, event.getConflictId()); + vehicleId, event.getConflictId().map(String::valueOf).orElse(null)); } } catch (Exception e) { log.error("发送车辆控制指令时出错: vehicleId={}, conflictId={}", - vehicleId, event.getConflictId(), e); + vehicleId, event.getConflictId().map(String::valueOf).orElse(null), e); } } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java index c04b522f..50f5f61f 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java @@ -1,132 +1,54 @@ package com.qaup.collision.pathconflict.model.dto; -import lombok.Data; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import org.locationtech.jts.geom.Point; import com.qaup.collision.pathconflict.model.entity.ConflictAlertLog; +import com.qaup.collision.common.model.MovingObject; +import lombok.Builder; +import lombok.Data; +import org.locationtech.jts.geom.Point; import java.time.LocalDateTime; +import java.util.Optional; /** - * 冲突告警事件DTO - * 用于在系统内部传递冲突告警信息 - * - * @author AI Assistant - * @version 1.0 - * @since 2025-01-17 + * 路径冲突告警事件 */ @Data @Builder -@NoArgsConstructor -@AllArgsConstructor public class ConflictAlertEvent { - - /** - * 冲突ID - */ - private String conflictId; - - /** - * 告警类型:WARNING, ALERT, EMERGENCY - */ - private ConflictAlertLog.AlertType alertType; - - /** - * 告警级别:INFO, WARNING, CRITICAL, EMERGENCY - */ - private ConflictAlertLog.AlertLevel alertLevel; - - /** - * 对象1 ID - */ - private String object1Id; - - /** - * 对象2 ID - */ - private String object2Id; - - /** - * 冲突点位置 - */ - private Point conflictPoint; - - /** - * 告警消息 - */ - private String message; - - /** - * 对象1距离冲突点距离 (米) - */ - private Double object1Distance; + private Optional conflictId; // 告警日志ID + private Optional alertType; // 告警类型 + private Optional alertLevel; // 告警级别 + private String message; // 告警消息 + private String object1Name; // 对象1名称 + private MovingObject.ObjectType object1Type; // 对象1类型 + private String object2Name; // 对象2名称 + private MovingObject.ObjectType object2Type; // 对象2类型 + private Point conflictPoint; // 冲突点 + private Double object1Distance; // 对象1距离冲突点距离 (米) + private Double object2Distance; // 对象2距离冲突点距离 (米) + private Integer estimatedTimeToConflictObj1; // 对象1预计到达冲突点的时间 (秒) + private Integer estimatedTimeToConflictObj2; // 对象2预计到达冲突点的时间 (秒) + private Double timeGapSeconds; // 两个对象到达冲突点的时间差 (秒) + private LocalDateTime eventTime; // 事件发生时间 - /** - * 对象2距离冲突点距离 (米) - */ - private Double object2Distance; - - /** - * 预计到达冲突点时间1 (秒) - */ - private Integer estimatedTimeToConflictObj1; - - /** - * 预计到达冲突点时间2 (秒) - */ - private Integer estimatedTimeToConflictObj2; - - /** - * 时间差 (秒) - */ - private Double timeGapSeconds; - - /** - * 事件发生时间 - */ - @Builder.Default - private LocalDateTime eventTime = LocalDateTime.now(); - - /** - * 事件来源 - */ - @Builder.Default - private String eventSource = "PATH_CONFLICT_DETECTION"; - - /** - * 判断是否为紧急告警 - */ + // 辅助方法,用于判断告警级别 public boolean isEmergencyAlert() { - return ConflictAlertLog.AlertType.EMERGENCY.equals(alertType) || ConflictAlertLog.AlertLevel.CRITICAL.equals(alertLevel); + return alertLevel.isPresent() && alertLevel.get() == ConflictAlertLog.AlertLevel.EMERGENCY; } - - /** - * 判断是否为预警 - */ - public boolean isWarning() { - return ConflictAlertLog.AlertType.WARNING.equals(alertType); - } - - /** - * 判断是否为告警 - */ + public boolean isAlert() { - return ConflictAlertLog.AlertType.ALERT.equals(alertType); + return alertLevel.isPresent() && alertLevel.get() == ConflictAlertLog.AlertLevel.CRITICAL; } - - /** - * 获取冲突点经度 - */ - public Double getConflictPointLongitude() { - return conflictPoint != null ? conflictPoint.getX() : null; + + public boolean isWarning() { + return alertLevel.isPresent() && alertLevel.get() == ConflictAlertLog.AlertLevel.WARNING; } - - /** - * 获取冲突点纬度 - */ + public Double getConflictPointLatitude() { return conflictPoint != null ? conflictPoint.getY() : null; } + + public Double getConflictPointLongitude() { + return conflictPoint != null ? conflictPoint.getX() : null; + } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ConflictAlertLog.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ConflictAlertLog.java index b6dc8a45..8f9e8bb1 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ConflictAlertLog.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ConflictAlertLog.java @@ -18,7 +18,6 @@ import java.time.LocalDateTime; */ @Entity @Table(name = "conflict_alert_logs", indexes = { - @Index(name = "idx_conflict_alerts_conflict_id", columnList = "conflict_id"), @Index(name = "idx_conflict_alerts_type", columnList = "alert_type"), @Index(name = "idx_conflict_alerts_level", columnList = "alert_level"), @Index(name = "idx_conflict_alerts_time", columnList = "alert_time DESC") @@ -34,20 +33,14 @@ public class ConflictAlertLog { private Long id; /** - * 冲突的唯一标识,可能来自实时计算或是一个事件ID - */ - @Column(name = "conflict_id", nullable = false, length = 50) - private String conflictId; - - /** - * 告警类型:WARNING, ALERT, EMERGENCY + * 告警类型:CONFLICT_WARNING, CONFLICT_ALERT */ @Enumerated(EnumType.STRING) - @Column(name = "alert_type", nullable = false, length = 20) + @Column(name = "alert_type", nullable = false, length = 50) // 增加长度以适应新类型 private AlertType alertType; /** - * 告警级别:INFO, WARNING, CRITICAL, EMERGENCY + * 告警级别:WARNING, CRITICAL, EMERGENCY */ @Enumerated(EnumType.STRING) @Column(name = "alert_level", nullable = false, length = 20) @@ -84,22 +77,20 @@ public class ConflictAlertLog { private LocalDateTime alertTime; /** - * 告警类型枚举 + * 告警类型枚举 - 用于区分告警的来源或类别 */ public enum AlertType { - WARNING, // 预警 - ALERT, // 告警 - EMERGENCY // 紧急 + CONFLICT_WARNING, // 冲突预警 + CONFLICT_ALERT // 冲突告警 } /** - * 告警级别枚举 + * 告警级别枚举 - 用于区分告警的严重程度 */ public enum AlertLevel { - INFO, // 信息 - WARNING, // 警告 - CRITICAL, // 严重 - EMERGENCY // 紧急 + WARNING, // 警告 + CRITICAL, // 严重 + EMERGENCY // 紧急 } @PrePersist diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ObjectRouteAssignment.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ObjectRouteAssignment.java index df81a5ae..ef68b172 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ObjectRouteAssignment.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/ObjectRouteAssignment.java @@ -18,7 +18,7 @@ import java.time.LocalDateTime; */ @Entity @Table(name = "object_route_assignments", indexes = { - @Index(name = "idx_object_route_assignments_object", columnList = "object_id,object_type"), + @Index(name = "idx_object_route_assignments_object_type", columnList = "object_type"), @Index(name = "idx_object_route_assignments_route", columnList = "assigned_route_id"), @Index(name = "idx_object_route_assignments_assigned_at", columnList = "assigned_at DESC") }) @@ -33,13 +33,7 @@ public class ObjectRouteAssignment { private Long id; /** - * 对象ID:无人车ID或航空器标识 - */ - @Column(name = "object_id", nullable = false, length = 50) - private String objectId; - - /** - * 对象类型:UNMANNED_VEHICLE, AIRCRAFT, SPECIAL_VEHICLE + * 对象类型:UNMANNED_VEHICLE, AIRCRAFT, SPECIAL_VEHICLE, NORMAL_VEHICLE */ @Enumerated(EnumType.STRING) @Column(name = "object_type", nullable = false, length = 20) @@ -48,14 +42,14 @@ public class ObjectRouteAssignment { /** * 对象名称:车牌号或航班号 */ - @Column(name = "object_name", length = 100) + @Column(name = "object_name", nullable = false, length = 100) private String objectName; /** * 分配的路径ID */ - @Column(name = "assigned_route_id", nullable = false, length = 50) - private String assignedRouteId; + @Column(name = "assigned_route_id", nullable = false) + private Long assignedRouteId; /** * 分配时间 @@ -69,7 +63,8 @@ public class ObjectRouteAssignment { public enum ObjectType { UNMANNED_VEHICLE, // 无人车 AIRCRAFT, // 航空器 - SPECIAL_VEHICLE // 特种车辆 + SPECIAL_VEHICLE, // 特种车辆 + NORMAL_VEHICLE // 普通车辆 } @PrePersist diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/TransportRoute.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/TransportRoute.java index 4dc27c45..b92cf2e2 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/TransportRoute.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/entity/TransportRoute.java @@ -20,7 +20,6 @@ import java.time.LocalDateTime; */ @Entity @Table(name = "transport_routes", indexes = { - @Index(name = "idx_transport_routes_route_id", columnList = "route_id"), @Index(name = "idx_transport_routes_type", columnList = "route_type"), @Index(name = "idx_transport_routes_status", columnList = "status") }) @@ -34,12 +33,6 @@ public class TransportRoute { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - /** - * 路径唯一标识符 - */ - @Column(name = "route_id", nullable = false, unique = true, length = 50) - private String routeId; - /** * 路径名称 */ @@ -47,7 +40,7 @@ public class TransportRoute { private String routeName; /** - * 路径类型:AIRCRAFT, UNMANNED_VEHICLE, SPECIAL_VEHICLE + * 路径类型:AIRCRAFT, UNMANNED_VEHICLE, SPECIAL_VEHICLE, NORMAL_VEHICLE */ @Enumerated(EnumType.STRING) @Column(name = "route_type", nullable = false, length = 20) @@ -134,7 +127,8 @@ public class TransportRoute { public enum RouteType { AIRCRAFT, // 航空器路径 UNMANNED_VEHICLE, // 无人车路径 - SPECIAL_VEHICLE // 特种车辆路径 + SPECIAL_VEHICLE, // 特种车辆路径 + NORMAL_VEHICLE // 普通车辆路径 } /** diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ConflictAlertLogRepository.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ConflictAlertLogRepository.java index 6ccbca5b..3b5263b7 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ConflictAlertLogRepository.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ConflictAlertLogRepository.java @@ -17,11 +17,6 @@ import java.util.Optional; @Repository public interface ConflictAlertLogRepository extends JpaRepository { - /** - * 根据冲突ID查找告警记录 - */ - List findByConflictId(String conflictId); - /** * 根据告警类型和级别查找告警记录 */ @@ -34,9 +29,4 @@ public interface ConflictAlertLogRepository extends JpaRepository findFirstByOrderByAlertTimeDesc(); - - /** - * 查找某个冲突ID下最新的告警记录 - */ - Optional findFirstByConflictIdOrderByAlertTimeDesc(String conflictId); } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ObjectRouteAssignmentRepository.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ObjectRouteAssignmentRepository.java index a3c8df4b..060c4942 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ObjectRouteAssignmentRepository.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/ObjectRouteAssignmentRepository.java @@ -18,20 +18,15 @@ import java.util.Optional; public interface ObjectRouteAssignmentRepository extends JpaRepository { /** - * 根据对象ID和对象类型查找最新的路径分配记录 + * 根据对象名称和对象类型查找最新的路径分配记录 */ - Optional findFirstByObjectIdAndObjectTypeOrderByAssignedAtDesc( - String objectId, + Optional findFirstByObjectNameAndObjectTypeOrderByAssignedAtDesc( + String objectName, ObjectRouteAssignment.ObjectType objectType ); /** * 根据路径ID查找所有分配到该路径的对象 */ - List findByAssignedRouteId(String assignedRouteId); - - /** - * 根据对象ID查找所有分配到该对象的路径记录 - */ - List findByObjectId(String objectId); + List findByAssignedRouteId(Long assignedRouteId); } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/TransportRouteRepository.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/TransportRouteRepository.java index d59b7d6e..ae1a9c7c 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/TransportRouteRepository.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/repository/TransportRouteRepository.java @@ -19,11 +19,6 @@ import java.util.Optional; @Repository public interface TransportRouteRepository extends JpaRepository { - /** - * 根据路径ID查找路径 - */ - Optional findByRouteId(String routeId); - /** * 根据路径类型和状态查找路径 */ @@ -46,10 +41,10 @@ public interface TransportRouteRepository extends JpaRepository findIntersectingRoutes(@Param("routeId") String routeId); + List findIntersectingRoutes(@Param("routeId") Long routeId); /** * 查找最近的路径 diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java index 7c7dc144..14a2e663 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java @@ -8,18 +8,17 @@ import com.qaup.collision.pathconflict.model.entity.ObjectRouteAssignment; import com.qaup.collision.pathconflict.repository.ObjectRouteAssignmentRepository; import com.qaup.collision.pathconflict.model.entity.ConflictAlertLog; import com.qaup.collision.pathconflict.repository.ConflictAlertLogRepository; +import com.qaup.collision.dataprocessing.service.CoordinateSystemService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.locationtech.jts.geom.*; import org.locationtech.jts.operation.distance.DistanceOp; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.context.ApplicationEventPublisher; -import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; +import java.util.Optional; /** * 路径冲突检测服务 @@ -38,6 +37,7 @@ public class PathConflictDetectionService { private final ObjectRouteAssignmentRepository objectRouteAssignmentRepository; private final ConflictAlertLogRepository conflictAlertLogRepository; private final ApplicationEventPublisher eventPublisher; + private final CoordinateSystemService coordinateSystemService; // 注入 CoordinateSystemService // 预警距离阈值(米) private static final double WARNING_DISTANCE_THRESHOLD = 200.0; @@ -84,13 +84,18 @@ public class PathConflictDetectionService { * 检测两个对象之间的路径冲突 */ private Optional detectConflictBetweenObjects(MovingObject obj1, MovingObject obj2) { + if (obj1.getObjectType() != MovingObject.ObjectType.UNMANNED_VEHICLE && obj2.getObjectType() != MovingObject.ObjectType.UNMANNED_VEHICLE) { + log.debug("对象 {} 和 {} 都不是无人车,跳过冲突检测", obj1.getObjectName(), obj2.getObjectName()); + return Optional.empty(); + } + try { // 获取对象的路径 TransportRoute route1 = getObjectRoute(obj1); TransportRoute route2 = getObjectRoute(obj2); if (route1 == null || route2 == null) { - log.debug("对象 {} 或 {} 没有分配路径,跳过冲突检测", obj1.getObjectId(), obj2.getObjectId()); + log.debug("对象 {} 或 {} 没有分配路径,跳过冲突检测", obj1.getObjectName(), obj2.getObjectName()); return Optional.empty(); } @@ -102,13 +107,11 @@ public class PathConflictDetectionService { ConflictCalculationResult result = calculateConflictDetails(obj1, obj2, route1, route2, intersectionPoint); if (result != null && isSignificantConflict(result)) { - String conflictId = generateConflictId(obj1.getObjectId(), obj2.getObjectId()); String description = generateConflictDescription(obj1, obj2, route1, route2); ConflictAlertLog alertLog = ConflictAlertLog.builder() - .conflictId(conflictId) - .alertType(result.getAlertType()) - .alertLevel(result.getAlertLevel()) + .alertType(result.getAlertType().orElse(null)) + .alertLevel(result.getAlertLevel().orElse(null)) .alertMessage(description) .object1Distance(result.getDistance1()) .object2Distance(result.getDistance2()) @@ -119,12 +122,14 @@ public class PathConflictDetectionService { log.info("保存冲突告警日志: {}", alertLog); return Optional.of(ConflictAlertEvent.builder() - .conflictId(conflictId) + .conflictId(Optional.of(alertLog.getId())) .alertType(result.getAlertType()) .alertLevel(result.getAlertLevel()) .message(description) - .object1Id(obj1.getObjectId()) - .object2Id(obj2.getObjectId()) + .object1Name(obj1.getObjectName()) + .object1Type(obj1.getObjectType()) + .object2Name(obj2.getObjectName()) + .object2Type(obj2.getObjectType()) .conflictPoint(intersectionPoint) .object1Distance(result.getDistance1()) .object2Distance(result.getDistance2()) @@ -136,7 +141,7 @@ public class PathConflictDetectionService { } } catch (Exception e) { - log.error("检测对象 {} 和 {} 之间的冲突时发生异常", obj1.getObjectId(), obj2.getObjectId(), e); + log.error("检测对象 {} 和 {} 之间的冲突时发生异常", obj1.getObjectName(), obj2.getObjectName(), e); } return Optional.empty(); @@ -146,38 +151,15 @@ public class PathConflictDetectionService { * 获取对象当前使用的路径 */ private TransportRoute getObjectRoute(MovingObject obj) { - Optional assignmentOptional = objectRouteAssignmentRepository.findFirstByObjectIdAndObjectTypeOrderByAssignedAtDesc( - obj.getObjectId(), + Optional assignmentOptional = objectRouteAssignmentRepository.findFirstByObjectNameAndObjectTypeOrderByAssignedAtDesc( + obj.getObjectName(), // Changed from getObjectId ObjectRouteAssignment.ObjectType.valueOf(obj.getObjectType().name()) ); - return assignmentOptional.map(assignment -> routeRepository.findByRouteId(assignment.getAssignedRouteId()).orElse(null)) + return assignmentOptional.map(assignment -> routeRepository.findById(assignment.getAssignedRouteId()).orElse(null)) // Changed findByRouteId to findById .orElse(null); } - /** - * 查找距离指定位置最近的路径 (此方法可能不再需要,因为我们现在通过分配表获取路径) - * 或者可以用于处理未分配路径的对象,但当前逻辑主要依赖于已分配路径 - */ - private TransportRoute findNearestRoute(Point position, List routes) { - TransportRoute nearestRoute = null; - double minDistance = Double.MAX_VALUE; - - for (TransportRoute route : routes) { - try { - double distance = position.distance(route.getRouteGeometry()); - if (distance < minDistance) { - minDistance = distance; - nearestRoute = route; - } - } catch (Exception e) { - log.warn("计算到路径 {} 的距离时出错", route.getRouteId(), e); - } - } - - return nearestRoute; - } - /** * 计算两条路径的交点 */ @@ -201,11 +183,16 @@ public class PathConflictDetectionService { // 如果是线段重叠,取线段的中点 LineString overlapLine = (LineString) intersection; Point midPoint = overlapLine.getInteriorPoint(); - intersections.add(midPoint); + // 确保中间点不为空且不是POINT EMPTY + if (midPoint != null && !midPoint.isEmpty()) { + intersections.add(midPoint); + } else { + log.warn("LineString交点生成了空的中间点或无效点,跳过此交点。OverlapLine: {}", overlapLine); + } } } catch (Exception e) { - log.error("计算路径 {} 和 {} 的交点时出错", route1.getRouteId(), route2.getRouteId(), e); + log.error("计算路径交点时出错", e); } return intersections; @@ -239,11 +226,13 @@ public class PathConflictDetectionService { double timeGap = Math.abs(timeToConflict1 - timeToConflict2); // 评估冲突告警级别和类型 - ConflictAlertLog.AlertLevel alertLevel = evaluateAlertLevel(distance1, distance2, timeGap); - ConflictAlertLog.AlertType alertType = evaluateAlertType(alertLevel); + Optional alertLevelOptional = evaluateAlertLevel(distance1, obj1.getObjectType(), distance2, obj2.getObjectType(), timeGap); + Optional alertTypeOptional = evaluateAlertType(alertLevelOptional.orElse(null)); // Changed to orElse(null) return new ConflictCalculationResult( - distance1, distance2, timeToConflict1, timeToConflict2, timeGap, alertType, alertLevel); + distance1, distance2, timeToConflict1, timeToConflict2, timeGap, + alertTypeOptional, alertLevelOptional, + obj1.getObjectType(), obj2.getObjectType()); // Pass object types } catch (Exception e) { log.error("计算冲突详细信息时出错", e); @@ -254,35 +243,56 @@ public class PathConflictDetectionService { /** * 评估冲突的告警级别 */ - private ConflictAlertLog.AlertLevel evaluateAlertLevel( - double distance1, double distance2, double timeGap) { - - double minDistance = Math.min(distance1, distance2); - - if (minDistance <= ALERT_DISTANCE_THRESHOLD && timeGap <= MIN_TIME_GAP_SECONDS) { - return ConflictAlertLog.AlertLevel.CRITICAL; - } else if (minDistance <= WARNING_DISTANCE_THRESHOLD && timeGap <= MIN_TIME_GAP_SECONDS * 2) { - return ConflictAlertLog.AlertLevel.WARNING; - } else if (minDistance <= WARNING_DISTANCE_THRESHOLD * 2 && timeGap <= MAX_PREDICTION_TIME_SECONDS) { - return ConflictAlertLog.AlertLevel.INFO; + private Optional evaluateAlertLevel( + double distance1, MovingObject.ObjectType obj1Type, + double distance2, MovingObject.ObjectType obj2Type, + double timeGap) { + + boolean obj1IsUnmannedVehicle = MovingObject.ObjectType.UNMANNED_VEHICLE.equals(obj1Type); + boolean obj2IsUnmannedVehicle = MovingObject.ObjectType.UNMANNED_VEHICLE.equals(obj2Type); + + if (!obj1IsUnmannedVehicle && !obj2IsUnmannedVehicle) { + // 双方都不是无人车,根据业务规则,不生成告警。 + return Optional.empty(); + } + + double distanceToEvaluate; + + if (obj1IsUnmannedVehicle && obj2IsUnmannedVehicle) { + // 双方都是无人车,关注两者中离交点最近的距离 + distanceToEvaluate = Math.min(distance1, distance2); + } else if (obj1IsUnmannedVehicle) { + // 只有 obj1 是无人车,关注 obj1 的距离。obj2 是无人车需要避让的对象。 + distanceToEvaluate = distance1; + } else { // obj2IsUnmannedVehicle + // 只有 obj2 是无人车,关注 obj2 的距离。obj1 是无人车需要避让的对象。 + distanceToEvaluate = distance2; + } + + // 现在根据确定的 relevantDistance 评估告警级别 + if (distanceToEvaluate <= ALERT_DISTANCE_THRESHOLD && timeGap <= MIN_TIME_GAP_SECONDS) { + return Optional.of(ConflictAlertLog.AlertLevel.CRITICAL); + } else if (distanceToEvaluate <= WARNING_DISTANCE_THRESHOLD && timeGap <= MIN_TIME_GAP_SECONDS * 2) { + return Optional.of(ConflictAlertLog.AlertLevel.WARNING); } else { - return ConflictAlertLog.AlertLevel.INFO; // 默认信息级别 + // 不满足 CRITICAL/WARNING 阈值,不生成告警。 + return Optional.empty(); } } /** * 根据告警级别评估告警类型 */ - private ConflictAlertLog.AlertType evaluateAlertType(ConflictAlertLog.AlertLevel level) { - switch (level) { + private Optional evaluateAlertType(ConflictAlertLog.AlertLevel alertLevel) { + switch (alertLevel) { + case WARNING: + return Optional.of(ConflictAlertLog.AlertType.CONFLICT_WARNING); case CRITICAL: case EMERGENCY: - return ConflictAlertLog.AlertType.ALERT; // 达到告警级别 - case WARNING: - return ConflictAlertLog.AlertType.WARNING; // 预警级别 - case INFO: + return Optional.of(ConflictAlertLog.AlertType.CONFLICT_ALERT); default: - return ConflictAlertLog.AlertType.WARNING; // 默认预警 + // 应该不会到达这里,因为所有的 AlertLevel 都已经被明确处理 + return Optional.empty(); } } @@ -290,8 +300,10 @@ public class PathConflictDetectionService { * 判断是否为显著冲突 */ private boolean isSignificantConflict(ConflictCalculationResult result) { - return result.getAlertType() == ConflictAlertLog.AlertType.ALERT || - result.getAlertType() == ConflictAlertLog.AlertType.WARNING; + return result.getAlertLevel().isPresent() && + (result.getAlertLevel().get() == ConflictAlertLog.AlertLevel.WARNING || + result.getAlertLevel().get() == ConflictAlertLog.AlertLevel.CRITICAL || + result.getAlertLevel().get() == ConflictAlertLog.AlertLevel.EMERGENCY); } /** @@ -299,27 +311,47 @@ public class PathConflictDetectionService { */ private double calculateDistanceAlongRoute(Point currentPos, Point targetPos, TransportRoute route) { try { - LineString routeLine = route.getRouteGeometry(); + // 1. 获取原始路径几何和点 + LineString originalRouteLine = route.getRouteGeometry(); - // 找到当前位置在路径上的最近点 - DistanceOp distOp1 = new DistanceOp(currentPos, routeLine); + // 2. 将所有相关几何对象转换为局部米制坐标系 + // 2.1 转换 currentPos 和 targetPos + double[] localCurrentCoords = coordinateSystemService.convertToLocalCoordinate(currentPos.getX(), currentPos.getY()); + double[] localTargetCoords = coordinateSystemService.convertToLocalCoordinate(targetPos.getX(), targetPos.getY()); + + GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 0); // SRID 0 for Cartesian system (meters) + Point localCurrentPos = geometryFactory.createPoint(new Coordinate(localCurrentCoords[0], localCurrentCoords[1])); + Point localTargetPos = geometryFactory.createPoint(new Coordinate(localTargetCoords[0], localTargetCoords[1])); + + // 2.2 转换 routeLine + Coordinate[] originalCoords = originalRouteLine.getCoordinates(); + Coordinate[] localRouteCoords = new Coordinate[originalCoords.length]; + for (int i = 0; i < originalCoords.length; i++) { + double[] localCoord = coordinateSystemService.convertToLocalCoordinate(originalCoords[i].x, originalCoords[i].y); + localRouteCoords[i] = new Coordinate(localCoord[0], localCoord[1]); + } + LineString localRouteLine = geometryFactory.createLineString(localRouteCoords); + + // 3. 在局部米制坐标系中执行距离计算 + // 3.1 找到当前位置在路径上的最近点 + DistanceOp distOp1 = new DistanceOp(localCurrentPos, localRouteLine); Coordinate[] nearestPoints1 = distOp1.nearestPoints(); - Point nearestOnRoute1 = currentPos.getFactory().createPoint(nearestPoints1[1]); + Point nearestOnRoute1 = geometryFactory.createPoint(nearestPoints1[1]); - // 找到目标位置在路径上的最近点 - DistanceOp distOp2 = new DistanceOp(targetPos, routeLine); + // 3.2 找到目标位置在路径上的最近点 + DistanceOp distOp2 = new DistanceOp(localTargetPos, localRouteLine); Coordinate[] nearestPoints2 = distOp2.nearestPoints(); - Point nearestOnRoute2 = currentPos.getFactory().createPoint(nearestPoints2[1]); + Point nearestOnRoute2 = geometryFactory.createPoint(nearestPoints2[1]); - // 计算沿路径的距离 - double distanceToCurrentOnRoute = getLinearLengthToPoint(routeLine, nearestOnRoute1); - double distanceToTargetOnRoute = getLinearLengthToPoint(routeLine, nearestOnRoute2); + // 3.3 计算沿路径的距离 (现在这些距离将是米) + double distanceToCurrentOnRoute = getLinearLengthToPoint(localRouteLine, nearestOnRoute1); + double distanceToTargetOnRoute = getLinearLengthToPoint(localRouteLine, nearestOnRoute2); return Math.abs(distanceToTargetOnRoute - distanceToCurrentOnRoute); } catch (Exception e) { - log.error("计算沿路径距离时出错: currentPos={}, targetPos={}, routeId={}", - currentPos, targetPos, route.getRouteId(), e); + log.error("计算沿路径距离时出错: currentPos={}, targetPos={}", + currentPos, targetPos, e); return -1.0; // 表示错误 } } @@ -327,11 +359,11 @@ public class PathConflictDetectionService { /** * 生成冲突ID */ - private String generateConflictId(String obj1Id, String obj2Id) { + private String generateConflictId(String obj1Name, String obj2Name) { // 确保ID的生成是确定性的,与对象的顺序无关 - String[] ids = {obj1Id, obj2Id}; - Arrays.sort(ids); - return "CONFLICT-" + ids[0] + "-" + ids[1] + "-" + UUID.randomUUID().toString().substring(0, 8); + String[] names = {obj1Name, obj2Name}; + Arrays.sort(names); + return "CONFLICT-" + names[0] + "-" + names[1] + "-" + UUID.randomUUID().toString().substring(0, 8); } /** @@ -340,9 +372,9 @@ public class PathConflictDetectionService { private String generateConflictDescription( MovingObject obj1, MovingObject obj2, TransportRoute route1, TransportRoute route2) { - return String.format("对象 %s (%s) 在路径 %s 与对象 %s (%s) 在路径 %s 上可能发生冲突。", - obj1.getObjectId(), obj1.getObjectType(), route1.getRouteId(), - obj2.getObjectId(), obj2.getObjectType(), route2.getRouteId()); + return String.format("对象 %s (%s) 在路径上与对象 %s (%s) 可能发生冲突。", + obj1.getObjectName(), obj1.getObjectType(), + obj2.getObjectName(), obj2.getObjectType()); } /** @@ -387,21 +419,27 @@ public class PathConflictDetectionService { private final int timeToConflict1; private final int timeToConflict2; private final double timeGap; - private final ConflictAlertLog.AlertType alertType; - private final ConflictAlertLog.AlertLevel alertLevel; + private final Optional alertType; // Changed to Optional + private final Optional alertLevel; // Changed to Optional + private final MovingObject.ObjectType object1Type; // New + private final MovingObject.ObjectType object2Type; // New public ConflictCalculationResult(double distance1, double distance2, int timeToConflict1, int timeToConflict2, double timeGap, - ConflictAlertLog.AlertType alertType, - ConflictAlertLog.AlertLevel alertLevel) { + Optional alertType, // Changed to Optional + Optional alertLevel, // Changed to Optional + MovingObject.ObjectType object1Type, // New + MovingObject.ObjectType object2Type) { // New this.distance1 = distance1; this.distance2 = distance2; this.timeToConflict1 = timeToConflict1; this.timeToConflict2 = timeToConflict2; this.timeGap = timeGap; - this.alertType = alertType; - this.alertLevel = alertLevel; + this.alertType = alertType; // Changed + this.alertLevel = alertLevel; // Changed + this.object1Type = object1Type; // New + this.object2Type = object2Type; // New } public double getDistance1() { return distance1; } @@ -409,7 +447,9 @@ public class PathConflictDetectionService { public int getTimeToConflict1() { return timeToConflict1; } public int getTimeToConflict2() { return timeToConflict2; } public double getTimeGap() { return timeGap; } - public ConflictAlertLog.AlertType getAlertType() { return alertType; } - public ConflictAlertLog.AlertLevel getAlertLevel() { return alertLevel; } + public Optional getAlertType() { return alertType; } // Changed + public Optional getAlertLevel() { return alertLevel; } // Changed + public MovingObject.ObjectType getObject1Type() { return object1Type; } // New + public MovingObject.ObjectType getObject2Type() { return object2Type; } // New } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/event/PathConflictAlertWebSocketEvent.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/event/PathConflictAlertWebSocketEvent.java index 9ac653d3..6ae2cb41 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/websocket/event/PathConflictAlertWebSocketEvent.java +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/event/PathConflictAlertWebSocketEvent.java @@ -70,7 +70,7 @@ public class PathConflictAlertWebSocketEvent implements WebSocketEvent { /** * 获取冲突ID */ - public String getConflictId() { + public Long getConflictId() { return getPayload() != null ? ((PathConflictAlertMessage) getPayload()).getConflictId() : null; } @@ -78,14 +78,16 @@ public class PathConflictAlertWebSocketEvent implements WebSocketEvent { * 获取告警类型 */ public String getAlertType() { - return getPayload() != null ? ((PathConflictAlertMessage) getPayload()).getAlertType() : null; + return getPayload() != null && ((PathConflictAlertMessage) getPayload()).getAlertType() != null ? + ((PathConflictAlertMessage) getPayload()).getAlertType().name() : (String) null; } /** * 获取告警级别 */ public String getAlertLevel() { - return getPayload() != null ? ((PathConflictAlertMessage) getPayload()).getAlertLevel() : null; + return getPayload() != null && ((PathConflictAlertMessage) getPayload()).getAlertLevel() != null ? + ((PathConflictAlertMessage) getPayload()).getAlertLevel().name() : (String) null; } /** diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/message/PathConflictAlertMessage.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/message/PathConflictAlertMessage.java index 23314911..863c4f14 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/websocket/message/PathConflictAlertMessage.java +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/message/PathConflictAlertMessage.java @@ -7,6 +7,7 @@ import lombok.AllArgsConstructor; import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDateTime; +import com.qaup.collision.pathconflict.model.entity.ConflictAlertLog; /** * 路径冲突告警WebSocket消息 @@ -31,17 +32,17 @@ public class PathConflictAlertMessage { /** * 冲突ID */ - private String conflictId; + private Long conflictId; /** * 告警类型:WARNING, ALERT, EMERGENCY */ - private String alertType; + private ConflictAlertLog.AlertType alertType; /** - * 告警级别:INFO, WARNING, CRITICAL, EMERGENCY + * 告警级别:WARNING, CRITICAL, EMERGENCY */ - private String alertLevel; + private ConflictAlertLog.AlertLevel alertLevel; /** * 告警消息 @@ -148,20 +149,20 @@ public class PathConflictAlertMessage { * 判断是否为紧急告警 */ public boolean isEmergencyAlert() { - return "EMERGENCY".equals(alertType) || "CRITICAL".equals(alertLevel); + return this.alertLevel == ConflictAlertLog.AlertLevel.EMERGENCY; } /** * 判断是否为预警 */ public boolean isWarning() { - return "WARNING".equals(alertType); + return this.alertLevel == ConflictAlertLog.AlertLevel.WARNING || this.alertType == ConflictAlertLog.AlertType.CONFLICT_WARNING; } /** - * 判断是否为告警 + * 判断是否为告警(对应 CRITICAL 级别) */ public boolean isAlert() { - return "ALERT".equals(alertType); + return this.alertLevel == ConflictAlertLog.AlertLevel.CRITICAL || this.alertLevel == ConflictAlertLog.AlertLevel.EMERGENCY || this.alertType == ConflictAlertLog.AlertType.CONFLICT_ALERT; } } \ No newline at end of file diff --git a/sql/create_path_conflict_detection_tables.sql b/sql/create_path_conflict_detection_tables.sql index 2082e3c9..62c88dda 100644 --- a/sql/create_path_conflict_detection_tables.sql +++ b/sql/create_path_conflict_detection_tables.sql @@ -1,3 +1,14 @@ +-- 删除触发器 +DROP TRIGGER IF EXISTS trigger_transport_routes_updated_at ON transport_routes; + +-- 删除函数 +DROP FUNCTION IF EXISTS update_timestamp_column(); + +-- 删除表(按依赖顺序) +DROP TABLE IF EXISTS object_route_assignments; +DROP TABLE IF EXISTS conflict_alert_logs; +DROP TABLE IF EXISTS transport_routes; + -- ============================================ -- 基于路径的冲突检测系统数据库表 -- 创建时间: 2025-01-17 @@ -14,9 +25,8 @@ CREATE TABLE IF NOT EXISTS transport_routes ( id BIGSERIAL PRIMARY KEY, -- 路径基本信息 - route_id VARCHAR(50) NOT NULL UNIQUE, route_name VARCHAR(100) NOT NULL, - route_type VARCHAR(20) NOT NULL CHECK (route_type IN ('AIRCRAFT', 'UNMANNED_VEHICLE', 'SPECIAL_VEHICLE')), + route_type VARCHAR(20) NOT NULL CHECK (route_type IN ('AIRCRAFT', 'UNMANNED_VEHICLE', 'SPECIAL_VEHICLE', 'NORMAL_VEHICLE')), -- 路径描述 description VARCHAR(500), @@ -44,7 +54,6 @@ CREATE TABLE IF NOT EXISTS transport_routes ( ); -- 路径表索引 -CREATE INDEX idx_transport_routes_route_id ON transport_routes(route_id); CREATE INDEX idx_transport_routes_type ON transport_routes(route_type); CREATE INDEX idx_transport_routes_status ON transport_routes(status); CREATE INDEX idx_transport_routes_geometry_gist ON transport_routes USING GIST(route_geometry); @@ -56,22 +65,21 @@ CREATE TABLE IF NOT EXISTS object_route_assignments ( id BIGSERIAL PRIMARY KEY, -- 对象信息 - object_id VARCHAR(50) NOT NULL, -- 车辆ID或航空器标识 - object_type VARCHAR(20) NOT NULL CHECK (object_type IN ('UNMANNED_VEHICLE', 'AIRCRAFT', 'SPECIAL_VEHICLE')), - object_name VARCHAR(100), -- 车牌号或航班号 + object_type VARCHAR(20) NOT NULL CHECK (object_type IN ('UNMANNED_VEHICLE', 'AIRCRAFT', 'SPECIAL_VEHICLE', 'NORMAL_VEHICLE')), + object_name VARCHAR(100) NOT NULL, -- 车牌号或航班号 -- 路径分配 - assigned_route_id VARCHAR(50) NOT NULL, + assigned_route_id BIGINT NOT NULL, -- 时间信息 assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- 外键约束 - FOREIGN KEY (assigned_route_id) REFERENCES transport_routes(route_id) + FOREIGN KEY (assigned_route_id) REFERENCES transport_routes(id) ); -- 对象路径分配表索引 -CREATE INDEX idx_object_route_assignments_object ON object_route_assignments(object_id, object_type); +CREATE INDEX idx_object_route_assignments_object_type ON object_route_assignments(object_type); CREATE INDEX idx_object_route_assignments_route ON object_route_assignments(assigned_route_id); CREATE INDEX idx_object_route_assignments_assigned_at ON object_route_assignments(assigned_at DESC); @@ -81,12 +89,9 @@ CREATE INDEX idx_object_route_assignments_assigned_at ON object_route_assignment CREATE TABLE IF NOT EXISTS conflict_alert_logs ( id BIGSERIAL PRIMARY KEY, - -- 关联冲突 (这里假设冲突ID仍然由算法生成并关联,即使path_conflict_detections表被删除) - conflict_id VARCHAR(50) NOT NULL, -- 冲突的唯一标识,可能来自实时计算或是一个事件ID - -- 告警信息 - alert_type VARCHAR(20) NOT NULL CHECK (alert_type IN ('WARNING', 'ALERT', 'EMERGENCY')), - alert_level VARCHAR(20) NOT NULL CHECK (alert_level IN ('INFO', 'WARNING', 'CRITICAL', 'EMERGENCY')), + alert_type VARCHAR(20) NOT NULL CHECK (alert_type IN ('CONFLICT_WARNING', 'CONFLICT_ALERT')), + alert_level VARCHAR(20) NOT NULL CHECK (alert_level IN ('WARNING', 'CRITICAL', 'EMERGENCY')), alert_message TEXT NOT NULL, -- 距离信息 @@ -99,7 +104,6 @@ CREATE TABLE IF NOT EXISTS conflict_alert_logs ( ); -- 告警记录表索引 -CREATE INDEX idx_conflict_alerts_conflict_id ON conflict_alert_logs(conflict_id); CREATE INDEX idx_conflict_alerts_type ON conflict_alert_logs(alert_type); CREATE INDEX idx_conflict_alerts_level ON conflict_alert_logs(alert_level); CREATE INDEX idx_conflict_alerts_time ON conflict_alert_logs(alert_time DESC); @@ -132,16 +136,32 @@ COMMENT ON TABLE conflict_alert_logs IS '冲突预警告警记录表 - 记录发 -- 8. 插入示例数据 -- ============================================ --- 示例航空器滑行路径 -INSERT INTO transport_routes (route_id, route_name, route_type, description, route_geometry, max_speed_kph, typical_speed_kph) VALUES -('TAXI_01', '1号滑行道路径', 'AIRCRAFT', '从停机坪到跑道的标准滑行路径', - ST_GeomFromText('LINESTRING(120.084174 36.367162, 120.085 36.368, 120.086 36.369)', 4326), - 30, 20), -('SERVICE_ROAD_A', 'A区服务道路', 'UNMANNED_VEHICLE', '无人车服务区域A的标准路径', - ST_GeomFromText('LINESTRING(120.084 36.367, 120.085 36.368, 120.086 36.369, 120.087 36.370)', 4326), - 25, 15); +-- 示例路径数据 (transport_routes) +INSERT INTO transport_routes (route_name, route_type, description, route_geometry, max_speed_kph, typical_speed_kph) VALUES +('航班CA1234路径', 'AIRCRAFT', 'CA1234航班的预定路径', + ST_GeomFromText('LINESTRING(120.086263 36.370484, 120.080996 36.369105)', 4326), + 50, 50), +('航班MU5123路径', 'AIRCRAFT', 'MU5123航班的预定路径', + ST_GeomFromText('LINESTRING(120.088076 36.374179, 120.077971 36.371503)', 4326), + 50, 50), +('特勤车鲁B123路径', 'SPECIAL_VEHICLE', '鲁B123特勤车的预定路径', + ST_GeomFromText('LINESTRING(120.080801 36.366626, 120.083899 36.367403)', 4326), + 30, 30), +('普通车鲁B234路径', 'UNMANNED_VEHICLE', '鲁B234普通车的预定路径', + ST_GeomFromText('LINESTRING(120.087259 36.368299, 120.083899 36.367403)', 4326), + 30, 30), +('无人车鲁B567路径', 'UNMANNED_VEHICLE', '鲁B567无人车的预定路径', + ST_GeomFromText('LINESTRING(120.083084 36.369696, 120.084637 36.365617)', 4326), + 25, 25), +('无人车鲁B579路径', 'UNMANNED_VEHICLE', '鲁B579无人车的预定路径', + ST_GeomFromText('LINESTRING(120.086965 36.368599, 120.086263 36.370484)', 4326), + 25, 25); --- 示例路径分配 -INSERT INTO object_route_assignments (object_id, object_type, object_name, assigned_route_id) VALUES -('UAV001', 'UNMANNED_VEHICLE', '无人车-001', 'SERVICE_ROAD_A'), -('AIRCRAFT001', 'AIRCRAFT', '航班CZ345', 'TAXI_01'); \ No newline at end of file +-- 示例路径分配数据 (object_route_assignments) +INSERT INTO object_route_assignments (object_type, object_name, assigned_route_id) VALUES +('AIRCRAFT', 'CA1234', (SELECT id FROM transport_routes WHERE route_name = '航班CA1234路径')), +('AIRCRAFT', 'MU5123', (SELECT id FROM transport_routes WHERE route_name = '航班MU5123路径')), +('SPECIAL_VEHICLE', '鲁B123', (SELECT id FROM transport_routes WHERE route_name = '特勤车鲁B123路径')), +('NORMAL_VEHICLE', '鲁B234', (SELECT id FROM transport_routes WHERE route_name = '普通车鲁B234路径')), +('UNMANNED_VEHICLE', '鲁B567', (SELECT id FROM transport_routes WHERE route_name = '无人车鲁B567路径')), +('UNMANNED_VEHICLE', '鲁B579', (SELECT id FROM transport_routes WHERE route_name = '无人车鲁B579路径')); \ No newline at end of file