Graph.java (10417B)
1 package osm.protobuf; 2 3 import java.util.Collection; 4 import java.util.HashMap; 5 import java.util.HashSet; 6 import java.util.Map; 7 import java.util.Map.Entry; 8 import java.util.NoSuchElementException; 9 import java.util.Set; 10 import java.util.function.Consumer; 11 import java.util.stream.Collector; 12 import java.util.stream.Collector.Characteristics; 13 14 import osm.common.Container; 15 import osm.common.ContainerCollector; 16 import osm.common.Edge; 17 import osm.geo.Point; 18 import osm.message.Entity; 19 import osm.message.Node; 20 import osm.message.Way; 21 import osm.routing.RoutingGraph; 22 23 /** 24 * Represents a graph used for routing on OpenStreetMap data. Implements the 25 * {@link Container} and {@link RoutingGraph} interfaces. 26 */ 27 public class Graph implements Container<Graph, Entity>, RoutingGraph<Node, Edge> { 28 /** 29 * Constant representing the nanosecond unit. 30 */ 31 public static final double NANO = 1e-9; 32 33 /** 34 * The default speed for the router, in meters per second. 35 */ 36 public static final double DEFAULT_SPEED = 60 / 3.6; 37 38 /** 39 * Constant representing the additional distance range when searching for nodes. 40 */ 41 public static final double DISTANCE_RANGE = 5; // adding 5km to the result 42 43 /** 44 * Constant representing the multiplier for the additional distance range. 45 */ 46 public static final double DISTANCE_RANGE_MULTIPLIER = 1.25; // adding 25% to result 47 48 /** 49 * Returns a {@link Collector} that accumulates elements into a {@link Graph}. 50 * 51 * @param points The collection of points to center the graph around. 52 * @return A {@link Collector} for accumulating elements into a {@link Graph}. 53 */ 54 public static Collector<Entity, ?, Graph> asCollector(Collection<Point> points) { 55 Point center = Point.center(points); 56 57 long diameter = points.stream() 58 .mapToLong(center::distanceTo) 59 .max() 60 .orElseThrow(); 61 62 long range = Math.round(DISTANCE_RANGE + DISTANCE_RANGE_MULTIPLIER * diameter); 63 64 return new ContainerCollector<>(() -> new Graph(points, center, range), 65 Set.of(Characteristics.CONCURRENT, Characteristics.UNORDERED)); 66 } 67 68 private final Collection<Point> points; 69 private final Point center; 70 private final long range; 71 72 public final Map<Long, Node> nodes = new HashMap<>(); 73 public final Map<Long, Way> ways = new HashMap<>(); 74 public final Map<Node, Set<Edge>> neighbors = new HashMap<>(); 75 public final Map<Point, Node> pointNode = new HashMap<>(); 76 public final Map<Point, Long> pointDistance = new HashMap<>(); 77 78 /** 79 * Constructs a {@link Graph} with the specified points, center, and range. 80 * 81 * @param points The collection of points in the graph. 82 * @param center The center point of the graph. 83 * @param range The range around the center within which nodes are considered. 84 */ 85 public Graph(Collection<Point> points, Point center, long range) { 86 this.points = points; 87 this.center = center; 88 this.range = range; 89 } 90 91 /** 92 * Combines this graph with another graph, updating its nodes and neighbors. 93 * 94 * @param other The other graph to combine with. 95 * @return The combined graph. 96 */ 97 @Override 98 public void fold(Graph other) { 99 nodes.putAll(other.nodes); 100 for (Entry<Node, Set<Edge>> neigh : other.neighbors.entrySet()) 101 neighbors.computeIfAbsent(neigh.getKey(), k -> new HashSet<>()) 102 .addAll(neigh.getValue()); 103 } 104 105 /** 106 * Finalizes the graph by connecting ways and determining the closest nodes to 107 * each point. 108 */ 109 @Override 110 public void finish() { 111 ways.forEach((i, w) -> connectWay(w)); 112 113 for (Node node : nodes.values()) { 114 if (!neighbors.containsKey(node)) 115 continue; 116 117 for (Point p : points) { 118 if (!pointNode.containsKey(p) || node.distanceTo(p) < pointDistance.get(p)) { 119 pointNode.put(p, node); 120 pointDistance.put(p, node.distanceTo(p)); 121 } 122 } 123 } 124 125 System.out.printf("nodes: \t\t%d\nneighbors: \t%d\n", nodes.size(), neighbors.size()); 126 } 127 128 /** 129 * Connects the nodes of a way by creating edges between consecutive nodes. 130 * 131 * @param w The way to connect. 132 */ 133 protected void connectWay(Way w) { 134 Node previous = null; 135 136 for (long currentID : w.children) { 137 if (!nodes.containsKey(currentID)) 138 continue; 139 140 Node current = nodes.get(currentID); 141 142 if (previous != null) { 143 long dist = current.distanceTo(previous); 144 145 neighbors.computeIfAbsent(previous, k -> new HashSet<>()) 146 .add(new Edge(current, dist, (double) dist / w.speedForwards)); 147 148 neighbors.computeIfAbsent(current, k -> new HashSet<>()) 149 .add(new Edge(previous, dist, (double) dist / w.speedBackwards)); 150 } 151 152 previous = current; 153 } 154 } 155 156 /** 157 * Accumulates an entity into the graph by adding nodes and ways based on their 158 * distance to the graph center. 159 * 160 * @param element The entity to accumulate. 161 */ 162 @Override 163 public void accumulate(Entity element) { 164 if (element instanceof Node node) { 165 if (node.distanceTo(center) < range) 166 nodes.put(node.getID(), node); 167 } else if (element instanceof Way way) { 168 ways.put(way.getID(), way); 169 } 170 } 171 172 /** 173 * Retrieves a node from the graph based on its point location. 174 * 175 * @param point The point location of the node to retrieve. 176 * @return The node corresponding to the specified point. 177 */ 178 public Node getNode(Point point) { 179 return pointNode.get(point); 180 } 181 182 /** 183 * Retrieves a node from the graph using its ID. 184 * 185 * @param id The ID of the node to retrieve. 186 * @return The node with the specified ID. 187 * @throws NoSuchElementException If the node with the given ID is not found. 188 */ 189 public Node getNode(long id) throws NoSuchElementException { 190 if (!nodes.containsKey(id)) 191 throw new NoSuchElementException(String.format("node `%d` not found", id)); 192 193 return nodes.get(id); 194 } 195 196 /** 197 * Calculates the cost of moving from the current node to the next node. 198 * 199 * @param current The current node. 200 * @param next The next node to move to. 201 * @return The cost of moving from the current node to the next node. 202 */ 203 @Override 204 public long getCost(Node current, Edge next) { 205 double time = next.getTime() 206 + Math.abs(Point.getAngle(current.getParent(), current, next.getDestination())) / Math.PI * 30; 207 return Math.round(next.getDistance() / DEFAULT_SPEED * 0.2 + time * 0.8); 208 } 209 210 /** 211 * Gets the closest distance from a node to a set of target nodes. 212 * 213 * @param targets The target nodes and their distances. 214 * @param node The node for which to find the closest distance. 215 * @return The closest distance from the node to any of the target nodes. 216 */ 217 @Override 218 public long getClosestDistance(Map<Node, Long> targets, Node node) { 219 return targets.entrySet().stream() 220 .map(e -> e.getKey().distanceTo(node) + e.getValue()) 221 .min(Long::compare) 222 .orElseThrow(); 223 } 224 225 /** 226 * Called when progress is made during the route calculation. 227 * 228 * @param progress The progress made, represented as a percentage. 229 * @param start The starting node. 230 * @param targets The target nodes. 231 */ 232 @Override 233 public void onProgress(double progress, Node start, Collection<Node> targets) { 234 System.out.printf("%s -> %s %6.2f%% [%s>%s]\r", 235 start, targets, 236 progress * 100, 237 "=".repeat((int) Math.round(progress * 50)), 238 " ".repeat((int) Math.round((1 - progress) * 50))); 239 } 240 241 /** 242 * Checks if a node is a target node. 243 * 244 * @param targets The target nodes and their distances. 245 * @param node The node to check. 246 * @return {@code true} if the node is a target node, {@code false} otherwise. 247 */ 248 @Override 249 public boolean isTarget(Map<Node, Long> targets, Node node) { 250 return targets.containsKey(node); 251 } 252 253 /** 254 * Calculates a heuristic value for a node based on its distance to target 255 * nodes. 256 * 257 * @param targets The target nodes and their distances. 258 * @param node The node for which to calculate the heuristic. 259 * @return The heuristic value for the given node. 260 */ 261 @Override 262 public long getHeuristic(Map<Node, Long> targets, Node node) { 263 long dist = targets.entrySet().stream() 264 .map(e -> e.getKey().distanceTo(node) + e.getValue()) 265 .min(Long::compare) 266 .orElseThrow(); 267 268 return Math.round(0.5 * dist / DEFAULT_SPEED); 269 } 270 271 /** 272 * Gets the distance from the start node to a given node. 273 * 274 * @param start The start node. 275 * @param node The node for which to calculate the distance. 276 * @return The distance from the start node to the given node. 277 */ 278 @Override 279 public long getStartDistance(Node start, Node node) { 280 return start.distanceTo(node); 281 } 282 283 /** 284 * Iterates over the neighbors of a node and applies the provided consumer 285 * function. 286 * 287 * @param node The node for which to iterate over neighbors. 288 * @param consumer The consumer function to apply to each neighbor. 289 */ 290 @Override 291 public void forNeighbor(Node node, Consumer<Edge> consumer) { 292 // System.out.printf("%s: %s\n", node, neighbors.get(node.getID())); 293 294 if (!neighbors.containsKey(node)) 295 return; 296 297 neighbors.get(node).forEach(n -> { 298 consumer.accept(n); 299 }); 300 } 301 302 /** 303 * Creates an array to store the history of a node with the given size. 304 * 305 * @param size The size of the history array. 306 * @return An array capable of storing the history of a node. 307 */ 308 @Override 309 public Node[] createHistoryArray(int size) { 310 return new Node[size]; 311 } 312 }