/** ** AStar.java ** ** Copyright 2011 by Sarah Wise, Mark Coletti, Andrew Crooks, and ** George Mason University. ** ** Licensed under the Academic Free License version 3.0 ** ** See the file "LICENSE" for more information ** ** $Id$ **/ package sim.app.geo.gridlock; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.planargraph.DirectedEdgeStar; import com.vividsolutions.jts.planargraph.Node; import java.util.ArrayList; import java.util.HashMap; import sim.util.geo.GeomPlanarGraphDirectedEdge; @SuppressWarnings("restriction") public class AStar { public ArrayList<GeomPlanarGraphDirectedEdge> astarPath(Node start, Node goal) { // initial check if (start == null || goal == null) { System.out.println("Error: invalid node provided to AStar"); } // set up the containers for the result ArrayList<GeomPlanarGraphDirectedEdge> result = new ArrayList<GeomPlanarGraphDirectedEdge>(); // containers for the metainformation about the Nodes relative to the // A* search HashMap<Node, AStarNodeWrapper> foundNodes = new HashMap<Node, AStarNodeWrapper>(); AStarNodeWrapper startNode = new AStarNodeWrapper(start); AStarNodeWrapper goalNode = new AStarNodeWrapper(goal); foundNodes.put(start, startNode); foundNodes.put(goal, goalNode); startNode.gx = 0; startNode.hx = heuristic(start, goal); startNode.fx = heuristic(start, goal); // A* containers: nodes to be investigated, nodes that have been investigated ArrayList<AStarNodeWrapper> closedSet = new ArrayList<AStarNodeWrapper>(), openSet = new ArrayList<AStarNodeWrapper>(); openSet.add(startNode); while (openSet.size() > 0) { // while there are reachable nodes to investigate AStarNodeWrapper x = findMin(openSet); // find the shortest path so far if (x.node == goal) { // we have found the shortest possible path to the goal! // Reconstruct the path and send it back. return reconstructPath(goalNode); } openSet.remove(x); // maintain the lists closedSet.add(x); // check all the edges out from this Node DirectedEdgeStar des = x.node.getOutEdges(); for (Object o : des.getEdges().toArray()) { GeomPlanarGraphDirectedEdge l = (GeomPlanarGraphDirectedEdge) o; Node next = null; next = l.getToNode(); // get the A* meta information about this Node AStarNodeWrapper nextNode; if (foundNodes.containsKey(next)) { nextNode = foundNodes.get(next); } else { nextNode = new AStarNodeWrapper(next); foundNodes.put(next, nextNode); } if (closedSet.contains(nextNode)) // it has already been considered { continue; } // otherwise evaluate the cost of this node/edge combo double tentativeCost = x.gx + length(l); boolean better = false; if (!openSet.contains(nextNode)) { openSet.add(nextNode); nextNode.hx = heuristic(next, goal); better = true; } else if (tentativeCost < nextNode.gx) { better = true; } // store A* information about this promising candidate node if (better) { nextNode.cameFrom = x; nextNode.edgeFrom = l; nextNode.gx = tentativeCost; nextNode.fx = nextNode.gx + nextNode.hx; } } } return result; } /** * Takes the information about the given node n and returns the path that * found it. * @param n the end point of the path * @return an ArrayList of GeomPlanarGraphDirectedEdges that lead from the * given Node to the Node from which the serach began */ ArrayList<GeomPlanarGraphDirectedEdge> reconstructPath(AStarNodeWrapper n) { ArrayList<GeomPlanarGraphDirectedEdge> result = new ArrayList<GeomPlanarGraphDirectedEdge>(); AStarNodeWrapper x = n; while (x.cameFrom != null) { result.add(0, x.edgeFrom); // add this edge to the front of the list x = x.cameFrom; } return result; } /** * Measure of the estimated distance between two Nodes. Extremely basic, just * Euclidean distance as implemented here. * @param x * @param y * @return notional "distance" between the given nodes. */ double heuristic(Node x, Node y) { Coordinate xnode = x.getCoordinate(); Coordinate ynode = y.getCoordinate(); return Math.sqrt(Math.pow(xnode.x - ynode.x, 2) + Math.pow(xnode.y - ynode.y, 2)); } /** * @param e * @return The length of an edge */ double length(GeomPlanarGraphDirectedEdge e) { Coordinate xnode = e.getFromNode().getCoordinate(); Coordinate ynode = e.getToNode().getCoordinate(); return Math.sqrt(Math.pow(xnode.x - ynode.x, 2) + Math.pow(xnode.y - ynode.y, 2)); } /** * Considers the list of Nodes open for consideration and returns the node * with minimum fx value * @param set list of open Nodes * @return */ AStarNodeWrapper findMin(ArrayList<AStarNodeWrapper> set) { double min = 100000; AStarNodeWrapper minNode = null; for (AStarNodeWrapper n : set) { if (n.fx < min) { min = n.fx; minNode = n; } } return minNode; } /** * A wrapper to contain the A* meta information about the Nodes * */ class AStarNodeWrapper { // the underlying Node associated with the metainformation Node node; // the Node from which this Node was most profitably linked AStarNodeWrapper cameFrom; // the edge by which this Node was discovered GeomPlanarGraphDirectedEdge edgeFrom; double gx, hx, fx; public AStarNodeWrapper(Node n) { node = n; gx = 0; hx = 0; fx = 0; cameFrom = null; edgeFrom = null; } } }