package com.algo.service; import com.algo.model.AGVStatus; import com.algo.model.CTUPhysicalConfig; import com.algo.model.PathCode; import com.algo.model.PlannedPath; import com.algo.util.JsonUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 剩余路径处理器 * 负责处理CTU未完成路径的续接逻辑,确保新路径与剩余路径的平滑连接 */ public class RemainingPathProcessor { /** * 路径映射表 */ private final Map> pathMapping; /** * 系统当前时间基准(毫秒) */ private long systemBaseTime; public RemainingPathProcessor(Map> pathMapping) { this.pathMapping = pathMapping; this.systemBaseTime = System.currentTimeMillis(); } /** * 处理所有CTU的剩余路径,构建时空占用表 * * @param agvStatusList CTU状态列表 * @return 时空占用表,key为"x,y,timeSlot",value为CTU编号 */ public Map buildSpaceTimeOccupancyMap(List agvStatusList) { Map occupancyMap = new HashMap<>(); for (AGVStatus agv : agvStatusList) { if (agv.hasRemainingPath()) { processRemainingPathOccupancy(agv, occupancyMap); } } return occupancyMap; } /** * 处理单个CTU的剩余路径时空占用 * * @param agv CTU状态 * @param occupancyMap 时空占用表 */ private void processRemainingPathOccupancy(AGVStatus agv, Map occupancyMap) { PlannedPath remainingPath = agv.getRemainingPath(); List codeList = remainingPath.getCodeList(); CTUPhysicalConfig config = agv.getPhysicalConfig(); // 从当前位置开始计算时空占用 long currentTime = agv.getNextPointArrivalTime(); int startIndex = agv.getCurrentPathIndex(); for (int i = startIndex; i < codeList.size(); i++) { PathCode pathCode = codeList.get(i); String position = pathCode.getCode(); // 获取位置坐标 int[] coord = JsonUtils.getCoordinate(position, pathMapping); if (coord == null) continue; // 计算在此位置的停留时间 double stayDuration = calculateStayDuration(agv, i, codeList, config); // 将停留时间转换为时间片 long startTimeSlot = currentTime / 1000; // 转换为秒 long endTimeSlot = (long) (currentTime / 1000 + stayDuration); // 占用时空 for (long timeSlot = startTimeSlot; timeSlot <= endTimeSlot; timeSlot++) { String spaceTimeKey = coord[0] + "," + coord[1] + "," + timeSlot; occupancyMap.put(spaceTimeKey, agv.getAgvId()); // 考虑CTU的物理尺寸,占用周围的格子 occupyAdjacentSpaces(coord, timeSlot, agv.getAgvId(), occupancyMap, config); } // 更新到下一个位置的时间 currentTime += (long) (stayDuration * 1000); if (i < codeList.size() - 1) { PathCode nextPathCode = codeList.get(i + 1); double travelTime = calculateTravelTime(pathCode, nextPathCode, config); currentTime += (long) (travelTime * 1000); } } } /** * 占用相邻空间(考虑CTU物理尺寸) * * @param centerCoord 中心坐标 * @param timeSlot 时间片 * @param agvId CTU编号 * @param occupancyMap 占用表 * @param config 物理配置 */ private void occupyAdjacentSpaces(int[] centerCoord, long timeSlot, String agvId, Map occupancyMap, CTUPhysicalConfig config) { // 根据CTU的长度和宽度计算需要占用的格子数 int lengthGrids = (int) Math.ceil(config.getCtuLength() / config.getStandardPointDistance()); int widthGrids = (int) Math.ceil(config.getCtuWidth() / config.getStandardPointDistance()); // 占用周围的格子 for (int dx = -lengthGrids / 2; dx <= lengthGrids / 2; dx++) { for (int dy = -widthGrids / 2; dy <= widthGrids / 2; dy++) { if (dx == 0 && dy == 0) continue; // 中心点已经占用 int adjX = centerCoord[0] + dx; int adjY = centerCoord[1] + dy; String spaceTimeKey = adjX + "," + adjY + "," + timeSlot; // 只有当该位置未被占用时才占用 if (!occupancyMap.containsKey(spaceTimeKey)) { occupancyMap.put(spaceTimeKey, agvId); } } } } /** * 计算CTU在指定路径点的停留时间 * * @param agv CTU状态 * @param pathIndex 路径点索引 * @param codeList 完整路径代码列表 * @param config 物理配置 * @return 停留时间(秒) */ private double calculateStayDuration(AGVStatus agv, int pathIndex, List codeList, CTUPhysicalConfig config) { PathCode currentCode = codeList.get(pathIndex); // 基础停留时间 double stayTime = 0.5; // 默认0.5秒 // 如果需要转向,增加转向时间 if (pathIndex < codeList.size() - 1) { PathCode nextCode = codeList.get(pathIndex + 1); String currentDirection = currentCode.getDirection(); String nextDirection = nextCode.getDirection(); if (!currentDirection.equals(nextDirection)) { stayTime += config.getTurnTime(currentDirection, nextDirection); } } // 如果是动作点(取货、放货等),增加操作时间 if (currentCode.getActionType() != null) { switch (currentCode.getActionType()) { case "1": // 取货 stayTime += 3.0; // 取货需要3秒 break; case "2": // 放货 stayTime += 2.0; // 放货需要2秒 break; case "3": // 充电 stayTime += 10.0; // 充电停靠需要10秒 break; default: stayTime += 1.0; // 其他动作需要1秒 break; } } return stayTime; } /** * 计算两个路径点之间的移动时间 * * @param fromCode 起始路径点 * @param toCode 目标路径点 * @param config 物理配置 * @return 移动时间(秒) */ private double calculateTravelTime(PathCode fromCode, PathCode toCode, CTUPhysicalConfig config) { // 获取坐标 int[] fromCoord = JsonUtils.getCoordinate(fromCode.getCode(), pathMapping); int[] toCoord = JsonUtils.getCoordinate(toCode.getCode(), pathMapping); if (fromCoord == null || toCoord == null) { return config.getStandardPointDistance() / config.getNormalSpeed(); } // 计算距离 double distance = Math.sqrt(Math.pow(toCoord[0] - fromCoord[0], 2) + Math.pow(toCoord[1] - fromCoord[1], 2)) * config.getStandardPointDistance(); // 计算移动时间 return config.getTravelTime(distance, config.getNormalSpeed()); } /** * 为新路径查找合适的起始时间,避免与现有路径冲突 * * @param newPath 新路径 * @param occupancyMap 现有时空占用表 * @param config 物理配置 * @return 建议的起始时间(毫秒) */ public long findSafeStartTime(PlannedPath newPath, Map occupancyMap, CTUPhysicalConfig config) { if (newPath.getCodeList() == null || newPath.getCodeList().isEmpty()) { return systemBaseTime; } // 从当前时间开始,逐步向后搜索安全的起始时间 long currentTime = systemBaseTime; long timeStep = 1000; // 1秒步长 long maxSearchTime = currentTime + 300000; // 最多搜索5分钟 while (currentTime < maxSearchTime) { if (isPathTimeSafe(newPath, currentTime, occupancyMap, config)) { return currentTime; } currentTime += timeStep; } // 如果找不到安全时间,返回较远的未来时间 return maxSearchTime; } /** * 检查指定时间开始的路径是否安全 * * @param path 路径 * @param startTime 起始时间 * @param occupancyMap 占用表 * @param config 物理配置 * @return true如果安全 */ private boolean isPathTimeSafe(PlannedPath path, long startTime, Map occupancyMap, CTUPhysicalConfig config) { List codeList = path.getCodeList(); long currentTime = startTime; for (int i = 0; i < codeList.size(); i++) { PathCode pathCode = codeList.get(i); int[] coord = JsonUtils.getCoordinate(pathCode.getCode(), pathMapping); if (coord == null) continue; // 计算在此位置的停留时间 double stayDuration = 2.0; // 简化计算,使用固定停留时间 long startTimeSlot = currentTime / 1000; long endTimeSlot = (long) (currentTime / 1000 + stayDuration); // 检查时空冲突 for (long timeSlot = startTimeSlot; timeSlot <= endTimeSlot; timeSlot++) { String spaceTimeKey = coord[0] + "," + coord[1] + "," + timeSlot; if (occupancyMap.containsKey(spaceTimeKey)) { return false; // 发现冲突 } // 检查安全距离内的冲突 if (!isSafeFromOtherCTUs(coord, timeSlot, occupancyMap, config)) { return false; } } // 更新到下一个位置的时间 currentTime += (long) (stayDuration * 1000); if (i < codeList.size() - 1) { currentTime += (long) (config.getStandardPointDistance() / config.getNormalSpeed() * 1000); } } return true; } /** * 检查指定位置和时间是否与其他CTU保持安全距离 * * @param coord 位置坐标 * @param timeSlot 时间片 * @param occupancyMap 占用表 * @param config 物理配置 * @return true如果安全 */ private boolean isSafeFromOtherCTUs(int[] coord, long timeSlot, Map occupancyMap, CTUPhysicalConfig config) { int safetyRadius = (int) Math.ceil(config.getMinSafetyDistance() / config.getStandardPointDistance()); // 检查安全半径内是否有其他CTU for (int dx = -safetyRadius; dx <= safetyRadius; dx++) { for (int dy = -safetyRadius; dy <= safetyRadius; dy++) { if (dx == 0 && dy == 0) continue; String checkKey = (coord[0] + dx) + "," + (coord[1] + dy) + "," + timeSlot; if (occupancyMap.containsKey(checkKey)) { // 计算实际距离 double distance = Math.sqrt(dx * dx + dy * dy) * config.getStandardPointDistance(); if (distance < config.getMinSafetyDistance()) { return false; // 距离太近 } } } } return true; } /** * 连接剩余路径和新路径 * * @param agv CTU状态 * @param newPath 新规划的路径 * @return 连接后的完整路径 */ public PlannedPath connectRemainingAndNewPath(AGVStatus agv, PlannedPath newPath) { if (!agv.hasRemainingPath()) { // 没有剩余路径,直接返回新路径 return newPath; } PlannedPath remainingPath = agv.getRemainingPath(); List remainingCodes = new ArrayList<>(); // 添加剩余路径(从当前位置开始) List originalCodes = remainingPath.getCodeList(); for (int i = agv.getCurrentPathIndex(); i < originalCodes.size(); i++) { remainingCodes.add(originalCodes.get(i)); } // 连接新路径 if (newPath != null && newPath.getCodeList() != null) { // 检查连接点是否一致,避免重复 if (!remainingCodes.isEmpty() && !newPath.getCodeList().isEmpty()) { PathCode lastRemaining = remainingCodes.get(remainingCodes.size() - 1); PathCode firstNew = newPath.getCodeList().get(0); if (lastRemaining.getCode().equals(firstNew.getCode())) { // 跳过重复的连接点 for (int i = 1; i < newPath.getCodeList().size(); i++) { remainingCodes.add(newPath.getCodeList().get(i)); } } else { // 直接连接 remainingCodes.addAll(newPath.getCodeList()); } } else { remainingCodes.addAll(newPath.getCodeList()); } } // 创建连接后的路径 PlannedPath connectedPath = new PlannedPath(); connectedPath.setAgvId(agv.getAgvId()); connectedPath.setCodeList(remainingCodes); // 生成新的段落ID String segId = agv.getAgvId() + "_CONNECTED_" + System.currentTimeMillis(); if (newPath != null && newPath.getSegId() != null) { segId = newPath.getSegId() + "_EXTENDED"; } connectedPath.setSegId(segId); return connectedPath; } /** * 更新系统基准时间 * * @param baseTime 新的基准时间 */ public void updateSystemBaseTime(long baseTime) { this.systemBaseTime = baseTime; } /** * 获取CTU预计完成剩余路径的时间 * * @param agv CTU状态 * @return 预计完成时间(毫秒) */ public long getEstimatedCompletionTime(AGVStatus agv) { if (!agv.hasRemainingPath()) { return System.currentTimeMillis(); } PlannedPath remainingPath = agv.getRemainingPath(); List codeList = remainingPath.getCodeList(); CTUPhysicalConfig config = agv.getPhysicalConfig(); long currentTime = agv.getNextPointArrivalTime(); // 计算完成剩余路径所需的时间 for (int i = agv.getCurrentPathIndex(); i < codeList.size(); i++) { double stayDuration = calculateStayDuration(agv, i, codeList, config); currentTime += (long) (stayDuration * 1000); if (i < codeList.size() - 1) { PathCode currentCode = codeList.get(i); PathCode nextCode = codeList.get(i + 1); double travelTime = calculateTravelTime(currentCode, nextCode, config); currentTime += (long) (travelTime * 1000); } } return currentTime; } }