7 changed files with 389 additions and 111 deletions
@ -1,128 +1,180 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
package com.galaxis.rcs.plan.path2; |
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
import com.google.common.collect.Maps; |
||||
|
|
||||
import java.util.*; |
import java.util.*; |
||||
|
|
||||
|
import static com.galaxis.rcs.plan.path2.PathUtils.*; |
||||
|
|
||||
public class AStarPathPlanner { |
public class AStarPathPlanner { |
||||
private static final float ROTATION_COST_PER_DEGREE = 0.1f; |
private static final float ROTATION_COST_PER_DEGREE = 0.1f; |
||||
|
private static final float BLOCKED_COST = 10000f; // 阻塞系数成本
|
||||
|
private static final float WEIGHT_FACTOR = 1.5f; // 权重因子
|
||||
|
|
||||
private final NavigationGraph graph; |
private final NavigationGraph graph; |
||||
|
private final Map<String, Float> nodeWeights = Maps.newConcurrentMap(); |
||||
|
private final Map<String, Float> blockedNodes = Maps.newConcurrentMap(); |
||||
|
|
||||
public AStarPathPlanner(NavigationGraph graph) { |
public AStarPathPlanner(NavigationGraph graph) { |
||||
this.graph = graph; |
this.graph = graph; |
||||
} |
} |
||||
|
|
||||
// 路径规划状态
|
// 路径规划状态
|
||||
public List<Node> findPath(String startId, LCCDirection startDirectionAngle, String endId, LCCDirection endDirectionAngle) { |
public List<State> findPath(String startId, LCCDirection startDirection, String endId, LCCDirection endDirection) { |
||||
Node start = graph.getNode(startId); |
Node start = graph.getNode(startId); |
||||
Node goal = graph.getNode(endId); |
Node end = graph.getNode(endId); |
||||
if (start == null || goal == null) return Collections.emptyList(); |
if (start == null) { |
||||
|
throw new RuntimeException("Start node not found: " + startId); |
||||
|
} |
||||
|
if (end == null) { |
||||
|
throw new RuntimeException("End node not found: " + endId); |
||||
|
} |
||||
|
|
||||
// 使用复合键避免状态重复
|
// 使用复合键避免状态重复
|
||||
Map<String, State> visited = new HashMap<>(); |
Map<String, State> visited = new HashMap<>(); |
||||
PriorityQueue<State> open = new PriorityQueue<>(); |
PriorityQueue<State> open = new PriorityQueue<>(); |
||||
|
|
||||
// 初始状态
|
// 初始状态
|
||||
State initialState = new State(start, startDirectionAngle, 0, heuristic(start, goal), null); |
State initialState = new State(start, startDirection, 0, heuristic(start, end), null); |
||||
open.add(initialState); |
open.add(initialState); |
||||
visited.put(stateKey(start.id(), startDirectionAngle), initialState); |
visited.put(stateKey(start.id(), startDirection), initialState); |
||||
|
|
||||
while (!open.isEmpty()) { |
while (!open.isEmpty()) { |
||||
State current = open.poll(); |
State current = open.poll(); |
||||
|
|
||||
// 到达目标节点且方向匹配
|
// 到达目标节点且方向匹配
|
||||
if (current.node().id().equals(endId) && |
if (current.node().id().equals(endId) && |
||||
current.directionAngle() == endDirectionAngle) { |
current.direction() == endDirection) { |
||||
return buildPath(current); |
return buildPath(current); |
||||
} |
} |
||||
|
|
||||
// 处理邻居移动
|
// 处理邻边移动
|
||||
for (Node neighbor : graph.getNeighbors(current.node())) { |
for (Node neighbor : graph.getNeighbors(current.node())) { |
||||
// 计算可能的两种方向(前进/后退)
|
// 检查节点是否被阻塞
|
||||
LCCDirection[] possibleHeadings; |
if (isBlocked(neighbor.id())) continue; |
||||
if (current.directionAngle() == LCCDirection.UP || current.directionAngle() == LCCDirection.DOWN) { |
|
||||
possibleHeadings = new LCCDirection[]{LCCDirection.UP, LCCDirection.DOWN}; |
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor); |
||||
} else { |
|
||||
possibleHeadings = new LCCDirection[]{LCCDirection.LEFT, LCCDirection.RIGHT}; |
// 前进所需方向
|
||||
} |
LCCDirection forwardHeading = moveDirection; |
||||
|
// 后退所需方向
|
||||
for (LCCDirection nextHeading : possibleHeadings) { |
LCCDirection backwardHeading = getOppositeDirection(moveDirection); |
||||
float moveCost = graph.distance(current.node(), neighbor); |
|
||||
considerState(current, neighbor, nextHeading, moveCost, open, visited, goal); |
// 尝试前进
|
||||
} |
if (current.direction() == forwardHeading) { |
||||
} |
float moveCost = calculateMoveCost(current.node(), neighbor); |
||||
|
considerState(current, neighbor, forwardHeading, |
||||
// 处理旋转(仅在可旋转节点)
|
moveCost, open, visited, end); |
||||
|
} else if (current.node().rotatable()) { |
||||
|
float rotationCost = calculateRotationCost( |
||||
|
current.direction(), forwardHeading, ROTATION_COST_PER_DEGREE |
||||
|
); |
||||
|
float moveCost = calculateMoveCost(current.node(), neighbor); |
||||
|
float totalCost = rotationCost + moveCost; |
||||
|
considerState(current, neighbor, forwardHeading, |
||||
|
totalCost, open, visited, end); |
||||
|
} |
||||
|
|
||||
|
// 尝试后退
|
||||
|
if (current.direction() == backwardHeading) { |
||||
|
float moveCost = calculateMoveCost(current.node(), neighbor); |
||||
|
considerState(current, neighbor, backwardHeading, |
||||
|
moveCost, open, visited, end); |
||||
|
} else if (current.node().rotatable()) { |
||||
|
float rotationCost = calculateRotationCost( |
||||
|
current.direction(), backwardHeading, ROTATION_COST_PER_DEGREE |
||||
|
); |
||||
|
float moveCost = calculateMoveCost(current.node(), neighbor); |
||||
|
float totalCost = rotationCost + moveCost; |
||||
|
considerState(current, neighbor, backwardHeading, |
||||
|
totalCost, open, visited, end); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 处理原地旋转
|
||||
if (current.node().rotatable()) { |
if (current.node().rotatable()) { |
||||
for (LCCDirection rotation : new LCCDirection[]{LCCDirection.UP, LCCDirection.DOWN, LCCDirection.LEFT, LCCDirection.RIGHT}) { |
for (LCCDirection rotation : LCCDirection.values()) { |
||||
if (rotation == current.directionAngle()) continue; |
if (rotation == current.direction()) continue; |
||||
|
|
||||
// 计算旋转代价
|
|
||||
int angleDiff = 0; |
|
||||
if (current.directionAngle() == LCCDirection.UP && rotation == LCCDirection.RIGHT) { |
|
||||
angleDiff = 90; |
|
||||
} else if (current.directionAngle() == LCCDirection.RIGHT && rotation == LCCDirection.DOWN) { |
|
||||
angleDiff = 90; |
|
||||
} else if (current.directionAngle() == LCCDirection.DOWN && rotation == LCCDirection.LEFT) { |
|
||||
angleDiff = 90; |
|
||||
} else if (current.directionAngle() == LCCDirection.LEFT && rotation == LCCDirection.UP) { |
|
||||
angleDiff = 90; |
|
||||
} else if (current.directionAngle() == rotation) { |
|
||||
// 无需旋转
|
|
||||
continue; |
|
||||
} else { |
|
||||
angleDiff = 180; // 反向旋转
|
|
||||
} |
|
||||
float rotationCost = angleDiff * ROTATION_COST_PER_DEGREE; |
|
||||
|
|
||||
considerState(current, current.node(), rotation, rotationCost, open, visited, goal); |
float rotationCost = calculateRotationCost( |
||||
|
current.direction(), rotation, ROTATION_COST_PER_DEGREE |
||||
|
); |
||||
|
considerState(current, current.node(), rotation, |
||||
|
rotationCost, open, visited, end); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
return Collections.emptyList(); |
return Collections.emptyList(); |
||||
} |
} |
||||
|
|
||||
private void considerState(State current, Node nextNode, LCCDirection nextHeading, |
/** |
||||
|
* 考虑新的状态并更新开放列表和访问记录 |
||||
|
* |
||||
|
* @param current 当前状态 |
||||
|
* @param nextNode 下一个节点 |
||||
|
* @param nextDirection 下一个方向 |
||||
|
* @param cost 移动成本 |
||||
|
* @param open 开放列表 |
||||
|
* @param visited 访问记录 |
||||
|
* @param end 目标节点 |
||||
|
*/ |
||||
|
private void considerState(State current, Node nextNode, LCCDirection nextDirection, |
||||
float cost, PriorityQueue<State> open, |
float cost, PriorityQueue<State> open, |
||||
Map<String, State> visited, Node goal) { |
Map<String, State> visited, Node end) { |
||||
String key = stateKey(nextNode.id(), nextHeading); |
String key = stateKey(nextNode.id(), nextDirection); |
||||
float newG = current.g() + cost; |
float newG = current.g() + cost; |
||||
|
|
||||
if (!visited.containsKey(key) || visited.get(key).g() > newG) { |
if (!visited.containsKey(key) || visited.get(key).g() > newG) { |
||||
float h = heuristic(nextNode, goal); |
float h = heuristic(nextNode, end); |
||||
State newState = new State(nextNode, nextHeading, newG, h, current); |
State newState = new State(nextNode, nextDirection, newG, h, current); |
||||
open.add(newState); |
open.add(newState); |
||||
visited.put(key, newState); |
visited.put(key, newState); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
private List<Node> buildPath(State state) { |
private List<State> buildPath(State state) { |
||||
LinkedList<Node> path = new LinkedList<>(); |
LinkedList<State> path = new LinkedList<>(); |
||||
while (state != null) { |
while (state != null) { |
||||
path.addFirst(state.node()); |
path.addFirst(state); |
||||
state = state.parent(); |
state = state.parent(); |
||||
} |
} |
||||
return path; |
return path; |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* 启发式函数,计算两个节点之间的距离 |
||||
|
*/ |
||||
private float heuristic(Node a, Node b) { |
private float heuristic(Node a, Node b) { |
||||
return graph.distance(a, b); |
return graph.distance(a, b); |
||||
} |
// 使用曼哈顿距离??
|
||||
|
// return Math.abs(a.x() - b.x()) + Math.abs(a.z() - b.z());
|
||||
private String stateKey(String nodeId, LCCDirection directionAngle) { |
|
||||
return nodeId + "|" + directionAngle; |
|
||||
} |
} |
||||
|
|
||||
/** |
/** |
||||
* 根据方向要求计算目标方向 |
* 生成状态的唯一键 |
||||
*/ |
*/ |
||||
public static float getRequiredDirection(LCCDirection direction) { |
private String stateKey(String nodeId, LCCDirection direction) { |
||||
return switch (direction) { |
return nodeId + "|" + direction; |
||||
case UP -> 90f; // 车头朝上
|
} |
||||
case DOWN -> 270f; // 车头朝下
|
|
||||
case LEFT -> 180f; // 车头朝左
|
public void setNodeWeight(String nodeId, float weight) { |
||||
case RIGHT -> 0f; // 车头朝右
|
nodeWeights.put(nodeId, weight); |
||||
default -> 0; // 默认为右
|
} |
||||
}; |
|
||||
|
public void setBlocked(String nodeId, float blockedFactor) { |
||||
|
blockedNodes.put(nodeId, blockedFactor); |
||||
|
} |
||||
|
|
||||
|
private float calculateMoveCost(Node from, Node to) { |
||||
|
float baseCost = graph.distance(from, to); |
||||
|
float weight = nodeWeights.getOrDefault(to.id(), 1.0f); |
||||
|
float blockedFactor = blockedNodes.getOrDefault(to.id(), 0f); |
||||
|
|
||||
|
return baseCost * weight * (1 + blockedFactor); |
||||
|
} |
||||
|
|
||||
|
private boolean isBlocked(String nodeId) { |
||||
|
return blockedNodes.containsKey(nodeId) && blockedNodes.get(nodeId) > 0.8f; |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,149 @@ |
|||||
|
package com.galaxis.rcs.plan.path2; |
||||
|
|
||||
|
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
|
||||
|
import java.util.*; |
||||
|
|
||||
|
public class PathUtils { |
||||
|
/** |
||||
|
* 计算移动方向 |
||||
|
*/ |
||||
|
public static LCCDirection calculateMoveDirection(Node from, Node to) { |
||||
|
float dx = to.x() - from.x(); |
||||
|
float dz = to.z() - from.z(); |
||||
|
|
||||
|
if (Math.abs(dx) > Math.abs(dz)) { |
||||
|
return dx > 1 ? LCCDirection.RIGHT : LCCDirection.LEFT; |
||||
|
} else { |
||||
|
return dz > 1 ? LCCDirection.DOWN : LCCDirection.UP; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查方向是否有效(侧插式专用) |
||||
|
*/ |
||||
|
public static boolean isValidForSideLoader(Node node, int bay, LCCDirection direction) { |
||||
|
Optional<StoreLink> link = node.storeLinks().stream() |
||||
|
.filter(l -> l.bay() == bay) |
||||
|
.findFirst(); |
||||
|
|
||||
|
if (link.isEmpty()) return false; |
||||
|
|
||||
|
LCCDirection requiredDirection = convertForSideLoader(link.get().direction()); |
||||
|
return direction == requiredDirection; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 转换货位方向到AGV所需方向(侧插式专用) |
||||
|
*/ |
||||
|
public static LCCDirection convertForSideLoader(LCCDirection storeDirection) { |
||||
|
/* |
||||
|
* 侧插式AGV方向规则: |
||||
|
* - 货位在上方 → 车头向右(货叉朝左) |
||||
|
* - 货位在下方 → 车头向左(货叉朝左) |
||||
|
* - 货位在左方 → 车头向上(货叉朝左) |
||||
|
* - 货位在右方 → 车头向下(货叉朝左) |
||||
|
*/ |
||||
|
return switch (storeDirection) { |
||||
|
case UP -> LCCDirection.RIGHT; |
||||
|
case DOWN -> LCCDirection.LEFT; |
||||
|
case LEFT -> LCCDirection.UP; |
||||
|
case RIGHT -> LCCDirection.DOWN; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取最近的旋转点 |
||||
|
*/ |
||||
|
public static Node findNearestRotationNode(NavigationGraph graph, Node from) { |
||||
|
// 使用BFS查找最近的旋转点
|
||||
|
Queue<Node> queue = new LinkedList<>(); |
||||
|
Set<String> visited = new HashSet<>(); |
||||
|
queue.add(from); |
||||
|
visited.add(from.id()); |
||||
|
|
||||
|
while (!queue.isEmpty()) { |
||||
|
Node current = queue.poll(); |
||||
|
if (current.rotatable()) { |
||||
|
return current; |
||||
|
} |
||||
|
|
||||
|
for (Node neighbor : graph.getNeighbors(current)) { |
||||
|
if (!visited.contains(neighbor.id())) { |
||||
|
visited.add(neighbor.id()); |
||||
|
queue.add(neighbor); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取相反方向 |
||||
|
*/ |
||||
|
public static LCCDirection getOppositeDirection(LCCDirection dir) { |
||||
|
return switch (dir) { |
||||
|
case UP -> LCCDirection.DOWN; |
||||
|
case DOWN -> LCCDirection.UP; |
||||
|
case LEFT -> LCCDirection.RIGHT; |
||||
|
case RIGHT -> LCCDirection.LEFT; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算旋转代价 |
||||
|
*/ |
||||
|
public static float calculateRotationCost(LCCDirection from, LCCDirection to, float ROTATION_COST_PER_DEGREE) { |
||||
|
float angle1 = getRequiredDirection(from); |
||||
|
float angle2 = getRequiredDirection(to); |
||||
|
float diff = Math.abs(angle1 - angle2); |
||||
|
diff = Math.min(diff, 360 - diff); |
||||
|
return diff * ROTATION_COST_PER_DEGREE; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取所需方向的角度 |
||||
|
*/ |
||||
|
public static float getRequiredDirection(LCCDirection direction) { |
||||
|
return switch (direction) { |
||||
|
case UP -> 90f; |
||||
|
case DOWN -> 270f; |
||||
|
case LEFT -> 180f; |
||||
|
case RIGHT -> 0f; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 转换货位方向到AGV所需方向 |
||||
|
*/ |
||||
|
public static LCCDirection convertStoreDirection(LCCDirection storeDirection) { |
||||
|
// 转换规则: 货位在路径点的方位 -> AGV所需方向
|
||||
|
return switch (storeDirection) { |
||||
|
case UP -> LCCDirection.RIGHT; // 货位在上方 → 车头向右
|
||||
|
case DOWN -> LCCDirection.LEFT; // 货位在下方 → 车头向左
|
||||
|
case LEFT -> LCCDirection.UP; // 货位在左方 → 车头向上
|
||||
|
case RIGHT -> LCCDirection.DOWN; // 货位在右方 → 车头向下
|
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将角度转换为 LCCDirection |
||||
|
*/ |
||||
|
public static LCCDirection convertAngleToDirection(float angle) { |
||||
|
// 标准化角度
|
||||
|
angle = ((angle % 360f) + 360f) % 360f; |
||||
|
|
||||
|
if (angle >= 315f || angle < 45f) return LCCDirection.RIGHT; |
||||
|
if (angle >= 45f && angle < 135f) return LCCDirection.UP; |
||||
|
if (angle >= 135f && angle < 225f) return LCCDirection.LEFT; |
||||
|
return LCCDirection.DOWN; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算方向角度差 |
||||
|
*/ |
||||
|
public static float angleDifference(float angle1, float angle2) { |
||||
|
float diff = Math.abs(angle1 - angle2) % 360; |
||||
|
return diff > 180 ? 360 - diff : diff; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue