Ticket #21881: josm21881_cycle_dector_v2.patch
File josm21881_cycle_dector_v2.patch, 35.1 KB (added by , 12 months ago) |
---|
-
new file src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java
Subject: [PATCH] Cycle detector #21881 --- IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java b/src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.ArrayDeque; 7 import java.util.ArrayList; 8 import java.util.Arrays; 9 import java.util.Collection; 10 import java.util.Collections; 11 import java.util.Deque; 12 import java.util.HashMap; 13 import java.util.HashSet; 14 import java.util.List; 15 import java.util.Map; 16 import java.util.Queue; 17 import java.util.Set; 18 import java.util.stream.Collectors; 19 20 import org.openstreetmap.josm.data.osm.Node; 21 import org.openstreetmap.josm.data.osm.NodeGraph; 22 import org.openstreetmap.josm.data.osm.NodePair; 23 import org.openstreetmap.josm.data.osm.OsmPrimitive; 24 import org.openstreetmap.josm.data.osm.Way; 25 import org.openstreetmap.josm.data.osm.WaySegment; 26 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 27 import org.openstreetmap.josm.data.validation.Severity; 28 import org.openstreetmap.josm.data.validation.Test; 29 import org.openstreetmap.josm.data.validation.TestError; 30 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 31 import org.openstreetmap.josm.spi.preferences.Config; 32 import org.openstreetmap.josm.tools.Pair; 33 34 /** 35 * Test for detecting <a href="https://en.wikipedia.org/wiki/Cycle_(graph_theory)">cycles</a> in a directed graph, 36 * currently used for waterways only. The processed graph consists of ways labeled as waterway. 37 * 38 * @author gaben 39 * @since xxx 40 */ 41 public class CycleDetector extends Test { 42 public static final int CYCLE_DETECTED = 4200; 43 44 /** All waterways for cycle detection */ 45 private final Set<Way> usableWaterways = new HashSet<>(); 46 47 /** Already visited primitives */ 48 private final Set<Way> visitedWays = new HashSet<>(); 49 50 /** Currently used directional waterways from the OSM wiki */ 51 private static List<String> directionalWaterways; 52 53 protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + CycleDetector.class.getSimpleName(); 54 55 public CycleDetector() { 56 super(tr("Cycle detector"), tr("Detects cycles in drainage systems.")); 57 } 58 59 @Override 60 public boolean isPrimitiveUsable(OsmPrimitive p) { 61 return p.isUsable() && (p instanceof Way) && (((Way) p).getNodesCount() > 1) && p.hasTag("waterway", directionalWaterways); 62 } 63 64 @Override 65 public void visit(Way w) { 66 if (isPrimitiveUsable(w)) 67 usableWaterways.add(w); 68 } 69 70 @Override 71 public void startTest(ProgressMonitor progressMonitor) { 72 super.startTest(progressMonitor); 73 directionalWaterways = Config.getPref().getList(PREFIX + ".directionalWaterways", 74 Arrays.asList("river", "stream", "tidal_channel", "drain", "ditch", "fish_pass", "fairway")); 75 } 76 77 @Override 78 public void endTest() { 79 for (Collection<Way> graph : getGraphs()) { 80 NodeGraph nodeGraph = NodeGraph.createDirectedGraphFromWays(graph); 81 Tarjan tarjan = new Tarjan(nodeGraph); 82 Collection<List<Node>> scc = tarjan.getSCC(); 83 Map<Node, List<Node>> graphMap = tarjan.getGraphMap(); 84 85 for (Collection<Node> possibleCycle : scc) { 86 // there is a cycle in the graph if a strongly connected component has more than one node 87 if (possibleCycle.size() > 1) { 88 errors.add( 89 TestError.builder(this, Severity.ERROR, CYCLE_DETECTED) 90 .message(tr("Cycle in directional waterway network")) 91 .primitives(possibleCycle) 92 .highlightWaySegments(createSegments(graphMap, possibleCycle)) 93 .build() 94 ); 95 } 96 } 97 } 98 99 super.endTest(); 100 } 101 102 @Override 103 public void clear() { 104 super.clear(); 105 usableWaterways.clear(); 106 visitedWays.clear(); 107 } 108 109 /** 110 * Creates WaySegments from Nodes for the error highlight function. 111 * @param graphMap the complete graph data 112 * @param nodes nodes to build the way segments from 113 * @return WaySegments from the Nodes 114 */ 115 private static Collection<WaySegment> createSegments(Map<Node, List<Node>> graphMap, Collection<Node> nodes) { 116 List<NodePair> pairs = new ArrayList<>(); 117 118 // build new graph exclusively from SCC nodes 119 for (Node node : nodes) { 120 for (Node successor : graphMap.get(node)) { 121 // check for outbound nodes 122 if (nodes.contains(successor)) { 123 pairs.add(new NodePair(node, successor)); 124 } 125 } 126 } 127 128 Collection<WaySegment> segments = new ArrayList<>(); 129 130 for (NodePair pair : pairs) { 131 final Node n = pair.getA(); 132 final Node m = pair.getB(); 133 134 if (n != null && m != null && !n.equals(m)) { 135 List<Way> intersect = new ArrayList<>(n.getParentWays()); 136 List<Way> mWays = m.getParentWays(); 137 intersect.retainAll(mWays); 138 139 for (Way w : intersect) { 140 if (w.getNeighbours(n).contains(m) && getNodeIndex(w, n) + 1 == getNodeIndex(w, m)) { 141 segments.add(WaySegment.forNodePair(w, n, m)); 142 } 143 } 144 } 145 } 146 147 return segments; 148 } 149 150 /** 151 * Returns the way index of a node. Only the first occurrence is considered in case it's a closed way. 152 * @param w parent way 153 * @param n the node to look up 154 * @return >=0 if the node is found or<br>-1 if node not part of the way 155 */ 156 private static int getNodeIndex(Way w, Node n) { 157 for (int i = 0; i < w.getNodesCount(); i++) { 158 if (w.getNode(i).equals(n)) { 159 return i; 160 } 161 } 162 163 return -1; 164 } 165 166 /** 167 * Returns all directional waterways which connect to at least one other usable way. 168 * 169 * @return all directional waterways which connect to at least one other usable way 170 */ 171 private Collection<Collection<Way>> getGraphs() { 172 // HashSet doesn't make a difference here 173 Collection<Collection<Way>> graphs = new ArrayList<>(); 174 175 for (Way waterway : usableWaterways) { 176 Collection<Way> graph = buildGraph(waterway); 177 178 if (!graph.isEmpty()) 179 graphs.add(graph); 180 } 181 182 return graphs; 183 } 184 185 /** 186 * Returns a collection of ways, which belongs to the same graph. 187 * 188 * @param way starting way to extend the graph from 189 * @return a collection of ways which belongs to the same graph 190 */ 191 private Collection<Way> buildGraph(Way way) { 192 if (visitedWays.contains(way)) 193 return Collections.emptySet(); 194 195 final Set<Way> graph = new HashSet<>(); 196 Queue<Way> queue = new ArrayDeque<>(); 197 queue.offer(way); 198 199 while (!queue.isEmpty()) { 200 Way currentWay = queue.poll(); 201 visitedWays.add(currentWay); 202 203 for (Node node : currentWay.getNodes()) { 204 Collection<Way> referrers = node.referrers(Way.class) 205 .filter(this::isPrimitiveUsable) 206 .filter(candidate -> candidate != currentWay) 207 .collect(Collectors.toSet()); 208 209 if (!referrers.isEmpty()) { 210 for (Way referrer : referrers) { 211 if (!visitedWays.contains(referrer)) { 212 queue.offer(referrer); 213 visitedWays.add(referrer); 214 } 215 } 216 graph.addAll(referrers); 217 } 218 } 219 } 220 return graph; 221 } 222 223 224 225 /** 226 * Tarjan's strongly connected components algorithm for JOSM. 227 * 228 * @see <a href="https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm"> 229 * Tarjan's strongly connected components algorithm</a> 230 */ 231 public static final class Tarjan { 232 233 /** 234 * Used to remember visited nodes and its metadata. Key is used for storing 235 * the unique ID of the nodes instead of the full data to save space. 236 */ 237 private final Map<Long, TarjanHelper> registry; 238 239 /** Used to store the graph data as a map. */ 240 private final Map<Node, List<Node>> graphMap; 241 242 /** Used to store strongly connected components. */ 243 private final Collection<List<Node>> scc = new HashSet<>(); 244 245 /** Used on algorithm runtime to keep track discovery progress. */ 246 private final Deque<Node> stack = new ArrayDeque<>(); 247 248 /** Used on algorithm runtime to keep track discovery progress. */ 249 private int index = 0; 250 251 public Tarjan(NodeGraph graph) { 252 graphMap = graph.createMap(); 253 254 this.registry = new HashMap<>((int) (graph.getEdges().size() / 0.75) + 1); 255 } 256 257 /** 258 * Returns the strongly connected components in the current graph. 259 * 260 * @return the strongly connected components in the current graph 261 */ 262 public Collection<List<Node>> getSCC() { 263 for (Node node : graphMap.keySet()) { 264 if (!registry.containsKey(node.getUniqueId())) { 265 strongConnect(node); 266 } 267 } 268 return scc; 269 } 270 271 /** 272 * Returns the graph data as a map. 273 * @see NodeGraph#createMap() 274 * @return the graph data as a map 275 */ 276 public Map<Node, List<Node>> getGraphMap() { 277 return graphMap; 278 } 279 280 /** 281 * Calculates strongly connected components available from the given node, in an iterative fashion. 282 * 283 * @param u0 the node to generate strongly connected components from 284 */ 285 private void strongConnect(final Node u0) { 286 final Deque<Pair<Node, Integer>> work = new ArrayDeque<>(); 287 work.push(new Pair<>(u0, 0)); 288 boolean recurse; 289 290 while (!work.isEmpty()) { 291 Pair<Node, Integer> popped = work.remove(); 292 Node u = popped.a; 293 int j = popped.b; 294 295 if (j == 0) { 296 index++; 297 registry.put(u.getUniqueId(), new TarjanHelper(index)); 298 stack.push(u); 299 } 300 301 recurse = false; 302 List<Node> successors = getSuccessors(u); 303 304 for (int i = j; i < successors.size(); i++) { 305 Node v = successors.get(i); 306 if (!registry.containsKey(v.getUniqueId())) { 307 work.push(new Pair<>(u, i + 1)); 308 work.push(new Pair<>(v, 0)); 309 recurse = true; 310 break; 311 } else if (stack.contains(v)) { 312 TarjanHelper uHelper = registry.get(u.getUniqueId()); 313 TarjanHelper vHelper = registry.get(v.getUniqueId()); 314 uHelper.lowlink = Math.min(uHelper.lowlink, vHelper.index); 315 } 316 } 317 318 if (!recurse) { 319 TarjanHelper uHelper = registry.get(u.getUniqueId()); 320 if (uHelper.lowlink == uHelper.index) { 321 List<Node> currentSCC = new ArrayList<>(); 322 Node v; 323 do { 324 v = stack.remove(); 325 currentSCC.add(v); 326 } while (!v.equals(u)); 327 scc.add(currentSCC); 328 } 329 if (!work.isEmpty()) { 330 Node v = u; 331 Pair<Node, Integer> peeked = work.peek(); 332 u = peeked.a; 333 TarjanHelper vHelper = registry.get(v.getUniqueId()); 334 uHelper = registry.get(u.getUniqueId()); 335 uHelper.lowlink = Math.min(uHelper.lowlink, vHelper.lowlink); 336 } 337 } 338 } 339 } 340 341 /** 342 * Returns the next direct successors from the graph of the given node. 343 * 344 * @param node a node to start search from 345 * @return direct successors of the node or an empty list, if it's a terminal node 346 */ 347 private List<Node> getSuccessors(Node node) { 348 return graphMap.getOrDefault(node, Collections.emptyList()); 349 } 350 351 /** 352 * Helper class for storing the Tarjan algorithm runtime metadata. 353 */ 354 private static class TarjanHelper { 355 private final int index; 356 private int lowlink; 357 358 private TarjanHelper(int index) { 359 this.index = index; 360 this.lowlink = index; 361 } 362 } 363 } 364 } -
new file test/data/CycleDetector_test_wikipedia.osm
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/test/data/CycleDetector_test_wikipedia.osm b/test/data/CycleDetector_test_wikipedia.osm new file mode 100644
- + 1 <?xml version='1.0' encoding='UTF-8'?> 2 <osm version='0.6' upload='never' generator='JOSM'> 3 <node id='-137726' action='modify' visible='true' lat='47.74161657891' lon='17.37769604149' /> 4 <node id='-137727' action='modify' visible='true' lat='47.74160961975' lon='17.37612305842' /> 5 <node id='-137728' action='modify' visible='true' lat='47.74043350867' lon='17.37611270985' /> 6 <node id='-137731' action='modify' visible='true' lat='47.74043350867' lon='17.37771673864' /> 7 <node id='-137732' action='modify' visible='true' lat='47.74071883984' lon='17.37897926452' /> 8 <node id='-137733' action='modify' visible='true' lat='47.74045438662' lon='17.38021074469' /> 9 <node id='-137734' action='modify' visible='true' lat='47.74011337916' lon='17.37895856738' /> 10 <node id='-137735' action='modify' visible='true' lat='47.74163049723' lon='17.38024179041' /> 11 <node id='-137736' action='modify' visible='true' lat='47.74119902778' lon='17.38124560197' /> 12 <node id='-137737' action='modify' visible='true' lat='47.74161657891' lon='17.38222871639' /> 13 <node id='-137738' action='modify' visible='true' lat='47.7420091937' lon='17.38123761625' /> 14 <node id='-137746' action='modify' visible='true' lat='47.74044046799' lon='17.38222871639' /> 15 <node id='-137759' action='modify' visible='true' lat='47.73993243552' lon='17.38222871639' /> 16 <node id='-137760' action='modify' visible='true' lat='47.73994635429' lon='17.38319113367' /> 17 <node id='-137761' action='modify' visible='true' lat='47.74046134593' lon='17.38319113367' /> 18 <way id='-103300' action='modify' visible='true'> 19 <nd ref='-137726' /> 20 <nd ref='-137727' /> 21 <tag k='waterway' v='ditch' /> 22 </way> 23 <way id='-103301' action='modify' visible='true'> 24 <nd ref='-137727' /> 25 <nd ref='-137728' /> 26 <tag k='waterway' v='ditch' /> 27 </way> 28 <way id='-103302' action='modify' visible='true'> 29 <nd ref='-137728' /> 30 <nd ref='-137726' /> 31 <tag k='waterway' v='ditch' /> 32 </way> 33 <way id='-103305' action='modify' visible='true'> 34 <nd ref='-137731' /> 35 <nd ref='-137728' /> 36 <tag k='waterway' v='ditch' /> 37 </way> 38 <way id='-103306' action='modify' visible='true'> 39 <nd ref='-137731' /> 40 <nd ref='-137726' /> 41 <tag k='waterway' v='ditch' /> 42 </way> 43 <way id='-103307' action='modify' visible='true'> 44 <nd ref='-137733' /> 45 <nd ref='-137732' /> 46 <nd ref='-137731' /> 47 <tag k='waterway' v='ditch' /> 48 </way> 49 <way id='-103309' action='modify' visible='true'> 50 <nd ref='-137731' /> 51 <nd ref='-137734' /> 52 <nd ref='-137733' /> 53 <tag k='waterway' v='ditch' /> 54 </way> 55 <way id='-103311' action='modify' visible='true'> 56 <nd ref='-137733' /> 57 <nd ref='-137735' /> 58 <tag k='waterway' v='ditch' /> 59 </way> 60 <way id='-103312' action='modify' visible='true'> 61 <nd ref='-137735' /> 62 <nd ref='-137726' /> 63 <tag k='waterway' v='ditch' /> 64 </way> 65 <way id='-103313' action='modify' visible='true'> 66 <nd ref='-137735' /> 67 <nd ref='-137736' /> 68 <nd ref='-137737' /> 69 <tag k='waterway' v='ditch' /> 70 </way> 71 <way id='-103315' action='modify' visible='true'> 72 <nd ref='-137737' /> 73 <nd ref='-137738' /> 74 <nd ref='-137735' /> 75 <tag k='waterway' v='ditch' /> 76 </way> 77 <way id='-103324' action='modify' visible='true'> 78 <nd ref='-137746' /> 79 <nd ref='-137733' /> 80 <tag k='waterway' v='ditch' /> 81 </way> 82 <way id='-103325' action='modify' visible='true'> 83 <nd ref='-137746' /> 84 <nd ref='-137737' /> 85 <tag k='waterway' v='ditch' /> 86 </way> 87 <way id='-103359' action='modify' visible='true'> 88 <nd ref='-137746' /> 89 <nd ref='-137759' /> 90 <nd ref='-137760' /> 91 <nd ref='-137761' /> 92 <nd ref='-137746' /> 93 <tag k='waterway' v='ditch' /> 94 </way> 95 </osm> -
new file test/unit/org/openstreetmap/josm/data/validation/tests/CycleDetectorTest.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/CycleDetectorTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/CycleDetectorTest.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 6 import java.nio.file.Files; 7 import java.nio.file.Paths; 8 9 import org.junit.jupiter.api.Test; 10 import org.junit.jupiter.api.extension.RegisterExtension; 11 import org.openstreetmap.josm.TestUtils; 12 import org.openstreetmap.josm.data.osm.DataSet; 13 import org.openstreetmap.josm.io.OsmReader; 14 import org.openstreetmap.josm.testutils.JOSMTestRules; 15 16 /** 17 * JUnit test for {@link CycleDetector} validation test. 18 */ 19 class CycleDetectorTest { 20 /** 21 * Setup test. 22 */ 23 @RegisterExtension 24 public JOSMTestRules test = new JOSMTestRules(); 25 26 @Test() 27 void testCycleDetection() throws Exception { 28 CycleDetector cycleDetector = new CycleDetector(); 29 DataSet ds = OsmReader.parseDataSet( 30 Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "CycleDetector_test_wikipedia.osm")), null); 31 cycleDetector.startTest(null); 32 cycleDetector.visit(ds.allPrimitives()); 33 cycleDetector.endTest(); 34 35 // we have 4 cycles in the test file 36 assertEquals(4, cycleDetector.getErrors().size()); 37 } 38 } 39 No newline at end of file -
src/org/openstreetmap/josm/data/osm/NodeGraph.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/osm/NodeGraph.java b/src/org/openstreetmap/josm/data/osm/NodeGraph.java
a b 24 24 25 25 /** 26 26 * A directed or undirected graph of nodes. 27 * 27 28 * @since 12463 (extracted from CombineWayAction) 28 29 */ 29 30 public class NodeGraph { 31 private final Set<NodePair> edges; 32 private final Map<Node, List<NodePair>> successors = new LinkedHashMap<>(); 33 private final Map<Node, List<NodePair>> predecessors = new LinkedHashMap<>(); 34 private int numUndirectedEdges; 35 36 /** 37 * The number of edges that were added. 38 */ 39 private int addedEdges; 40 41 /** 42 * Constructs a new {@code NodeGraph}. 43 */ 44 public NodeGraph() { 45 edges = new LinkedHashSet<>(); 46 } 30 47 31 48 /** 32 49 * Builds a list of pair of nodes from the given way. 33 * @param way way50 * @param way way 34 51 * @param directed if {@code true} each pair of nodes will occur once, in the way nodes order. 35 * if {@code false} each pair of nodes will occur twice (the pair and its inverse dcopy)52 * if {@code false} each pair of nodes will occur twice (the pair and its inverse copy) 36 53 * @return a list of pair of nodes from the given way 37 54 */ 38 55 public static List<NodePair> buildNodePairs(Way way, boolean directed) { 39 56 List<NodePair> pairs = new ArrayList<>(); 40 for (Pair<Node, Node> pair : way.getNodePairs(false /* don't sort */)) {57 for (Pair<Node, Node> pair : way.getNodePairs(false)) { 41 58 pairs.add(new NodePair(pair)); 42 59 if (!directed) { 43 60 pairs.add(new NodePair(pair).swap()); … … 48 65 49 66 /** 50 67 * Builds a list of pair of nodes from the given ways. 51 * @param ways ways52 * @param directed if {@code true} each pair of nodes will occur once, in the way nodes order. 53 * if {@code false} each pair of nodes will occur twice (the pair and its inverse dcopy)68 * @param ways ways 69 * @param directed if {@code true} each pair of nodes will occur once, in the way nodes order.<p> 70 * if {@code false} each pair of nodes will occur twice (the pair and its inverse copy) 54 71 * @return a list of pair of nodes from the given ways 55 72 */ 56 73 public static List<NodePair> buildNodePairs(List<Way> ways, boolean directed) { 57 74 List<NodePair> pairs = new ArrayList<>(); 58 for (Way w : ways) {75 for (Way w : ways) { 59 76 pairs.addAll(buildNodePairs(w, directed)); 60 77 } 61 78 return pairs; 62 79 } 63 80 64 81 /** 65 * Builds a new list of pair nodes without the duplicated pairs (including inverse dcopies).82 * Builds a new list of pair nodes without the duplicated pairs (including inverse copies). 66 83 * @param pairs existing list of pairs 67 84 * @return a new list of pair nodes without the duplicated pairs 68 85 */ 69 86 public static List<NodePair> eliminateDuplicateNodePairs(List<NodePair> pairs) { 70 87 List<NodePair> cleaned = new ArrayList<>(); 71 for (NodePair p : pairs) {88 for (NodePair p : pairs) { 72 89 if (!cleaned.contains(p) && !cleaned.contains(p.swap())) { 73 90 cleaned.add(p); 74 91 } … … 76 93 return cleaned; 77 94 } 78 95 96 /** 97 * Create a directed graph from the given node pairs. 98 * @param pairs Node pairs to build the graph from 99 * @return node graph structure 100 */ 79 101 public static NodeGraph createDirectedGraphFromNodePairs(List<NodePair> pairs) { 80 102 NodeGraph graph = new NodeGraph(); 81 for (NodePair pair : pairs) {103 for (NodePair pair : pairs) { 82 104 graph.add(pair); 83 105 } 84 106 return graph; 85 107 } 86 108 109 /** 110 * Create a directed graph from the given ways. 111 * @param ways ways to build the graph from 112 * @return node graph structure 113 */ 87 114 public static NodeGraph createDirectedGraphFromWays(Collection<Way> ways) { 88 115 NodeGraph graph = new NodeGraph(); 89 for (Way w : ways) {90 graph.add(buildNodePairs(w, true /* directed */));116 for (Way w : ways) { 117 graph.add(buildNodePairs(w, true)); 91 118 } 92 119 return graph; 93 120 } … … 99 126 */ 100 127 public static NodeGraph createUndirectedGraphFromNodeList(List<NodePair> pairs) { 101 128 NodeGraph graph = new NodeGraph(); 102 for (NodePair pair : pairs) {129 for (NodePair pair : pairs) { 103 130 graph.add(pair); 104 131 graph.add(pair.swap()); 105 132 } … … 108 135 109 136 /** 110 137 * Create an undirected graph from the given ways, but prevent reversing of all 111 * non-new ways by fix one direction.138 * non-new ways by fixing one direction. 112 139 * @param ways Ways to build the graph from 113 140 * @return node graph structure 114 141 * @since 8181 115 142 */ 116 143 public static NodeGraph createUndirectedGraphFromNodeWays(Collection<Way> ways) { 117 144 NodeGraph graph = new NodeGraph(); 118 for (Way w : ways) {119 graph.add(buildNodePairs(w, false /* undirected */));145 for (Way w : ways) { 146 graph.add(buildNodePairs(w, false)); 120 147 } 121 148 return graph; 122 149 } 123 150 151 /** 152 * Create a nearly undirected graph from the given ways, but prevent reversing of all 153 * non-new ways by fixing one direction. 154 * The first new way gives the direction of the graph. 155 * @param ways Ways to build the graph from 156 * @return node graph structure 157 */ 124 158 public static NodeGraph createNearlyUndirectedGraphFromNodeWays(Collection<Way> ways) { 125 159 boolean dir = true; 126 160 NodeGraph graph = new NodeGraph(); 127 for (Way w : ways) {161 for (Way w : ways) { 128 162 if (!w.isNew()) { 129 163 /* let the first non-new way give the direction (see #5880) */ 130 164 graph.add(buildNodePairs(w, dir)); 131 165 dir = false; 132 166 } else { 133 graph.add(buildNodePairs(w, false /* undirected */));167 graph.add(buildNodePairs(w, false)); 134 168 } 135 169 } 136 170 return graph; 137 171 } 138 172 139 private final Set<NodePair> edges; 140 private int numUndirectedEges; 141 /** counts the number of edges that were added */ 142 private int addedEdges; 143 private final Map<Node, List<NodePair>> successors = new LinkedHashMap<>(); 144 private final Map<Node, List<NodePair>> predecessors = new LinkedHashMap<>(); 173 /** 174 * Add a node pair. 175 * @param pair node pair 176 */ 177 public void add(NodePair pair) { 178 addedEdges++; 179 edges.add(pair); 180 } 181 182 /** 183 * Add a list of node pairs. 184 * @param pairs collection of node pairs 185 */ 186 public void add(Collection<NodePair> pairs) { 187 for (NodePair pair : pairs) { 188 add(pair); 189 } 190 } 191 192 /** 193 * Return the edges containing the node pairs of the graph. 194 * @return the edges containing the node pairs of the graph 195 */ 196 public Set<NodePair> getEdges() { 197 return edges; 198 } 199 200 /** 201 * Return the graph's nodes. 202 * @return the graph's nodes 203 */ 204 public Set<Node> getNodes() { 205 Set<Node> nodes = new LinkedHashSet<>(2 * edges.size()); 206 for (NodePair pair : edges) { 207 nodes.add(pair.getA()); 208 nodes.add(pair.getB()); 209 } 210 return nodes; 211 } 212 213 /** 214 * Creates a lookup table from the existing edges to make querying possible. 215 * @return a map containing all the graph data, where the key is queryable, values are direct successors of the key node 216 * @since xxx 217 */ 218 public Map<Node, List<Node>> createMap() { 219 final Map<Node, List<Node>> result = new HashMap<>((int) (edges.size() / 0.75) + 1); 145 220 221 for (NodePair edge : edges) { 222 result.computeIfAbsent(edge.getA(), k -> new ArrayList<>()).add(edge.getB()); 223 } 224 225 return result; 226 } 227 228 /** 229 * See {@link #prepare()} 230 */ 146 231 protected void rememberSuccessor(NodePair pair) { 147 232 List<NodePair> l = successors.computeIfAbsent(pair.getA(), k -> new ArrayList<>()); 148 233 if (!l.contains(pair)) { … … 150 235 } 151 236 } 152 237 238 /** 239 * See {@link #prepare()} 240 */ 153 241 protected void rememberPredecessors(NodePair pair) { 154 242 List<NodePair> l = predecessors.computeIfAbsent(pair.getB(), k -> new ArrayList<>()); 155 243 if (!l.contains(pair)) { … … 157 245 } 158 246 } 159 247 248 /** 249 * Replies true if {@code n} is a terminal node of the graph. Internal variables should be initialized first. 250 * @param n Node to check 251 * @return {@code true} if it is a terminal node 252 * @see #prepare() 253 */ 160 254 protected boolean isTerminalNode(Node n) { 161 255 if (successors.get(n) == null) return false; 162 256 if (successors.get(n).size() != 1) return false; … … 174 268 successors.clear(); 175 269 predecessors.clear(); 176 270 177 for (NodePair pair : edges) {271 for (NodePair pair : edges) { 178 272 if (!undirectedEdges.contains(pair) && !undirectedEdges.contains(pair.swap())) { 179 273 undirectedEdges.add(pair); 180 274 } 181 275 rememberSuccessor(pair); 182 276 rememberPredecessors(pair); 183 277 } 184 numUndirectedEges = undirectedEdges.size(); 185 } 186 187 /** 188 * Constructs a new {@code NodeGraph}. 189 */ 190 public NodeGraph() { 191 edges = new LinkedHashSet<>(); 278 numUndirectedEdges = undirectedEdges.size(); 192 279 } 193 280 194 281 /** 195 * Add a node pair.196 * @ param pair node pair282 * Return the terminal nodes of the graph. 283 * @return the terminal nodes of the graph 197 284 */ 198 public void add(NodePair pair) {199 addedEdges++;200 edges.add(pair);201 }202 203 /**204 * Add a list of node pairs.205 * @param pairs list of node pairs206 */207 public void add(Collection<NodePair> pairs) {208 for (NodePair pair: pairs) {209 add(pair);210 }211 }212 213 285 protected Set<Node> getTerminalNodes() { 214 286 return getNodes().stream().filter(this::isTerminalNode).collect(Collectors.toCollection(LinkedHashSet::new)); 215 287 } … … 229 301 return Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList); 230 302 } 231 303 232 protected Set<Node> getNodes() {233 Set<Node> nodes = new LinkedHashSet<>(2 * edges.size());234 for (NodePair pair: edges) {235 nodes.add(pair.getA());236 nodes.add(pair.getB());237 }238 return nodes;239 }240 241 304 protected boolean isSpanningWay(Collection<NodePair> way) { 242 return numUndirectedE ges == way.size();305 return numUndirectedEdges == way.size(); 243 306 } 244 307 245 308 protected List<Node> buildPathFromNodePairs(Deque<NodePair> path) { … … 248 311 } 249 312 250 313 /** 251 * Tries to find a spanning path starting from node <code>startNode</code>.252 * 314 * Tries to find a spanning path starting from node {@code startNode}. 315 * <p> 253 316 * Traverses the path in depth-first order. 254 *255 317 * @param startNode the start node 256 318 * @return the spanning path; empty list if no path is found 257 319 */ … … 259 321 if (startNode != null) { 260 322 Deque<NodePair> path = new ArrayDeque<>(); 261 323 Set<NodePair> dupCheck = new HashSet<>(); 262 Deque<NodePair> nextPairs = new ArrayDeque<>(); 263 nextPairs.addAll(getOutboundPairs(startNode)); 324 Deque<NodePair> nextPairs = new ArrayDeque<>(getOutboundPairs(startNode)); 264 325 while (!nextPairs.isEmpty()) { 265 326 NodePair cur = nextPairs.removeLast(); 266 327 if (!dupCheck.contains(cur) && !dupCheck.contains(cur.swap())) { … … 280 341 281 342 /** 282 343 * Tries to find a path through the graph which visits each edge (i.e. 283 * the segment of a way) exactly once. 284 * < p><b>Note that duplicated edges are removed first!</b>344 * the segment of a way) exactly once.<p> 345 * <b>Note that duplicated edges are removed first!</b> 285 346 * 286 * @return the path; null,if no path was found347 * @return the path; {@code null} if no path was found 287 348 */ 288 349 public List<Node> buildSpanningPath() { 289 350 prepare(); 290 if (numUndirectedE ges > 0 && isConnected()) {291 // try to find a path from each "terminal node", i.e. from a292 // node which is connected by exactly one undirected edge s(or293 // two directed edges in opposite direction) to the graph. A351 if (numUndirectedEdges > 0 && isConnected()) { 352 // Try to find a path from each "terminal node", i.e. from a 353 // node which is connected by exactly one undirected edge (or 354 // two directed edges in the opposite direction) to the graph. A 294 355 // graph built up from way segments is likely to include such 295 356 // nodes, unless the edges build one or more closed rings. 296 357 // We order the nodes to start with the best candidates, but … … 324 385 325 386 /** 326 387 * Find out if the graph is connected. 327 * @return true if it is connected.388 * @return {@code true} if it is connected 328 389 */ 329 390 private boolean isConnected() { 330 391 Set<Node> nodes = getNodes(); … … 350 411 351 412 /** 352 413 * Sort the nodes by number of appearances in the edges. 353 * @return set of nodes which can be start nodes in a spanning way .414 * @return set of nodes which can be start nodes in a spanning way 354 415 */ 355 416 private Set<Node> getMostFrequentVisitedNodesFirst() { 356 417 if (edges.isEmpty()) 357 418 return Collections.emptySet(); 358 // count appearance of nodes in edges419 // count the appearance of nodes in edges 359 420 Map<Node, Integer> counters = new HashMap<>(); 360 421 for (NodePair pair : edges) { 361 422 Integer c = counters.get(pair.getA()); -
src/org/openstreetmap/josm/data/validation/OsmValidator.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/validation/OsmValidator.java b/src/org/openstreetmap/josm/data/validation/OsmValidator.java
a b 44 44 import org.openstreetmap.josm.data.validation.tests.ConditionalKeys; 45 45 import org.openstreetmap.josm.data.validation.tests.ConnectivityRelations; 46 46 import org.openstreetmap.josm.data.validation.tests.CrossingWays; 47 import org.openstreetmap.josm.data.validation.tests.CycleDetector; 47 48 import org.openstreetmap.josm.data.validation.tests.DirectionNodes; 48 49 import org.openstreetmap.josm.data.validation.tests.DuplicateNode; 49 50 import org.openstreetmap.josm.data.validation.tests.DuplicateRelation; … … 154 155 // 3700 .. 3799 is automatically removed since it clashed with pt_assistant. 155 156 SharpAngles.class, // 3800 .. 3899 156 157 ConnectivityRelations.class, // 3900 .. 3999 157 DirectionNodes.class, // 4000 -4099158 DirectionNodes.class, // 4000 .. 4099 158 159 RightAngleBuildingTest.class, // 4100 .. 4199 160 CycleDetector.class, // 4200 .. 4299 159 161 }; 160 162 161 163 /**