When graphs become very large, the basic routing approach described in the Calculating the shortest route tutorial might not scale well enough anymore to perform sufficiently fast computations on the graph. Partitioning allows to improve both the memory and CPU usage by using specialized graphs and algorithms.
Initial setup
The initial setup of this tutorial is the same as what was done and explained in the Calculating the shortest route tutorial:
-
Create a graph with nodes and edges
-
Assign values to each edge
-
Implement a heuristic distance function
The resulting code is:
// Set up a model TLcdXYPoint[] nodes = new TLcdXYPoint[4]; nodes[0] = new TLcdXYPoint(0, 0); nodes[1] = new TLcdXYPoint(5, 0); nodes[2] = new TLcdXYPoint(7, 5); nodes[3] = new TLcdXYPoint(9, 0); TLcdXYLine[] edges = new TLcdXYLine[4]; edges[0] = new TLcdXYLine(nodes[0], nodes[1]); edges[1] = new TLcdXYLine(nodes[1], nodes[2]); edges[2] = new TLcdXYLine(nodes[2], nodes[3]); edges[3] = new TLcdXYLine(nodes[1], nodes[3]); // Create graph TLcdGraph<TLcdXYPoint, TLcdXYLine> graph = new TLcdGraph<>(); // Add nodes to graph for (TLcdXYPoint aNode : nodes) { graph.addNode(aNode, ILcdFireEventMode.NO_EVENT); } // Add edges to graph for (TLcdXYLine aEdge : edges) { graph.addEdge(aEdge, (TLcdXYPoint) aEdge.getPoint(0), (TLcdXYPoint) aEdge.getPoint(1), ILcdFireEventMode.NO_EVENT); } // Implement edge value function ILcdEdgeValueFunction<TLcdXYPoint, TLcdXYLine> edgeValueFunction = new ALcdSimpleEdgeValueFunction<TLcdXYPoint, TLcdXYLine>() { @Override public double computeEdgeValue(ILcdGraph<TLcdXYPoint, TLcdXYLine> aGraph, TLcdXYLine aEdge, TLcdTraversalDirection aTraversalDirection) { //Calculate the cartesian length of the edge double x = aEdge.getPoint(1).getX() - aEdge.getPoint(0).getX(); double y = aEdge.getPoint(1).getY() - aEdge.getPoint(0).getY(); return Math.sqrt(x * x + y * y); } }; // Implement heuristic distance function ILcdDistanceFunction<TLcdXYPoint, TLcdXYLine> distanceFunction = new ALcdNodeDistanceFunction<TLcdXYPoint, TLcdXYLine>() { @Override public double computeDistance(ILcdGraph<TLcdXYPoint, TLcdXYLine> aGraph, TLcdXYPoint aStartNode, TLcdXYPoint aEndNode, TLcdTraversalDirection aTraversalDirection) { double x = aEndNode.getX() - aStartNode.getX(); double y = aEndNode.getY() - aStartNode.getY(); return Math.sqrt(x * x + y * y); } };
Partitioning a graph
Partitioning a graph can be done in many ways. The API provides a partitioning algorithm which is optimized for use with the partitioned routing algorithm, which we use in this tutorial:
// Partitioned routing TLcdClusteredPartitioningAlgorithm partitioningAlgorithm = new TLcdClusteredPartitioningAlgorithm(); ILcdPartitionedGraph<TLcdXYPoint, TLcdXYLine> partitionedGraph = partitioningAlgorithm.partition(graph);
Preprocessing a graph
The main reason why the partitioned routing algorithm is so fast, is because it can take advantage of precomputed information representing a high-level description of the partitioned graph. Preprocessing can be done fully automatically using the partitioned routing preprocessor. The result of the preprocessing is a set of shortest route tables, one per partition in the graph.
TLcdPartitionedShortestRoutePreprocessor preprocessor = new TLcdPartitionedShortestRoutePreprocessor(); ILcdShortestRouteDistanceTableProvider<TLcdXYPoint, TLcdXYLine> distanceTableProvider = preprocessor.preprocess(partitionedGraph, edgeValueFunction, distanceFunction);
Computing the shortest route between two nodes
Once the graph has been partitioned and the shortest route tables have been precomputed, shortest routes can be computed the same way as with the basic routing algorithm
TLcdRoute<TLcdXYPoint, TLcdXYLine> precedingRoute = new TLcdRoute<>(graph, nodes[0]); TLcdRoute<TLcdXYPoint, TLcdXYLine> succeedingRoute = new TLcdRoute<>(graph, nodes[3]); TLcdPartitionedShortestRouteAlgorithm partitionedRouteAlgorithm = new TLcdPartitionedShortestRouteAlgorithm(); partitionedRouteAlgorithm.setDistanceTableProvider(distanceTableProvider); ILcdRoute<TLcdXYPoint, TLcdXYLine> partitionedRoute = partitionedRouteAlgorithm.getShortestRoute(partitionedGraph, precedingRoute, succeedingRoute, edgeValueFunction, distanceFunction); for (int i = 0; i < partitionedRoute.getEdgeCount(); i++) { TLcdXYLine edge = partitionedRoute.getEdge(i); ILcdPoint startPoint = edge.getStartPoint(); ILcdPoint endPoint = edge.getEndPoint(); System.out.println(String.format("Shortest route edge %d: (%.0f,%.0f) - (%.0f,%.0f)", i, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY())); }
The output of running this is
Shortest route edge 0: (0,0) - (5,0) Shortest route edge 1: (5,0) - (9,0)
Full code
import com.luciad.network.algorithm.partitioning.TLcdClusteredPartitioningAlgorithm; import com.luciad.network.algorithm.routing.ILcdShortestRouteDistanceTableProvider; import com.luciad.network.algorithm.routing.TLcdPartitionedShortestRouteAlgorithm; import com.luciad.network.algorithm.routing.TLcdPartitionedShortestRoutePreprocessor; import com.luciad.network.function.ALcdNodeDistanceFunction; import com.luciad.network.function.ALcdSimpleEdgeValueFunction; import com.luciad.network.function.ILcdDistanceFunction; import com.luciad.network.function.ILcdEdgeValueFunction; import com.luciad.network.graph.ILcdGraph; import com.luciad.network.graph.TLcdGraph; import com.luciad.network.graph.TLcdTraversalDirection; import com.luciad.network.graph.partition.ILcdPartitionedGraph; import com.luciad.network.graph.route.ILcdRoute; import com.luciad.network.graph.route.TLcdRoute; import com.luciad.shape.ILcdPoint; import com.luciad.shape.shape2D.TLcdXYLine; import com.luciad.shape.shape2D.TLcdXYPoint; import com.luciad.util.ILcdFireEventMode; public class PartitionedGraphTutorial { public static void main(String[] args) { // Set up a model TLcdXYPoint[] nodes = new TLcdXYPoint[4]; nodes[0] = new TLcdXYPoint(0, 0); nodes[1] = new TLcdXYPoint(5, 0); nodes[2] = new TLcdXYPoint(7, 5); nodes[3] = new TLcdXYPoint(9, 0); TLcdXYLine[] edges = new TLcdXYLine[4]; edges[0] = new TLcdXYLine(nodes[0], nodes[1]); edges[1] = new TLcdXYLine(nodes[1], nodes[2]); edges[2] = new TLcdXYLine(nodes[2], nodes[3]); edges[3] = new TLcdXYLine(nodes[1], nodes[3]); // Create graph TLcdGraph<TLcdXYPoint, TLcdXYLine> graph = new TLcdGraph<>(); // Add nodes to graph for (TLcdXYPoint aNode : nodes) { graph.addNode(aNode, ILcdFireEventMode.NO_EVENT); } // Add edges to graph for (TLcdXYLine aEdge : edges) { graph.addEdge(aEdge, (TLcdXYPoint) aEdge.getPoint(0), (TLcdXYPoint) aEdge.getPoint(1), ILcdFireEventMode.NO_EVENT); } // Implement edge value function ILcdEdgeValueFunction<TLcdXYPoint, TLcdXYLine> edgeValueFunction = new ALcdSimpleEdgeValueFunction<TLcdXYPoint, TLcdXYLine>() { @Override public double computeEdgeValue(ILcdGraph<TLcdXYPoint, TLcdXYLine> aGraph, TLcdXYLine aEdge, TLcdTraversalDirection aTraversalDirection) { //Calculate the cartesian length of the edge double x = aEdge.getPoint(1).getX() - aEdge.getPoint(0).getX(); double y = aEdge.getPoint(1).getY() - aEdge.getPoint(0).getY(); return Math.sqrt(x * x + y * y); } }; // Implement heuristic distance function ILcdDistanceFunction<TLcdXYPoint, TLcdXYLine> distanceFunction = new ALcdNodeDistanceFunction<TLcdXYPoint, TLcdXYLine>() { @Override public double computeDistance(ILcdGraph<TLcdXYPoint, TLcdXYLine> aGraph, TLcdXYPoint aStartNode, TLcdXYPoint aEndNode, TLcdTraversalDirection aTraversalDirection) { double x = aEndNode.getX() - aStartNode.getX(); double y = aEndNode.getY() - aStartNode.getY(); return Math.sqrt(x * x + y * y); } }; // Partitioned routing TLcdClusteredPartitioningAlgorithm partitioningAlgorithm = new TLcdClusteredPartitioningAlgorithm(); ILcdPartitionedGraph<TLcdXYPoint, TLcdXYLine> partitionedGraph = partitioningAlgorithm.partition(graph); TLcdPartitionedShortestRoutePreprocessor preprocessor = new TLcdPartitionedShortestRoutePreprocessor(); ILcdShortestRouteDistanceTableProvider<TLcdXYPoint, TLcdXYLine> distanceTableProvider = preprocessor.preprocess(partitionedGraph, edgeValueFunction, distanceFunction); TLcdRoute<TLcdXYPoint, TLcdXYLine> precedingRoute = new TLcdRoute<>(graph, nodes[0]); TLcdRoute<TLcdXYPoint, TLcdXYLine> succeedingRoute = new TLcdRoute<>(graph, nodes[3]); TLcdPartitionedShortestRouteAlgorithm partitionedRouteAlgorithm = new TLcdPartitionedShortestRouteAlgorithm(); partitionedRouteAlgorithm.setDistanceTableProvider(distanceTableProvider); ILcdRoute<TLcdXYPoint, TLcdXYLine> partitionedRoute = partitionedRouteAlgorithm.getShortestRoute(partitionedGraph, precedingRoute, succeedingRoute, edgeValueFunction, distanceFunction); for (int i = 0; i < partitionedRoute.getEdgeCount(); i++) { TLcdXYLine edge = partitionedRoute.getEdge(i); ILcdPoint startPoint = edge.getStartPoint(); ILcdPoint endPoint = edge.getEndPoint(); System.out.println(String.format("Shortest route edge %d: (%.0f,%.0f) - (%.0f,%.0f)", i, startPoint.getX(), startPoint.getY(), endPoint.getX(), endPoint.getY())); } } }