Ticket #22614: 22614_check_eulerian.patch
File 22614_check_eulerian.patch, 9.3 KB (added by , 3 years ago) |
---|
-
src/org/openstreetmap/josm/actions/CombineWayAction.java
232 232 } 233 233 234 234 protected static List<Node> tryJoin(Collection<Way> ways) { 235 List<Node> path = joinWithMultipolygonCode(ways); 235 List<Node> path; 236 NodeGraph graph = NodeGraph.createDirectedGraphFromWays(ways); 237 path = graph.buildSpanningPathNoRemove(); 236 238 if (path.isEmpty()) { 237 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways); 239 path = joinWithMultipolygonCode(ways); 240 } 241 if (path.isEmpty()) { 242 graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways); 238 243 path = graph.buildSpanningPathNoRemove(); 239 244 } 240 245 return path; -
src/org/openstreetmap/josm/data/osm/NodeGraph.java
11 11 import java.util.HashSet; 12 12 import java.util.LinkedHashMap; 13 13 import java.util.LinkedHashSet; 14 import java.util.LinkedList; 14 15 import java.util.List; 15 16 import java.util.Map; 16 17 import java.util.Map.Entry; … … 140 141 private int numUndirectedEges; 141 142 /** counts the number of edges that were added */ 142 143 private int addedEdges; 144 private LinkedList<Integer>[] adj; 145 private final List<Node> allNodes = new ArrayList<>(); 146 private final HashMap<Node, Integer> nodeIdMap = new HashMap<>(); 143 147 private final Map<Node, List<NodePair>> successors = new LinkedHashMap<>(); 144 148 private final Map<Node, List<NodePair>> predecessors = new LinkedHashMap<>(); 145 149 … … 182 186 rememberPredecessors(pair); 183 187 } 184 188 numUndirectedEges = undirectedEdges.size(); 189 190 // calculate an index for each node contained in this graph 191 allNodes.clear(); 192 allNodes.addAll(getNodes()); 193 // calculate the adjen 194 nodeIdMap.clear(); 195 adj = new LinkedList[allNodes.size()]; 196 for (int i = 0; i < allNodes.size(); i++) { 197 adj[i] = new LinkedList<>(); 198 } 199 // map the nodes in this graph to an Integer 200 for (int i = 0; i < allNodes.size(); i++) { 201 nodeIdMap.put(allNodes.get(i), i); 202 } 203 204 for (NodePair edge : undirectedEdges) { 205 int idxA = nodeIdMap.get(edge.getA()); 206 int idxB = nodeIdMap.get(edge.getB()); 207 addEdge(idxA, idxB); 208 } 185 209 } 186 210 187 211 /** … … 210 234 } 211 235 } 212 236 213 protected Set<Node> getTerminalNodes() {214 return getNodes().stream().filter(this::isTerminalNode).collect(Collectors.toCollection(LinkedHashSet::new));215 }216 217 237 private List<NodePair> getConnectedPairs(Node node) { 218 238 List<NodePair> connected = new ArrayList<>(); 219 239 connected.addAll(Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList)); … … 287 307 */ 288 308 public List<Node> buildSpanningPath() { 289 309 prepare(); 290 if (numUndirectedEges > 0 && isConnected()) { 291 // try to find a path from each "terminal node", i.e. from a 292 // node which is connected by exactly one undirected edges (or 293 // two directed edges in opposite direction) to the graph. A 294 // graph built up from way segments is likely to include such 295 // nodes, unless the edges build one or more closed rings. 296 // We order the nodes to start with the best candidates, but 297 // it might take very long if there is no valid path as we iterate over all nodes 298 // to find out. 299 Set<Node> nodes = getTerminalNodes(); 300 nodes = nodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : nodes; 301 return nodes.stream() 302 .map(this::buildSpanningPath) 303 .filter(path -> !path.isEmpty()) 304 .findFirst().orElse(null); 310 if (numUndirectedEges > 0) { 311 int indicator = isEulerian(); 312 if (indicator > 0) { 313 // try to find a path from each "terminal node", i.e. from a 314 // node which is connected by exactly one undirected edges (or 315 // two directed edges in opposite direction) to the graph. A 316 // graph built up from way segments is likely to include such 317 // nodes, unless the edges build one or more closed rings. 318 // We order the nodes to start with the best candidates, but 319 // it might take very long if there is no valid path as we iterate over all nodes 320 // to find out. 321 Collection<Node> startNodes = getOddNodes(); 322 startNodes = startNodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : startNodes; 323 return startNodes.stream() 324 .map(this::buildSpanningPath) 325 .filter(path -> !path.isEmpty()) 326 .findFirst().orElse(null); 327 } 305 328 } 306 329 return null; 307 330 } … … 327 350 * @return true if it is connected. 328 351 */ 329 352 private boolean isConnected() { 330 Set<Node> nodes = getNodes(); 331 if (nodes.isEmpty()) 353 if (allNodes.isEmpty()) 332 354 return false; 333 355 Deque<Node> toVisit = new ArrayDeque<>(); 334 356 HashSet<Node> visited = new HashSet<>(); 335 toVisit.add( nodes.iterator().next());357 toVisit.add(allNodes.iterator().next()); 336 358 while (!toVisit.isEmpty()) { 337 359 Node n = toVisit.pop(); 338 360 if (!visited.contains(n)) { … … 345 367 visited.add(n); 346 368 } 347 369 } 348 return nodes.size() == visited.size();370 return allNodes.size() == visited.size(); 349 371 } 350 372 351 373 /** … … 377 399 return Collections.unmodifiableSet(result); 378 400 } 379 401 402 private void addEdge(int v, int w) { 403 //Function to add an edge into the graph 404 adj[v].add(w); // Add w to v's list. 405 adj[w].add(v); //The graph is undirected 406 } 407 408 private List<Node> getOddNodes() { 409 List<Node> odd = new ArrayList<>(); 410 // Count vertices with odd degree 411 for (int i = 0; i < allNodes.size(); i++) { 412 if (adj[i].size() % 2 != 0) 413 odd.add(allNodes.get(i)); 414 } 415 return odd; 416 } 417 418 /** 419 * Check if graph has Eulerian path or Circuit or none of both 420 * Code taken from https://www.geeksforgeeks.org/eulerian-path-and-circuit/ 421 * @return 0 if graph is not Eulerian, 1 if there is a Eulerian Path, 2 if there is an Eulerian Circuit 422 */ 423 private int isEulerian() { 424 // Check if all vertices are connected 425 if (!isConnected()) 426 return 0; 427 428 // Count vertices with odd degree 429 int odd = 0; 430 for (int i = 0; i < allNodes.size(); i++) { 431 if (adj[i].size() % 2 != 0) 432 odd++; 433 } 434 435 // If count is more than 2, then graph is not Eulerian 436 if (odd > 2) 437 return 0; 438 439 // If odd count is 2, then semi-eulerian. 440 // If odd count is 0, then eulerian 441 // Note that odd count can never be 1 for undirected graph 442 return (odd == 2) ? 1 : 2; 443 } 380 444 } -
src/org/openstreetmap/josm/data/osm/NodePair.java
77 77 public String toString() { 78 78 return new StringBuilder() 79 79 .append('[') 80 .append(a.get Id())80 .append(a.getUniqueId()) 81 81 .append(',') 82 .append(b.get Id())82 .append(b.getUniqueId()) 83 83 .append(']') 84 84 .toString(); 85 85 } -
test/unit/org/openstreetmap/josm/actions/CombineWayActionTest.java
3 3 4 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 5 import static org.junit.jupiter.api.Assertions.assertFalse; 6 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 7 8 import java.io.IOException; 8 9 import java.io.InputStream; … … 14 15 import java.util.List; 15 16 import java.util.Set; 16 17 18 import org.junit.jupiter.api.Test; 17 19 import org.junit.jupiter.api.extension.RegisterExtension; 18 import org.junit.jupiter.api.Test;19 20 import org.openstreetmap.josm.TestUtils; 20 21 import org.openstreetmap.josm.data.osm.DataSet; 21 22 import org.openstreetmap.josm.data.osm.Node; … … 137 138 List<Way> reversedWays = new LinkedList<>(); 138 139 List<Way> unreversedWays = new LinkedList<>(); 139 140 CombineWayAction.detectReversedWays(selection, path, reversedWays, unreversedWays); 140 assert False(reversedWays.isEmpty());141 assertTrue(reversedWays.isEmpty()); 141 142 } 142 143 } 143 144