13 changed files with 443 additions and 168 deletions
@ -1,8 +0,0 @@ |
|||
package com.galaxis.rcs.plan.path2; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
|
|||
public class AGVPathPlanner { |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
package com.galaxis.rcs.plan.path2; |
|||
|
|||
import com.galaxis.rcs.common.enums.LCCDirection; |
|||
|
|||
import java.util.*; |
|||
|
|||
public class AStarPathPlanner { |
|||
private static final float ROTATION_COST_PER_DEGREE = 0.1f; |
|||
private final NavigationGraph graph; |
|||
|
|||
public AStarPathPlanner(NavigationGraph graph) { |
|||
this.graph = graph; |
|||
} |
|||
|
|||
// 路径规划状态
|
|||
public List<Node> findPath(String startId, float startDirectionAngle, String endId, float endDirectionAngle) { |
|||
Node start = graph.getNode(startId); |
|||
Node goal = graph.getNode(endId); |
|||
if (start == null || goal == null) return Collections.emptyList(); |
|||
|
|||
// 使用复合键避免状态重复
|
|||
Map<String, State> visited = new HashMap<>(); |
|||
PriorityQueue<State> open = new PriorityQueue<>(); |
|||
|
|||
// 初始状态
|
|||
State initialState = new State(start, startDirectionAngle, 0, heuristic(start, goal), null); |
|||
open.add(initialState); |
|||
visited.put(stateKey(start.id(), startDirectionAngle), initialState); |
|||
|
|||
while (!open.isEmpty()) { |
|||
State current = open.poll(); |
|||
|
|||
// 到达目标节点且方向匹配
|
|||
if (current.node().id().equals(endId) && |
|||
current.directionAngle() == endDirectionAngle) { |
|||
return buildPath(current); |
|||
} |
|||
|
|||
// 处理邻居移动
|
|||
for (Node neighbor : graph.getNeighbors(current.node())) { |
|||
// 计算可能的两种方向(前进/后退)
|
|||
float[] possibleHeadings = { |
|||
current.directionAngle(), // 前进方向不变
|
|||
(current.directionAngle() + 180) % 360 // 后退方向反转
|
|||
}; |
|||
|
|||
for (float nextHeading : possibleHeadings) { |
|||
float moveCost = graph.distance(current.node(), neighbor); |
|||
considerState(current, neighbor, nextHeading, moveCost, open, visited, goal); |
|||
} |
|||
} |
|||
|
|||
// 处理旋转(仅在可旋转节点)
|
|||
if (current.node().rotatable()) { |
|||
for (float rotation : new float[]{0, 90, 180, 270}) { |
|||
if (rotation == current.directionAngle()) continue; |
|||
|
|||
float angleDiff = Math.min( |
|||
Math.abs(rotation - current.directionAngle()), |
|||
360 - Math.abs(rotation - current.directionAngle()) |
|||
); |
|||
float rotationCost = angleDiff * ROTATION_COST_PER_DEGREE; |
|||
|
|||
considerState(current, current.node(), rotation, |
|||
rotationCost, open, visited, goal); |
|||
} |
|||
} |
|||
} |
|||
return Collections.emptyList(); |
|||
} |
|||
|
|||
private void considerState(State current, Node nextNode, float nextHeading, |
|||
float cost, PriorityQueue<State> open, |
|||
Map<String, State> visited, Node goal) { |
|||
String key = stateKey(nextNode.id(), nextHeading); |
|||
float newG = current.g() + cost; |
|||
|
|||
if (!visited.containsKey(key) || visited.get(key).g() > newG) { |
|||
float h = heuristic(nextNode, goal); |
|||
State newState = new State(nextNode, nextHeading, newG, h, current); |
|||
open.add(newState); |
|||
visited.put(key, newState); |
|||
} |
|||
} |
|||
|
|||
private List<Node> buildPath(State state) { |
|||
LinkedList<Node> path = new LinkedList<>(); |
|||
while (state != null) { |
|||
path.addFirst(state.node()); |
|||
state = state.parent(); |
|||
} |
|||
return path; |
|||
} |
|||
|
|||
private float heuristic(Node a, Node b) { |
|||
return graph.distance(a, b); |
|||
} |
|||
|
|||
private String stateKey(String nodeId, float directionAngle) { |
|||
return nodeId + "|" + directionAngle; |
|||
} |
|||
|
|||
/** |
|||
* 根据方向要求计算目标方向 |
|||
*/ |
|||
public static float getRequiredDirection(LCCDirection direction) { |
|||
return switch (direction) { |
|||
case UP -> 90f; // 车头朝上
|
|||
case DOWN -> 270f; // 车头朝下
|
|||
case LEFT -> 180f; // 车头朝左
|
|||
case RIGHT -> 0f; // 车头朝右
|
|||
default -> 0; // 默认为右
|
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
package com.galaxis.rcs.plan.path2; |
|||
|
|||
import com.galaxis.rcs.plan.PlanTaskSequence; |
|||
import com.galaxis.rcs.plan.task.CarryTask; |
|||
|
|||
import java.util.List; |
|||
|
|||
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 planCarryTask(PlanTaskSequence seq, String startId, float initDirectionAngle, CarryTask task) { |
|||
// 取货点
|
|||
String pickupRackId = task.from().rackId(); |
|||
int pickupBay = task.from().bay(); |
|||
Node pickupNode = findStoreNode(pickupRackId, pickupBay); |
|||
float pickupRotationAngle = getRequiredHeading(pickupNode, pickupBay); |
|||
|
|||
// 放货点
|
|||
String dropRackId = task.to().rackId(); |
|||
int dropBay = task.to().bay(); |
|||
Node dropNode = findStoreNode(dropRackId, dropBay); |
|||
float dropRotationAngle = getRequiredHeading(dropNode, 0); |
|||
|
|||
// 规划到取货点路径
|
|||
List<Node> toPickupPath = astar.findPath(startId, initDirectionAngle, pickupNode.id(), pickupRotationAngle); |
|||
|
|||
// 规划到放货点路径
|
|||
List<Node> toDeliverPath = astar.findPath(pickupNode.id(), pickupRotationAngle, dropNode.id(), dropRotationAngle); |
|||
|
|||
// 生成指令序列
|
|||
generateMoves(seq, toPickupPath); |
|||
seq.addLoad(task.lpn(), pickupRackId, pickupBay, task.from().level(), task.from().cell()); |
|||
generateMoves(seq, toDeliverPath); |
|||
seq.addUnload(dropRackId, task.to().level(), task.to().bay(), task.to().cell()); |
|||
seq.addFinish(); |
|||
} |
|||
|
|||
private Node findStoreNode(String storeId, int bay) { |
|||
return graph.getNodesForStore(storeId).stream() |
|||
.map(graph::getNode) |
|||
.filter(node -> node.storeLinks().stream() |
|||
.anyMatch(link -> link.storeId().equals(storeId) && link.bay() == bay)) |
|||
.findFirst() |
|||
.orElseThrow(); |
|||
} |
|||
|
|||
private float getRequiredHeading(Node node, int bay) { |
|||
return node.storeLinks().stream() |
|||
.filter(link -> link.bay() == bay) |
|||
.findFirst() |
|||
.map(link -> AStarPathPlanner.getRequiredDirection(link.direction())) |
|||
.orElse(0f); |
|||
} |
|||
|
|||
private void generateMoves(PlanTaskSequence seq, List<Node> path) { |
|||
// 简化的指令生成(实际需处理方向变化)
|
|||
for (int i = 1; i < path.size(); i++) { |
|||
Node node = path.get(i); |
|||
seq.addMoveTo(node.id()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package com.galaxis.rcs.plan.path2; |
|||
|
|||
public record State(Node node, |
|||
float directionAngle, |
|||
float g, |
|||
float h, |
|||
State parent) |
|||
implements Comparable<State> { |
|||
@Override |
|||
public int compareTo(State other) { |
|||
return Float.compare(g + h, other.g + other.h); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
package com.galaxis.rcs.plan.task; |
|||
|
|||
import com.galaxis.rcs.common.entity.StoreLocation; |
|||
|
|||
/** |
|||
* 搬运任务 |
|||
* <pre> |
|||
* { |
|||
* type: 'carry', // 任务类型
|
|||
* agv: 'cl2', // 车号
|
|||
* lpn: 'pallet1124', // 托盘ID,用于校验,规划器不用管
|
|||
* priority: 1, // 优先级,用于排车,规划器不用管
|
|||
* // 搬运源货位
|
|||
* from: { |
|||
* item: 'rack1',// 货架编号
|
|||
* bay: 0, // 货架列
|
|||
* level: 1, // 货架层(用于机械臂,规划器不用管)
|
|||
* cell: 0 // 货架格(用于机械臂,规划器不用管)
|
|||
* }, |
|||
* // 目标货位
|
|||
* to: { |
|||
* item: '54' // 地堆货位号
|
|||
* } |
|||
* } |
|||
* </pre> |
|||
*/ |
|||
public record CarryTask( |
|||
String agv, |
|||
String lpn, |
|||
int priority, |
|||
StoreLocation from, |
|||
StoreLocation to |
|||
) { |
|||
} |
|||
Loading…
Reference in new issue