43 changed files with 2147 additions and 2159 deletions
@ -1,29 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path; |
|
||||
|
|
||||
/** |
|
||||
* A*路径节点状态 |
|
||||
*/ |
|
||||
public record AStarNodeState( |
|
||||
String nodeId, // 当前节点ID
|
|
||||
int direction, // 当前方向 (0,90,180,270)
|
|
||||
float gCost, // 实际代价
|
|
||||
float hCost, // 启发式代价
|
|
||||
AStarNodeState parent // 父节点
|
|
||||
) implements Comparable<AStarNodeState> { |
|
||||
|
|
||||
// 状态唯一标识
|
|
||||
public String stateKey() { |
|
||||
return nodeId + ":" + direction; |
|
||||
} |
|
||||
|
|
||||
// 总代价
|
|
||||
public float fCost() { |
|
||||
return gCost + hCost; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int compareTo(AStarNodeState other) { |
|
||||
return Float.compare(this.fCost(), other.fCost()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@ -1,122 +1,220 @@ |
|||||
package com.galaxis.rcs.plan.path; |
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
|
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.path.PathUtils.*; |
||||
* A*路径规划器 |
|
||||
*/ |
|
||||
public class AStarPathPlanner { |
public class AStarPathPlanner { |
||||
|
private static final float ROTATION_COST_PER_DEGREE = 0.01f; |
||||
|
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<State> findPath(String startId, LCCDirection startDirection, String endId, LCCDirection endDirection) { |
||||
* |
Node start = graph.getNode(startId); |
||||
* @param startNodeId 起始节点ID |
Node end = graph.getNode(endId); |
||||
* @param startDirection 起始方向 |
if (start == null) { |
||||
* @param endNodeId 目标节点ID |
throw new RuntimeException("Start node not found: " + startId); |
||||
* @param endDirection 目标方向 |
} |
||||
* @return 路径节点序列 (包含方向信息) |
if (end == null) { |
||||
*/ |
throw new RuntimeException("End node not found: " + endId); |
||||
public List<AStarNodeState> planPath(String startNodeId, int startDirection, String endNodeId, int endDirection) { |
} |
||||
// 开放集 (优先队列)
|
|
||||
PriorityQueue<AStarNodeState> openSet = new PriorityQueue<>(); |
// 使用复合键避免状态重复
|
||||
|
Map<String, State> visited = new HashMap<>(); |
||||
// 状态管理
|
PriorityQueue<State> open = new PriorityQueue<>(); |
||||
Map<String, Float> gScoreMap = new HashMap<>(); |
|
||||
Map<String, AStarNodeState> cameFrom = new HashMap<>(); |
|
||||
|
|
||||
// 初始化起点
|
|
||||
AStarNodeState start = new AStarNodeState( |
|
||||
startNodeId, |
|
||||
startDirection, |
|
||||
0, |
|
||||
graph.heuristicCost(startNodeId, endNodeId), |
|
||||
null |
|
||||
); |
|
||||
|
|
||||
openSet.add(start); |
// 初始状态
|
||||
gScoreMap.put(start.stateKey(), 0.0f); |
State initialState = new State(start, startDirection, 0, heuristic(start, end), null); |
||||
|
open.add(initialState); |
||||
|
visited.put(stateKey(start.id(), startDirection), initialState); |
||||
|
|
||||
while (!openSet.isEmpty()) { |
while (!open.isEmpty()) { |
||||
AStarNodeState current = openSet.poll(); |
State current = open.poll(); |
||||
|
|
||||
// 到达目标状态
|
// 到达目标节点且方向匹配
|
||||
if (current.nodeId().equals(endNodeId) && |
if (current.node().id().equals(endId) && current.direction() == endDirection) { |
||||
current.direction() == endDirection) { |
return buildPath(current); |
||||
return reconstructPath(cameFrom, current); |
|
||||
} |
} |
||||
|
|
||||
// 处理移动操作 (到相邻节点)
|
// 处理邻边移动
|
||||
for (NavigationNode neighbor : graph.getAdjacentNodes(current.nodeId())) { |
for (Node neighbor : graph.getNeighbors(current.node())) { |
||||
PathSegment segment = graph.getPathSegment(current.nodeId(), neighbor.id()); |
// 检查节点是否被阻塞
|
||||
if (segment == null) continue; |
if (isBlocked(neighbor.id())) continue; |
||||
|
|
||||
float moveCost = segment.distance(); |
// 计算移动方向
|
||||
float tentativeGCost = current.gCost() + moveCost; |
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor); |
||||
String neighborKey = neighbor.id() + ":" + current.direction(); |
|
||||
|
|
||||
// 发现更好路径
|
// 尝试前进
|
||||
if (tentativeGCost < gScoreMap.getOrDefault(neighborKey, Float.MAX_VALUE)) { |
if (canMoveForward(current.direction(), moveDirection)) { |
||||
AStarNodeState neighborState = new AStarNodeState( |
float moveCost = calculateMoveCost(current.node(), neighbor, false); |
||||
neighbor.id(), |
considerState(current, neighbor, current.direction(), |
||||
current.direction(), |
moveCost, open, visited, end); |
||||
tentativeGCost, |
} |
||||
graph.heuristicCost(neighbor.id(), endNodeId), |
// 尝试后退
|
||||
|
else if (canMoveBackward(current.direction(), moveDirection)) { |
||||
|
float moveCost = calculateMoveCost(current.node(), neighbor, true); |
||||
|
considerState(current, neighbor, current.direction(), |
||||
|
moveCost, open, visited, end); |
||||
|
} |
||||
|
// 需要旋转
|
||||
|
else if (current.node().rotatable()) { |
||||
|
// 计算需要旋转到的方向
|
||||
|
LCCDirection requiredDirection = calculateRequiredDirection(moveDirection); |
||||
|
|
||||
|
// 考虑旋转后移动
|
||||
|
float rotationCost = calculateRotationCost( |
||||
|
current.direction(), requiredDirection, ROTATION_COST_PER_DEGREE |
||||
|
); |
||||
|
float moveCost = calculateMoveCost(current.node(), neighbor, false); |
||||
|
float totalCost = rotationCost + moveCost; |
||||
|
|
||||
|
// 创建旋转状态
|
||||
|
State rotatedState = new State( |
||||
|
current.node(), requiredDirection, |
||||
|
current.g() + rotationCost, |
||||
|
heuristic(current.node(), end), |
||||
current |
current |
||||
); |
); |
||||
|
|
||||
cameFrom.put(neighborKey, current); |
// 考虑旋转后的移动
|
||||
gScoreMap.put(neighborKey, tentativeGCost); |
considerState(rotatedState, neighbor, requiredDirection, |
||||
openSet.add(neighborState); |
moveCost, open, visited, end); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// 处理旋转操作 (当前节点可旋转时)
|
// 处理原地旋转 - 只考虑目标方向
|
||||
NavigationNode currentNode = graph.nodes.get(current.nodeId()); |
if (current.node().rotatable()) { |
||||
if (currentNode != null && currentNode.rotatable()) { |
// 只考虑旋转到目标方向(如果可能)
|
||||
for (int rotation : new int[]{90, -90}) { |
if (current.direction() != endDirection) { |
||||
int newDirection = (current.direction() + rotation + 360) % 360; |
float rotationCost = calculateRotationCost( |
||||
float rotateCost = 1.0f; // 旋转代价
|
current.direction(), endDirection, ROTATION_COST_PER_DEGREE |
||||
float tentativeGCost = current.gCost() + rotateCost; |
); |
||||
String rotatedKey = current.nodeId() + ":" + newDirection; |
considerState(current, current.node(), endDirection, |
||||
|
rotationCost, open, visited, end); |
||||
|
} |
||||
|
|
||||
// 发现更好路径
|
// 另外考虑旋转到移动所需的方向
|
||||
if (tentativeGCost < gScoreMap.getOrDefault(rotatedKey, Float.MAX_VALUE)) { |
for (Node neighbor : graph.getNeighbors(current.node())) { |
||||
AStarNodeState rotatedState = new AStarNodeState( |
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor); |
||||
current.nodeId(), |
LCCDirection requiredDirection = calculateRequiredDirection(moveDirection); |
||||
newDirection, |
|
||||
tentativeGCost, |
if (requiredDirection != current.direction()) { |
||||
current.hCost(), // 旋转不改变位置,启发值不变
|
float rotationCost = calculateRotationCost( |
||||
current |
current.direction(), requiredDirection, ROTATION_COST_PER_DEGREE |
||||
); |
); |
||||
|
considerState(current, current.node(), requiredDirection, |
||||
|
rotationCost, open, visited, end); |
||||
|
} |
||||
|
} |
||||
|
|
||||
cameFrom.put(rotatedKey, current); |
// for (LCCDirection rotation : LCCDirection.values()) {
|
||||
gScoreMap.put(rotatedKey, tentativeGCost); |
// if (rotation == current.direction()) continue;
|
||||
openSet.add(rotatedState); |
//
|
||||
|
// float rotationCost = calculateRotationCost(
|
||||
|
// current.direction(), rotation, ROTATION_COST_PER_DEGREE
|
||||
|
// );
|
||||
|
// considerState(current, current.node(), rotation,
|
||||
|
// rotationCost, open, visited, end);
|
||||
|
// }
|
||||
} |
} |
||||
} |
} |
||||
|
return Collections.emptyList(); |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* 考虑新的状态并更新开放列表和访问记录 |
||||
|
* |
||||
|
* @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, |
||||
|
Map<String, State> visited, Node end) { |
||||
|
String key = stateKey(nextNode.id(), nextDirection); |
||||
|
float newG = current.g() + cost; |
||||
|
|
||||
|
if (!visited.containsKey(key) || visited.get(key).g() > newG) { |
||||
|
float h = heuristic(nextNode, end); |
||||
|
State newState = new State(nextNode, nextDirection, newG, h, current); |
||||
|
open.add(newState); |
||||
|
visited.put(key, newState); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private List<State> buildPath(State state) { |
||||
|
LinkedList<State> path = new LinkedList<>(); |
||||
|
while (state != null) { |
||||
|
path.addFirst(state); |
||||
|
state = state.parent(); |
||||
|
} |
||||
|
return path; |
||||
} |
} |
||||
|
|
||||
return Collections.emptyList(); // 未找到路径
|
/** |
||||
|
* 启发式函数,计算两个节点之间的距离 |
||||
|
*/ |
||||
|
private float heuristic(Node a, Node b) { |
||||
|
return graph.distance(a, b); |
||||
|
// 使用曼哈顿距离??
|
||||
|
// return Math.abs(a.x() - b.x()) + Math.abs(a.z() - b.z());
|
||||
} |
} |
||||
|
|
||||
private List<AStarNodeState> reconstructPath( |
/** |
||||
Map<String, AStarNodeState> cameFrom, |
* 生成状态的唯一键 |
||||
AStarNodeState endState |
*/ |
||||
) { |
private String stateKey(String nodeId, LCCDirection direction) { |
||||
LinkedList<AStarNodeState> path = new LinkedList<>(); |
return nodeId + "|" + direction; |
||||
AStarNodeState current = endState; |
} |
||||
|
|
||||
while (current != null) { |
public void setNodeWeight(String nodeId, float weight) { |
||||
path.addFirst(current); |
nodeWeights.put(nodeId, weight); |
||||
current = cameFrom.get(current.stateKey()); |
|
||||
} |
} |
||||
|
|
||||
return path; |
public void setBlocked(String nodeId, float blockedFactor) { |
||||
|
blockedNodes.put(nodeId, blockedFactor); |
||||
|
} |
||||
|
|
||||
|
private float calculateMoveCost(Node from, Node to, boolean isBackward) { |
||||
|
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) * (isBackward ? 1.2f : 1.0f);
|
||||
|
return baseCost * weight * (1 + blockedFactor); |
||||
|
} |
||||
|
|
||||
|
private boolean isBlocked(String nodeId) { |
||||
|
return blockedNodes.containsKey(nodeId) && blockedNodes.get(nodeId) > 0.8f; |
||||
|
} |
||||
|
|
||||
|
private boolean canMoveForward(LCCDirection currentDir, LCCDirection moveDir) { |
||||
|
return currentDir == moveDir; |
||||
|
} |
||||
|
|
||||
|
private boolean canMoveBackward(LCCDirection currentDir, LCCDirection moveDir) { |
||||
|
return currentDir == getOppositeDirection(moveDir); |
||||
|
} |
||||
|
|
||||
|
private LCCDirection calculateRequiredDirection(LCCDirection moveDirection) { |
||||
|
// 侧插式AGV只能前进或后退,不能横移
|
||||
|
return moveDirection; |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,95 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path; |
|
||||
|
|
||||
import com.galaxis.rcs.common.enums.OperationSide; |
|
||||
import com.yvan.logisticsModel.LogisticsRuntime; |
|
||||
import org.clever.core.Conv; |
|
||||
|
|
||||
import java.util.List; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
public class GraphInitializer { |
|
||||
|
|
||||
// public NavigationGraph initializeGraph(LogisticsRuntime runtime, String agvType, List<Map<String, Object>> jsonData) {
|
|
||||
// if (runtime.pathPlannerMap.containsKey(agvType)) {
|
|
||||
// return runtime.pathPlannerMap.get(agvType);
|
|
||||
// }
|
|
||||
// NavigationGraph graph = new NavigationGraph();
|
|
||||
// runtime.pathPlannerMap.put(agvType, graph);
|
|
||||
//
|
|
||||
// // 第一步:创建所有节点
|
|
||||
// for (Map<String, Object> nodeData : jsonData) {
|
|
||||
// String id = (String) nodeData.get("id");
|
|
||||
//
|
|
||||
// // 判断是否是 way 类型才创建 NavigationNode
|
|
||||
// if (!"way".equals(nodeData.get("t"))) {
|
|
||||
// continue;
|
|
||||
// }
|
|
||||
//
|
|
||||
// List<List<Float>> tf = (List<List<Float>>) nodeData.get("tf");
|
|
||||
// float x = tf.get(0).get(0);
|
|
||||
// float z = tf.get(0).get(2);
|
|
||||
//
|
|
||||
// // 检查是否为可旋转点
|
|
||||
// Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
|
|
||||
// boolean rotatable = false;
|
|
||||
// if (dt.containsKey("agvRotation")) {
|
|
||||
// rotatable = true;
|
|
||||
// }
|
|
||||
//
|
|
||||
// // 添加节点
|
|
||||
// graph.addNode(new NavigationNode(id, x, z, rotatable));
|
|
||||
// }
|
|
||||
//
|
|
||||
// // 第二步:添加路径连接
|
|
||||
// for (Map<String, Object> nodeData : jsonData) {
|
|
||||
// if (!"way".equals(nodeData.get("t"))) continue;
|
|
||||
//
|
|
||||
// String id = (String) nodeData.get("id");
|
|
||||
// Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
|
|
||||
//
|
|
||||
// List<String> outEdges = (List<String>) dt.get("out");
|
|
||||
// if (outEdges != null) {
|
|
||||
// for (String neighborId : outEdges) {
|
|
||||
// if (graph.nodes.containsKey(id) && graph.nodes.containsKey(neighborId)) {
|
|
||||
// NavigationNode from = graph.nodes.get(id);
|
|
||||
// NavigationNode to = graph.nodes.get(neighborId);
|
|
||||
// graph.addBidirectionalPath(from, to);
|
|
||||
// }
|
|
||||
// }
|
|
||||
// }
|
|
||||
// }
|
|
||||
//
|
|
||||
// // 第三步:添加操作点 OperationPoint
|
|
||||
// for (Map<String, Object> nodeData : jsonData) {
|
|
||||
// if (!"way".equals(nodeData.get("t"))) continue;
|
|
||||
//
|
|
||||
// String nodeId = (String) nodeData.get("id");
|
|
||||
// Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
|
|
||||
//
|
|
||||
// if (dt.containsKey("linkStore")) {
|
|
||||
// List<Map<String, Object>> linkStores = (List<Map<String, Object>>) dt.get("linkStore");
|
|
||||
// for (Map<String, Object> store : linkStores) {
|
|
||||
// String targetId = (String) store.get("item");
|
|
||||
// Integer bay = Conv.asInteger(store.get("bay"));
|
|
||||
// Integer level = Conv.asInteger(store.get("level"));
|
|
||||
// Integer cell = Conv.asInteger(store.get("cell"));
|
|
||||
//
|
|
||||
// // 根据位置确定方位(这里假设固定为 TOP,可根据 tf 中的方向判断更精确)
|
|
||||
// OperationSide side = OperationSide.TOP;
|
|
||||
//
|
|
||||
// OperationPoint point = new OperationPoint(
|
|
||||
// graph.nodes.get(nodeId),
|
|
||||
// targetId,
|
|
||||
// side,
|
|
||||
// bay,
|
|
||||
// level,
|
|
||||
// cell
|
|
||||
// );
|
|
||||
// graph.addOperationPoint(point);
|
|
||||
// }
|
|
||||
// }
|
|
||||
// }
|
|
||||
//
|
|
||||
// return graph;
|
|
||||
// }
|
|
||||
} |
|
||||
@ -1,69 +1,147 @@ |
|||||
package com.galaxis.rcs.plan.path; |
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
|
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
import com.google.common.collect.Maps; |
||||
|
import com.yvan.logisticsModel.LogisticsRuntime; |
||||
|
import com.yvan.logisticsModel.StaticItem; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
import java.util.*; |
import java.util.*; |
||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||
|
|
||||
/** |
/** |
||||
* 导航图管理器 |
* A* 导航图 |
||||
*/ |
*/ |
||||
|
@Slf4j |
||||
public class NavigationGraph { |
public class NavigationGraph { |
||||
final Map<String, NavigationNode> nodes = new ConcurrentHashMap<>(); |
private final LogisticsRuntime runtime; |
||||
final Map<String, OperationPoint> operationPoints = new ConcurrentHashMap<>(); |
|
||||
final Map<String, PathSegment> pathSegments = new ConcurrentHashMap<>(); |
/** |
||||
final Map<String, List<NavigationNode>> adjacencyList = new ConcurrentHashMap<>(); |
* 缓存距离计算结果,避免重复计算 |
||||
|
*/ |
||||
|
private final Map<String, Float> distanceCache = Maps.newConcurrentMap(); |
||||
|
|
||||
|
/** |
||||
|
* 缓存邻居节点列表,避免重复查询 |
||||
|
*/ |
||||
|
private final Map<String, List<Node>> neighborCache = Maps.newConcurrentMap(); |
||||
|
|
||||
|
/** |
||||
|
* 添加路径缓存 |
||||
|
*/ |
||||
|
private final Map<String, List<State>> pathCache = Maps.newConcurrentMap(); |
||||
|
|
||||
|
private final Map<String, Node> nodeMap = new HashMap<>(); |
||||
|
private final Map<String, List<String>> storeToNodes = new HashMap<>(); |
||||
|
|
||||
|
public NavigationGraph(LogisticsRuntime runtime) { |
||||
|
this.runtime = runtime; |
||||
|
} |
||||
|
|
||||
|
public List<State> getCachedPath(String startId, LCCDirection startDirection, |
||||
|
String endId, LCCDirection endDirection) { |
||||
|
String cacheKey = startId + "|" + startDirection + "->" + endId + "|" + endDirection; |
||||
|
return pathCache.get(cacheKey); |
||||
|
} |
||||
|
|
||||
// 添加节点
|
public void cachePath(String startId, LCCDirection startDirection, |
||||
public void addNode(NavigationNode node) { |
String endId, LCCDirection endDirection, |
||||
nodes.put(node.id(), node); |
List<State> path) { |
||||
adjacencyList.put(node.id(), new ArrayList<>()); |
String cacheKey = startId + "|" + startDirection + "->" + endId + "|" + endDirection; |
||||
|
pathCache.put(cacheKey, path); |
||||
} |
} |
||||
|
|
||||
// 添加双向路径
|
public Node getNodeById(String nodeId){ |
||||
public void addBidirectionalPath(NavigationNode a, NavigationNode b) { |
return nodeMap.get(nodeId); |
||||
float distance = a.distanceTo(b); |
} |
||||
pathSegments.put(a.id() + "->" + b.id(), new PathSegment(a, b, distance)); |
|
||||
pathSegments.put(b.id() + "->" + a.id(), new PathSegment(b, a, distance)); |
|
||||
|
|
||||
adjacencyList.get(a.id()).add(b); |
public List<Node> getNodesForStore(String storeId) { |
||||
adjacencyList.get(b.id()).add(a); |
List<Node> nodes = new ArrayList<>(); |
||||
|
List<String> nodeIds = storeToNodes.get(storeId); |
||||
|
if (nodeIds != null) { |
||||
|
for (String id : nodeIds) { |
||||
|
Node node = nodeMap.get(id); |
||||
|
if (node != null) { |
||||
|
nodes.add(node); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return nodes; |
||||
} |
} |
||||
|
|
||||
// 添加操作点
|
public List<Node> getNeighbors(Node node) { |
||||
public void addOperationPoint(OperationPoint point) { |
return node.neighbors().stream() |
||||
operationPoints.put(point.locationKey(), point); |
.map(nodeMap::get) |
||||
|
.filter(Objects::nonNull) |
||||
|
.toList(); |
||||
} |
} |
||||
|
|
||||
// 查找操作点
|
public float distance(Node a, Node b) { |
||||
public OperationPoint findOperationPoint(String locationKey) { |
float dx = a.x() - b.x(); |
||||
return operationPoints.get(locationKey); |
float dz = a.z() - b.z(); |
||||
|
return (float) Math.sqrt(dx * dx + dz * dz); |
||||
} |
} |
||||
|
|
||||
// 获取路径段
|
|
||||
public PathSegment getPathSegment(String startId, String endId) { |
public void init() { |
||||
return pathSegments.get(startId + "->" + endId); |
var items = runtime.getStaticItems(); |
||||
|
for (StaticItem item : items) { |
||||
|
if ("way".equals(item.getT())) { |
||||
|
float[][] tf = item.getTf(); |
||||
|
String id = item.getId(); |
||||
|
Map<String, Object> dt = item.getDt(); |
||||
|
|
||||
|
// 提取坐标
|
||||
|
float x = tf[0][0]; |
||||
|
float z = tf[0][2]; // Z向下增长
|
||||
|
|
||||
|
// 检查可旋转性
|
||||
|
boolean rotatable = dt.containsKey("agvRotation"); |
||||
|
if (rotatable) { |
||||
|
log.info("Node {} is rotatable", id); |
||||
} |
} |
||||
|
|
||||
// 获取相邻节点
|
// 提取邻居节点
|
||||
public List<NavigationNode> getAdjacentNodes(String nodeId) { |
List<String> in = (List<String>) dt.get("in"); |
||||
return adjacencyList.getOrDefault(nodeId, Collections.emptyList()); |
List<String> out = (List<String>) dt.get("out"); |
||||
|
Set<String> neighbors = new HashSet<>(); |
||||
|
if (in != null) neighbors.addAll(in); |
||||
|
if (out != null) neighbors.addAll(out); |
||||
|
|
||||
|
// 提取货位链接
|
||||
|
List<StoreLink> storeLinks = new ArrayList<>(); |
||||
|
List<Map<String, Object>> links = (List<Map<String, Object>>) dt.get("linkStore"); |
||||
|
if (links != null) { |
||||
|
for (Map<String, Object> link : links) { |
||||
|
String storeId = (String) link.get("item"); |
||||
|
int bay = (Integer) link.get("bay"); |
||||
|
int level = (Integer) link.get("level"); |
||||
|
int cell = (Integer) link.get("cell"); |
||||
|
String direction = (String) link.get("direction"); |
||||
|
storeLinks.add(new StoreLink(storeId, bay, level, cell, LCCDirection.fromString(direction))); |
||||
|
|
||||
|
// 建立货位到节点的反向映射
|
||||
|
storeToNodes.computeIfAbsent(storeId, k -> new ArrayList<>()).add(id); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
// 获取最近的旋转点
|
nodeMap.put(id, new Node(id, x, z, rotatable, |
||||
public NavigationNode findNearestRotationPoint(String startId) { |
new ArrayList<>(neighbors), storeLinks)); |
||||
NavigationNode start = nodes.get(startId); |
} |
||||
if (start == null) return null; |
} |
||||
|
|
||||
return nodes.values().parallelStream() |
// 2. 验证邻居双向连接
|
||||
.filter(NavigationNode::rotatable) |
for (Node node : nodeMap.values()) { |
||||
.min(Comparator.comparing(start::distanceTo)) |
Iterator<String> it = node.neighbors().iterator(); |
||||
.orElse(null); |
while (it.hasNext()) { |
||||
|
String neighborId = it.next(); |
||||
|
if (!nodeMap.containsKey(neighborId)) { |
||||
|
it.remove(); // 移除无效邻居
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
// 计算启发式代价 (使用欧几里得距离)
|
public Node getNode(String id) { |
||||
public float heuristicCost(String fromId, String toId) { |
return nodeMap.get(id); |
||||
NavigationNode from = nodes.get(fromId); |
|
||||
NavigationNode to = nodes.get(toId); |
|
||||
if (from == null || to == null) return Float.MAX_VALUE; |
|
||||
return from.distanceTo(to); |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,25 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path; |
|
||||
|
|
||||
import org.clever.core.Conv; |
|
||||
|
|
||||
/** |
|
||||
* 路标节点(地图中的顶点) |
|
||||
*/ |
|
||||
public record NavigationNode( |
|
||||
String id, // 节点ID (如 "20", "charger1")
|
|
||||
float x, // 向右增长
|
|
||||
float z, // 向下增长 (y无效)
|
|
||||
boolean rotatable // 是否为可旋转位
|
|
||||
) implements Comparable<NavigationNode> { |
|
||||
// 计算到另一个节点的欧几里得距离
|
|
||||
public float distanceTo(NavigationNode other) { |
|
||||
float dx = this.x - other.x; |
|
||||
float dz = this.z - other.z; |
|
||||
return Conv.asFloat(Math.sqrt(dx * dx + dz * dz)); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int compareTo(NavigationNode other) { |
|
||||
return this.id.compareTo(other.id); |
|
||||
} |
|
||||
} |
|
||||
@ -1,4 +1,4 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
import java.util.List; |
import java.util.List; |
||||
|
|
||||
@ -1,33 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path; |
|
||||
|
|
||||
import com.galaxis.rcs.common.enums.OperationSide; |
|
||||
|
|
||||
/** |
|
||||
* 操作位置(取货/放货/充电点) |
|
||||
*/ |
|
||||
public record OperationPoint( |
|
||||
NavigationNode node, // 关联的路标节点
|
|
||||
String targetId, // 货位ID (如 "54") 或货架ID (如 "rack1")
|
|
||||
OperationSide side, // 方位
|
|
||||
Integer bay, // 列 (货架时非空)
|
|
||||
Integer level, // 层 (货架时非空)
|
|
||||
Integer cell // 格 (货架时非空)
|
|
||||
) { |
|
||||
// 计算操作所需方向
|
|
||||
public int requiredDirection() { |
|
||||
return switch (side) { |
|
||||
case LEFT -> 90; |
|
||||
case RIGHT -> 270; |
|
||||
case TOP -> 0; |
|
||||
case BOTTOM -> 180; |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
// 位置唯一标识
|
|
||||
public String locationKey() { |
|
||||
if (bay == null || level == null || cell == null) { |
|
||||
return targetId; // 地堆货位
|
|
||||
} |
|
||||
return targetId + "-" + bay + "-" + level + "-" + cell; |
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path; |
|
||||
|
|
||||
/** |
|
||||
* 路径段(节点之间的连接) |
|
||||
*/ |
|
||||
public record PathSegment( |
|
||||
NavigationNode start, // 起点节点
|
|
||||
NavigationNode end, // 终点节点
|
|
||||
float distance // 路径距离
|
|
||||
) { |
|
||||
// 路径唯一标识
|
|
||||
public String key() { |
|
||||
return start.id() + "->" + end.id(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,4 +1,4 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
|
||||
@ -0,0 +1,236 @@ |
|||||
|
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
|
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
import com.galaxis.rcs.plan.PlanTaskSequence; |
||||
|
import com.galaxis.rcs.plan.task.CarryTask; |
||||
|
import com.galaxis.rcs.plan.task.LoadTask; |
||||
|
import com.galaxis.rcs.plan.task.MoveTask; |
||||
|
import com.galaxis.rcs.plan.task.UnloadTask; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import static com.galaxis.rcs.plan.path.PathUtils.convertStoreDirection; |
||||
|
|
||||
|
public class PtrPathPlanner { |
||||
|
private final NavigationGraph graph; |
||||
|
private final AStarPathPlanner astar; |
||||
|
|
||||
|
public PtrPathPlanner(NavigationGraph graph) { |
||||
|
this.graph = graph; |
||||
|
this.astar = new AStarPathPlanner(graph); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void planMoveTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, MoveTask moveTask) { |
||||
|
|
||||
|
Node node = this.graph.getNodeById(moveTask.targetWayPointId()); |
||||
|
|
||||
|
List<State> toPath = astar.findPath(startNodeId, startDirection, node.id(), moveTask.targetDirection()); |
||||
|
generateMoves(plan, toPath); |
||||
|
plan.addFinish(); |
||||
|
} |
||||
|
|
||||
|
public void planCarryTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, CarryTask task) { |
||||
|
// 取货点
|
||||
|
String loadRackId = task.from().rackId(); |
||||
|
int pickupBay = task.from().bay(); |
||||
|
NodeDirection loadNodeDirection = findNodeForStore(loadRackId, pickupBay); |
||||
|
if (loadNodeDirection == null) { |
||||
|
throw new RuntimeException("Pickup node not found for rackId=" + loadRackId + ", bay=" + pickupBay); |
||||
|
} |
||||
|
|
||||
|
// 放货点
|
||||
|
String unloadRackId = task.to().rackId(); |
||||
|
int unloadBay = task.to().bay(); |
||||
|
NodeDirection unloadNodeDirection = findNodeForStore(unloadRackId, unloadBay); |
||||
|
if (unloadNodeDirection == null) { |
||||
|
throw new RuntimeException("Drop node not found for rackId=" + unloadRackId + ", bay=" + unloadBay); |
||||
|
} |
||||
|
|
||||
|
// 规划到取货点路径
|
||||
|
List<State> toLoadPath = astar.findPath(startNodeId, startDirection, loadNodeDirection.node().id(), loadNodeDirection.direction()); |
||||
|
|
||||
|
// 检查方向是否匹配,如果不匹配则插入旋转点
|
||||
|
if (!toLoadPath.isEmpty()) { |
||||
|
State lastState = toLoadPath.get(toLoadPath.size() - 1); |
||||
|
if (lastState.direction() != loadNodeDirection.direction()) { |
||||
|
Node rotationNode = PathUtils.findNearestRotationNode( |
||||
|
graph, lastState.node(), lastState.direction(), loadNodeDirection.direction() |
||||
|
); |
||||
|
|
||||
|
if (rotationNode != null) { |
||||
|
// 插入旋转路径
|
||||
|
List<State> toRotation = astar.findPath( |
||||
|
lastState.node().id(), lastState.direction(), |
||||
|
rotationNode.id(), loadNodeDirection.direction() |
||||
|
); |
||||
|
toLoadPath.addAll(toRotation); |
||||
|
|
||||
|
// 从旋转点到目标点
|
||||
|
List<State> fromRotation = astar.findPath( |
||||
|
rotationNode.id(), loadNodeDirection.direction(), |
||||
|
loadNodeDirection.node().id(), loadNodeDirection.direction() |
||||
|
); |
||||
|
toLoadPath.addAll(fromRotation); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 规划到放货点路径
|
||||
|
List<State> toUnloadPath = astar.findPath(loadNodeDirection.node().id(), loadNodeDirection.direction(), unloadNodeDirection.node().id(), unloadNodeDirection.direction()); |
||||
|
|
||||
|
// 生成指令序列
|
||||
|
generateMoves(plan, toLoadPath); |
||||
|
plan.addLoad(task.lpn(), loadRackId, pickupBay, task.from().level(), task.from().cell()); |
||||
|
|
||||
|
generateMoves(plan, toUnloadPath); |
||||
|
plan.addUnload(unloadRackId, task.to().level(), task.to().bay(), task.to().cell()); |
||||
|
|
||||
|
plan.addFinish(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void planUnloadTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, UnloadTask task) { |
||||
|
Node fromNode = this.graph.getNodeById(startNodeId); |
||||
|
|
||||
|
// 放货点
|
||||
|
String unloadRackId = task.to().rackId(); |
||||
|
int unloadBay = task.to().bay(); |
||||
|
NodeDirection unloadNodeDirection = findNodeForStore(unloadRackId, unloadBay); |
||||
|
if (unloadNodeDirection == null) { |
||||
|
throw new RuntimeException("Drop node not found for rackId=" + unloadRackId + ", bay=" + unloadBay); |
||||
|
} |
||||
|
|
||||
|
// 规划到放货点路径
|
||||
|
List<State> toUnloadPath = astar.findPath(startNodeId, startDirection, unloadNodeDirection.node().id(), unloadNodeDirection.direction()); |
||||
|
|
||||
|
// 检查方向是否匹配,如果不匹配则插入旋转点
|
||||
|
// State lastState = toUnloadPath.get(toUnloadPath.size() - 1);
|
||||
|
// if (startDirection != lastState.direction()) {
|
||||
|
// Node rotationNode = PathUtils.findNearestRotationNode(
|
||||
|
// graph, fromNode, startDirection, lastState.direction()
|
||||
|
// );
|
||||
|
//
|
||||
|
// if (rotationNode != null) {
|
||||
|
// // 插入旋转路径
|
||||
|
// List<State> toRotation = astar.findPath(
|
||||
|
// startNodeId, startDirection,
|
||||
|
// rotationNode.id(), unloadNodeDirection.direction()
|
||||
|
// );
|
||||
|
// toUnloadPath.addAll(toRotation);
|
||||
|
//
|
||||
|
// // 从旋转点到目标点
|
||||
|
// List<State> fromRotation = astar.findPath(
|
||||
|
// rotationNode.id(), unloadNodeDirection.direction(),
|
||||
|
// unloadNodeDirection.node().id(), unloadNodeDirection.direction()
|
||||
|
// );
|
||||
|
// toUnloadPath.addAll(fromRotation);
|
||||
|
// }
|
||||
|
// }
|
||||
|
|
||||
|
// 生成指令序列
|
||||
|
generateMoves(plan, toUnloadPath); |
||||
|
plan.addUnload(unloadRackId, task.to().level(), task.to().bay(), task.to().cell()); |
||||
|
|
||||
|
plan.addFinish(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void planLoadTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, LoadTask task) { |
||||
|
Node fromNode = this.graph.getNodeById(startNodeId); |
||||
|
|
||||
|
// 放货点
|
||||
|
String loadRackId = task.to().rackId(); |
||||
|
int unloadBay = task.to().bay(); |
||||
|
NodeDirection loadNodeDirection = findNodeForStore(loadRackId, unloadBay); |
||||
|
if (loadNodeDirection == null) { |
||||
|
throw new RuntimeException("Drop node not found for rackId=" + loadRackId + ", bay=" + unloadBay); |
||||
|
} |
||||
|
|
||||
|
// 规划到取货点路径
|
||||
|
List<State> toLoadPath = astar.findPath(startNodeId, startDirection, loadNodeDirection.node().id(), loadNodeDirection.direction()); |
||||
|
|
||||
|
// 检查方向是否匹配,如果不匹配则插入旋转点
|
||||
|
// State lastState = toLoadPath.get(toLoadPath.size() - 1);
|
||||
|
// if (startDirection != lastState.direction()) {
|
||||
|
// Node rotationNode = PathUtils.findNearestRotationNode(
|
||||
|
// graph, fromNode, startDirection, lastState.direction()
|
||||
|
// );
|
||||
|
//
|
||||
|
// if (rotationNode != null) {
|
||||
|
// // 插入旋转路径
|
||||
|
// List<State> toRotation = astar.findPath(
|
||||
|
// startNodeId, startDirection,
|
||||
|
// rotationNode.id(), loadNodeDirection.direction()
|
||||
|
// );
|
||||
|
// toLoadPath.addAll(toRotation);
|
||||
|
//
|
||||
|
// // 从旋转点到目标点
|
||||
|
// List<State> fromRotation = astar.findPath(
|
||||
|
// rotationNode.id(), loadNodeDirection.direction(),
|
||||
|
// loadNodeDirection.node().id(), loadNodeDirection.direction()
|
||||
|
// );
|
||||
|
// toLoadPath.addAll(fromRotation);
|
||||
|
// }
|
||||
|
// }
|
||||
|
|
||||
|
// 生成指令序列
|
||||
|
generateMoves(plan, toLoadPath); |
||||
|
plan.addLoad("N/A", loadRackId, task.to().level(), task.to().bay(), task.to().cell()); |
||||
|
|
||||
|
plan.addFinish(); |
||||
|
} |
||||
|
|
||||
|
private NodeDirection findNodeForStore(String storeId, int bay) { |
||||
|
List<Node> nodes = this.graph.getNodesForStore(storeId); |
||||
|
for (Node node : nodes) { |
||||
|
for (StoreLink link : node.storeLinks()) { |
||||
|
if (link.storeId().equals(storeId) && link.bay() == bay) { |
||||
|
LCCDirection agvDirection = convertStoreDirection(link.direction()); |
||||
|
return new NodeDirection(node, agvDirection); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据A*状态,生成移动指令序列 |
||||
|
*/ |
||||
|
private void generateMoves(PlanTaskSequence sequence, List<State> path) { |
||||
|
if (path.isEmpty()) return; |
||||
|
|
||||
|
// 第一个状态是起点,跳过
|
||||
|
State prevState = path.get(0); |
||||
|
|
||||
|
for (int i = 1; i < path.size(); i++) { |
||||
|
State current = path.get(i); |
||||
|
|
||||
|
// 如果是旋转动作
|
||||
|
if (current.node().equals(prevState.node())) { |
||||
|
float angle = PathUtils.getRequiredDirection(current.direction()); |
||||
|
sequence.addRotationTo(angle); |
||||
|
} |
||||
|
// 移动动作 - 检查是否需要后退
|
||||
|
else { |
||||
|
// 检查移动方向
|
||||
|
LCCDirection moveDir = PathUtils.calculateMoveDirection(prevState.node(), current.node()); |
||||
|
|
||||
|
// 如果移动方向与车头方向相反,需要后退
|
||||
|
boolean isBackward = (current.direction() == PathUtils.getOppositeDirection(moveDir)); |
||||
|
|
||||
|
if (isBackward) { |
||||
|
sequence.addMoveBackward(current.node().id()); |
||||
|
} else { |
||||
|
sequence.addMoveTo(current.node().id()); |
||||
|
} |
||||
|
} |
||||
|
prevState = current; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// 辅助记录类
|
||||
|
private record NodeDirection(Node node, LCCDirection direction) { |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
|
||||
@ -1,4 +1,4 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
package com.galaxis.rcs.plan.path; |
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
|
||||
@ -1,220 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
|
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
|
||||
import com.google.common.collect.Maps; |
|
||||
|
|
||||
import java.util.*; |
|
||||
|
|
||||
import static com.galaxis.rcs.plan.path2.PathUtils.*; |
|
||||
|
|
||||
public class AStarPathPlanner { |
|
||||
private static final float ROTATION_COST_PER_DEGREE = 0.01f; |
|
||||
private static final float BLOCKED_COST = 10000f; // 阻塞系数成本
|
|
||||
private static final float WEIGHT_FACTOR = 1.5f; // 权重因子
|
|
||||
|
|
||||
private final NavigationGraph graph; |
|
||||
private final Map<String, Float> nodeWeights = Maps.newConcurrentMap(); |
|
||||
private final Map<String, Float> blockedNodes = Maps.newConcurrentMap(); |
|
||||
|
|
||||
public AStarPathPlanner(NavigationGraph graph) { |
|
||||
this.graph = graph; |
|
||||
} |
|
||||
|
|
||||
// 路径规划状态
|
|
||||
public List<State> findPath(String startId, LCCDirection startDirection, String endId, LCCDirection endDirection) { |
|
||||
Node start = graph.getNode(startId); |
|
||||
Node end = graph.getNode(endId); |
|
||||
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<>(); |
|
||||
PriorityQueue<State> open = new PriorityQueue<>(); |
|
||||
|
|
||||
// 初始状态
|
|
||||
State initialState = new State(start, startDirection, 0, heuristic(start, end), null); |
|
||||
open.add(initialState); |
|
||||
visited.put(stateKey(start.id(), startDirection), initialState); |
|
||||
|
|
||||
while (!open.isEmpty()) { |
|
||||
State current = open.poll(); |
|
||||
|
|
||||
// 到达目标节点且方向匹配
|
|
||||
if (current.node().id().equals(endId) && current.direction() == endDirection) { |
|
||||
return buildPath(current); |
|
||||
} |
|
||||
|
|
||||
// 处理邻边移动
|
|
||||
for (Node neighbor : graph.getNeighbors(current.node())) { |
|
||||
// 检查节点是否被阻塞
|
|
||||
if (isBlocked(neighbor.id())) continue; |
|
||||
|
|
||||
// 计算移动方向
|
|
||||
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor); |
|
||||
|
|
||||
// 尝试前进
|
|
||||
if (canMoveForward(current.direction(), moveDirection)) { |
|
||||
float moveCost = calculateMoveCost(current.node(), neighbor, false); |
|
||||
considerState(current, neighbor, current.direction(), |
|
||||
moveCost, open, visited, end); |
|
||||
} |
|
||||
// 尝试后退
|
|
||||
else if (canMoveBackward(current.direction(), moveDirection)) { |
|
||||
float moveCost = calculateMoveCost(current.node(), neighbor, true); |
|
||||
considerState(current, neighbor, current.direction(), |
|
||||
moveCost, open, visited, end); |
|
||||
} |
|
||||
// 需要旋转
|
|
||||
else if (current.node().rotatable()) { |
|
||||
// 计算需要旋转到的方向
|
|
||||
LCCDirection requiredDirection = calculateRequiredDirection(moveDirection); |
|
||||
|
|
||||
// 考虑旋转后移动
|
|
||||
float rotationCost = calculateRotationCost( |
|
||||
current.direction(), requiredDirection, ROTATION_COST_PER_DEGREE |
|
||||
); |
|
||||
float moveCost = calculateMoveCost(current.node(), neighbor, false); |
|
||||
float totalCost = rotationCost + moveCost; |
|
||||
|
|
||||
// 创建旋转状态
|
|
||||
State rotatedState = new State( |
|
||||
current.node(), requiredDirection, |
|
||||
current.g() + rotationCost, |
|
||||
heuristic(current.node(), end), |
|
||||
current |
|
||||
); |
|
||||
|
|
||||
// 考虑旋转后的移动
|
|
||||
considerState(rotatedState, neighbor, requiredDirection, |
|
||||
moveCost, open, visited, end); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 处理原地旋转 - 只考虑目标方向
|
|
||||
if (current.node().rotatable()) { |
|
||||
// 只考虑旋转到目标方向(如果可能)
|
|
||||
if (current.direction() != endDirection) { |
|
||||
float rotationCost = calculateRotationCost( |
|
||||
current.direction(), endDirection, ROTATION_COST_PER_DEGREE |
|
||||
); |
|
||||
considerState(current, current.node(), endDirection, |
|
||||
rotationCost, open, visited, end); |
|
||||
} |
|
||||
|
|
||||
// 另外考虑旋转到移动所需的方向
|
|
||||
for (Node neighbor : graph.getNeighbors(current.node())) { |
|
||||
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor); |
|
||||
LCCDirection requiredDirection = calculateRequiredDirection(moveDirection); |
|
||||
|
|
||||
if (requiredDirection != current.direction()) { |
|
||||
float rotationCost = calculateRotationCost( |
|
||||
current.direction(), requiredDirection, ROTATION_COST_PER_DEGREE |
|
||||
); |
|
||||
considerState(current, current.node(), requiredDirection, |
|
||||
rotationCost, open, visited, end); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// for (LCCDirection rotation : LCCDirection.values()) {
|
|
||||
// if (rotation == current.direction()) continue;
|
|
||||
//
|
|
||||
// float rotationCost = calculateRotationCost(
|
|
||||
// current.direction(), rotation, ROTATION_COST_PER_DEGREE
|
|
||||
// );
|
|
||||
// considerState(current, current.node(), rotation,
|
|
||||
// rotationCost, open, visited, end);
|
|
||||
// }
|
|
||||
} |
|
||||
} |
|
||||
return Collections.emptyList(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 考虑新的状态并更新开放列表和访问记录 |
|
||||
* |
|
||||
* @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, |
|
||||
Map<String, State> visited, Node end) { |
|
||||
String key = stateKey(nextNode.id(), nextDirection); |
|
||||
float newG = current.g() + cost; |
|
||||
|
|
||||
if (!visited.containsKey(key) || visited.get(key).g() > newG) { |
|
||||
float h = heuristic(nextNode, end); |
|
||||
State newState = new State(nextNode, nextDirection, newG, h, current); |
|
||||
open.add(newState); |
|
||||
visited.put(key, newState); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private List<State> buildPath(State state) { |
|
||||
LinkedList<State> path = new LinkedList<>(); |
|
||||
while (state != null) { |
|
||||
path.addFirst(state); |
|
||||
state = state.parent(); |
|
||||
} |
|
||||
return path; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 启发式函数,计算两个节点之间的距离 |
|
||||
*/ |
|
||||
private float heuristic(Node a, Node 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 direction) { |
|
||||
return nodeId + "|" + direction; |
|
||||
} |
|
||||
|
|
||||
public void setNodeWeight(String nodeId, float weight) { |
|
||||
nodeWeights.put(nodeId, weight); |
|
||||
} |
|
||||
|
|
||||
public void setBlocked(String nodeId, float blockedFactor) { |
|
||||
blockedNodes.put(nodeId, blockedFactor); |
|
||||
} |
|
||||
|
|
||||
private float calculateMoveCost(Node from, Node to, boolean isBackward) { |
|
||||
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) * (isBackward ? 1.2f : 1.0f);
|
|
||||
return baseCost * weight * (1 + blockedFactor); |
|
||||
} |
|
||||
|
|
||||
private boolean isBlocked(String nodeId) { |
|
||||
return blockedNodes.containsKey(nodeId) && blockedNodes.get(nodeId) > 0.8f; |
|
||||
} |
|
||||
|
|
||||
private boolean canMoveForward(LCCDirection currentDir, LCCDirection moveDir) { |
|
||||
return currentDir == moveDir; |
|
||||
} |
|
||||
|
|
||||
private boolean canMoveBackward(LCCDirection currentDir, LCCDirection moveDir) { |
|
||||
return currentDir == getOppositeDirection(moveDir); |
|
||||
} |
|
||||
|
|
||||
private LCCDirection calculateRequiredDirection(LCCDirection moveDirection) { |
|
||||
// 侧插式AGV只能前进或后退,不能横移
|
|
||||
return moveDirection; |
|
||||
} |
|
||||
} |
|
||||
@ -1,147 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
|
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
|
||||
import com.google.common.collect.Maps; |
|
||||
import com.yvan.logisticsModel.LogisticsRuntime; |
|
||||
import com.yvan.logisticsModel.StaticItem; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
|
|
||||
import java.util.*; |
|
||||
|
|
||||
/** |
|
||||
* A* 导航图 |
|
||||
*/ |
|
||||
@Slf4j |
|
||||
public class NavigationGraph { |
|
||||
private final LogisticsRuntime runtime; |
|
||||
|
|
||||
/** |
|
||||
* 缓存距离计算结果,避免重复计算 |
|
||||
*/ |
|
||||
private final Map<String, Float> distanceCache = Maps.newConcurrentMap(); |
|
||||
|
|
||||
/** |
|
||||
* 缓存邻居节点列表,避免重复查询 |
|
||||
*/ |
|
||||
private final Map<String, List<Node>> neighborCache = Maps.newConcurrentMap(); |
|
||||
|
|
||||
/** |
|
||||
* 添加路径缓存 |
|
||||
*/ |
|
||||
private final Map<String, List<State>> pathCache = Maps.newConcurrentMap(); |
|
||||
|
|
||||
private final Map<String, Node> nodeMap = new HashMap<>(); |
|
||||
private final Map<String, List<String>> storeToNodes = new HashMap<>(); |
|
||||
|
|
||||
public NavigationGraph(LogisticsRuntime runtime) { |
|
||||
this.runtime = runtime; |
|
||||
} |
|
||||
|
|
||||
public List<State> getCachedPath(String startId, LCCDirection startDirection, |
|
||||
String endId, LCCDirection endDirection) { |
|
||||
String cacheKey = startId + "|" + startDirection + "->" + endId + "|" + endDirection; |
|
||||
return pathCache.get(cacheKey); |
|
||||
} |
|
||||
|
|
||||
public void cachePath(String startId, LCCDirection startDirection, |
|
||||
String endId, LCCDirection endDirection, |
|
||||
List<State> path) { |
|
||||
String cacheKey = startId + "|" + startDirection + "->" + endId + "|" + endDirection; |
|
||||
pathCache.put(cacheKey, path); |
|
||||
} |
|
||||
|
|
||||
public Node getNodeById(String nodeId){ |
|
||||
return nodeMap.get(nodeId); |
|
||||
} |
|
||||
|
|
||||
public List<Node> getNodesForStore(String storeId) { |
|
||||
List<Node> nodes = new ArrayList<>(); |
|
||||
List<String> nodeIds = storeToNodes.get(storeId); |
|
||||
if (nodeIds != null) { |
|
||||
for (String id : nodeIds) { |
|
||||
Node node = nodeMap.get(id); |
|
||||
if (node != null) { |
|
||||
nodes.add(node); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return nodes; |
|
||||
} |
|
||||
|
|
||||
public List<Node> getNeighbors(Node node) { |
|
||||
return node.neighbors().stream() |
|
||||
.map(nodeMap::get) |
|
||||
.filter(Objects::nonNull) |
|
||||
.toList(); |
|
||||
} |
|
||||
|
|
||||
public float distance(Node a, Node b) { |
|
||||
float dx = a.x() - b.x(); |
|
||||
float dz = a.z() - b.z(); |
|
||||
return (float) Math.sqrt(dx * dx + dz * dz); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public void init() { |
|
||||
var items = runtime.getStaticItems(); |
|
||||
for (StaticItem item : items) { |
|
||||
if ("way".equals(item.getT())) { |
|
||||
float[][] tf = item.getTf(); |
|
||||
String id = item.getId(); |
|
||||
Map<String, Object> dt = item.getDt(); |
|
||||
|
|
||||
// 提取坐标
|
|
||||
float x = tf[0][0]; |
|
||||
float z = tf[0][2]; // Z向下增长
|
|
||||
|
|
||||
// 检查可旋转性
|
|
||||
boolean rotatable = dt.containsKey("agvRotation"); |
|
||||
if (rotatable) { |
|
||||
log.info("Node {} is rotatable", id); |
|
||||
} |
|
||||
|
|
||||
// 提取邻居节点
|
|
||||
List<String> in = (List<String>) dt.get("in"); |
|
||||
List<String> out = (List<String>) dt.get("out"); |
|
||||
Set<String> neighbors = new HashSet<>(); |
|
||||
if (in != null) neighbors.addAll(in); |
|
||||
if (out != null) neighbors.addAll(out); |
|
||||
|
|
||||
// 提取货位链接
|
|
||||
List<StoreLink> storeLinks = new ArrayList<>(); |
|
||||
List<Map<String, Object>> links = (List<Map<String, Object>>) dt.get("linkStore"); |
|
||||
if (links != null) { |
|
||||
for (Map<String, Object> link : links) { |
|
||||
String storeId = (String) link.get("item"); |
|
||||
int bay = (Integer) link.get("bay"); |
|
||||
int level = (Integer) link.get("level"); |
|
||||
int cell = (Integer) link.get("cell"); |
|
||||
String direction = (String) link.get("direction"); |
|
||||
storeLinks.add(new StoreLink(storeId, bay, level, cell, LCCDirection.fromString(direction))); |
|
||||
|
|
||||
// 建立货位到节点的反向映射
|
|
||||
storeToNodes.computeIfAbsent(storeId, k -> new ArrayList<>()).add(id); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
nodeMap.put(id, new Node(id, x, z, rotatable, |
|
||||
new ArrayList<>(neighbors), storeLinks)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 2. 验证邻居双向连接
|
|
||||
for (Node node : nodeMap.values()) { |
|
||||
Iterator<String> it = node.neighbors().iterator(); |
|
||||
while (it.hasNext()) { |
|
||||
String neighborId = it.next(); |
|
||||
if (!nodeMap.containsKey(neighborId)) { |
|
||||
it.remove(); // 移除无效邻居
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public Node getNode(String id) { |
|
||||
return nodeMap.get(id); |
|
||||
} |
|
||||
} |
|
||||
@ -1,143 +0,0 @@ |
|||||
package com.galaxis.rcs.plan.path2; |
|
||||
|
|
||||
import com.galaxis.rcs.common.enums.LCCDirection; |
|
||||
import com.galaxis.rcs.plan.PlanTaskSequence; |
|
||||
import com.galaxis.rcs.plan.task.CarryTask; |
|
||||
import com.galaxis.rcs.plan.task.MoveTask; |
|
||||
|
|
||||
import java.util.List; |
|
||||
|
|
||||
import static com.galaxis.rcs.plan.path2.PathUtils.convertStoreDirection; |
|
||||
import static com.galaxis.rcs.plan.path2.PathUtils.getRequiredDirection; |
|
||||
|
|
||||
public class PtrPathPlanner { |
|
||||
private final NavigationGraph graph; |
|
||||
private final AStarPathPlanner astar; |
|
||||
|
|
||||
public PtrPathPlanner(NavigationGraph graph) { |
|
||||
this.graph = graph; |
|
||||
this.astar = new AStarPathPlanner(graph); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public void planMoveTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, MoveTask moveTask) { |
|
||||
|
|
||||
Node node = this.graph.getNodeById(moveTask.targetWayPointId()); |
|
||||
|
|
||||
List<State> toPath = astar.findPath(startNodeId, startDirection, node.id(), startDirection); |
|
||||
generateMoves(plan, toPath); |
|
||||
plan.addFinish(); |
|
||||
} |
|
||||
|
|
||||
public void planCarryTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, CarryTask task) { |
|
||||
// 取货点
|
|
||||
String loadRackId = task.from().rackId(); |
|
||||
int pickupBay = task.from().bay(); |
|
||||
NodeDirection loadNodeDirection = findNodeForStore(loadRackId, pickupBay); |
|
||||
if (loadNodeDirection == null) { |
|
||||
throw new RuntimeException("Pickup node not found for rackId=" + loadRackId + ", bay=" + pickupBay); |
|
||||
} |
|
||||
|
|
||||
// 放货点
|
|
||||
String unloadRackId = task.to().rackId(); |
|
||||
int unloadBay = task.to().bay(); |
|
||||
NodeDirection unloadNodeDirection = findNodeForStore(unloadRackId, unloadBay); |
|
||||
if (unloadNodeDirection == null) { |
|
||||
throw new RuntimeException("Drop node not found for rackId=" + unloadRackId + ", bay=" + unloadBay); |
|
||||
} |
|
||||
|
|
||||
// 规划到取货点路径
|
|
||||
List<State> toLoadPath = astar.findPath(startNodeId, startDirection, loadNodeDirection.node().id(), loadNodeDirection.direction()); |
|
||||
|
|
||||
// 检查方向是否匹配,如果不匹配则插入旋转点
|
|
||||
if (!toLoadPath.isEmpty()) { |
|
||||
State lastState = toLoadPath.get(toLoadPath.size() - 1); |
|
||||
if (lastState.direction() != loadNodeDirection.direction()) { |
|
||||
Node rotationNode = PathUtils.findNearestRotationNode( |
|
||||
graph, lastState.node(), lastState.direction(), loadNodeDirection.direction() |
|
||||
); |
|
||||
|
|
||||
if (rotationNode != null) { |
|
||||
// 插入旋转路径
|
|
||||
List<State> toRotation = astar.findPath( |
|
||||
lastState.node().id(), lastState.direction(), |
|
||||
rotationNode.id(), loadNodeDirection.direction() |
|
||||
); |
|
||||
toLoadPath.addAll(toRotation); |
|
||||
|
|
||||
// 从旋转点到目标点
|
|
||||
List<State> fromRotation = astar.findPath( |
|
||||
rotationNode.id(), loadNodeDirection.direction(), |
|
||||
loadNodeDirection.node().id(), loadNodeDirection.direction() |
|
||||
); |
|
||||
toLoadPath.addAll(fromRotation); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 规划到放货点路径
|
|
||||
List<State> toUnloadPath = astar.findPath(loadNodeDirection.node().id(), loadNodeDirection.direction(), unloadNodeDirection.node().id(), unloadNodeDirection.direction()); |
|
||||
|
|
||||
// 生成指令序列
|
|
||||
generateMoves(plan, toLoadPath); |
|
||||
plan.addLoad(task.lpn(), loadRackId, pickupBay, task.from().level(), task.from().cell()); |
|
||||
|
|
||||
generateMoves(plan, toUnloadPath); |
|
||||
plan.addUnload(unloadRackId, task.to().level(), task.to().bay(), task.to().cell()); |
|
||||
|
|
||||
plan.addFinish(); |
|
||||
} |
|
||||
|
|
||||
private NodeDirection findNodeForStore(String storeId, int bay) { |
|
||||
List<Node> nodes = this.graph.getNodesForStore(storeId); |
|
||||
for (Node node : nodes) { |
|
||||
for (StoreLink link : node.storeLinks()) { |
|
||||
if (link.storeId().equals(storeId) && link.bay() == bay) { |
|
||||
LCCDirection agvDirection = convertStoreDirection(link.direction()); |
|
||||
return new NodeDirection(node, agvDirection); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 根据A*状态,生成移动指令序列 |
|
||||
*/ |
|
||||
private void generateMoves(PlanTaskSequence sequence, List<State> path) { |
|
||||
if (path.isEmpty()) return; |
|
||||
|
|
||||
// 第一个状态是起点,跳过
|
|
||||
State prevState = path.get(0); |
|
||||
|
|
||||
for (int i = 1; i < path.size(); i++) { |
|
||||
State current = path.get(i); |
|
||||
|
|
||||
// 如果是旋转动作
|
|
||||
if (current.node().equals(prevState.node())) { |
|
||||
float angle = PathUtils.getRequiredDirection(current.direction()); |
|
||||
sequence.addRotationTo(angle); |
|
||||
} |
|
||||
// 移动动作 - 检查是否需要后退
|
|
||||
else { |
|
||||
// 检查移动方向
|
|
||||
LCCDirection moveDir = PathUtils.calculateMoveDirection(prevState.node(), current.node()); |
|
||||
|
|
||||
// 如果移动方向与车头方向相反,需要后退
|
|
||||
boolean isBackward = (current.direction() == PathUtils.getOppositeDirection(moveDir)); |
|
||||
|
|
||||
if (isBackward) { |
|
||||
sequence.addMoveBackward(current.node().id()); |
|
||||
} else { |
|
||||
sequence.addMoveTo(current.node().id()); |
|
||||
} |
|
||||
} |
|
||||
prevState = current; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
// 辅助记录类
|
|
||||
private record NodeDirection(Node node, LCCDirection direction) { |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,15 @@ |
|||||
|
package com.galaxis.rcs.plan.task; |
||||
|
|
||||
|
import com.galaxis.rcs.common.entity.StoreLocation; |
||||
|
|
||||
|
/** |
||||
|
* 装入任务 |
||||
|
*/ |
||||
|
public record LoadTask( |
||||
|
String agv, |
||||
|
int priority, |
||||
|
StoreLocation to |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -1,8 +1,11 @@ |
|||||
package com.galaxis.rcs.plan.task; |
package com.galaxis.rcs.plan.task; |
||||
|
|
||||
|
import com.galaxis.rcs.common.enums.LCCDirection; |
||||
|
|
||||
public record MoveTask( |
public record MoveTask( |
||||
String agv, |
String agv, |
||||
String targetWayPointId, |
String targetWayPointId, |
||||
|
LCCDirection targetDirection, |
||||
int priority |
int priority |
||||
) { |
) { |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,15 @@ |
|||||
|
package com.galaxis.rcs.plan.task; |
||||
|
|
||||
|
import com.galaxis.rcs.common.entity.StoreLocation; |
||||
|
|
||||
|
/** |
||||
|
* 卸载任务 |
||||
|
*/ |
||||
|
public record UnloadTask( |
||||
|
String agv, |
||||
|
int priority, |
||||
|
StoreLocation to |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -0,0 +1,5 @@ |
|||||
|
package com.galaxis.rcs.ptr; |
||||
|
|
||||
|
public abstract class PtrAgvConnector { |
||||
|
|
||||
|
} |
||||
@ -1,7 +0,0 @@ |
|||||
package com.yvan.logisticsModel; |
|
||||
|
|
||||
import com.galaxis.rcs.common.entity.RcsTaskPlan; |
|
||||
|
|
||||
public abstract class PtrAgvConnector { |
|
||||
|
|
||||
} |
|
||||
Loading…
Reference in new issue