Ticket #21881: josm21881_loop_detector_v1.patch
File josm21881_loop_detector_v1.patch, 31.4 KB (added by , 3 years ago) |
---|
-
src/org/openstreetmap/josm/data/osm/NodeGraph.java
Subject: [PATCH] josm21881_loop_detector_v1 --- 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 * counts 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 } 145 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 216 * @since xxx 217 */ 218 public Map<Node, Set<Node>> createMap() { 219 Map<Node, Set<Node>> result = new HashMap<>((int) (edges.size() / 0.75) + 1); 220 for (NodePair edge : edges) { 221 if (result.containsKey(edge.getA())) { 222 result.get(edge.getA()).add(edge.getB()); 223 } else { 224 result.put(edge.getA(), new HashSet<>(Collections.singletonList(edge.getB()))); 225 } 226 } 227 228 return result; 229 } 230 231 /** 232 * See {@link #prepare()} 233 */ 146 234 protected void rememberSuccessor(NodePair pair) { 147 235 List<NodePair> l = successors.computeIfAbsent(pair.getA(), k -> new ArrayList<>()); 148 236 if (!l.contains(pair)) { … … 150 238 } 151 239 } 152 240 241 /** 242 * See {@link #prepare()} 243 */ 153 244 protected void rememberPredecessors(NodePair pair) { 154 245 List<NodePair> l = predecessors.computeIfAbsent(pair.getB(), k -> new ArrayList<>()); 155 246 if (!l.contains(pair)) { … … 157 248 } 158 249 } 159 250 251 /** 252 * Replies true if {@code n} is a terminal node of the graph. Internal variables should be initialized first. 253 * @param n Node to check 254 * @return {@code true} if it is a terminal node 255 * @see #prepare() 256 */ 160 257 protected boolean isTerminalNode(Node n) { 161 258 if (successors.get(n) == null) return false; 162 259 if (successors.get(n).size() != 1) return false; … … 174 271 successors.clear(); 175 272 predecessors.clear(); 176 273 177 for (NodePair pair : edges) {274 for (NodePair pair : edges) { 178 275 if (!undirectedEdges.contains(pair) && !undirectedEdges.contains(pair.swap())) { 179 276 undirectedEdges.add(pair); 180 277 } 181 278 rememberSuccessor(pair); 182 279 rememberPredecessors(pair); 183 280 } 184 numUndirectedEges = undirectedEdges.size(); 185 } 186 187 /** 188 * Constructs a new {@code NodeGraph}. 189 */ 190 public NodeGraph() { 191 edges = new LinkedHashSet<>(); 281 numUndirectedEdges = undirectedEdges.size(); 192 282 } 193 283 194 284 /** 195 * Add a node pair.196 * @ param pair node pair285 * Return the terminal nodes of the graph. 286 * @return the terminal nodes of the graph 197 287 */ 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 288 protected Set<Node> getTerminalNodes() { 214 289 return getNodes().stream().filter(this::isTerminalNode).collect(Collectors.toCollection(LinkedHashSet::new)); 215 290 } … … 229 304 return Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList); 230 305 } 231 306 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 307 protected boolean isSpanningWay(Collection<NodePair> way) { 242 return numUndirectedE ges == way.size();308 return numUndirectedEdges == way.size(); 243 309 } 244 310 245 311 protected List<Node> buildPathFromNodePairs(Deque<NodePair> path) { … … 248 314 } 249 315 250 316 /** 251 * Tries to find a spanning path starting from node <code>startNode</code>.252 * 317 * Tries to find a spanning path starting from node {@code startNode}. 318 * <p> 253 319 * Traverses the path in depth-first order. 254 *255 320 * @param startNode the start node 256 321 * @return the spanning path; empty list if no path is found 257 322 */ … … 283 348 * the segment of a way) exactly once. 284 349 * <p><b>Note that duplicated edges are removed first!</b> 285 350 * 286 * @return the path; null, if no path was found351 * @return the path; {@code null}, if no path was found 287 352 */ 288 353 public List<Node> buildSpanningPath() { 289 354 prepare(); 290 if (numUndirectedE ges > 0 && isConnected()) {355 if (numUndirectedEdges > 0 && isConnected()) { 291 356 // try to find a path from each "terminal node", i.e. from a 292 357 // node which is connected by exactly one undirected edges (or 293 358 // two directed edges in opposite direction) to the graph. A … … 324 389 325 390 /** 326 391 * Find out if the graph is connected. 327 * @return true if it is connected.392 * @return {@code true} if it is connected 328 393 */ 329 394 private boolean isConnected() { 330 395 Set<Node> nodes = getNodes(); … … 350 415 351 416 /** 352 417 * Sort the nodes by number of appearances in the edges. 353 * @return set of nodes which can be start nodes in a spanning way .418 * @return set of nodes which can be start nodes in a spanning way 354 419 */ 355 420 private Set<Node> getMostFrequentVisitedNodesFirst() { 356 421 if (edges.isEmpty()) -
new file src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java
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.Map; 15 import java.util.Set; 16 import java.util.stream.Collectors; 17 18 import org.openstreetmap.josm.data.osm.Node; 19 import org.openstreetmap.josm.data.osm.NodeGraph; 20 import org.openstreetmap.josm.data.osm.OsmPrimitive; 21 import org.openstreetmap.josm.data.osm.Way; 22 import org.openstreetmap.josm.data.validation.Severity; 23 import org.openstreetmap.josm.data.validation.Test; 24 import org.openstreetmap.josm.data.validation.TestError; 25 26 /** 27 * Test for detecting <a href="https://en.wikipedia.org/wiki/Cycle_(graph_theory)">cycles</a> in a directed graph, 28 * currently used for waterways only. The graph consists of OSM dataset ways labeled as waterway. 29 * 30 * @author gaben 31 * @since xxx 32 */ 33 public class CycleDetector extends Test { 34 public static final int CYCLE_DETECTED = 4100; 35 36 /** 37 * All waterways for cycle detection. 38 */ 39 private final Set<Way> usableWaterways = new HashSet<>(); 40 41 private final Set<Way> visitedWays = new HashSet<>(); 42 43 /** 44 * Currently used directional waterways from the OSM wiki 45 */ 46 private static final Set<String> directionalWaterways = new HashSet<>( 47 Arrays.asList("river", "stream", "tidal_channel", "drain", "ditch", "fish_pass", "fairway")); 48 49 public CycleDetector() { 50 super(tr("Loop detector"), tr("Detects loops in connected, directional waterways.")); 51 } 52 53 @Override 54 public boolean isPrimitiveUsable(OsmPrimitive p) { 55 return p.isUsable() && (p instanceof Way) && ((Way) p).getNodesCount() > 1 && p.hasTag("waterway", directionalWaterways); 56 } 57 58 @Override 59 public void visit(Way w) { 60 if (isPrimitiveUsable(w)) 61 usableWaterways.add(w); 62 } 63 64 @Override 65 public void endTest() { 66 Collection<Collection<Way>> graphs = getGraphs(); 67 68 for (Collection<Way> graph : graphs) { 69 NodeGraph nodeGraph = NodeGraph.createDirectedGraphFromWays(graph); 70 Tarjan tarjan = new Tarjan(nodeGraph); 71 Collection<Collection<Node>> scc = tarjan.getSCC(); 72 for (Collection<Node> possibleCycle : scc) { 73 if (possibleCycle.size() > 1) { 74 errors.add( 75 TestError.builder(this, Severity.ERROR, CYCLE_DETECTED) 76 .message(tr("Cycle in directional waterway network")) 77 .primitives(possibleCycle) 78 .build() 79 ); 80 } 81 } 82 } 83 84 super.endTest(); 85 } 86 87 @Override 88 public void clear() { 89 super.clear(); 90 usableWaterways.clear(); 91 visitedWays.clear(); 92 } 93 94 /** 95 * Returns all directional waterways which connect to at least one other usable way. 96 * 97 * @return all directional waterways which connect to at least one other usable way 98 */ 99 private Collection<Collection<Way>> getGraphs() { 100 // HashSet doesn't make a difference here 101 Collection<Collection<Way>> graphs = new ArrayList<>(); 102 103 for (Way waterway : usableWaterways) { 104 Collection<Way> graph = buildGraph(waterway); 105 106 if (!graph.isEmpty()) 107 graphs.add(graph); 108 } 109 110 return graphs; 111 } 112 113 /** 114 * Returns a collection of ways which belongs to the same graph. 115 * 116 * @param way starting way to extend the graph from 117 * @return a collection of ways which belongs to the same graph 118 */ 119 private Collection<Way> buildGraph(Way way) { 120 if (visitedWays.contains(way)) 121 return Collections.emptySet(); 122 123 final Set<Way> graph = new HashSet<>(); 124 125 for (Node node : way.getNodes()) { 126 Collection<Way> referrers = node.referrers(Way.class) 127 .filter(this::isPrimitiveUsable) 128 .filter(candidate -> candidate != way) 129 .collect(Collectors.toSet()); 130 131 if (!referrers.isEmpty()) { 132 visitedWays.add(way); 133 for (Way referrer : referrers) { 134 graph.addAll(buildGraph(referrer)); 135 } 136 graph.addAll(referrers); 137 } 138 } 139 return graph; 140 } 141 142 /** 143 * Tarjan's algorithm implementation for JOSM. 144 * 145 * @see <a href="https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm"> 146 * Tarjan's strongly connected components algorithm</a> 147 */ 148 public static final class Tarjan { 149 150 /** 151 * A simple key-value registry to remember visited nodes and its metadata. Key is used for storing the unique 152 * ID of the primitives instead of the full primitives to save space. 153 */ 154 private final Map<Long, TarjanHelper> registry; 155 private final Collection<Collection<Node>> scc = new HashSet<>(); 156 private final Deque<Node> stack = new ArrayDeque<>(); 157 private final NodeGraph graph; 158 private final Map<Node, Set<Node>> graphMap; 159 private int index = 0; 160 161 public Tarjan(NodeGraph graph) { 162 this.graph = graph; 163 164 graphMap = this.graph.createMap(); 165 166 this.registry = new HashMap<>((int) (this.graph.getEdges().size() / 0.75) + 1); 167 } 168 169 /** 170 * Returns the strongly connected components in the current graph. 171 * 172 * @return the strongly connected components in the current graph 173 */ 174 public Collection<Collection<Node>> getSCC() { 175 for (Node node : graph.getNodes()) { 176 if (!registry.containsKey(node.getUniqueId())) { 177 strongConnect(node); 178 } 179 } 180 return scc; 181 } 182 183 /** 184 * Calculates strongly connected components available from the given node. 185 * 186 * @param v the node to generate strongly connected components from 187 */ 188 private void strongConnect(Node v) { 189 registry.put(v.getUniqueId(), new TarjanHelper(index, index)); 190 index++; 191 stack.push(v); 192 193 for (Node w : getSuccessors(v)) { 194 if (!registry.containsKey(w.getUniqueId())) { 195 strongConnect(w); 196 TarjanHelper vHelper = registry.get(v.getUniqueId()); 197 TarjanHelper wHelper = registry.get(w.getUniqueId()); 198 vHelper.lowlink = Math.min(vHelper.lowlink, wHelper.lowlink); 199 } else if (stack.contains(w)) { 200 TarjanHelper vHelper = registry.get(v.getUniqueId()); 201 TarjanHelper wHelper = registry.get(w.getUniqueId()); 202 vHelper.lowlink = Math.min(vHelper.lowlink, wHelper.index); 203 } 204 } 205 206 final TarjanHelper vHelper = registry.get(v.getUniqueId()); 207 if (vHelper.lowlink == vHelper.index) { 208 Collection<Node> currentSCC = new HashSet<>(); 209 Node w; 210 do { 211 w = stack.remove(); 212 currentSCC.add(w); 213 } while (!w.equals(v)); 214 scc.add(currentSCC); 215 } 216 } 217 218 /** 219 * Returns the next direct successors from the graph of the given node. 220 * 221 * @param node a node to start search from 222 * @return a collection of nodes from the graph which are direct neighbors of the given node 223 */ 224 private Set<Node> getSuccessors(Node node) { 225 return graphMap.getOrDefault(node, Collections.emptySet()); 226 } 227 228 /** 229 * Helper class for storing the Tarjan algorithm runtime metadata. 230 */ 231 private static final class TarjanHelper { 232 private final int index; 233 private int lowlink; 234 235 private TarjanHelper(int index, int lowlink) { 236 this.index = index; 237 this.lowlink = lowlink; 238 } 239 } 240 } 241 } -
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 53 53 import org.openstreetmap.josm.data.validation.tests.InternetTags; 54 54 import org.openstreetmap.josm.data.validation.tests.Lanes; 55 55 import org.openstreetmap.josm.data.validation.tests.LongSegment; 56 import org.openstreetmap.josm.data.validation.tests.CycleDetector; 56 57 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker; 57 58 import org.openstreetmap.josm.data.validation.tests.MultipolygonTest; 58 59 import org.openstreetmap.josm.data.validation.tests.NameMismatch; … … 154 155 SharpAngles.class, // 3800 .. 3899 155 156 ConnectivityRelations.class, // 3900 .. 3999 156 157 DirectionNodes.class, // 4000-4099 158 CycleDetector.class, // 4100-4199 157 159 }; 158 160 159 161 /** … … 607 609 } 608 610 609 611 /** 610 * Initialize grid details based on current projection system. Values based on612 * Initialize grid details based on the current projection system. Values based on 611 613 * the original value fixed for EPSG:4326 (10000) using heuristics (that is, test&error 612 614 * until most bugs were discovered while keeping the processing time reasonable) 613 615 */ … … 636 638 private static boolean testsInitialized; 637 639 638 640 /** 639 * Initializes all tests if this operation shasn't been performed already.641 * Initializes all tests if this operation hasn't been performed already. 640 642 */ 641 643 public static synchronized void initializeTests() { 642 644 if (!testsInitialized) { -
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 /** 27 * Test all error cases manually created in direction-nodes.osm. 28 * 29 * @throws Exception in case of error 30 */ 31 @Test 32 void testCoverage() throws Exception { 33 CycleDetector cycleDetector = new CycleDetector(); 34 ValidatorTestUtils.testSampleFile("test/data/CycleDetector_test_wikipedia.osm", DataSet::getWays, null, cycleDetector); 35 } 36 37 @Test 38 void testCycleDetection() throws Exception { 39 CycleDetector cycleDetector = new CycleDetector(); 40 DataSet ds = OsmReader.parseDataSet( 41 Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "CycleDetector_test_wikipedia.osm")), null); 42 cycleDetector.startTest(null); 43 cycleDetector.visit(ds.allPrimitives()); 44 cycleDetector.endTest(); 45 46 // we have 4 cycles in the test file 47 assertEquals(4, cycleDetector.getErrors().size()); 48 } 49 } 50 No newline at end of file -
test/unit/org/openstreetmap/josm/data/validation/tests/DirectionNodesTest.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/DirectionNodesTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/DirectionNodesTest.java
a b 8 8 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 9 9 10 10 /** 11 * JUnit Test of Multipolygonvalidation test.11 * JUnit Test of {@link DirectionNodes} validation test. 12 12 */ 13 13 class DirectionNodesTest { 14 14 15 16 15 /** 17 16 * Setup test. 18 17 */ … … 25 24 * @throws Exception in case of error 26 25 */ 27 26 @Test 28 void testDirection sNodesTestFile() throws Exception {27 void testDirectionNodesTestFile() throws Exception { 29 28 final DirectionNodes test = new DirectionNodes(); 30 29 ValidatorTestUtils.testSampleFile("nodist/data/direction-nodes.osm", ds -> ds.getNodes(), null, test); 31 30 }