package com.zy.acs.manager.core.service; import com.alibaba.fastjson.JSON; import com.zy.acs.common.enums.AgvDirectionType; import com.zy.acs.framework.common.Cools; import com.zy.acs.manager.core.constant.MapDataConstant; import com.zy.acs.manager.core.domain.DirectionDto; import com.zy.acs.manager.core.domain.SortCodeDto; import com.zy.acs.manager.core.domain.UnlockPathTask; import com.zy.acs.manager.core.domain.type.CodeDirectionType; import com.zy.acs.manager.core.service.astart.*; import com.zy.acs.manager.core.service.astart.domain.AStarNavigateNode; import com.zy.acs.manager.core.service.astart.domain.DynamicNode; import com.zy.acs.manager.core.service.floyd.FloydNavigateService; import com.zy.acs.manager.manager.entity.Code; import com.zy.acs.manager.manager.entity.Loc; import com.zy.acs.manager.manager.entity.Segment; import com.zy.acs.manager.manager.entity.Sta; import com.zy.acs.manager.manager.enums.CodeSpinType; import com.zy.acs.manager.manager.service.ActionService; import com.zy.acs.manager.manager.service.CodeService; import com.zy.acs.manager.system.service.ConfigService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * Created by vincent on 2023/6/14 */ @Slf4j @Component("mapService") public class MapService { private static final double EPS = 1e-7; private final static int[][] DIRECTIONS = {{0,1},{0,-1},{-1,0},{1,0}}; @Value("${floyd.enable}") private Boolean floydEnable; @Autowired private CodeService codeService; @Autowired private FloydNavigateService floydNavigateService; @Autowired private MapDataDispatcher mapDataDispatcher; @Autowired private AStarNavigateService aStarNavigateService; @Autowired private ConfigService configService; @Autowired private ActionService actionService; @Autowired private LinkedBlockingQueue unlockTaskQueue; /** * 寻址 ===>> A Star */ public synchronized List checkoutPath(String agvNo, Code startCode, Code endCode , Boolean lock, List blackList, Segment segment) { int[] startMapIdx = mapDataDispatcher.getCodeMatrixIdx(null, startCode.getData()); int[] endMapIdx = mapDataDispatcher.getCodeMatrixIdx(null, endCode.getData()); AStarNavigateNode startNode = new AStarNavigateNode(startMapIdx[0], startMapIdx[1], startCode.getData()); AStarNavigateNode endNode = new AStarNavigateNode(endMapIdx[0], endMapIdx[1], endCode.getData()); // long startTime = System.currentTimeMillis(); AStarNavigateNode finishNode = aStarNavigateService.execute(agvNo, startNode, endNode, lock, blackList, segment); // System.out.println("A Star Spend time :" + (System.currentTimeMillis() - startTime)); if (null == finishNode) { return new ArrayList<>(); } ArrayList navigateNodes = new ArrayList<>(); while (finishNode != null) { navigateNodes.add(finishNode); finishNode = finishNode.getParent(); } Collections.reverse(navigateNodes); List navigatePath = navigateNodes.stream().map(AStarNavigateNode::getCodeData).collect(Collectors.toList()); // max count of steps if (navigatePath.size() > MapDataConstant.MAX_STEPS_SINGLE) { navigatePath = navigatePath.subList(0, MapDataConstant.MAX_STEPS_SINGLE); } return navigatePath; } /** * 寻址 ===>> Floyd */ public synchronized List validFeasibility(Code startCode, Code endCode) { if (!floydEnable) { return Arrays.asList("00000001", "00000002", "00000003", "00000004", "00000005", "00000006"); } int startIdx = floydNavigateService.codeIdx(startCode.getId()); int endIdx = floydNavigateService.codeIdx(endCode.getId()); Double distance; try { distance = floydNavigateService.getFloydMatrix()[startIdx][endIdx]; } catch (ArrayIndexOutOfBoundsException e) { floydNavigateService.generateMatrix(); startIdx = floydNavigateService.codeIdx(startCode.getId()); endIdx = floydNavigateService.codeIdx(endCode.getId()); distance = floydNavigateService.getFloydMatrix()[startIdx][endIdx]; } if (distance.equals(FloydNavigateService.INF)) { return null; } return floydNavigateService.calculatePath(startIdx, endIdx).stream().map(path -> { Long codeId = floydNavigateService.getCodeId(path); Code code = codeService.getCacheById(codeId); return code.getData(); }).collect(Collectors.toList()); } public static Double mapToNearest(Double angle) { if (null == angle) { return null; } angle = (angle + 360) % 360; double remainder = angle % 45; if (remainder < 22.5) { return angle - remainder; } else { return angle + (45 - remainder); } } // 角度计算 public Double calculateDirection(Code startCode, Code endCode, int angleOffsetVal) { Double x0 = startCode.getX(); Double y0 = startCode.getY(); Double x1 = endCode.getX(); Double y1 = endCode.getY(); double deltaX = x1 - x0; double deltaY = y1 - y0; double angle = Math.atan2(deltaX, deltaY); angle = Math.toDegrees(angle) + angleOffsetVal; angle = (angle + 360) % 360; // 将角度转换为正值 return angle; } // 坐标货架阈值 todo:luxiaotao public AgvDirectionType calculateAgvWorkDirectionByShelf(Loc loc, Code code) { Integer compDirect = loc.getCompDirect(); AgvDirectionType agvDirectionType = null; if (compDirect == 0) { agvDirectionType = AgvDirectionType.RIGHT; } if (compDirect == 1) { agvDirectionType = AgvDirectionType.LEFT; } return agvDirectionType; } public Double getStaAngle(Sta sta, Double workDirection) { if (null == sta) { return null; } if (Cools.isEmpty(sta.getAngle())) { return workDirection; } return Double.parseDouble(sta.getAngle()); } public Double calculateAgvWorkDirectionByStation(Double staWorkDirection, Double lastDirection) { return Math.abs(staWorkDirection - lastDirection) + 90.0D; } public double calculateDistance(double x1, double y1, double x2, double y2) { double deltaX = x2 - x1; double deltaY = y2 - y1; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); } public void lockPath(Integer lev, List pathList, String agvNo) { List codeMatrixIdxList = mapDataDispatcher.getCodeMatrixIdxList(lev, pathList); mapDataDispatcher.modifyDynamicMatrix(lev, codeMatrixIdxList, agvNo); } public void unlockPath(String agvNo, String codeData) { if (Cools.isEmpty(agvNo, codeData)) { return; } try { unlockTaskQueue.offer(new UnlockPathTask(agvNo, codeData), 5, TimeUnit.SECONDS); // Integer lev = null; // // String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(null); // int[] codeMatrixIdx = mapDataDispatcher.getCodeMatrixIdx(lev, codeData); // // // DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); // // DynamicNode dynamicNode = dynamicMatrix[codeMatrixIdx[0]][codeMatrixIdx[1]]; // // // int serial = dynamicNode.getSerial(); // // List resetCodeIdxList = new ArrayList<>(); // // for (int i = 0; i < dynamicMatrix.length; i++) { // for (int j = 0; j < dynamicMatrix[i].length; j++) { // //// if (i == codeMatrixIdx[0] && j == codeMatrixIdx[1]) { continue; } // // DynamicNode node = dynamicMatrix[i][j]; // if (node.getVehicle().equals(agvNo)) { // if (node.getSerial() < serial) { // resetCodeIdxList.add(new int[] {i, j}); // } // } // } // } // // if (!Cools.isEmpty(resetCodeIdxList)) { // // mapDataDispatcher.clearDynamicMatrixByCodeList(lev, resetCodeIdxList); // } } catch (InterruptedException e) { log.error("unlockTaskQueue", e); } } public List getWaveScopeByCodeList(Integer lev, List codeList, Double radiusLen) { if (Cools.isEmpty(codeList)) { return new ArrayList<>(); } if (Cools.isEmpty(radiusLen) || radiusLen == 0D) { return codeList; } radiusLen = Math.abs(radiusLen); List nodeList = new ArrayList<>(); for (String code : codeList) { nodeList.addAll(this.getWaveScopeByCode(lev, code, radiusLen)); } return nodeList.stream().map(NavigateNode::getCodeData).distinct().collect(Collectors.toList()); } // v1 BFS ------------------------------------------------------------------------------ public List getWaveScopeByCode(Integer lev, String code, Double radiusLen) { double radiusLenSquared = Math.pow(radiusLen, 2); String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); Double[][][] cdaMatrix = mapDataDispatcher.getCdaMatrix(lev); int[] codeMatrixIdx = mapDataDispatcher.getCodeMatrixIdx(lev, code); NavigateNode originNode = new NavigateNode(codeMatrixIdx[0], codeMatrixIdx[1], code); List includeList = new ArrayList<>(); Set existNodes = new HashSet<>(); includeList.add(originNode); existNodes.add(originNode); this.spreadWaveNode(originNode, originNode, codeMatrix, cdaMatrix, radiusLenSquared, includeList, existNodes); return includeList; } public void spreadWaveNode(NavigateNode originNode, NavigateNode currNode , String[][] codeMatrix, Double[][][] cdaMatrix, Double radiusLenSquared , List includeList, Set existNodes) { int x = currNode.getX(); int y = currNode.getY(); this.extendNeighborNodes(originNode, new NavigateNode(x, y + 1), codeMatrix, cdaMatrix, radiusLenSquared, includeList, existNodes); this.extendNeighborNodes(originNode, new NavigateNode(x, y - 1), codeMatrix, cdaMatrix, radiusLenSquared, includeList, existNodes); this.extendNeighborNodes(originNode, new NavigateNode(x - 1, y), codeMatrix, cdaMatrix, radiusLenSquared, includeList, existNodes); this.extendNeighborNodes(originNode, new NavigateNode(x + 1, y), codeMatrix, cdaMatrix, radiusLenSquared, includeList, existNodes); } public void extendNeighborNodes(NavigateNode originNode, NavigateNode nextNode , String[][] codeMatrix, Double[][][] cdaMatrix, Double radiusLenSquared , List includeList, Set existNodes) { int x = nextNode.getX(); int y = nextNode.getY(); if (x < 0 || x >= codeMatrix.length || y < 0 || y >= codeMatrix[0].length) { return; } if (existNodes.contains(nextNode)) { return; } existNodes.add(nextNode); Double[] o1Cda = cdaMatrix[originNode.getX()][originNode.getY()]; Double[] o2Cda = cdaMatrix[x][y]; if (Math.pow(o1Cda[0] - o2Cda[0], 2) + Math.pow(o1Cda[1] - o2Cda[1], 2) <= radiusLenSquared) { nextNode.setCodeData(codeMatrix[x][y]); if (!nextNode.getCodeData().equals(CodeNodeType.NONE.val)) { includeList.add(nextNode); } this.spreadWaveNode(originNode, nextNode, codeMatrix, cdaMatrix, radiusLenSquared, includeList, existNodes); } } // v2 BFS ------------------------------------------------------------------------------ // public List getWaveScopeByCode0(Integer lev, String code, Double radiusLen) { // String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); // // int[] codeMatrixIdx = mapDataDispatcher.getCodeMatrixIdx(lev, code); // NavigateNode originNode = new NavigateNode(codeMatrixIdx[0], codeMatrixIdx[1], code); // // List includeList = new ArrayList<>(); // Set visited = new HashSet<>(); // Track visited nodes to avoid re-processing // Queue queue = new LinkedList<>(); // // includeList.add(originNode); // visited.add(originNode); // queue.offer(originNode); // // while (!queue.isEmpty()) { // NavigateNode currNode = queue.poll(); // this.spreadWaveNode0(originNode, currNode, codeMatrix, radiusLen, includeList, visited, queue); // } // // return includeList; // } // // public void spreadWaveNode0(NavigateNode originNode, NavigateNode currNode, // String[][] codeMatrix, Double radiusLen, // List includeList, Set visited, Queue queue) { // // int x = currNode.getX(); // int y = currNode.getY(); // // // Expand neighbors in all four directions (up, down, left, right) // this.extendNeighborNodes0(originNode, new NavigateNode(x, y + 1), codeMatrix, radiusLen, includeList, visited, queue); // this.extendNeighborNodes0(originNode, new NavigateNode(x, y - 1), codeMatrix, radiusLen, includeList, visited, queue); // this.extendNeighborNodes0(originNode, new NavigateNode(x - 1, y), codeMatrix, radiusLen, includeList, visited, queue); // this.extendNeighborNodes0(originNode, new NavigateNode(x + 1, y), codeMatrix, radiusLen, includeList, visited, queue); // } // // public void extendNeighborNodes0(NavigateNode originNode, NavigateNode nextNode, // String[][] codeMatrix, Double radiusLen, // List includeList, Set visited, Queue queue) { // int x = nextNode.getX(); // int y = nextNode.getY(); // // // Check if the node is out of bounds // if (x < 0 || x >= codeMatrix.length || y < 0 || y >= codeMatrix[0].length) { // return; // } // // // If the node has already been visited, skip it // if (visited.contains(nextNode)) { // return; // } // // visited.add(nextNode); // // String nextNodeCodeData = codeMatrix[x][y]; // // // If it's a NONE node, we still need to check its surroundings // if (nextNodeCodeData.equals(CodeNodeType.NONE.val)) { // this.spreadWaveNode0(originNode, nextNode, codeMatrix, radiusLen, includeList, visited, queue); // } else { // Integer lev = MapDataDispatcher.MAP_DEFAULT_LEV; // String[][] cdaMatrix = mapDataDispatcher.getCdaMatrix(lev); // // // Check if the distance between nodes is within the radius length // List o1Cda = MapDataUtils.parseCdaNode(cdaMatrix[originNode.getX()][originNode.getY()]); // List o2Cda = MapDataUtils.parseCdaNode(cdaMatrix[nextNode.getX()][nextNode.getY()]); // // // Calculate Euclidean distance between the nodes // if (Math.pow(o1Cda.get(0) - o2Cda.get(0), 2) + Math.pow(o1Cda.get(1) - o2Cda.get(1), 2) <= Math.pow(radiusLen, 2)) { // nextNode.setCodeData(nextNodeCodeData); // includeList.add(nextNode); // // // Add the node to the queue to expand its neighbors // queue.offer(nextNode); // } // } // } public Boolean isTurnCorner(String codeData) { if (Cools.isEmpty(codeData)) { return false; } int[][] turnMatrix = mapDataDispatcher.getTurnMatrix(null); int[] codeMatrixIdx = mapDataDispatcher.getCodeMatrixIdx(null, codeData); int turnMatrixNode = turnMatrix[codeMatrixIdx[0]][codeMatrixIdx[1]]; return turnMatrixNode == TurnNodeType.TURN.val; } public CodeSpinType spinDirection(Code code) { if (Cools.isEmpty(code)) { return CodeSpinType.NA; } return CodeSpinType.of(code.getSpin()); } public static void main(String[] args) { List directionDtoList = DirectionDto.initCodeDirections(); System.out.println(JSON.toJSONString(directionDtoList)); } public CodeSpinType calcSpinDirection(Code code, Double currDir, Double nextDir) { if (Cools.isEmpty(code, currDir, nextDir)) { return CodeSpinType.NA; } List disableAngleList = new ArrayList<>(); List directionDtoList = DirectionDto.initCodeDirections(); for (DirectionDto dto : directionDtoList) { if (null != dto.getEnabled() && !dto.getEnabled()) { disableAngleList.add(dto.getAngle()); } } if (Cools.isEmpty(disableAngleList)) { return CodeSpinType.NA; } double curr = norm360(currDir); double next = norm360(nextDir); // 如果几乎相等,认为不需要旋转 if (almostEqual(curr, next)) { return CodeSpinType.NA; } double cwDelta = cwDelta(curr, next); // (0, 360) double ccwDelta = 360.0 - cwDelta; // (0, 360) boolean cwBlocked = false; boolean ccwBlocked = false; for (Double disableAngle : disableAngleList) { if (disableAngle == null) continue; double disable = norm360(disableAngle); if (isOnArcCW(curr, next, disable)) { cwBlocked = true; } if (isOnArcCCW(curr, next, disable)) { ccwBlocked = true; } } if (cwBlocked && !ccwBlocked) { return CodeSpinType.CCW; } if (!cwBlocked && ccwBlocked) { return CodeSpinType.CW; } return CodeSpinType.NA; } private static double norm360(double a) { double x = a % 360.0; if (x < 0) x += 360.0; // 360 归一化为 0 if (almostEqual(x, 360.0)) return 0.0; return x; } private static boolean almostEqual(double a, double b) { return Math.abs(a - b) <= EPS; } /** * 顺时针距离:从 start 到 end,沿 CW 方向走多少度,范围 (0, 360],这里不会返回 0(除非你 curr==next 已提前处理) */ private static double cwDelta(double start, double end) { double d = end - start; if (d < 0) d += 360.0; if (almostEqual(d, 0.0)) return 0.0; return d; } /** * 判断 angle 是否落在从 start -> end 的顺时针弧段上(不含端点)。 * 即:沿 CW 从 start 走 cwDelta(start,end) 的路径上是否“经过”angle。 */ private static boolean isOnArcCW(double start, double end, double angle) { double total = cwDelta(start, end); // 弧长为 0 表示无旋转(已提前处理),这里直接 false if (almostEqual(total, 0.0)) return false; double a = cwDelta(start, angle); // 不包含端点:a 在 (0, total) 之间才算经过 return a > EPS && a < total - EPS; } /** * 判断 angle 是否落在从 start -> end 的逆时针弧段上(不含端点)。 * 逆时针等价于:顺时针从 end -> start 的弧段。 */ private static boolean isOnArcCCW(double start, double end, double angle) { // 逆时针 start->end == 顺时针 end->start return isOnArcCW(end, start, angle); } /** * That vehicle is walking if the dynamic node count > 1 */ @SuppressWarnings("all") public boolean isWalkingByVehicle(Integer lev, String vehicle) { if (Cools.isEmpty(vehicle)) { return false; } lev = Optional.ofNullable(lev).orElse(MapDataDispatcher.MAP_DEFAULT_LEV); DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); int count = 0; for (int i = 0; i < dynamicMatrix.length; i++) { for (int j = 0; j < dynamicMatrix[i].length; j++) { if (vehicle.equals(dynamicMatrix[i][j].getVehicle())) { count++; if (count > 1) { return true; } } } } return false; } public List queryCodeListFromDynamicNode(Integer lev, String nodeType) { if (Cools.isEmpty(nodeType)) { return new ArrayList<>(); } lev = Optional.ofNullable(lev).orElse(MapDataDispatcher.MAP_DEFAULT_LEV); DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); // concurrent ConcurrentLinkedQueue codeList = new ConcurrentLinkedQueue<>(); IntStream.range(0, codeMatrix.length).parallel().forEach(i -> { for (int j = 0; j < codeMatrix[i].length; j++) { DynamicNode dynamicNode = dynamicMatrix[i][j]; if (nodeType.equals(dynamicNode.getVehicle())) { codeList.add(new SortCodeDto(codeMatrix[i][j], dynamicNode.getSerial())); } } }); // synchronize // List codeList = new ArrayList<>(); // for (int i = 0; i < codeMatrix.length; i++) { // for (int j = 0; j < codeMatrix[i].length; j++) { // DynamicNode dynamicNode = dynamicMatrix[i][j]; // if (nodeType.equals(dynamicNode.getVehicle())) { // codeList.add(new SortCodeDto(codeMatrix[i][j], dynamicNode.getSerial())); // } // } // } return codeList.stream() .sorted(Comparator.comparingInt(SortCodeDto::getSerial)) .map(SortCodeDto::getCode) .collect(Collectors.toList()); } public Map> queryCodeListFromDynamicNode(Integer lev, Set vehicles) { if (Cools.isEmpty(vehicles)) { return new HashMap<>(); } lev = Optional.ofNullable(lev).orElse(MapDataDispatcher.MAP_DEFAULT_LEV); DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); Map> map = new HashMap<>(vehicles.size()); for (int i = 0; i < codeMatrix.length; i++) { for (int j = 0; j < codeMatrix[i].length; j++) { DynamicNode dynamicNode = dynamicMatrix[i][j]; String vehicle = dynamicNode.getVehicle(); if (vehicles.contains(vehicle)) { int serial = dynamicNode.getSerial(); map.computeIfAbsent(vehicle, k -> new ArrayList<>()).add(new SortCodeDto(codeMatrix[i][j], serial)); } } } Map> result = new HashMap<>(vehicles.size()); for (Map.Entry> entry : map.entrySet()) { List codeDataList = entry.getValue().stream() .sorted(Comparator.comparingInt(SortCodeDto::getSerial)) .map(SortCodeDto::getCode) .collect(Collectors.toList()); result.put(entry.getKey(), codeDataList); } return result; } }