package com.zy.acs.manager.core.service.astart;
|
|
import com.zy.acs.common.utils.RedisSupport;
|
import com.zy.acs.framework.common.Cools;
|
import com.zy.acs.manager.common.utils.MapDataUtils;
|
import com.zy.acs.manager.core.service.LaneService;
|
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.utils.RouteGenerator;
|
import com.zy.acs.manager.manager.entity.Segment;
|
import com.zy.acs.manager.manager.service.JamService;
|
import com.zy.acs.manager.system.service.ConfigService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
|
import java.util.*;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
/**
|
* Created by vincent on 6/12/2024
|
*/
|
@Slf4j
|
@Service
|
public class AStarNavigateService {
|
|
private final RedisSupport redis = RedisSupport.defaultRedisSupport;
|
|
public static final boolean OPEN_TURN_COST_WEIGHT = Boolean.TRUE;
|
|
public static final int WEIGHT_CALC_FACTOR = 1;
|
|
@Autowired
|
private MapDataDispatcher mapDataDispatcher;
|
@Autowired
|
private JamService jamService;
|
@Autowired
|
private LaneService laneService;
|
@Autowired
|
private ConfigService configService;
|
|
public synchronized AStarNavigateNode execute(String agvNo, AStarNavigateNode start, AStarNavigateNode end
|
, Boolean lock, List<String> blackList, Segment segment) {
|
if (start.getX() == end.getX() && start.getY() == end.getY()) {
|
return end;
|
}
|
Integer maxAgvCountInLane = configService.getVal("maxAgvCountInLane", Integer.class);
|
|
PriorityQueue<AStarNavigateNode> openQueue = new PriorityQueue<>();
|
Set<AStarNavigateNode> existNodes = new HashSet<>();
|
Map<String, Integer> bestGMap = new HashMap<>();
|
|
start.setG(0);
|
start.setH(calcNodeCost(start, end));
|
start.setF(start.getG() + start.getH());
|
String startKey = start.getX() + "_" + start.getY();
|
bestGMap.put(startKey, start.getG());
|
|
openQueue.add(start);
|
existNodes.add(start);
|
|
int[][] mapMatrix = mapDataDispatcher.getMapMatrix(null, null);
|
String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(null);
|
DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(null);
|
String[][] waveMatrix = mapDataDispatcher.getWaveMatrix(null);
|
while (!openQueue.isEmpty()) {
|
// 取优先队列顶部元素并且把这个元素从Open表中删除,取F值最小的节点
|
AStarNavigateNode currentNode = openQueue.poll();
|
|
// 终点
|
if (currentNode.getX() == end.getX() && currentNode.getY() == end.getY()) {
|
return currentNode;
|
}
|
List<AStarNavigateNode> neighbourNodes = this.getNeighborNodes(currentNode, mapMatrix, existNodes);
|
for (AStarNavigateNode node : neighbourNodes) {
|
node.setCodeData(codeMatrix[node.getX()][node.getY()]);
|
|
boolean isEndNode = node.getX() == end.getX() && node.getY() == end.getY();
|
|
int weight = 0;
|
|
if (!Cools.isEmpty(blackList) && blackList.contains(node.getCodeData())) {
|
continue;
|
}
|
// 特殊情况,当blackList有且只有一个元素且为startNode时
|
// 说明blackList已经知道当前导航起始点和目标点为相邻节点
|
// 但是当前blackList的任务是不让系统走相邻的最短路径,所以才会有下面的判断和continue
|
if (blackList.size() == 1 && blackList.get(0).equals(start.getCodeData())) {
|
if (isEndNode && currentNode.getCodeData().equals(start.getCodeData())) {
|
continue;
|
}
|
}
|
|
// 节点被占用
|
DynamicNode dynamicNode = dynamicMatrix[node.getX()][node.getY()];
|
String vehicle = dynamicNode.getVehicle();
|
assert !vehicle.equals(DynamicNodeType.BLOCK.val);
|
if (!vehicle.equals(DynamicNodeType.ACCESS.val)) {
|
if (!vehicle.equals(agvNo)) {
|
|
// 如果存在车辆,则增加权重 2 或者 3,因为拐点会增加权重 1
|
// vehicle已经为当前segment做过了避让,且避让任务已完成,则权重值增加
|
if (null != segment) {
|
if (!Cools.isEmpty(jamService.getJamFromSegmentByAvo(segment, vehicle))) {
|
weight += (WEIGHT_CALC_FACTOR * 3);
|
} else {
|
weight += (WEIGHT_CALC_FACTOR * 2);
|
}
|
}
|
|
if (lock) {
|
continue;
|
}
|
}
|
}
|
|
// 避障波
|
String waveNode = waveMatrix[node.getX()][node.getY()];
|
assert !waveNode.equals(WaveNodeType.DISABLE.val);
|
if (!waveNode.equals(WaveNodeType.ENABLE.val)) {
|
List<String> waveNodeList = MapDataUtils.parseWaveNode(waveNode);
|
List<String> otherWaveList = MapDataUtils.hasOtherWave(waveNodeList, agvNo);
|
|
if (!Cools.isEmpty(otherWaveList)) {
|
|
if (lock) {
|
continue;
|
}
|
}
|
}
|
|
// 单巷道车辆容载数量
|
List<int[]> laneCodeIdxList = laneService.getLaneCodeIdxList(node.getCodeData());
|
if (!Cools.isEmpty(laneCodeIdxList)) {
|
Set<String> lanVehicleSet = new HashSet<>();
|
|
for (int[] codeMatrixIdx : laneCodeIdxList) {
|
DynamicNode laneDynamicNode = dynamicMatrix[codeMatrixIdx[0]][codeMatrixIdx[1]];
|
String laneVehicle = laneDynamicNode.getVehicle();
|
assert !laneVehicle.equals(DynamicNodeType.BLOCK.val);
|
if (!laneVehicle.equals(DynamicNodeType.ACCESS.val)) {
|
if (!laneVehicle.equals(agvNo)) {
|
lanVehicleSet.add(laneVehicle);
|
}
|
}
|
}
|
if (lanVehicleSet.size() + 1 > maxAgvCountInLane) {
|
continue;
|
}
|
}
|
|
if (OPEN_TURN_COST_WEIGHT) {
|
if (this.isTurning(currentNode, node)) {
|
weight += WEIGHT_CALC_FACTOR;
|
node.setTurnCount(currentNode.getTurnCount() + 1);
|
} else {
|
// 方向没变
|
node.setTurnCount(currentNode.getTurnCount());
|
}
|
}
|
|
//进行计算对 G, F, H 等值
|
node.setWeight(weight);
|
node.setLastDistance(calcNodeCost(currentNode, node));
|
node.initNode(currentNode, end);
|
node.setH(calcNodeCost(node, end));
|
node.setF(node.getG() + node.getH());
|
|
String key = node.getX() + "_" + node.getY();
|
Integer recordedG = bestGMap.get(key);
|
if (recordedG == null || node.getG() <= recordedG) {
|
bestGMap.put(key, node.getG());
|
|
openQueue.add(node);
|
}
|
|
// openQueue.add(node);
|
// existNodes.add(node);
|
}
|
}
|
|
return null;
|
}
|
|
// right left up down
|
private final static int[][] DIRECTIONS = {{0,1},{0,-1},{-1,0},{1,0}};
|
|
// 获取四周节点
|
private List<AStarNavigateNode> getNeighborNodes(AStarNavigateNode currentNode, int[][] mapMatrix, Set<AStarNavigateNode> existNodes) {
|
int x = currentNode.getX();
|
int y = currentNode.getY();
|
AStarNavigateNode parent = currentNode.getParent();
|
|
List<AStarNavigateNode> neighbourNodes = new CopyOnWriteArrayList<>();
|
|
// List<AStarNavigateNode> possibleNodes = Arrays.asList(
|
// new AStarNavigateNode(x, y + 1), // right
|
// new AStarNavigateNode(x, y - 1), // left
|
// new AStarNavigateNode(x - 1, y), // up
|
// new AStarNavigateNode(x + 1, y) // down
|
// );
|
|
List<AStarNavigateNode> possibleNodes = new ArrayList<>();
|
for (int[] d: DIRECTIONS) {
|
int nx = x + d[0];
|
int ny = y + d[1];
|
// 如果父节点不为空,并且 (nx,ny) 等于父节点坐标,则跳过
|
if (parent != null && nx == parent.getX() && ny == parent.getY()) {
|
continue;
|
}
|
possibleNodes.add(new AStarNavigateNode(nx, ny));
|
}
|
|
possibleNodes.parallelStream()
|
.map(extendNode -> extendNeighborNodes(currentNode, extendNode, mapMatrix, existNodes, null, null))
|
.filter(Objects::nonNull)
|
.forEach(neighbourNodes::add);
|
|
return neighbourNodes;
|
}
|
|
private AStarNavigateNode extendNeighborNodes(AStarNavigateNode currentNode, AStarNavigateNode extendNode, int[][] mapMatrix, Set<AStarNavigateNode> existNodes, Integer dx, Integer dy) {
|
AStarNavigateNode nextNode;
|
|
if (null == dx || null == dy) {
|
dx = extendNode.getX() - currentNode.getX();
|
dy = extendNode.getY() - currentNode.getY();
|
nextNode = extendNode;
|
} else {
|
nextNode = new AStarNavigateNode(extendNode.getX() + dx, extendNode.getY() + dy);
|
}
|
|
int x = nextNode.getX();
|
int y = nextNode.getY();
|
// 数组越界
|
if (x < 0 || x >= mapMatrix.length
|
|| y < 0 || y >= mapMatrix[0].length) {
|
return null;
|
}
|
|
if (mapMatrix[x][y] == MapNodeType.DISABLE.val) {
|
|
return extendNeighborNodes(currentNode, nextNode, mapMatrix, existNodes, dx, dy);
|
}
|
|
assert mapMatrix[x][y] == MapNodeType.ENABLE.val;
|
|
// if (existNodes.contains(nextNode)) {
|
// return null;
|
// }
|
|
// 判断通过性
|
String routeCdaKey = RouteGenerator.generateRouteCdaKey(new int[]{currentNode.getX(), currentNode.getY()}, new int[]{nextNode.getX(), nextNode.getY()});
|
if (!mapDataDispatcher.validRouteCdaKey(routeCdaKey)) {
|
return null;
|
}
|
|
return nextNode;
|
}
|
|
//------------------A*启发函数------------------//
|
|
//计算通过现在的结点的位置和最终结点的位置计算H值(曼哈顿法:坐标分别取差值相加)
|
private int calcNodeCost(AStarNavigateNode node1, AStarNavigateNode node2) {
|
return Math.abs(node2.getX() - node1.getX()) + Math.abs(node2.getY() - node1.getY());
|
}
|
|
// 转弯判断:只允许“垂直或水平”运动
|
private boolean isTurning(AStarNavigateNode currNode, AStarNavigateNode nextNode) {
|
// 第一个点
|
if (currNode.getParent() == null) {
|
return false;
|
}
|
AStarNavigateNode parent = currNode.getParent();
|
// 如果下一点(nextNode)与 parent 在同一行或同一列 => 没有转弯
|
// 注意,这实际上等同于“(curr->next) 的方向 == (parent->curr) 的方向”。
|
boolean sameRowOrCol =
|
(nextNode.getX() == parent.getX())
|
|| (nextNode.getY() == parent.getY());
|
return !sameRowOrCol;
|
}
|
|
// 转弯判断:如果这两个向量相同(例如都等于 (0,1)),说明方向相同;否则说明转弯。
|
// private boolean isTurning(AStarNavigateNode currNode, AStarNavigateNode nextNode) {
|
// // 如果 currNode 没有父节点,说明是起点,不算转弯
|
// if (currNode.getParent() == null) {
|
// return false;
|
// }
|
// // 取出坐标
|
// AStarNavigateNode parent = currNode.getParent();
|
// int px = currNode.getX() - parent.getX(); // parent -> curr 的x偏移
|
// int py = currNode.getY() - parent.getY(); // parent -> curr 的y偏移
|
//
|
// int nx = nextNode.getX() - currNode.getX(); // curr -> next 的x偏移
|
// int ny = nextNode.getY() - currNode.getY(); // curr -> next 的y偏移
|
//
|
// // 如果 (px, py) 与 (nx, ny) 不一样,就说明转弯
|
// return (px != nx) || (py != ny);
|
// }
|
|
}
|