简化了路径冲突检测的数据库表结构,优化了告警逻辑和告警类型及消息结构

This commit is contained in:
Tian jianyong 2025-07-11 18:15:39 +08:00
parent f5d830b713
commit 3f323361ef
18 changed files with 387 additions and 347 deletions

View File

@ -1 +1 @@
0.3.4
0.3.5

View File

@ -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
### 🚀 **核心功能增强:路径冲突与违规检测全面优化**

View File

@ -53,7 +53,8 @@ public class MovingObject {
public enum ObjectType {
UNMANNED_VEHICLE, // 无人车
AIRCRAFT, // 航空器
SPECIAL_VEHICLE // 特种车辆如机场车辆
SPECIAL_VEHICLE, // 特种车辆如机场车辆
NORMAL_VEHICLE // 普通车辆
}
/**

View File

@ -7,6 +7,8 @@ public enum MovingObjectType {
AIRPORT_VEHICLE,
// 特勤车辆具有特殊权限- 特勤车辆如警车消防车救护车等
SPECIAL_VEHICLE,
// 普通车普通车辆- 新增的普通车辆类型
NORMAL_VEHICLE,
// 无人车可控- 无人车控制接口
UNMANNED_VEHICLE,
// 未知类型

View File

@ -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();

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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<Long> conflictId; // 告警日志ID
private Optional<ConflictAlertLog.AlertType> alertType; // 告警类型
private Optional<ConflictAlertLog.AlertLevel> 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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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 // 普通车辆路径
}
/**

View File

@ -17,11 +17,6 @@ import java.util.Optional;
@Repository
public interface ConflictAlertLogRepository extends JpaRepository<ConflictAlertLog, Long> {
/**
* 根据冲突ID查找告警记录
*/
List<ConflictAlertLog> findByConflictId(String conflictId);
/**
* 根据告警类型和级别查找告警记录
*/
@ -34,9 +29,4 @@ public interface ConflictAlertLogRepository extends JpaRepository<ConflictAlertL
* 查找最新的一条告警记录
*/
Optional<ConflictAlertLog> findFirstByOrderByAlertTimeDesc();
/**
* 查找某个冲突ID下最新的告警记录
*/
Optional<ConflictAlertLog> findFirstByConflictIdOrderByAlertTimeDesc(String conflictId);
}

View File

@ -18,20 +18,15 @@ import java.util.Optional;
public interface ObjectRouteAssignmentRepository extends JpaRepository<ObjectRouteAssignment, Long> {
/**
* 根据对象ID和对象类型查找最新的路径分配记录
* 根据对象名称和对象类型查找最新的路径分配记录
*/
Optional<ObjectRouteAssignment> findFirstByObjectIdAndObjectTypeOrderByAssignedAtDesc(
String objectId,
Optional<ObjectRouteAssignment> findFirstByObjectNameAndObjectTypeOrderByAssignedAtDesc(
String objectName,
ObjectRouteAssignment.ObjectType objectType
);
/**
* 根据路径ID查找所有分配到该路径的对象
*/
List<ObjectRouteAssignment> findByAssignedRouteId(String assignedRouteId);
/**
* 根据对象ID查找所有分配到该对象的路径记录
*/
List<ObjectRouteAssignment> findByObjectId(String objectId);
List<ObjectRouteAssignment> findByAssignedRouteId(Long assignedRouteId);
}

View File

@ -19,11 +19,6 @@ import java.util.Optional;
@Repository
public interface TransportRouteRepository extends JpaRepository<TransportRoute, Long> {
/**
* 根据路径ID查找路径
*/
Optional<TransportRoute> findByRouteId(String routeId);
/**
* 根据路径类型和状态查找路径
*/
@ -46,10 +41,10 @@ public interface TransportRouteRepository extends JpaRepository<TransportRoute,
* 查找与指定路径相交的路径
*/
@Query(value = "SELECT r2.* FROM transport_routes r1, transport_routes r2 WHERE " +
"r1.route_id = :routeId AND r2.route_id != :routeId " +
"r1.id = :routeId AND r2.id != :routeId " +
"AND ST_Intersects(r1.route_geometry, r2.route_geometry) " +
"AND r2.status = 'ACTIVE'", nativeQuery = true)
List<TransportRoute> findIntersectingRoutes(@Param("routeId") String routeId);
List<TransportRoute> findIntersectingRoutes(@Param("routeId") Long routeId);
/**
* 查找最近的路径

View File

@ -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<ConflictAlertEvent> 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<ObjectRouteAssignment> assignmentOptional = objectRouteAssignmentRepository.findFirstByObjectIdAndObjectTypeOrderByAssignedAtDesc(
obj.getObjectId(),
Optional<ObjectRouteAssignment> 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<TransportRoute> 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<ConflictAlertLog.AlertLevel> alertLevelOptional = evaluateAlertLevel(distance1, obj1.getObjectType(), distance2, obj2.getObjectType(), timeGap);
Optional<ConflictAlertLog.AlertType> 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<ConflictAlertLog.AlertLevel> 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<ConflictAlertLog.AlertType> 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<ConflictAlertLog.AlertType> alertType; // Changed to Optional
private final Optional<ConflictAlertLog.AlertLevel> 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<ConflictAlertLog.AlertType> alertType, // Changed to Optional
Optional<ConflictAlertLog.AlertLevel> 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<ConflictAlertLog.AlertType> getAlertType() { return alertType; } // Changed
public Optional<ConflictAlertLog.AlertLevel> getAlertLevel() { return alertLevel; } // Changed
public MovingObject.ObjectType getObject1Type() { return object1Type; } // New
public MovingObject.ObjectType getObject2Type() { return object2Type; } // New
}
}

View File

@ -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;
}
/**

View File

@ -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;
}
}

View File

@ -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');
-- 示例路径分配数据 (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路径'));