Ticket #17528: intersectionissues_v9.patch
File intersectionissues_v9.patch, 37.0 KB (added by , 6 years ago) |
---|
-
src/org/openstreetmap/josm/actions/ValidateAction.java
116 116 private boolean canceled; 117 117 private List<TestError> errors; 118 118 119 private List<Class<? extends Test>> runTests; 120 119 121 /** 120 122 * Constructs a new {@code ValidationTask} 121 123 * @param tests the tests to run … … 153 155 @Override 154 156 protected void realRun() throws SAXException, IOException, 155 157 OsmTransferException { 158 runTests = new ArrayList<>(); 156 159 if (tests == null || tests.isEmpty()) 157 160 return; 158 161 errors = new ArrayList<>(200); 159 162 getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size()); 160 int testCounter = 0; 163 runTests(tests, 0); 164 tests = null; 165 if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) { 166 getProgressMonitor().setCustomText(""); 167 getProgressMonitor().subTask(tr("Updating ignored errors ...")); 168 for (TestError error : errors) { 169 if (canceled) return; 170 error.updateIgnored(); 171 } 172 } 173 } 174 175 protected int runTests(Collection<Test> tests, int testCounter) { 176 ArrayList<Test> remaining = new ArrayList<>(); 161 177 for (Test test : tests) { 162 178 if (canceled) 163 return; 179 return testCounter; 180 if (test.getAfterClass() != null && !runTests.contains(test.getAfterClass())) { 181 remaining.add(test); 182 continue; 183 } 164 184 testCounter++; 165 getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, t ests.size(), test.getName()));185 getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, this.tests.size(), test.getName())); 166 186 test.setPartialSelection(formerValidatedPrimitives != null); 187 test.setPreviousErrors(errors); 167 188 test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false)); 168 189 test.visit(validatedPrimitives); 169 190 test.endTest(); 170 191 errors.addAll(test.getErrors()); 171 192 test.clear(); 193 runTests.add(test.getClass()); 172 194 } 173 tests = null; 174 if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) { 175 getProgressMonitor().setCustomText(""); 176 getProgressMonitor().subTask(tr("Updating ignored errors ...")); 177 for (TestError error : errors) { 178 if (canceled) return; 179 error.updateIgnored(); 180 } 195 if (!remaining.isEmpty()) { 196 testCounter = runTests(remaining, testCounter); 181 197 } 198 return testCounter; 182 199 } 183 200 } 184 201 } -
src/org/openstreetmap/josm/data/validation/OsmValidator.java
49 49 import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes; 50 50 import org.openstreetmap.josm.data.validation.tests.Highways; 51 51 import org.openstreetmap.josm.data.validation.tests.InternetTags; 52 import org.openstreetmap.josm.data.validation.tests.IntersectionIssues; 52 53 import org.openstreetmap.josm.data.validation.tests.Lanes; 53 54 import org.openstreetmap.josm.data.validation.tests.LongSegment; 54 55 import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker; … … 148 149 LongSegment.class, // 3500 .. 3599 149 150 PublicTransportRouteTest.class, // 3600 .. 3699 150 151 RightAngleBuildingTest.class, // 3700 .. 3799 152 IntersectionIssues.class, // 3800 .. 3899 151 153 }; 152 154 153 155 /** -
src/org/openstreetmap/josm/data/validation/Test.java
46 46 /** Name of the test */ 47 47 protected final String name; 48 48 49 /** Test to run after */ 50 protected Class<? extends Test> afterTest; 51 49 52 /** Description of the test */ 50 53 protected final String description; 51 54 … … 67 70 /** The list of errors */ 68 71 protected List<TestError> errors = new ArrayList<>(30); 69 72 73 /** The list of previously found errors */ 74 protected List<TestError> previousErrors; 75 70 76 /** Whether the test is run on a partial selection data */ 71 77 protected boolean partialSelection; 72 78 … … 84 90 * @param description Description of the test 85 91 */ 86 92 public Test(String name, String description) { 93 this(name, description, null); 94 } 95 96 /** 97 * Constructor 98 * @param name Name of the test 99 * @param description Description of the test 100 * @param afterTest Ensure the test is run after a test with this name 101 * 102 * @since xxx 103 */ 104 public Test(String name, String description, Class<? extends Test> afterTest) { 87 105 this.name = name; 88 106 this.description = description; 107 this.afterTest = afterTest; 89 108 } 90 109 91 110 /** … … 178 197 } 179 198 180 199 /** 200 * Set the validation errors accumulated by other tests until this moment 201 * For validation errors accumulated by this test, use {@code getErrors()} 202 * @param errors The errors from previous tests 203 */ 204 public void setPreviousErrors(List<TestError> errors) { 205 previousErrors = errors; 206 } 207 208 /** 181 209 * Notification of the end of the test. The tester may perform additional 182 210 * actions and destroy the used structures. 183 211 * <p> … … 319 347 } 320 348 321 349 /** 350 * Get the class that the test must run after 351 * @return A class that extends {@code Test} 352 * 353 * @since xxx 354 */ 355 public Class<? extends Test> getAfterClass() { 356 return afterTest; 357 } 358 359 /** 322 360 * Determines if the test has been canceled. 323 361 * @return {@code true} if the test has been canceled, {@code false} otherwise 324 362 */ -
src/org/openstreetmap/josm/data/validation/tests/IntersectionIssues.java
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.ArrayList; 7 import java.util.Collection; 8 import java.util.HashMap; 9 import java.util.HashSet; 10 import java.util.List; 11 import java.util.Set; 12 13 import org.openstreetmap.josm.data.coor.EastNorth; 14 import org.openstreetmap.josm.data.coor.LatLon; 15 import org.openstreetmap.josm.data.osm.Node; 16 import org.openstreetmap.josm.data.osm.OsmPrimitive; 17 import org.openstreetmap.josm.data.osm.Way; 18 import org.openstreetmap.josm.data.osm.WaySegment; 19 import org.openstreetmap.josm.data.validation.Severity; 20 import org.openstreetmap.josm.data.validation.Test; 21 import org.openstreetmap.josm.data.validation.TestError; 22 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 23 import org.openstreetmap.josm.tools.Geometry; 24 import org.openstreetmap.josm.tools.Logging; 25 import org.openstreetmap.josm.tools.Utils; 26 27 /** 28 * Finds issues with highway intersections 29 * @author Taylor Smock 30 * @since xxx 31 */ 32 public class IntersectionIssues extends Test { 33 private static final int INTERSECTIONISSUESCODE = 3800; 34 /** The code for an intersection which briefly interrupts a road */ 35 protected static final int SHORT_DISCONNECT = INTERSECTIONISSUESCODE + 0; 36 /** The code for a node that is almost on a way */ 37 protected static final int NEARBY_NODE = INTERSECTIONISSUESCODE + 1; 38 /** The distance to consider for nearby nodes/short disconnects */ 39 protected static final double MAX_DISTANCE = 5.0; // meters 40 /** The distance to consider for nearby nodes with tags */ 41 protected static final double MAX_DISTANCE_NODE_INFORMATION = MAX_DISTANCE / 5.0; // meters 42 /** The maximum angle for almost overlapping ways */ 43 protected static final double MAX_ANGLE = 15.0; // degrees 44 /** The maximum distance to consider for almost overlapping ways. 45 * Please note that lane width should be at least 2.6m (if it is a full-size lane) */ 46 protected static final double MAX_DISTANCE_OVERLAPPING = 1.0; // meters 47 48 private HashMap<String, ArrayList<Way>> ways; 49 ArrayList<Way> allWays; 50 51 /** 52 * Construct a new {@code IntersectionIssues} object 53 */ 54 public IntersectionIssues() { 55 super(tr("Intersection Issues"), tr("Check for issues at intersections"), OverlappingWays.class); 56 } 57 58 @Override 59 public void startTest(ProgressMonitor monitor) { 60 super.startTest(monitor); 61 ways = new HashMap<>(); 62 allWays = new ArrayList<>(); 63 } 64 65 @Override 66 public void endTest() { 67 Way pWay = null; 68 try { 69 for (ArrayList<Way> comparison : ways.values()) { 70 pWay = comparison.get(0); 71 checkNearbyEnds(comparison); 72 } 73 for (Way way : allWays) { 74 pWay = way; 75 for (Way way2 : allWays) { 76 if (way2.equals(way)) continue; 77 pWay = way2; 78 if (way.getBBox().intersects(way2.getBBox())) { 79 checkNearbyNodes(way, way2); 80 } 81 } 82 } 83 } catch (Exception e) { 84 if (pWay != null) { 85 Logging.debug("Way https://osm.org/way/{0} caused an error", pWay.getOsmId()); 86 } 87 Logging.warn(e); 88 } 89 ways = null; 90 allWays = null; 91 super.endTest(); 92 } 93 94 @Override 95 public void visit(Way way) { 96 if (!way.isUsable()) return; 97 String highway = "highway"; 98 if (way.hasKey(highway) && !way.get(highway).contains("_link") && 99 !way.get(highway).contains("proposed")) { 100 String[] identityTags = new String[] {"name", "ref"}; 101 for (String tag : identityTags) { 102 if (way.hasKey(tag)) { 103 ArrayList<Way> similar = ways.get(way.get(tag)) == null ? new ArrayList<>() : ways.get(way.get(tag)); 104 if (!similar.contains(way)) similar.add(way); 105 ways.put(way.get(tag), similar); 106 } 107 } 108 if (!allWays.contains(way)) allWays.add(way); 109 } 110 } 111 112 /** 113 * Check for ends that are nearby but not directly connected 114 * @param comparison Ways to look at 115 */ 116 public void checkNearbyEnds(List<Way> comparison) { 117 ArrayList<Way> errored = new ArrayList<>(); 118 for (Way one : comparison) { 119 LatLon oneLast = one.lastNode().getCoor(); 120 LatLon oneFirst = one.firstNode().getCoor(); 121 for (Way two : comparison) { 122 if (one.equals(two) || one.isFirstLastNode(two.firstNode()) 123 || one.isFirstLastNode(two.lastNode()) || 124 (errored.contains(one) && errored.contains(two))) continue; 125 LatLon twoLast = two.lastNode().getCoor(); 126 LatLon twoFirst = two.firstNode().getCoor(); 127 int nearCase = getNearCase(oneFirst, oneLast, twoFirst, twoLast); 128 if (nearCase != 0 && !checkForConnection(one, two, nearCase)) { 129 createCheckNearbyEndsError(nearCase, errored, one, two); 130 return; 131 } 132 } 133 } 134 } 135 136 // 8421 -> twoFirst/oneFirst, twoFirst/oneLast, twoLast/oneFirst, twoLast/oneLast 137 private boolean checkForConnection(Way one, Way two, int nearCase) { 138 List<Node> nodes = new ArrayList<>(); 139 if ((nearCase & 1) == 1) { 140 nodes.add(one.lastNode()); 141 nodes.add(two.lastNode()); 142 } 143 if ((nearCase & 2) == 2) { 144 nodes.add(one.firstNode()); 145 nodes.add(two.lastNode()); 146 } 147 if ((nearCase & 4) == 4) { 148 nodes.add(one.lastNode()); 149 nodes.add(two.firstNode()); 150 } 151 if ((nearCase & 8) == 8) { 152 nodes.add(one.firstNode()); 153 nodes.add(two.firstNode()); 154 } 155 for (Node node : nodes) { 156 Collection<Way> parents = node.getParentWays(); 157 parents.remove(one); 158 parents.remove(two); 159 for (Way way : parents) { 160 if ((one.hasKey("name") && way.hasKey("name") && way.get("name").equals(one.get("name"))) || 161 (one.hasKey("ref") && way.hasKey("ref") && way.get("ref").equals(one.get("ref")))) { 162 return true; 163 } 164 } 165 } 166 return false; 167 } 168 169 /** 170 * Returns true if an exception is found 171 * @param nodes Nodes that may be parts of roundabouts or other exceptions. 172 * @return true if an exception is found 173 */ 174 private boolean checkForExceptions(Node... nodes) { 175 Collection<String> exceptions = new HashSet<>(); 176 exceptions.add("junction"); 177 for (Node node : nodes) { 178 for (Way way : Utils.filteredCollection(node.getReferrers(), Way.class)) { 179 for (String exceptionKey : exceptions) { 180 if (way.hasKey(exceptionKey)) return true; 181 } 182 } 183 } 184 return false; 185 } 186 187 private void createCheckNearbyEndsError(int nearCase, List<Way> errored, Way one, Way two) { 188 if (nearCase <= 0) return; 189 List<Way> nearby = new ArrayList<>(); 190 nearby.add(one); 191 nearby.add(two); 192 List<WaySegment> segments = new ArrayList<>(); 193 if ((nearCase & 1) != 0 && !checkForExceptions(one.lastNode(), two.lastNode())) { 194 segments.add(new WaySegment(two, two.getNodesCount() - 2)); 195 segments.add(new WaySegment(one, one.getNodesCount() - 2)); 196 } 197 if ((nearCase & 2) != 0 && !checkForExceptions(two.lastNode(), one.firstNode())) { 198 segments.add(new WaySegment(two, two.getNodesCount() - 2)); 199 segments.add(new WaySegment(one, 0)); 200 } 201 if ((nearCase & 4) != 0 && !checkForExceptions(two.firstNode(), one.lastNode())) { 202 segments.add(new WaySegment(two, 0)); 203 segments.add(new WaySegment(one, one.getNodesCount() - 2)); 204 } 205 if ((nearCase & 8) != 0 && !checkForExceptions(two.firstNode(), one.firstNode())) { 206 segments.add(new WaySegment(two, 0)); 207 segments.add(new WaySegment(one, 0)); 208 } 209 errored.addAll(nearby); 210 allWays.removeAll(errored); 211 TestError.Builder testError = TestError.builder(this, Severity.WARNING, SHORT_DISCONNECT) 212 .primitives(nearby) 213 .highlightWaySegments(segments) 214 .message(tr("Disconnected road")); 215 errors.add(testError.build()); 216 } 217 218 /** 219 * Get nearby cases 220 * @param oneFirst The {@code LatLon} of the the first node of the first way 221 * @param oneLast The {@code LatLon} of the the last node of the first way 222 * @param twoFirst The {@code LatLon} of the the first node of the second way 223 * @param twoLast The {@code LatLon} of the the last node of the second way 224 * @return A bitwise int (8421 -> twoFirst/oneFirst, twoFirst/oneLast, twoLast/oneFirst, twoLast/oneLast) 225 * 226 */ 227 private int getNearCase(LatLon oneFirst, LatLon oneLast, LatLon twoFirst, LatLon twoLast) { 228 int returnInt = 0; 229 if (twoLast.greatCircleDistance(oneLast) <= MAX_DISTANCE) { 230 returnInt = returnInt | 1; 231 } 232 if (twoLast.greatCircleDistance(oneFirst) <= MAX_DISTANCE) { 233 returnInt = returnInt | 2; 234 } 235 if (twoFirst.greatCircleDistance(oneLast) <= MAX_DISTANCE) { 236 returnInt = returnInt | 4; 237 } 238 if (twoFirst.greatCircleDistance(oneFirst) <= MAX_DISTANCE) { 239 returnInt = returnInt | 8; 240 } 241 return returnInt; 242 } 243 244 /** 245 * Check nearby nodes to an intersection of two ways 246 * @param way1 A way to check an almost intersection with 247 * @param way2 A way to check an almost intersection with 248 */ 249 public void checkNearbyNodes(Way way1, Way way2) { 250 Collection<Node> intersectingNodes = getIntersectingNode(way1, way2); 251 if (intersectingNodes.isEmpty() || 252 (way1.isOneway() != 0 && way2.isOneway() != 0 && 253 ((way1.hasKey("name") && way1.get("name").equals(way2.get("name"))) || 254 (way1.hasKey("ref") && way1.get("ref").equals(way2.get("ref")))))) return; 255 for (Node intersectingNode : intersectingNodes) { 256 checkNearbyNodes(way1, way2, intersectingNode); 257 checkNearbyNodes(way2, way1, intersectingNode); 258 } 259 } 260 261 private void checkNearbyNodes(Way way1, Way way2, Node nearby) { 262 for (Node node : way1.getNeighbours(nearby)) { 263 if (node.equals(nearby) || way2.containsNode(node)) continue; 264 double distance = Geometry.getDistance(way2, node); 265 double angle = getSmallestAngle(way2, nearby, node); 266 if (((distance < MAX_DISTANCE && !node.isTagged()) 267 || (distance < MAX_DISTANCE_NODE_INFORMATION && node.isTagged())) 268 && angle < MAX_ANGLE) { 269 List<Way> primitiveIssues = new ArrayList<>(); 270 primitiveIssues.add(way1); 271 primitiveIssues.add(way2); 272 if (alreadyFoundInRelevantTest(primitiveIssues)) return; 273 274 List<WaySegment> waysegmentsOne = buildWaySegmentAroundNode(way1, nearby); 275 List<WaySegment> waysegmentsTwo = buildWaySegmentAroundNode(way2, nearby); 276 List<WaySegment> waysegments = new ArrayList<>(); 277 Node twoNear = null; 278 Node oneNear = null; 279 for (WaySegment twoSegment : waysegmentsTwo) { 280 if (angle == getSmallestAngle(twoSegment.toWay(), nearby, node)) { 281 waysegments.add(twoSegment); 282 twoNear = getNearNode(twoSegment, nearby); 283 break; 284 } 285 } 286 for (WaySegment oneSegment: waysegmentsOne) { 287 if (oneSegment.toWay().containsNode(node)) { 288 waysegments.add(oneSegment); 289 oneNear = getNearNode(oneSegment, nearby); 290 break; 291 } 292 } 293 double distance1 = Geometry.getDistance(waysegments.get(0).toWay(), twoNear); 294 double distance2 = Geometry.getDistance(waysegments.get(1).toWay(), oneNear); 295 if (Math.min(distance1, distance2) < MAX_DISTANCE_OVERLAPPING) 296 createNearlyOverlappingError(primitiveIssues, waysegments); 297 } 298 } 299 } 300 301 private void createNearlyOverlappingError(Collection<? extends OsmPrimitive> primitiveIssues, Collection<WaySegment> waysegments) { 302 TestError.Builder testError = TestError.builder(this, Severity.WARNING, NEARBY_NODE) 303 .primitives(primitiveIssues) 304 .highlightWaySegments(waysegments) 305 .message(tr("Nearly overlapping ways")); 306 errors.add(testError.build()); 307 } 308 309 private Node getNearNode(WaySegment segment, Node nearby) { 310 return segment.getFirstNode() != nearby ? segment.getFirstNode() : segment.getSecondNode(); 311 } 312 313 private List<WaySegment> buildWaySegmentAroundNode(Way way, Node node) { 314 List<WaySegment> waysegments = new ArrayList<>(); 315 int index = way.getNodes().indexOf(node); 316 if (index >= way.getNodesCount() - 1) index--; 317 waysegments.add(new WaySegment(way, index)); 318 if (index > 0) waysegments.add(new WaySegment(way, index - 1)); 319 return waysegments; 320 } 321 322 private boolean alreadyFoundInRelevantTest(Collection<? extends OsmPrimitive> primitiveIssues) { 323 List<TestError> tErrors = new ArrayList<>(); 324 if (previousErrors != null) tErrors.addAll(previousErrors); 325 tErrors.addAll(getErrors()); 326 for (TestError error : tErrors) { 327 int code = error.getCode(); 328 if ((code == SHORT_DISCONNECT || code == NEARBY_NODE 329 || code == OverlappingWays.OVERLAPPING_HIGHWAY 330 || code == OverlappingWays.DUPLICATE_WAY_SEGMENT 331 || code == OverlappingWays.OVERLAPPING_HIGHWAY_AREA 332 || code == OverlappingWays.OVERLAPPING_WAY 333 || code == OverlappingWays.OVERLAPPING_WAY_AREA 334 || code == OverlappingWays.OVERLAPPING_RAILWAY 335 || code == OverlappingWays.OVERLAPPING_RAILWAY_AREA) 336 && primitiveIssues.containsAll(error.getPrimitives())) { 337 return true; 338 } 339 } 340 return false; 341 } 342 343 /** 344 * Get the intersecting node of two ways 345 * @param way1 A way that (hopefully) intersects with way2 346 * @param way2 A way to find an intersection with 347 * @return A collection of nodes where the ways intersect 348 */ 349 public Collection<Node> getIntersectingNode(Way way1, Way way2) { 350 HashSet<Node> nodes = new HashSet<>(); 351 for (Node node : way1.getNodes()) { 352 if (way2.containsNode(node)) { 353 nodes.add(node); 354 } 355 } 356 return nodes; 357 } 358 359 /** 360 * Get the corner angle between nodes 361 * @param way The way with additional nodes 362 * @param intersection The node to get angles around 363 * @param comparison The node to get angles from 364 * @return The angle for comparison->intersection->(additional node) (normalized degrees) 365 */ 366 public double getSmallestAngle(Way way, Node intersection, Node comparison) { 367 Set<Node> neighbours = way.getNeighbours(intersection); 368 double angle = Double.MAX_VALUE; 369 EastNorth eastNorthIntersection = intersection.getEastNorth(); 370 EastNorth eastNorthComparison = comparison.getEastNorth(); 371 for (Node node : neighbours) { 372 EastNorth eastNorthNode = node.getEastNorth(); 373 double tAngle = Geometry.getCornerAngle(eastNorthComparison, eastNorthIntersection, eastNorthNode); 374 if (Math.abs(tAngle) < angle) angle = Math.abs(tAngle); 375 } 376 return Geometry.getNormalizedAngleInDegrees(angle); 377 } 378 } -
test/unit/org/openstreetmap/josm/data/validation/tests/IntersectionIssuesTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import java.util.Collection; 5 import java.util.HashSet; 6 import java.util.List; 7 8 import org.junit.Assert; 9 import org.junit.Rule; 10 import org.junit.Test; 11 import org.openstreetmap.josm.TestUtils; 12 import org.openstreetmap.josm.data.coor.LatLon; 13 import org.openstreetmap.josm.data.osm.DataSet; 14 import org.openstreetmap.josm.data.osm.Node; 15 import org.openstreetmap.josm.data.osm.OsmPrimitive; 16 import org.openstreetmap.josm.data.osm.Relation; 17 import org.openstreetmap.josm.data.osm.Way; 18 import org.openstreetmap.josm.data.validation.TestError; 19 import org.openstreetmap.josm.testutils.JOSMTestRules; 20 import org.openstreetmap.josm.tools.Utils; 21 22 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 23 24 /** 25 * JUnit Test of "Intersection Issues" validation test. 26 */ 27 public class IntersectionIssuesTest { 28 29 /** 30 * Setup test. 31 */ 32 @Rule 33 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 34 public JOSMTestRules test = new JOSMTestRules().projection(); 35 36 private static List<TestError> test(DataSet ds) throws Exception { 37 IntersectionIssues test = new IntersectionIssues(); 38 test.initialize(); 39 test.startTest(null); 40 for (Way w : ds.getWays()) { 41 test.visit(w); 42 } 43 test.endTest(); 44 return test.getErrors(); 45 } 46 47 private static Collection<OsmPrimitive> createCollection(OsmPrimitive... osmPrimitives) { 48 HashSet<OsmPrimitive> collection = new HashSet<>(); 49 for (OsmPrimitive primitive : osmPrimitives) { 50 if (collection.contains(primitive)) continue; 51 if (primitive instanceof Node) { 52 collection.add(primitive); 53 } else if (primitive instanceof Way) { 54 collection.addAll(((Way) primitive).getNodes()); 55 collection.add(primitive); 56 } else if (primitive instanceof Relation) { 57 Relation relation = (Relation) primitive; 58 collection.add(relation); 59 for (OsmPrimitive tPrim : relation.getMemberPrimitives()) { 60 collection.addAll(createCollection(tPrim)); 61 } 62 } 63 } 64 return collection; 65 } 66 67 /** 68 * This test area has 3 ways, where two have the same name and one has a 69 * different name where there is a short disconnect between the two with 70 * the same name. 71 * @return The collection of ways to test. 72 */ 73 private static Collection<OsmPrimitive> getTestArea1() { 74 Node node1 = new Node(new LatLon(43.85619540309, 18.36535033094)); 75 Node node2 = new Node(new LatLon(43.85658651031, 18.36534961159)); 76 Node node3 = new Node(new LatLon(43.85662897034, 18.36534953349)); 77 Node node4 = new Node(new LatLon(43.85694640771, 18.36534894963)); 78 Node node5 = new Node(new LatLon(43.85658576291, 18.36456808743)); 79 Node node6 = new Node(new LatLon(43.8566296379, 18.36604757608)); 80 81 Way way1 = TestUtils.newWay("highway=residential name=\"Test Road 1\"", 82 node1, node2); 83 Way way2 = TestUtils.newWay("highway=residential name=\"Test Road 1\"", 84 node3, node4); 85 Way way3 = TestUtils.newWay("highway=residential name=\"Test Road 2\"", 86 node5, node2, node3, node6); 87 88 return createCollection(way1, way2, way3); 89 } 90 91 private static Collection<OsmPrimitive> getTestArea2() { 92 Node node1 = new Node(new LatLon(43.85641709632, 18.36725849681)); 93 Node node2 = new Node(new LatLon(43.85680820208, 18.36725777746)); 94 Node node3 = new Node(new LatLon(43.85685066196, 18.36725769936)); 95 Node node4 = new Node(new LatLon(43.85716809815, 18.3672571155)); 96 Node node5 = new Node(new LatLon(43.85680745469, 18.3664762533)); 97 Node node6 = new Node(new LatLon(43.85685132951, 18.36795574195)); 98 Way way1 = TestUtils.newWay("highway=residential name=\"Test Road 1\"", 99 node1, node2); 100 Way way2 = TestUtils.newWay("highway=residential name=\"Test Road 1\"", 101 node2, node3); 102 Way way3 = TestUtils.newWay("highway=residential name=\"Test Road 1\"", 103 node3, node4); 104 Way way4 = TestUtils.newWay("highway=residential name=\"Test Road 2\"", 105 node5, node2); 106 Way way5 = TestUtils.newWay("highway=residential name=\"Test Road 2\"", 107 node3, node6); 108 109 return createCollection(way1, way2, way3, way4, way5); 110 } 111 112 private static Collection<OsmPrimitive> getTestArea3() { 113 Node node1 = new Node(new LatLon(43.85570051259, 18.36651114378)); 114 Node node2 = new Node(new LatLon(43.85613408344, 18.36651034633)); 115 Node node3 = new Node(new LatLon(43.85645152344, 18.36650976248)); 116 Node node4 = new Node(new LatLon(43.85609087565, 18.36572890027)); 117 Node node5 = new Node(new LatLon(43.85609162303, 18.3665104064)); 118 Node node6 = new Node(new LatLon(43.85613475101, 18.36720838893)); 119 Way way1 = TestUtils.newWay("highway=residential name=\"Test Road 1\"", 120 node1, node2, node3); 121 Way way2 = TestUtils.newWay("highway=residential name=\"Test Road 2\"", 122 node4, node5, node2, node6); 123 124 return createCollection(way1, way2); 125 } 126 127 /** 128 * This is a section of road that should be found (almost overlapping) 129 * @return A collection of ways and nodes to be tested 130 */ 131 private static Collection<OsmPrimitive> getTestAreaRealWorld1() { 132 // This was at https://www.openstreetmap.org/node/6123937677 133 Node node1 = new Node(new LatLon(16.4151329, -95.0267841)); 134 Node node2 = new Node(new LatLon(16.4150313, -95.0267948)); 135 Node node3 = new Node(new LatLon(16.4149297, -95.0268057)); 136 Way way1 = TestUtils.newWay("highway=residential name=Calle Los Olivos", 137 node1, node3); 138 Way way2 = TestUtils.newWay( 139 "highway=residential name=Calle Camino Carretero (La Amistad)", 140 node1, node2, node3); 141 142 return createCollection(way1, way2); 143 } 144 145 /** 146 * This is a section of road that is almost overlapping 147 * @return A collection of ways and nodes to be tested 148 */ 149 private static Collection<OsmPrimitive> getTestAreaRealWorld2() { 150 // this was at https://www.openstreetmap.org/way/435783782 151 Node node1 = new Node(new LatLon(38.9881335, 31.1968857)); 152 Node node2 = new Node(new LatLon(38.9879932, 31.1968928)); 153 Node node3 = new Node(new LatLon(38.9877181, 31.1969176)); 154 Node node4 = new Node(new LatLon(38.9873522, 31.1969247)); 155 Node node5 = new Node(new LatLon(38.9870915, 31.1969116)); 156 157 Way way1 = TestUtils.newWay("highway=track", node1, node2, node3, node4, node5); 158 Way way2 = TestUtils.newWay("highway=track", node1, node5); 159 160 return createCollection(way1, way2); 161 } 162 163 /** 164 * This is a roundabout that happened to be a false positive for the 165 * disconnected road test 166 * @return A collection of ways and nodes to be tested 167 */ 168 private static Collection<OsmPrimitive> getTestAreaRealWorld3() { 169 // this was at https://www.openstreetmap.org/way/50635342 170 Node node1 = new Node(new LatLon(38.3260143, 26.3085291)); 171 172 // This probably should have been a junction=roundabout, but I don't know that for certain 173 Way way1 = TestUtils.newWay("highway=secondary name=\"Atatürk Blv.\" oneway=yes ref=D300", node1); 174 way1.addNode(new Node(new LatLon(38.3260353, 26.308504))); 175 way1.addNode(new Node(new LatLon(38.3260507, 26.308473))); 176 way1.addNode(new Node(new LatLon(38.3260596, 26.308438))); 177 way1.addNode(new Node(new LatLon(38.3260612, 26.3083978))); 178 way1.addNode(new Node(new LatLon(38.3260542, 26.3083586))); 179 way1.addNode(new Node(new LatLon(38.326039, 26.3083233))); 180 way1.addNode(new Node(new LatLon(38.3260169, 26.3082945))); 181 way1.addNode(new Node(new LatLon(38.3259895, 26.3082745))); 182 way1.addNode(new Node(new LatLon(38.3259562, 26.3082644))); 183 way1.addNode(new Node(new LatLon(38.325922, 26.3082673))); 184 way1.addNode(new Node(new LatLon(38.3258899, 26.3082829))); 185 way1.addNode(new Node(new LatLon(38.3258629, 26.3083099))); 186 way1.addNode(new Node(new LatLon(38.3258434, 26.3083458))); 187 way1.addNode(new Node(new LatLon(38.325833, 26.3083874))); 188 way1.addNode(new Node(new LatLon(38.3258326, 26.3084308))); 189 way1.addNode(new Node(new LatLon(38.3258422, 26.3084723))); 190 way1.addNode(new Node(new LatLon(38.3258609, 26.3085085))); 191 way1.addNode(new Node(new LatLon(38.3258821, 26.3085321))); 192 way1.addNode(new Node(new LatLon(38.3259071, 26.3085484))); 193 way1.addNode(new Node(new LatLon(38.3259345, 26.3085564))); 194 way1.addNode(new Node(new LatLon(38.3259626, 26.3085558))); 195 way1.addNode(new Node(new LatLon(38.3259898, 26.3085465))); 196 way1.addNode(node1); 197 198 Way way2 = TestUtils.newWay("highway=secondary name=\"Atatürk Blv.\" oneway=yes ref=D300", node1); 199 way2.addNode(new Node(new LatLon(38.3259914, 26.3086145))); 200 way2.addNode(new Node(new LatLon(38.3259765, 26.3087124))); 201 way2.addNode(new Node(new LatLon(38.3259728, 26.3087982))); 202 203 return createCollection(way1, way2); 204 } 205 206 private static Collection<OsmPrimitive> getTestAreaRealWorld4() { 207 // This was at https://www.openstreetmap.org/way/584296023 208 Node node1 = new Node(new LatLon(14.3272233, 120.9600503)); 209 Node node2 = new Node(new LatLon(14.326624, 120.9605805)); 210 Way way1 = TestUtils.newWay("highway=service oneway=yes name=\'Congressional Avenue Transit Lane\"", node1, 211 new Node(new LatLon(14.3270856, 120.9601202)), 212 new Node(new LatLon(14.326801, 120.9603738)), node2); 213 Way way2 = TestUtils.newWay("highway=tertiary name=\"Congressional Avenue\" oneway=yes", node1, node2); 214 215 return createCollection(way1, way2); 216 } 217 218 private static DataSet createDataSet(Collection<OsmPrimitive> primitives) { 219 DataSet ds = new DataSet(); 220 for (Node node : Utils.filteredCollection(primitives, Node.class)) { 221 if (ds.containsNode(node)) continue; 222 ds.addPrimitive(node); 223 } 224 for (Way way : Utils.filteredCollection(primitives, Way.class)) { 225 for (Node node : way.getNodes()) { 226 if (ds.containsNode(node)) continue; 227 ds.addPrimitive(node); 228 } 229 if (ds.containsWay(way)) continue; 230 ds.addPrimitive(way); 231 } 232 return ds; 233 } 234 235 /** 236 * Unit test for {@link IntersectionIssues#checkNearbyEnds} 237 * @throws Exception if any error occurs 238 */ 239 @Test 240 public void testCheckNearbyEnds() throws Exception { 241 /** TODO add the following real world areas: 242 * https://www.openstreetmap.org/way/261432285 243 */ 244 DataSet area1 = createDataSet(getTestArea1()); 245 List<TestError> testResults = test(area1); 246 Assert.assertEquals(1, testResults.size()); 247 Assert.assertEquals(IntersectionIssues.SHORT_DISCONNECT, testResults.get(0).getCode()); 248 249 DataSet area2 = createDataSet(getTestArea2()); 250 testResults = test(area2); 251 Assert.assertEquals(1, testResults.size()); 252 Assert.assertEquals(IntersectionIssues.SHORT_DISCONNECT, testResults.get(0).getCode()); 253 254 area1.mergeFrom(area2); 255 testResults = test(area1); 256 Assert.assertEquals(2, testResults.size()); 257 for (TestError error : testResults) { 258 Assert.assertEquals(IntersectionIssues.SHORT_DISCONNECT, error.getCode()); 259 } 260 261 testResults = test(createDataSet(getTestAreaRealWorld2())); 262 Assert.assertEquals(1, testResults.size()); 263 Assert.assertNotEquals(IntersectionIssues.SHORT_DISCONNECT, testResults.get(0).getCode()); 264 265 testResults = test(createDataSet(getTestAreaRealWorld3())); 266 if (testResults.size() == 1) 267 Assert.assertNotEquals(IntersectionIssues.SHORT_DISCONNECT, testResults.get(0).getCode()); 268 Assert.assertTrue(testResults.size() <= 1); 269 } 270 271 /** 272 * Unit test for {@link IntersectionIssues#checkNearbyNodes} 273 * @throws Exception if any error occurs 274 */ 275 @Test 276 public void testCheckAlmostOverlappingWays() throws Exception { 277 List<TestError> testResults = test(createDataSet(getTestArea1())); 278 Assert.assertEquals(1, testResults.size()); 279 Assert.assertNotEquals(IntersectionIssues.NEARBY_NODE, testResults.get(0).getCode()); 280 281 testResults = test(createDataSet(getTestArea3())); 282 Assert.assertEquals(1, testResults.size()); 283 Assert.assertEquals(IntersectionIssues.NEARBY_NODE, testResults.get(0).getCode()); 284 285 testResults = test(createDataSet(getTestAreaRealWorld1())); 286 Assert.assertEquals(1, testResults.size()); 287 Assert.assertEquals(IntersectionIssues.NEARBY_NODE, testResults.get(0).getCode()); 288 289 DataSet area4 = createDataSet(getTestAreaRealWorld2()); 290 testResults = test(area4); 291 Assert.assertEquals(1, testResults.size()); 292 Assert.assertEquals(IntersectionIssues.NEARBY_NODE, testResults.get(0).getCode()); 293 294 testResults = test(createDataSet(getTestAreaRealWorld4())); 295 Assert.assertEquals(0, testResults.size()); 296 } 297 }