package com.algo.service; import com.algo.model.CTUPhysicalConfig; import com.algo.model.Conflict; import com.algo.model.PathCode; import com.algo.model.PlannedPath; import com.algo.util.JsonUtils; import java.util.*; /** * 碰撞检测器 */ public class CollisionDetector { /** * 路径映射表 */ private final Map> pathMapping; /** * 默认最小安全距离 */ private final double defaultMinDistance; /** * 时间精度(毫秒) * 提高时间精度以更准确地检测碰撞 */ private final long timeResolution = 50; /** * 空间分辨率(米) */ private final double spaceResolution = 0.1; /** * 构造函数 * * @param pathMapping 路径映射表 * @param defaultMinDistance 默认最小安全距离 */ public CollisionDetector(Map> pathMapping, double defaultMinDistance) { this.pathMapping = pathMapping; this.defaultMinDistance = defaultMinDistance; } /** * 构造函数 * * @param pathMapping 路径映射表 */ public CollisionDetector(Map> pathMapping) { this(pathMapping, 3.0); } /** * 检测路径冲突 * * @param plannedPaths 规划路径列表 * @return 冲突列表 */ public List detectConflicts(List plannedPaths) { List conflicts = new ArrayList<>(); if (plannedPaths == null || plannedPaths.size() < 2) { return conflicts; } System.out.println("开始碰撞检测,路径数量: " + plannedPaths.size()); // 构建时空表 Map> spaceTimeTable = buildSpaceTimeTable(plannedPaths); // 检测顶点冲突(同一时间同一位置) List vertexConflicts = detectVertexConflicts(spaceTimeTable); conflicts.addAll(vertexConflicts); System.out.println("顶点冲突数量: " + vertexConflicts.size()); // 检测边冲突(CTU交换位置) List edgeConflicts = detectEdgeConflicts(spaceTimeTable); conflicts.addAll(edgeConflicts); System.out.println("边冲突数量: " + edgeConflicts.size()); // 检测跟随冲突 List followingConflicts = detectFollowingConflicts(spaceTimeTable); conflicts.addAll(followingConflicts); System.out.println("跟随冲突数量: " + followingConflicts.size()); // 检测物理尺寸冲突 List physicalConflicts = detectPhysicalSizeConflicts(spaceTimeTable); conflicts.addAll(physicalConflicts); System.out.println("物理尺寸冲突数量: " + physicalConflicts.size()); System.out.println("碰撞检测完成,总冲突数量: " + conflicts.size()); return conflicts; } /** * 构建时空表 * * @param plannedPaths 规划路径列表 * @return 时空表 */ private Map> buildSpaceTimeTable(List plannedPaths) { Map> spaceTimeTable = new HashMap<>(); for (PlannedPath path : plannedPaths) { String agvId = path.getAgvId(); List codeList = path.getCodeList(); if (codeList == null || codeList.isEmpty()) { continue; } // 获取CTU的物理配置 CTUPhysicalConfig physicalConfig = getPhysicalConfigForCTU(agvId); for (PathCode pathCode : codeList) { String position = pathCode.getCode(); Long arrivalTime = pathCode.getArrivalTime(); Long departureTime = pathCode.getDepartureTime(); // 如果时间戳为空,跳过 if (arrivalTime == null || departureTime == null) { System.out.println("警告: AGV " + agvId + " 在位置 " + position + " 的时间戳为空,跳过"); continue; } int[] coordinates = JsonUtils.getCoordinate(position, pathMapping); if (coordinates != null) { // 创建时间段内的多个时间点(使用实际时间戳) for (long timePoint = arrivalTime; timePoint <= departureTime; timePoint += timeResolution) { SpaceTimeNode node = new SpaceTimeNode( agvId, position, coordinates, timePoint, arrivalTime, departureTime, physicalConfig ); spaceTimeTable.computeIfAbsent(timePoint, k -> new ArrayList<>()).add(node); } } } } return spaceTimeTable; } /** * 获取CTU的物理配置 * * @param agvId CTU编号 * @return 物理配置 */ private CTUPhysicalConfig getPhysicalConfigForCTU(String agvId) { // 从AGV状态中获取 CTUPhysicalConfig config = new CTUPhysicalConfig(); // 标准配置 // maxSpeed=1.0, normalSpeed=1.0, maxAcceleration=0.4, maxDeceleration=0.4 // turnTime90=4.0, turnTime180=8.0, minSafetyDistance=3.0, minFollowingDistance=3.0 // ctuLength=1.5, ctuWidth=1.0, startupTime=1.0, stopTime=1.5, standardPointDistance=1.0 return config; // 使用默认构造函数的标准配置 } /** * 计算到达时间 * * @param startTime 起始时间 * @param pathIndex 路径索引 * @param codeList 路径代码列表 * @param config 物理配置 * @return 到达时间 */ private long calculateArrivalTime(long startTime, int pathIndex, List codeList, CTUPhysicalConfig config) { if (pathIndex == 0) { return startTime; } long travelTime = 0; for (int i = 1; i <= pathIndex; i++) { PathCode currentCode = codeList.get(i); PathCode previousCode = codeList.get(i - 1); // 计算移动时间 double distance = config.getStandardPointDistance(); double speed = config.getNormalSpeed(); travelTime += (long) ((distance / speed) * 1000); // 转换为毫秒 // 如果有方向变化,增加转向时间 // 添加null检查 if (currentCode.getDirection() != null && previousCode.getDirection() != null) { if (!currentCode.getDirection().equals(previousCode.getDirection())) { double turnTime = calculateTurnTime(previousCode.getDirection(), currentCode.getDirection(), config); travelTime += (long) (turnTime * 1000); } } // 考虑加速和减速时间 if (i == 1) { travelTime += (long) (config.getStartupTime() * 1000); } } return startTime + travelTime; } /** * 计算离开时间 * * @param arrivalTime 到达时间 * @param pathCode 路径代码 * @param config 物理配置 * @return 离开时间 */ private long calculateDepartureTime(long arrivalTime, PathCode pathCode, CTUPhysicalConfig config) { long stayTime = 500; // 默认停留时间500毫秒 // 根据路径代码类型确定停留时间 if (pathCode.isTargetPoint()) { if ("1".equals(pathCode.getPosType())) { // 取货 stayTime = 3000; // 取货需要3秒 } else if ("2".equals(pathCode.getPosType())) { // 放货 stayTime = 2000; // 放货需要2秒 } } return arrivalTime + stayTime; } /** * 计算转向时间 * * @param fromDirection 起始方向 * @param toDirection 目标方向 * @param config 物理配置 * @return 转向时间(秒) */ private double calculateTurnTime(String fromDirection, String toDirection, CTUPhysicalConfig config) { if (fromDirection.equals(toDirection)) { return 0.0; } // 方向转换计算 int angleDiff = Math.abs(Integer.parseInt(toDirection) - Integer.parseInt(fromDirection)); angleDiff = Math.min(angleDiff, 4 - angleDiff); // 考虑环形 switch (angleDiff) { case 1: return config.getTurnTime90(); case 2: return config.getTurnTime180(); default: return config.getTurnTime90() * angleDiff; } } /** * 检测顶点冲突 * * @param spaceTimeTable 时空表 * @return 顶点冲突列表 */ private List detectVertexConflicts(Map> spaceTimeTable) { List conflicts = new ArrayList<>(); for (Map.Entry> entry : spaceTimeTable.entrySet()) { long timePoint = entry.getKey(); List nodes = entry.getValue(); // 按位置分组 Map> positionGroups = new HashMap<>(); for (SpaceTimeNode node : nodes) { positionGroups.computeIfAbsent(node.position, k -> new ArrayList<>()).add(node); } // 检查每个位置是否有多个CTU for (Map.Entry> posEntry : positionGroups.entrySet()) { String position = posEntry.getKey(); List ctuNodes = posEntry.getValue(); if (ctuNodes.size() > 1) { // 进一步检查时间重叠 for (int i = 0; i < ctuNodes.size(); i++) { for (int j = i + 1; j < ctuNodes.size(); j++) { SpaceTimeNode node1 = ctuNodes.get(i); SpaceTimeNode node2 = ctuNodes.get(j); // 检查时间段是否重叠 if (timeRangeOverlap(node1.arrivalTime, node1.departureTime, node2.arrivalTime, node2.departureTime)) { Conflict conflict = new Conflict( "vertex", node1.agvId, node2.agvId, (int) (timePoint / 1000), // 转换为秒用于显示 position, position, String.format("CTU %s 和 %s 在时间 %d 占用同一位置 %s(时间重叠)", node1.agvId, node2.agvId, timePoint / 1000, position) ); conflicts.add(conflict); } } } } } } return conflicts; } /** * 检测边冲突 * * @param spaceTimeTable 时空表 * @return 边冲突列表 */ private List detectEdgeConflicts(Map> spaceTimeTable) { List conflicts = new ArrayList<>(); if (spaceTimeTable.isEmpty()) { return conflicts; } List sortedTimes = new ArrayList<>(spaceTimeTable.keySet()); Collections.sort(sortedTimes); for (int i = 0; i < sortedTimes.size() - 1; i++) { long currentTime = sortedTimes.get(i); long nextTime = sortedTimes.get(i + 1); // 只检查连续的时间点 if (nextTime - currentTime <= timeResolution * 2) { List currentNodes = spaceTimeTable.get(currentTime); List nextNodes = spaceTimeTable.get(nextTime); conflicts.addAll(detectPositionSwapping(currentNodes, nextNodes, currentTime, nextTime)); } } return conflicts; } /** * 检测位置交换 * * @param currentNodes 当前时间的节点 * @param nextNodes 下一时间的节点 * @param currentTime 当前时间 * @param nextTime 下一时间 * @return 冲突列表 */ private List detectPositionSwapping(List currentNodes, List nextNodes, long currentTime, long nextTime) { List conflicts = new ArrayList<>(); Map currentPositions = new HashMap<>(); Map nextPositions = new HashMap<>(); for (SpaceTimeNode node : currentNodes) { currentPositions.put(node.agvId, node.position); } for (SpaceTimeNode node : nextNodes) { nextPositions.put(node.agvId, node.position); } for (Map.Entry entry1 : currentPositions.entrySet()) { String ctu1 = entry1.getKey(); String pos1Current = entry1.getValue(); for (Map.Entry entry2 : currentPositions.entrySet()) { String ctu2 = entry2.getKey(); String pos2Current = entry2.getValue(); if (ctu1.compareTo(ctu2) >= 0) { continue; } String pos1Next = nextPositions.get(ctu1); String pos2Next = nextPositions.get(ctu2); if (pos1Next != null && pos2Next != null && pos1Current.equals(pos2Next) && pos2Current.equals(pos1Next)) { Conflict conflict = new Conflict( "edge", ctu1, ctu2, (int) (currentTime / 1000), pos1Current, pos2Current, String.format("CTU %s 和 %s 在时间 %d-%d 交换位置 %s<->%s", ctu1, ctu2, currentTime / 1000, nextTime / 1000, pos1Current, pos2Current) ); conflicts.add(conflict); } } } return conflicts; } /** * 检测跟随冲突 * * @param spaceTimeTable 时空表 * @return 跟随冲突列表 */ private List detectFollowingConflicts(Map> spaceTimeTable) { List conflicts = new ArrayList<>(); // 第一部分:检查同一时间点的节点 for (Map.Entry> entry : spaceTimeTable.entrySet()) { long timePoint = entry.getKey(); List nodes = entry.getValue(); for (int i = 0; i < nodes.size(); i++) { for (int j = i + 1; j < nodes.size(); j++) { SpaceTimeNode node1 = nodes.get(i); SpaceTimeNode node2 = nodes.get(j); double distance = calculateDistance(node1.coordinates, node2.coordinates); double minSafeDistance = Math.max( node1.physicalConfig.getMinFollowingDistance(), node2.physicalConfig.getMinFollowingDistance() ); if (distance > 0 && distance < minSafeDistance) { Conflict conflict = new Conflict( "follow", node1.agvId, node2.agvId, (int) (timePoint / 1000), node1.position, node2.position, String.format("CTU %s 和 %s 在时间 %d 距离过近 (%.2f < %.2f)", node1.agvId, node2.agvId, timePoint / 1000, distance, minSafeDistance) ); conflicts.add(conflict); } // 检查同一时间点的时间范围重叠(同时占用) if (distance <= minSafeDistance && timeRangeOverlap( node1.arrivalTime, node1.departureTime, node2.arrivalTime, node2.departureTime)) { double normalSpeed = Math.min( node1.physicalConfig.getNormalSpeed(), node2.physicalConfig.getNormalSpeed() ); long minTimeGap = (long) ((minSafeDistance / normalSpeed + 10.0) * 1000); long timeGap = Math.abs(node1.arrivalTime - node2.arrivalTime); if (timeGap < minTimeGap) { Conflict conflict = new Conflict( "time_gap_insufficient", node1.agvId, node2.agvId, (int) (timePoint / 1000), node1.position, node2.position, String.format("CTU %s 和 %s 在位置 %s/%s 同时占用且时间间隔不足 (%.2f秒 < %.2f秒)", node1.agvId, node2.agvId, node1.position, node2.position, timeGap / 1000.0, minTimeGap / 1000.0) ); conflicts.add(conflict); } } } } } // 第二部分:检查同一位置连续经过的时间间隔 Map> positionAgvMap = new HashMap<>(); for (List nodes : spaceTimeTable.values()) { for (SpaceTimeNode node : nodes) { positionAgvMap.computeIfAbsent(node.position, k -> new HashMap<>()) .compute(node.agvId, (k, existing) -> { if (existing == null) { return node; } else { // 合并时间范围 long minArrival = Math.min(existing.arrivalTime, node.arrivalTime); long maxDeparture = Math.max(existing.departureTime, node.departureTime); return new SpaceTimeNode( node.agvId, node.position, node.coordinates, minArrival, minArrival, maxDeparture, node.physicalConfig ); } }); } } // 对于每个位置,检查所有经过该位置的AGV的时间间隔 Set processedPairs = new HashSet<>(); for (Map.Entry> positionEntry : positionAgvMap.entrySet()) { String position = positionEntry.getKey(); Map agvNodes = positionEntry.getValue(); List nodes = new ArrayList<>(agvNodes.values()); nodes.sort((n1, n2) -> Long.compare(n1.arrivalTime, n2.arrivalTime)); for (int i = 0; i < nodes.size(); i++) { for (int j = i + 1; j < nodes.size(); j++) { SpaceTimeNode node1 = nodes.get(i); SpaceTimeNode node2 = nodes.get(j); if (node1.agvId.equals(node2.agvId)) { continue; } String pairKey1 = node1.agvId + "_" + node2.agvId + "_" + position; String pairKey2 = node2.agvId + "_" + node1.agvId + "_" + position; if (processedPairs.contains(pairKey1) || processedPairs.contains(pairKey2)) { continue; } // 计算时间间隔:node1离开后,node2到达的时间间隔 long timeGap; if (node1.departureTime <= node2.arrivalTime) { timeGap = node2.arrivalTime - node1.departureTime; } else if (node2.departureTime <= node1.arrivalTime) { timeGap = node1.arrivalTime - node2.departureTime; } else { continue; } // 计算最小安全时间间隔 double minSafeDistance = Math.max( node1.physicalConfig.getMinSafetyDistance(), node2.physicalConfig.getMinSafetyDistance() ); double normalSpeed = Math.min( node1.physicalConfig.getNormalSpeed(), node2.physicalConfig.getNormalSpeed() ); // 最小时间间隔 = 安全距离/速度 + 额外安全缓冲(7秒) long minTimeGap = (long) ((minSafeDistance / normalSpeed + 7.0) * 1000); // 检查时间间隔是否足够 if (timeGap < minTimeGap) { String firstAgv = node1.arrivalTime < node2.arrivalTime ? node1.agvId : node2.agvId; String secondAgv = node1.arrivalTime < node2.arrivalTime ? node2.agvId : node1.agvId; long secondArrival = node1.arrivalTime < node2.arrivalTime ? node2.arrivalTime : node1.arrivalTime; Conflict conflict = new Conflict( "time_gap_insufficient", firstAgv, secondAgv, (int) (secondArrival / 1000), position, position, String.format("CTU %s 和 %s 在位置 %s 连续经过时间间隔不足 (%.2f秒 < %.2f秒, %s离开后%.2f秒%s到达)", firstAgv, secondAgv, position, timeGap / 1000.0, minTimeGap / 1000.0, firstAgv, timeGap / 1000.0, secondAgv) ); conflicts.add(conflict); processedPairs.add(pairKey1); } } } } return conflicts; } /** * 检测物理尺寸冲突 * * @param spaceTimeTable 时空表 * @return 物理冲突列表 */ private List detectPhysicalSizeConflicts(Map> spaceTimeTable) { List conflicts = new ArrayList<>(); for (Map.Entry> entry : spaceTimeTable.entrySet()) { long timePoint = entry.getKey(); List nodes = entry.getValue(); for (int i = 0; i < nodes.size(); i++) { for (int j = i + 1; j < nodes.size(); j++) { SpaceTimeNode node1 = nodes.get(i); SpaceTimeNode node2 = nodes.get(j); if (checkPhysicalCollision(node1, node2)) { Conflict conflict = new Conflict( "physical_size", node1.agvId, node2.agvId, (int) (timePoint / 1000), node1.position, node2.position, String.format("CTU %s 和 %s 在时间 %d 发生物理尺寸冲突", node1.agvId, node2.agvId, timePoint / 1000) ); conflicts.add(conflict); } } } } return conflicts; } /** * 检查两个CTU是否发生物理碰撞 * * @param node1 CTU节点1 * @param node2 CTU节点2 * @return 是否碰撞 */ private boolean checkPhysicalCollision(SpaceTimeNode node1, SpaceTimeNode node2) { double distance = calculateDistance(node1.coordinates, node2.coordinates); // 考虑CTU的物理尺寸 double ctu1Radius = Math.max(node1.physicalConfig.getCtuLength(), node1.physicalConfig.getCtuWidth()) / 2; double ctu2Radius = Math.max(node2.physicalConfig.getCtuLength(), node2.physicalConfig.getCtuWidth()) / 2; double minClearance = ctu1Radius + ctu2Radius + spaceResolution; return distance > 0 && distance < minClearance; } /** * 检查时间范围是否重叠 * * @param start1 开始时间1 * @param end1 结束时间1 * @param start2 开始时间2 * @param end2 结束时间2 * @return 是否重叠 */ private boolean timeRangeOverlap(long start1, long end1, long start2, long end2) { return Math.max(start1, start2) < Math.min(end1, end2); } /** * 计算两个坐标之间的距离 * * @param coord1 坐标1 * @param coord2 坐标2 * @return 距离值 */ private double calculateDistance(int[] coord1, int[] coord2) { if (coord1 == null || coord2 == null || coord1.length != 2 || coord2.length != 2) { return -1; } return Math.sqrt(Math.pow(coord1[0] - coord2[0], 2) + Math.pow(coord1[1] - coord2[1], 2)); } /** * 时空节点内部类 */ private static class SpaceTimeNode { final String agvId; final String position; final int[] coordinates; final long timePoint; final long arrivalTime; final long departureTime; final CTUPhysicalConfig physicalConfig; public SpaceTimeNode(String agvId, String position, int[] coordinates, long timePoint, long arrivalTime, long departureTime, CTUPhysicalConfig physicalConfig) { this.agvId = agvId; this.position = position; this.coordinates = coordinates; this.timePoint = timePoint; this.arrivalTime = arrivalTime; this.departureTime = departureTime; this.physicalConfig = physicalConfig; } } }