package edu.vanderbilt.vm.guide.container; //Credit to Sjaak Priester ( mailto:sjaak@sjaakpriester.nl ) import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("serial") public class Graph extends ArrayList<Node> { private static final Logger logger = LoggerFactory.getLogger("container.Graph"); public Node getNodeWithLowestScore() { double score = Double.MAX_VALUE; Node lowest = null; for (Node node : this) { if (node.getScore() < score) { score = node.getScore(); lowest = node; } } return lowest; } public Graph getNodeNeighbour(Node n) { Graph graph = new Graph(); if (!this.contains(n)) { return graph; } for (int id : n.getNeighbours()) { Node nn = this.findNodeById(id); if (nn != null) { graph.add(nn); } } return null; } public Node findNodeById(int id) { for (Node n : this) { if (n.getId() == id) { return n; } } return null; } public static Graph createGraph(Agenda a) { Graph g = new Graph(); for (Place plc : a) { g.add(new Node(plc)); } return g; } public Graph findPath(Node start, Node end) { // Assert that "start" and "end" are elements of "g" // Assert that Graph is a typedef of Arraylist<Node> // Initialization routine for (Node node : this) { if (node.getId() == start.getId()) { node.setScore(0); } else { node.setScore(Double.MAX_VALUE); } node.setPrevious(-1); } // Create a set of nodes not yet examined. This initially contain all // the nodes in "g" Graph unvisited = (Graph)this.clone(); // This is the bulk of the algorithm while (!unvisited.isEmpty()) { Node u = unvisited.getNodeWithLowestScore(); unvisited.remove(u); if (u.getId() == end.getId()) { break; } if (u.getScore() == Double.MAX_VALUE) { break; } for (Node neigh : this.getNodeNeighbour(u)) { // getNeighbours() returns a Graph object if (!unvisited.contains(neigh)) { continue; } double dist = u.getScore() + u.distanceTo(neigh); if (dist < neigh.getScore()) { neigh.setScore(dist); neigh.setPrevious(u.getId()); } } } // backtracing the path // Since "start" and "end" are elements of "g", They should have // received // the result of the algorithm run Graph path = new Graph(); int prev = end.getPrevious(); while (prev != -1) { path.add(this.findNodeById(prev)); prev = (this.findNodeById(prev)).getPrevious(); } return path; } /** * Given a graph which contain a set of at least three points, build * adjacency list for each Nodes according to the rules laid out by the * Delaunay Triangulation. Adapted from an implementation by Sjaak Priester * ( mailto:sjaak@sjaakpriester.nl ) */ public void buildNetwork() { if (this.size() < 2) { logger.debug("Can't build network with less than 2 points"); return; } if (this.size() < 3) { this.get(0).addNeighbour(this.get(1).getId()); this.get(1).addNeighbour(this.get(0).getId()); logger.debug("Can't build triangle with less than 3 points"); return; } // Initialization ArrayList<Triangle> workingSet = new ArrayList<Triangle>(); Collections.sort(this, new HorizontalComparator()); ArrayList<Triangle> finishedSet = new ArrayList<Triangle>(); // Find bounding rectangle double xMin = this.get(0).getLng(); double yMin = this.get(0).getLat(); double xMax = xMin; double yMax = yMin; for (Node n : this) { if (n.getLng() > xMax) { xMax = n.getLng(); } if (n.getLat() > yMax) { yMax = n.getLat(); } if (n.getLat() < yMin) { yMin = n.getLat(); } } // Give a little padding to the bounding rectangle yMax += 2 * Node.EPSILON; xMax += 2 * Node.EPSILON; yMin -= 2 * Node.EPSILON; xMin -= 2 * Node.EPSILON; double h = yMax - yMin; double w = xMax - xMin; // logger.debug("Bounding Rect: " + xMin + " " + xMax + " " + yMin + " " // + yMax + " " + h + " " + w ); // Create supertriangle that encompasses all Nodes Triangle superTri = new Triangle(); superTri.add(new Node(-1, yMin, xMin - w, null)); superTri.add(new Node(-2, yMin, xMax + w, null)); superTri.add(new Node(-3, yMax + h, (xMin + xMax) / 2, null)); workingSet.add(superTri); // [Delaunaaaaaaaaaaaay] // Add each nodes in this Graph one by one. // Each node is guaranteed to be inside a Triangle, due to the // super Triangle. // For each addition, find a list of triangles that encompasses the // node. Remove the Triangle but keep its edges in an edgebuffer. // Remove duplicate Edges, then build new triangles from each edge to // the Node being added ArrayList<Edge> edgeSet = new ArrayList<Edge>(); for (Node n : this) { // Search for Triangles that contain this Vertex // store its edges, and then remove the Triangle edgeSet.clear(); //logger.debug("Size of workingSet before remove: " + workingSet.size()); for (int k = 0; k < workingSet.size(); k++) { Triangle tri = workingSet.get(k); if (tri.isLeftOf(n)) { // should move completed triangle to the finished set finishedSet.add(tri); workingSet.remove(k); k--; } else if (tri.isEncompassing(n)) { edgeSet.add(new Edge(tri.get(0), tri.get(1))); edgeSet.add(new Edge(tri.get(1), tri.get(2))); edgeSet.add(new Edge(tri.get(0), tri.get(2))); workingSet.remove(k); k--; } else { // logger.debug("Not encompassing"); } } // logger.debug("Size of workingSet after remove: " + workingSet.size()); // logger.debug("Size of edgeSet before remove: " + edgeSet.size()); removeDuplicateEdge(edgeSet); // logger.debug("Size of edgeSet after remove: " + edgeSet.size()); for (Edge e : edgeSet) { Triangle newT = new Triangle(); newT.add(e.get(0)); newT.add(e.get(1)); newT.add(n); workingSet.add(newT); } } // Remove super triangle workingSet.remove(superTri); // Add all remaining Triangles to the finished set for (Triangle tri : workingSet) { finishedSet.add(tri); } workingSet = null; // logger.debug("Size of workingSet: " + workingSet.size()); // convert to adjacency list for (Triangle tri : finishedSet) { // logger.debug("Id: "+tri.get(0).getId()); if ( (tri.get(0).getId() <= 0) || (tri.get(1).getId() <= 0) || (tri.get(2).getId() <= 0)) { continue; } Node nn = this.findNodeById(tri.get(0).getId()); nn.addNeighbour(tri.get(1).getId()); nn.addNeighbour(tri.get(2).getId()); nn = this.findNodeById(tri.get(1).getId()); nn.addNeighbour(tri.get(0).getId()); nn.addNeighbour(tri.get(2).getId()); nn = this.findNodeById(tri.get(2).getId()); nn.addNeighbour(tri.get(0).getId()); nn.addNeighbour(tri.get(1).getId()); } } private class HorizontalComparator implements Comparator<Node> { @Override public int compare(Node n1, Node n2) { return (n1.getLng() > n2.getLng()) ? 1 : -1; } } private void removeDuplicateEdge(ArrayList<Edge> edgeSet) { for (int i = 0; i < edgeSet.size() - 1; i++) { for (int j = i + 1; j < edgeSet.size(); j++) { if ((edgeSet.get(i).get(0).getId() == edgeSet.get(j).get(0).getId() && edgeSet .get(i).get(1).getId() == edgeSet.get(j).get(1).getId()) || (edgeSet.get(i).get(0).getId() == edgeSet.get(j).get(1).getId() && edgeSet .get(i).get(1).getId() == edgeSet.get(j).get(0).getId())) { // Remove duplicate edgeSet.remove(j); edgeSet.remove(i); i--; break; } } } } private static class Edge extends ArrayList<Node> { public Edge(Node n1, Node n2) { this.add(n1); this.add(n2); } } static class Triangle extends ArrayList<Node> { private Node mCentre; private double mRad; // private static final Logger logger = // LoggerFactory.getLogger("container.Triangle"); @Override public boolean add(Node n) { if (this.size() > 3) { throw new IllegalStateException("Can't add more than three " + "Nodes to Triangle."); } super.add(n); if (this.size() == 3) { calculateCircle(); // logger.debug("This triangle has " + mCentre.getLat() + " " + // mCentre.getLng() + " " + mRad); } return true; } @Override public Node get(int index) { if (index > 3) { throw new IllegalStateException("A triangle has only three points"); } return super.get(index); } public boolean isEncompassing(Node n) { // logger.debug("Values for isEncompassing: "+n.naiveDist(mCentre)+" "+mRad // ); return n.naiveDist(mCentre) <= mRad; } private void calculateCircle() { mCentre = new Node(0, 0); double x1 = this.get(0).getLng(); double y1 = this.get(0).getLat(); double x2 = this.get(1).getLng(); double y2 = this.get(1).getLat(); double x3 = this.get(2).getLng(); double y3 = this.get(2).getLat(); double dy21 = y2 - y1; double dy32 = y3 - y2; // logger.debug("Values: " + x1 + " " + y1 + " " + x2 + " " + y2 + // " " + x3 + " " + y3 ); // Checking for edge cases // Mostly to prevent division by zero if (isZero(dy21) && isZero(dy32)) { // All three are on the same horizontal line if (x2 > x1) { if (x3 > x2) { x2 = x3; } } else if (x3 < x1) { x1 = x3; } mCentre.setLng((x1 + x2) / 2); mCentre.setLat(y1); // logger.debug("branch 1"); } else if (isZero(dy21)) { // Node1 and Node2 are on the same horizontal line // logger.debug("branch 2: " + dy21 + " " +dy32); double m1 = -(x3 - x2) / dy32; double mx1 = (x2 + x3) / 2; double my1 = (y2 + y3) / 2; mCentre.setLng((x1 + x2) / 2); mCentre.setLat(m1 * (mCentre.getLng() - mx1) + my1); // logger.debug("branch 2: " + mCentre.getLat() + " " + // mCentre.getLng()); } else if (isZero(dy32)) { // Node2 and Node3 are on the same horizontal line double m0 = -(x2 - x1) / dy21; double mx0 = (x1 + x2) / 2; double my0 = (y1 + y2) / 2; mCentre.setLng((x2 + x3) / 2); mCentre.setLat(m0 * (mCentre.getLng() - mx0) + my0); // logger.debug("branch 3"); // Common case. No Nodes are on the same horizontal line } else { double m0 = -(x2 - x1) / dy21; double m1 = -(x3 - x2) / dy32; double mx0 = (x1 + x2) / 2; double my0 = (y1 + y2) / 2; double mx1 = (x2 + x3) / 2; double my1 = (y2 + y3) / 2; mCentre.setLng((m0 * mx0 - m1 * mx1 + my1 - my0) / (m0 - m1)); mCentre.setLat(m0 * (mCentre.getLng() - mx0) + my0); // logger.debug("branch 4"); } // Calculating circle's radius double rx = x3 - mCentre.getLng(); double ry = y3 - mCentre.getLat(); double r2 = rx * rx + ry * ry; mRad = Math.sqrt(r2); // make the radius slightly bigger mRad = mRad + 2 * Node.EPSILON; } private boolean isZero(double d) { return Math.abs(d) < Node.EPSILON; } public boolean isLeftOf(Node n) { return n.getLng() > (mCentre.getLng() + mRad); } } }