Ticket #18364: 18364.2.patch
File 18364.2.patch, 65.6 KB (added by , 4 years ago) |
---|
-
src/org/openstreetmap/josm/data/validation/OsmValidator.java
60 60 import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest; 61 61 import org.openstreetmap.josm.data.validation.tests.RelationChecker; 62 62 import org.openstreetmap.josm.data.validation.tests.RightAngleBuildingTest; 63 import org.openstreetmap.josm.data.validation.tests.RoutingIslandsTest; 63 64 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay; 64 65 import org.openstreetmap.josm.data.validation.tests.SharpAngles; 65 66 import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays; … … 150 151 PublicTransportRouteTest.class, // 3600 .. 3699 151 152 RightAngleBuildingTest.class, // 3700 .. 3799 152 153 SharpAngles.class, // 3800 .. 3899 154 RoutingIslandsTest.class, // 3900 .. 3999 153 155 }; 154 156 155 157 /** -
src/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTest.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.Arrays; 8 import java.util.Collection; 9 import java.util.Collections; 10 import java.util.HashMap; 11 import java.util.HashSet; 12 import java.util.List; 13 import java.util.Map; 14 import java.util.Set; 15 import java.util.function.BiPredicate; 16 import java.util.stream.Collectors; 17 18 import org.openstreetmap.josm.data.osm.Node; 19 import org.openstreetmap.josm.data.osm.OsmPrimitive; 20 import org.openstreetmap.josm.data.osm.Relation; 21 import org.openstreetmap.josm.data.osm.TagMap; 22 import org.openstreetmap.josm.data.osm.Way; 23 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 24 import org.openstreetmap.josm.data.validation.Severity; 25 import org.openstreetmap.josm.data.validation.Test; 26 import org.openstreetmap.josm.data.validation.TestError; 27 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 28 import org.openstreetmap.josm.spi.preferences.Config; 29 import org.openstreetmap.josm.tools.Access; 30 import org.openstreetmap.josm.tools.Pair; 31 32 /** 33 * A test for routing islands 34 * 35 * @author Taylor Smock 36 * @since xxx 37 */ 38 public class RoutingIslandsTest extends Test { 39 40 private static final Map<Integer, Severity> SEVERITY_MAP = new HashMap<>(); 41 /** The code for the routing island validation test */ 42 public static final int ROUTING_ISLAND = 3900; 43 static { 44 SEVERITY_MAP.put(ROUTING_ISLAND, Severity.OTHER); 45 } 46 /** 47 * This is mostly as a sanity check, and to avoid infinite recursion (shouldn't 48 * happen, but still) 49 */ 50 private static final int MAX_LOOPS = 1000; 51 private Set<Way> potentialWays; 52 53 /** 54 * Constructs a new {@code RightAngleBuildingTest} test. 55 */ 56 public RoutingIslandsTest() { 57 super(tr("Routing islands"), tr("Checks for roads that cannot be reached or left.")); 58 super.setPartialSelection(false); 59 } 60 61 @Override 62 public void startTest(ProgressMonitor monitor) { 63 super.startTest(monitor); 64 potentialWays = new HashSet<>(); 65 } 66 67 @Override 68 public void endTest() { 69 for (String mode : Access.getTransportModes()) { 70 progressMonitor.setCustomText(mode); 71 runTest(mode); 72 } 73 super.endTest(); 74 } 75 76 /** 77 * TODO Turn restrictions 78 */ 79 80 /** 81 * May connect to: 1) A road leaving the fully downloaded BBox 2) A 82 * dock/ferry/other waterway loading point 3) An aeroport 83 */ 84 85 @Override 86 public void visit(Way way) { 87 if ((ValidatorPrefHelper.PREF_OTHER.get() || ValidatorPrefHelper.PREF_OTHER_UPLOAD.get() 88 || !Severity.OTHER.equals(SEVERITY_MAP.get(ROUTING_ISLAND))) 89 && way.hasKey("highway") && way.isUsable() 90 && way.getNodes().parallelStream().anyMatch(node -> way.getDataSet().getDataSourceBounds() 91 .parallelStream().anyMatch(source -> source.contains(node.getCoor())))) 92 potentialWays.add(way); 93 } 94 95 private void runTest(String currentTransportMode) { 96 Set<Way> incomingWays = new HashSet<>(); 97 Set<Way> outgoingWays = new HashSet<>(); 98 for (Way way : potentialWays) { 99 if (way.isUsable() && way.isOutsideDownloadArea()) { 100 Node firstNode = firstNode(way, currentTransportMode); 101 Node lastNode = lastNode(way, currentTransportMode); 102 if (isOneway(way, currentTransportMode) != 0 103 && firstNode != null && firstNode.isOutsideDownloadArea()) 104 incomingWays.add(way); 105 if (isOneway(way, currentTransportMode) != 0 106 && lastNode != null && lastNode.isOutsideDownloadArea()) { 107 outgoingWays.add(way); 108 } 109 if (isOneway(way, currentTransportMode) == 0 && firstNode != null // Don't need to test lastNode 110 && (way.firstNode().isOutsideDownloadArea() || way.lastNode().isOutsideDownloadArea())) { 111 incomingWays.add(way); 112 outgoingWays.add(way); 113 } 114 } 115 } 116 Set<Way> toIgnore = potentialWays.parallelStream() 117 .filter(way -> incomingWays.contains(way) || outgoingWays.contains(way)) 118 .filter(way -> !Access.getPositiveAccessValues().contains( 119 getDefaultAccessTags(way).getOrDefault(currentTransportMode, Access.AccessTags.NO.getKey()))) 120 .collect(Collectors.toSet()); 121 incomingWays.removeAll(toIgnore); 122 outgoingWays.removeAll(toIgnore); 123 124 Set<Way> toCheck = potentialWays.parallelStream() 125 .filter(way -> !incomingWays.contains(way) && !outgoingWays.contains(way)).collect(Collectors.toSet()); 126 checkForUnconnectedWays(incomingWays, outgoingWays, currentTransportMode); 127 List<Pair<String, Set<Way>>> problematic = collectConnected(potentialWays.parallelStream() 128 .filter(way -> !incomingWays.contains(way) || !outgoingWays.contains(way)) 129 .filter(way -> Access.getPositiveAccessValues().contains( 130 getDefaultAccessTags(way).getOrDefault(currentTransportMode, Access.AccessTags.NO.getKey()))) 131 .collect(Collectors.toSet())).parallelStream() 132 .map(way -> new Pair<>((incomingWays.containsAll(way) ? "outgoing" : "incoming"), way)) 133 .collect(Collectors.toList()); 134 createErrors(problematic, currentTransportMode); 135 } 136 137 private static List<Set<Way>> collectConnected(Collection<Way> ways) { 138 ArrayList<Set<Way>> collected = new ArrayList<>(); 139 ArrayList<Way> listOfWays = new ArrayList<>(ways); 140 final int maxLoop = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS); 141 for (int i = 0; i < listOfWays.size(); i++) { 142 Way initial = listOfWays.get(i); 143 Set<Way> connected = new HashSet<>(); 144 connected.add(initial); 145 int loopCounter = 0; 146 while (!getConnected(connected) && loopCounter < maxLoop) { 147 loopCounter++; 148 } 149 if (listOfWays.removeAll(connected)) 150 i--; // NOSONAR not an issue -- this ensures that everything is accounted for, only 151 // triggers when ways removed 152 collected.add(connected); 153 } 154 return collected; 155 } 156 157 private static boolean getConnected(Collection<Way> ways) { 158 TagMap defaultAccess = getDefaultAccessTags(ways.iterator().next()); 159 return ways.addAll(ways.parallelStream().flatMap(way -> way.getNodes().parallelStream()) 160 .flatMap(node -> node.getReferrers().parallelStream()).filter(Way.class::isInstance) 161 .map(Way.class::cast).filter(way -> getDefaultAccessTags(way).equals(defaultAccess)) 162 .collect(Collectors.toSet())); 163 } 164 165 private void createErrors(List<Pair<String, Set<Way>>> problematic, String mode) { 166 for (Pair<String, Set<Way>> ways : problematic) { 167 errors.add(TestError 168 .builder(this, SEVERITY_MAP.getOrDefault(ROUTING_ISLAND, Severity.OTHER), ROUTING_ISLAND) 169 .message(tr("Routing island"), "{0}: {1}", tr(ways.a), mode == null ? "default" : mode) 170 .primitives(ways.b).build()); 171 potentialWays.removeAll(ways.b); 172 } 173 } 174 175 /** 176 * Check for unconnected ways 177 * 178 * @param incoming The current incoming ways (will be modified) 179 * @param outgoing The current outgoing ways (will be modified) 180 * @param currentTransportMode The transport mode we are investigating (may be 181 * {@code null}) 182 */ 183 public static void checkForUnconnectedWays(Collection<Way> incoming, 184 Collection<Way> outgoing, String currentTransportMode) { 185 int loopCount = 0; 186 int maxLoops = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS); 187 do { 188 loopCount++; 189 } while (loopCount <= maxLoops && getWaysFor(incoming, currentTransportMode, 190 (way, oldWay) -> oldWay.containsNode(firstNode(way, currentTransportMode)))); 191 loopCount = 0; 192 do { 193 loopCount++; 194 } while (loopCount <= maxLoops && getWaysFor(outgoing, currentTransportMode, 195 (way, oldWay) -> oldWay.containsNode(lastNode(way, currentTransportMode)))); 196 } 197 198 private static boolean getWaysFor(Collection<Way> directional, String currentTransportMode, 199 BiPredicate<Way, Way> predicate) { 200 Set<Way> toAdd = new HashSet<>(); 201 for (Way way : directional) { 202 for (Node node : way.getNodes()) { 203 Set<Way> referrers = node.getReferrers(true).parallelStream().filter(Way.class::isInstance) 204 .map(Way.class::cast).filter(tWay -> !directional.contains(tWay)).collect(Collectors.toSet()); 205 for (Way tWay : referrers) { 206 if (isOneway(tWay, currentTransportMode) == 0 || predicate.test(tWay, way) 207 || tWay.hasKey("junction")) { 208 toAdd.add(tWay); 209 } 210 } 211 } 212 } 213 return directional.addAll(toAdd); 214 } 215 216 /** 217 * Check if I can get to way to from way from (currently doesn't work with via 218 * ways) 219 * 220 * @param from The from way 221 * @param to The to way 222 * @param currentTransportMode The specific transport mode to check 223 * @return {@code true} if the to way can be accessed from the from way TODO 224 * clean up and work with via ways 225 */ 226 public static boolean checkAccessibility(Way from, Way to, String currentTransportMode) { 227 boolean isAccessible = true; 228 229 List<Relation> relations = from.getReferrers().parallelStream().distinct().filter(Relation.class::isInstance) 230 .map(Relation.class::cast).filter(relation -> "restriction".equals(relation.get("type"))) 231 .collect(Collectors.toList()); 232 for (Relation relation : relations) { 233 if (((relation.hasKey("except") && relation.get("except").contains(currentTransportMode)) 234 || (currentTransportMode == null || currentTransportMode.trim().isEmpty())) 235 && relation.getMembersFor(Collections.singleton(from)).parallelStream() 236 .anyMatch(member -> "from".equals(member.getRole())) 237 && relation.getMembersFor(Collections.singleton(to)).parallelStream() 238 .anyMatch(member -> "to".equals(member.getRole()))) { 239 isAccessible = false; 240 } 241 } 242 243 return isAccessible; 244 } 245 246 /** 247 * Check if a node connects to the outside world 248 * 249 * @param node The node to check 250 * @return true if outside download area, connects to an aeroport, or a water 251 * transport 252 */ 253 public static Boolean outsideConnections(Node node) { 254 boolean outsideConnections = false; 255 if (node.isOutsideDownloadArea() || node.hasTag("amenity", "parking_entrance", "parking", "parking_space", 256 "motorcycle_parking", "ferry_terminal")) 257 outsideConnections = true; 258 return outsideConnections; 259 } 260 261 /** 262 * Check if a way is oneway for a specific transport type 263 * 264 * @param way The way to look at 265 * @param transportType The specific transport type 266 * @return See {@link Way#isOneway} (but may additionally return {@code null} if 267 * the transport type cannot route down that way) 268 */ 269 public static Integer isOneway(Way way, String transportType) { 270 if (transportType == null || transportType.trim().isEmpty()) { 271 return way.isOneway(); 272 } 273 String forward = transportType.concat(":forward"); 274 String backward = transportType.concat(":backward"); 275 boolean possibleForward = "yes".equals(way.get(forward)) 276 || (!way.hasKey(forward) && way.isOneway() != -1); 277 boolean possibleBackward = "yes".equals(way.get(backward)) 278 || (!way.hasKey(backward) && way.isOneway() != 1); 279 if (possibleForward && !possibleBackward) { 280 return 1; 281 } else if (!possibleForward && possibleBackward) { 282 return -1; 283 } else if (!possibleBackward) { 284 return null; 285 } 286 return 0; 287 } 288 289 /** 290 * Get the first node of a way respecting the oneway for a transport type 291 * 292 * @param way The way to get the node from 293 * @param transportType The transport type 294 * @return The first node for the specified transport type, or null if it is not 295 * routable 296 */ 297 public static Node firstNode(Way way, String transportType) { 298 Integer oneway = isOneway(way, transportType); 299 Node node = (Integer.valueOf(-1).equals(oneway)) ? way.lastNode() : way.firstNode(); 300 Way tWay = new Way(way); 301 tWay.clearOsmMetadata(); 302 303 Map<String, String> accessValues = getDefaultAccessTags(way); 304 boolean accessible = Access.getPositiveAccessValues() 305 .contains(accessValues.getOrDefault(transportType, Access.AccessTags.NO.getKey())); 306 return (transportType == null || accessible) ? node : null; 307 308 } 309 310 /** 311 * Get the last node of a way respecting the oneway for a transport type 312 * 313 * @param way The way to get the node from 314 * @param transportType The transport type 315 * @return The last node for the specified transport type, or the last node of 316 * the way, or null if it is not routable 317 */ 318 public static Node lastNode(Way way, String transportType) { 319 Integer oneway = isOneway(way, transportType); 320 Node node = (Integer.valueOf(-1).equals(oneway)) ? way.firstNode() : way.lastNode(); 321 Map<String, String> accessValues = getDefaultAccessTags(way); 322 boolean accessible = Access.getPositiveAccessValues() 323 .contains(accessValues.getOrDefault(transportType, Access.AccessTags.NO.getKey())); 324 return (transportType == null || accessible) ? node : null; 325 } 326 327 /** 328 * Get the default access tags for a primitive 329 * 330 * @param primitive The primitive to get access tags for 331 * @return The map of access tags to access 332 */ 333 public static TagMap getDefaultAccessTags(OsmPrimitive primitive) { 334 TagMap access = new TagMap(); 335 if (primitive.hasKey("highway")) { 336 TagMap tags = primitive.getKeys(); 337 String highway = primitive.get("highway"); 338 339 if (tags.containsKey("sidewalk") && !tags.get("sidewalk").equals(Access.AccessTags.NO.getKey())) { 340 tags.putIfAbsent(Access.AccessTags.FOOT.getKey(), Access.AccessTags.YES.getKey()); 341 } 342 343 if ("residential".equals(highway)) { 344 tags.putIfAbsent(Access.AccessTags.VEHICLE.getKey(), Access.AccessTags.YES.getKey()); 345 tags.putIfAbsent(Access.AccessTags.FOOT.getKey(), Access.AccessTags.YES.getKey()); 346 tags.putIfAbsent(Access.AccessTags.BICYCLE.getKey(), Access.AccessTags.YES.getKey()); 347 } else if (Arrays.asList("service", "unclassified", "tertiary", "tertiary_link").contains(highway)) { 348 tags.putIfAbsent(Access.AccessTags.VEHICLE.getKey(), Access.AccessTags.YES.getKey()); 349 } else if (Arrays.asList("secondary", "secondary_link").contains(highway)) { 350 tags.putIfAbsent(Access.AccessTags.VEHICLE.getKey(), Access.AccessTags.YES.getKey()); 351 } else if (Arrays.asList("primary", "primary_link").contains(highway)) { 352 tags.putIfAbsent(Access.AccessTags.VEHICLE.getKey(), Access.AccessTags.YES.getKey()); 353 tags.putIfAbsent(Access.AccessTags.HGV.getKey(), Access.AccessTags.YES.getKey()); 354 } else if (Arrays.asList("motorway", "trunk", "motorway_link", "trunk_link").contains(highway)) { 355 tags.putIfAbsent(Access.AccessTags.VEHICLE.getKey(), Access.AccessTags.YES.getKey()); 356 tags.putIfAbsent(Access.AccessTags.BICYCLE.getKey(), Access.AccessTags.NO.getKey()); 357 tags.putIfAbsent(Access.AccessTags.FOOT.getKey(), Access.AccessTags.NO.getKey()); 358 } else if ("steps".equals(highway)) { 359 tags.putIfAbsent(Access.AccessTags.ACCESS_KEY.getKey(), Access.AccessTags.NO.getKey()); 360 tags.putIfAbsent(Access.AccessTags.FOOT.getKey(), Access.AccessTags.YES.getKey()); 361 } else if ("path".equals(highway)) { 362 tags.putIfAbsent(Access.AccessTags.MOTOR_VEHICLE.getKey(), Access.AccessTags.NO.getKey()); 363 tags.putIfAbsent(Access.AccessTags.EMERGENCY.getKey(), Access.AccessTags.DESTINATION.getKey()); 364 } else if ("footway".equals(highway)) { 365 tags.putIfAbsent(Access.AccessTags.FOOT.getKey(), Access.AccessTags.DESIGNATED.getKey()); 366 } else if ("bus_guideway".equals(highway)) { 367 tags.putIfAbsent(Access.AccessTags.ACCESS_KEY.getKey(), Access.AccessTags.NO.getKey()); 368 tags.putIfAbsent(Access.AccessTags.BUS.getKey(), Access.AccessTags.DESIGNATED.getKey()); 369 } else if ("road".equals(highway)) { // Don't expect these to be routable 370 tags.putIfAbsent(Access.AccessTags.ACCESS_KEY.getKey(), Access.AccessTags.NO.getKey()); 371 } else { 372 tags.putIfAbsent(Access.AccessTags.ACCESS_KEY.getKey(), Access.AccessTags.YES.getKey()); 373 } 374 375 tags.putAll(Access.expandAccessValues(tags)); 376 377 for (String direction : Arrays.asList("", "forward:", "backward:")) { 378 Access.getTransportModes().parallelStream().map(direction::concat).filter(tags::containsKey) 379 .forEach(mode -> access.put(mode, tags.get(direction.concat(mode)))); 380 } 381 } 382 return access; 383 } 384 385 /** 386 * Get the error level for a test 387 * 388 * @param test The integer value of the test error 389 * @return The severity for the test 390 */ 391 public static Severity getErrorLevel(int test) { 392 return SEVERITY_MAP.get(test); 393 } 394 395 /** 396 * Set the error level for a test 397 * 398 * @param test The integer value of the test error 399 * @param severity The new severity for the test 400 */ 401 public static void setErrorLevel(int test, Severity severity) { 402 SEVERITY_MAP.put(test, severity); 403 } 404 } -
src/org/openstreetmap/josm/tools/Access.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.tools; 3 4 import java.util.ArrayList; 5 import java.util.Arrays; 6 import java.util.Collections; 7 import java.util.Comparator; 8 import java.util.HashMap; 9 import java.util.HashSet; 10 import java.util.List; 11 import java.util.Map; 12 import java.util.Map.Entry; 13 import java.util.Objects; 14 import java.util.Set; 15 import java.util.stream.Collectors; 16 17 import org.openstreetmap.josm.data.osm.OsmPrimitive; 18 19 /** 20 * Access tag related utilities 21 * 22 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:access">Key:access</a> 23 * 24 * @author Taylor Smock 25 * @since xxx 26 */ 27 public class Access { 28 /** 29 * Holds access tags to avoid typos 30 */ 31 public enum AccessTags { 32 /** Air, land, and sea */ 33 ALL_TRANSPORT_TYPE("all"), 34 35 /** 36 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:access">Key:access</a> 37 */ 38 ACCESS_KEY("access", ALL_TRANSPORT_TYPE), 39 40 // Access tag values 41 /** 42 * @see <a href= 43 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dyes">Tag:access%3Dyes</a> 44 */ 45 YES("yes"), 46 /** 47 * @see <a href= 48 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dofficial">Tag:access%3Dofficial</a> 49 */ 50 OFFICIAL("official"), 51 /** 52 * @see <a href= 53 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Ddesignated">Tag:access%3Ddesignated</a> 54 */ 55 DESIGNATED("designated"), 56 /** 57 * @see <a href= 58 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Ddestination">Tag:access%3Ddestination</a> 59 */ 60 DESTINATION("destination"), 61 /** 62 * @see <a href= 63 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Ddelivery">Tag:access%3Ddelivery</a> 64 */ 65 DELIVERY("delivery"), 66 /** 67 * @see <a href= 68 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dcustomers">Tag:access%3Dcustomers</a> 69 */ 70 CUSTOMERS("customers"), 71 /** 72 * @see <a href= 73 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dpermissive">Tag:access%3Dpermissive</a> 74 */ 75 PERMISSIVE("permissive"), 76 /** 77 * @see <a href= 78 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dagricultural">Tag:access%3Dagricultural</a> 79 */ 80 AGRICULTURAL("agricultural"), 81 /** 82 * @see <a href= 83 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dforestry">Tag:access%3Dforestry</a> 84 */ 85 FORESTRY("forestry"), 86 /** 87 * @see <a href= 88 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dprivate">Tag:access%3Dprivate</a> 89 */ 90 PRIVATE("private"), 91 /** 92 * @see <a href= 93 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Dno">Tag:access%3Dno</a> 94 */ 95 NO("no"), 96 /** 97 * @see <a href= 98 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Ddiscouraged">Tag:access%3Ddiscouraged</a> 99 */ 100 DISCOURAGED("discouraged"), 101 /** 102 * @see <a href= 103 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Duse_sidepath">Tag:access%3Duse_sidepath</a> 104 */ 105 USE_SIDEPATH("use_sidepath"), 106 /** 107 * @see <a href= 108 * "https://wiki.openstreetmap.org/wiki/Tag:access%3Ddismount">Tag:access%3Ddismount</a> 109 */ 110 DISMOUNT("dismount"), 111 // Land 112 /** Land transport types */ 113 LAND_TRANSPORT_TYPE("land", ALL_TRANSPORT_TYPE), 114 /** 115 * @see <a href= 116 * "https://wiki.openstreetmap.org/wiki/Key:vehicle">Key:vehicle</a> 117 */ 118 VEHICLE("vehicle", LAND_TRANSPORT_TYPE), 119 /** 120 * @see <a href= 121 * "https://wiki.openstreetmap.org/wiki/Key:motor_vehicle">Key:motor_vehicle</a> 122 */ 123 MOTOR_VEHICLE("motor_vehicle", LAND_TRANSPORT_TYPE), 124 /** 125 * @see <a href= 126 * "https://wiki.openstreetmap.org/wiki/Key:trailer">Key:trailer</a> 127 */ 128 TRAILER("trailer", LAND_TRANSPORT_TYPE), 129 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:foot">Key:foot</a> */ 130 FOOT("foot", LAND_TRANSPORT_TYPE), 131 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:ski">Key:ski</a> */ 132 SKI("ski", LAND_TRANSPORT_TYPE), 133 /** 134 * @see <a href= 135 * "https://wiki.openstreetmap.org/wiki/Key:inline_skates">Key:inline_skates</a> 136 */ 137 INLINE_SKATES("inline_skates", LAND_TRANSPORT_TYPE), 138 /** 139 * @see <a href= 140 * "https://wiki.openstreetmap.org/wiki/Key:ice_skates">Key:ice_skates</a> 141 */ 142 ICE_SKATES("ice_skates", LAND_TRANSPORT_TYPE), 143 /** 144 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:horse">Key:horse</a> 145 */ 146 HORSE("horse", LAND_TRANSPORT_TYPE), 147 /** 148 * @see <a href= 149 * "https://wiki.openstreetmap.org/wiki/Key:bicycle">Key:bicycle</a> 150 */ 151 BICYCLE("bicycle", LAND_TRANSPORT_TYPE), 152 /** 153 * @see <a href= 154 * "https://wiki.openstreetmap.org/wiki/Key:carriage">Key:carriage</a> 155 */ 156 CARRIAGE("carriage", LAND_TRANSPORT_TYPE), 157 /** 158 * @see <a href= 159 * "https://wiki.openstreetmap.org/wiki/Key:caravan">Key:caravan</a> 160 */ 161 CARAVAN("caravan", LAND_TRANSPORT_TYPE), 162 /** 163 * @see <a href= 164 * "https://wiki.openstreetmap.org/wiki/Key:motorcycle">Key:motorcycle</a> 165 */ 166 MOTORCYCLE("motorcycle", LAND_TRANSPORT_TYPE), 167 /** 168 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:moped">Key:moped</a> 169 */ 170 MOPED("moped", LAND_TRANSPORT_TYPE), 171 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:mofa">Key:mofa</a> */ 172 MOFA("mofa", LAND_TRANSPORT_TYPE), 173 /** 174 * @see <a href= 175 * "https://wiki.openstreetmap.org/wiki/Key:motorcar">Key:motorcar</a> 176 */ 177 MOTORCAR("motorcar", LAND_TRANSPORT_TYPE), 178 /** 179 * @see <a href= 180 * "https://wiki.openstreetmap.org/wiki/Key:motorhome">Key:motorhome</a> 181 */ 182 MOTORHOME("motorhome", LAND_TRANSPORT_TYPE), 183 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:psv">Key:psv</a> */ 184 PSV("psv", LAND_TRANSPORT_TYPE), 185 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:bus">Key:bus</a> */ 186 BUS("bus", LAND_TRANSPORT_TYPE), 187 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:taxi">Key:taxi</a> */ 188 TAXI("taxi", LAND_TRANSPORT_TYPE), 189 /** 190 * @see <a href= 191 * "https://wiki.openstreetmap.org/wiki/Key:tourist_bus">Key:tourist_bus</a> 192 */ 193 TOURIST_BUS("tourist_bus", LAND_TRANSPORT_TYPE), 194 /** 195 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:goods">Key:goods</a> 196 */ 197 GOODS("goods", LAND_TRANSPORT_TYPE), 198 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:hgv">Key:hgv</a> */ 199 HGV("hgv", LAND_TRANSPORT_TYPE), 200 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:atv">Key:atv</a> */ 201 ATV("atv", LAND_TRANSPORT_TYPE), 202 /** 203 * @see <a href= 204 * "https://wiki.openstreetmap.org/wiki/Key:snowmobile">Key:snowmobile</a> 205 */ 206 SNOWMOBILE("snowmobile", LAND_TRANSPORT_TYPE), 207 /** 208 * @see <a href= 209 * "https://wiki.openstreetmap.org/wiki/Key:hgv_articulated">Key:hgv_articulated</a> 210 */ 211 HGV_ARTICULATED("hgv_articulated", LAND_TRANSPORT_TYPE), 212 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:ski">Key:ski</a> */ 213 SKI_NORDIC("ski:nordic", LAND_TRANSPORT_TYPE), 214 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:ski">Key:ski</a> */ 215 SKI_ALPINE("ski:alpine", LAND_TRANSPORT_TYPE), 216 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:ski">Key:ski</a> */ 217 SKI_TELEMARK("ski:telemark", LAND_TRANSPORT_TYPE), 218 /** 219 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:coach">Key:coach</a> 220 */ 221 COACH("coach", LAND_TRANSPORT_TYPE), 222 /** 223 * @see <a href= 224 * "https://wiki.openstreetmap.org/wiki/Key:golf_cart">Key:golf_cart</a> 225 */ 226 GOLF_CART("golf_cart", LAND_TRANSPORT_TYPE), 227 /** 228 * @see <a href= 229 * "https://wiki.openstreetmap.org/wiki/Key:minibus">Key:minibus</a> 230 */ 231 MINIBUS("minibus", LAND_TRANSPORT_TYPE), 232 /** 233 * @see <a href= 234 * "https://wiki.openstreetmap.org/wiki/Key:share_taxi">Key:share_taxi</a> 235 */ 236 SHARE_TAXI("share_taxi", LAND_TRANSPORT_TYPE), 237 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:hov">Key:hov</a> */ 238 HOV("hov", LAND_TRANSPORT_TYPE), 239 /** 240 * @see <a href= 241 * "https://wiki.openstreetmap.org/wiki/Key:car_sharing">Key:car_sharing</a> 242 */ 243 CAR_SHARING("car_sharing", LAND_TRANSPORT_TYPE), 244 /** 245 * Routers should default to {@code yes}, regardless of higher access rules, 246 * assuming it is navigatible by vehicle 247 * 248 * @see <a href= 249 * "https://wiki.openstreetmap.org/wiki/Key:emergency">Key:emergency</a> 250 */ 251 EMERGENCY("emergency", LAND_TRANSPORT_TYPE), 252 /** 253 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:hazmat">Key:hazmat</a> 254 */ 255 HAZMAT("hazmat", LAND_TRANSPORT_TYPE), 256 /** 257 * @see <a href= 258 * "https://wiki.openstreetmap.org/wiki/Key:disabled">Key:disabled</a> 259 */ 260 DISABLED("disabled", LAND_TRANSPORT_TYPE), 261 262 // Water 263 /** Water transport type */ 264 WATER_TRANSPORT_TYPE("water", ALL_TRANSPORT_TYPE), 265 /** 266 * @see <a href= 267 * "https://wiki.openstreetmap.org/wiki/Key:swimming">Key:swimming</a> 268 */ 269 SWIMMING("swimming", WATER_TRANSPORT_TYPE), 270 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:boat">Key:boat</a> */ 271 BOAT("boat", WATER_TRANSPORT_TYPE), 272 /** 273 * @see <a href= 274 * "https://wiki.openstreetmap.org/wiki/Key:fishing_vessel">Key:fishing_vessel</a> 275 */ 276 FISHING_VESSEL("fishing_vessel", WATER_TRANSPORT_TYPE), 277 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:ship">Key:ship</a> */ 278 SHIP("ship", WATER_TRANSPORT_TYPE), 279 /** 280 * @see <a href= 281 * "https://wiki.openstreetmap.org/wiki/Key:motorboat">Key:motorboat</a> 282 */ 283 MOTORBOAT("motorboat", WATER_TRANSPORT_TYPE), 284 /** 285 * @see <a href= 286 * "https://wiki.openstreetmap.org/wiki/Key:sailboat">Key:sailboat</a> 287 */ 288 SAILBOAT("sailboat", WATER_TRANSPORT_TYPE), 289 /** 290 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:canoe">Key:canoe</a> 291 */ 292 CANOE("canoe", WATER_TRANSPORT_TYPE), 293 /** 294 * @see <a href= 295 * "https://wiki.openstreetmap.org/wiki/Key:passenger">Key:passenger</a> 296 */ 297 PASSENGER("passenger", WATER_TRANSPORT_TYPE), 298 /** 299 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:cargo">Key:cargo</a> 300 */ 301 CARGO("cargo", WATER_TRANSPORT_TYPE), 302 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:isps">Key:isps</a> */ 303 ISPS("isps", WATER_TRANSPORT_TYPE), 304 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:bulk">Key:bulk</a> */ 305 BULK("bulk", WATER_TRANSPORT_TYPE), 306 /** 307 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:tanker">Key:tanker</a> 308 */ 309 TANKER("tanker", WATER_TRANSPORT_TYPE), 310 /** 311 * @see <a href= 312 * "https://wiki.openstreetmap.org/wiki/Key:container">Key:container</a> 313 */ 314 CONTAINER("container", WATER_TRANSPORT_TYPE), 315 /** @see <a href="https://wiki.openstreetmap.org/wiki/Key:imdg">Key:imdg</a> */ 316 IMDG("imdg", WATER_TRANSPORT_TYPE), 317 /** 318 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:tanker">Key:tanker</a> 319 */ 320 TANKER_GAS("tanker:gas", WATER_TRANSPORT_TYPE), 321 /** 322 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:tanker">Key:tanker</a> 323 */ 324 TANKER_OIL("tanker:oil", WATER_TRANSPORT_TYPE), 325 /** 326 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:tanker">Key:tanker</a> 327 */ 328 TANKER_CHEMICAL("tanker:chemical", WATER_TRANSPORT_TYPE), 329 /** 330 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:tanker">Key:tanker</a> 331 */ 332 TANKER_SINGLEHULL("tanker:singlehull", WATER_TRANSPORT_TYPE), 333 334 // Trains 335 /** Rail transport type */ 336 RAIL_TRANSPORT_TYPE("rail", ALL_TRANSPORT_TYPE), 337 /** 338 * @see <a href="https://wiki.openstreetmap.org/wiki/Key:train">Key:train</a> 339 */ 340 TRAIN("train", RAIL_TRANSPORT_TYPE); 341 342 private String key; 343 private AccessTags type; 344 345 AccessTags(String key) { 346 this.key = key; 347 this.type = null; 348 } 349 350 AccessTags(String key, AccessTags type) { 351 this.key = key; 352 this.type = type; 353 } 354 355 /** 356 * @return The key for the enum 357 */ 358 public String getKey() { 359 return key; 360 } 361 362 /** 363 * @return The AccessTags transport type 364 * (RAIL_TRANSPORT_TYPE/WATER_TRANSPORT_TYPE/etc) 365 */ 366 public AccessTags getTransportType() { 367 return type; 368 } 369 370 /** 371 * Check if this is a parent transport type (air/sea/water/all) 372 * 373 * @param potentialDescendant The AccessTags that we want to check 374 * @return true if valueOf is a child transport type of this 375 */ 376 public boolean parentOf(AccessTags potentialDescendant) { 377 AccessTags tmp = potentialDescendant; 378 while (tmp != null && tmp != this) { 379 tmp = tmp.getTransportType(); 380 } 381 return tmp == this; 382 } 383 384 /** 385 * Get the enum that matches the mode 386 * 387 * @param childrenMode The mode to get the access tag 388 * @return The AccessTags enum that matches the childrenMode, or null 389 */ 390 public static AccessTags get(String childrenMode) { 391 for (AccessTags value : values()) { 392 if (value.getKey().equalsIgnoreCase(childrenMode)) { 393 return value; 394 } 395 } 396 return null; 397 } 398 } 399 /** 400 * The key for children modes for the map, see {@link Access#getAccessMethods} 401 */ 402 public static final String CHILDREN = "children"; 403 /** The key for parent modes for the map, see {@link Access#getAccessMethods} */ 404 public static final String PARENT = "parent"; 405 /** This set has keys that indicate that access is possible */ 406 private static final Set<String> POSITIVE_ACCESS = new HashSet<>(Arrays.asList(AccessTags.YES, AccessTags.OFFICIAL, 407 AccessTags.DESIGNATED, AccessTags.DESTINATION, AccessTags.DELIVERY, AccessTags.CUSTOMERS, 408 AccessTags.PERMISSIVE, AccessTags.AGRICULTURAL, AccessTags.FORESTRY).stream().map(AccessTags::getKey) 409 .collect(Collectors.toSet())); 410 /** This set has all basic restriction values (yes/no/permissive/private/...) */ 411 private static final Set<String> RESTRICTION_VALUES = new HashSet<>( 412 Arrays.asList(AccessTags.PRIVATE, AccessTags.NO).stream().map(AccessTags::getKey) 413 .collect(Collectors.toSet())); 414 /** This set has transport modes (access/foot/ski/motor_vehicle/vehicle/...) */ 415 private static final Set<String> TRANSPORT_MODES = new HashSet<>( 416 Arrays.asList(AccessTags.ACCESS_KEY, AccessTags.FOOT, AccessTags.SKI, AccessTags.INLINE_SKATES, 417 AccessTags.ICE_SKATES, AccessTags.HORSE, AccessTags.VEHICLE, AccessTags.BICYCLE, 418 AccessTags.CARRIAGE, AccessTags.TRAILER, AccessTags.CARAVAN, AccessTags.MOTOR_VEHICLE, 419 AccessTags.MOTORCYCLE, AccessTags.MOPED, AccessTags.MOFA, AccessTags.MOTORCAR, AccessTags.MOTORHOME, 420 AccessTags.PSV, AccessTags.BUS, AccessTags.TAXI, AccessTags.TOURIST_BUS, AccessTags.GOODS, 421 AccessTags.HGV, AccessTags.AGRICULTURAL, AccessTags.ATV, 422 AccessTags.SNOWMOBILE, AccessTags.HGV_ARTICULATED, AccessTags.SKI_NORDIC, AccessTags.SKI_ALPINE, 423 AccessTags.SKI_TELEMARK, AccessTags.COACH, AccessTags.GOLF_CART 424 /* 425 * ,"minibus","share_taxi","hov","car_sharing","emergency","hazmat","disabled" 426 */).stream().map(AccessTags::getKey).collect(Collectors.toSet())); 427 428 /** Map<Access Method, Map<Parent/Child, List<Access Methods>> */ 429 private static final Map<String, Map<String, List<String>>> accessMethods = new HashMap<>(); 430 static { 431 RESTRICTION_VALUES.addAll(POSITIVE_ACCESS); 432 defaultInheritance(); 433 } 434 435 private Access() { 436 // Hide the constructor 437 } 438 439 /** 440 * Create the default access inheritance, as defined at 441 * {@link "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions"} 442 */ 443 private static void defaultInheritance() { 444 addMode(null, AccessTags.ACCESS_KEY); 445 446 // Land 447 addModes(AccessTags.ACCESS_KEY, AccessTags.FOOT, AccessTags.SKI, AccessTags.INLINE_SKATES, 448 AccessTags.ICE_SKATES, AccessTags.HORSE, AccessTags.VEHICLE); 449 addModes(AccessTags.SKI, AccessTags.SKI_NORDIC, AccessTags.SKI_ALPINE, AccessTags.SKI_TELEMARK); 450 addModes(AccessTags.VEHICLE, AccessTags.BICYCLE, AccessTags.CARRIAGE, AccessTags.TRAILER, 451 AccessTags.MOTOR_VEHICLE); 452 addModes(AccessTags.TRAILER, AccessTags.CARAVAN); 453 addModes(AccessTags.MOTOR_VEHICLE, AccessTags.MOTORCYCLE, AccessTags.MOPED, AccessTags.MOFA, 454 AccessTags.MOTORCAR, AccessTags.MOTORHOME, AccessTags.TOURIST_BUS, AccessTags.COACH, AccessTags.GOODS, 455 AccessTags.HGV, AccessTags.AGRICULTURAL, AccessTags.GOLF_CART, AccessTags.ATV, AccessTags.SNOWMOBILE, 456 AccessTags.PSV, AccessTags.HOV, AccessTags.CAR_SHARING, AccessTags.EMERGENCY, AccessTags.HAZMAT, 457 AccessTags.DISABLED); 458 addMode(AccessTags.HGV, AccessTags.HGV_ARTICULATED); 459 addModes(AccessTags.PSV, AccessTags.BUS, AccessTags.MINIBUS, AccessTags.SHARE_TAXI, AccessTags.TAXI); 460 461 // Water 462 addModes(AccessTags.ACCESS_KEY, AccessTags.SWIMMING, AccessTags.BOAT, AccessTags.FISHING_VESSEL, 463 AccessTags.SHIP); 464 addModes(AccessTags.BOAT, AccessTags.MOTORBOAT, AccessTags.SAILBOAT, AccessTags.CANOE); 465 addModes(AccessTags.SHIP, AccessTags.PASSENGER, AccessTags.CARGO, AccessTags.ISPS); 466 addModes(AccessTags.CARGO, AccessTags.BULK, AccessTags.TANKER, AccessTags.CONTAINER, AccessTags.IMDG); 467 addModes(AccessTags.TANKER, AccessTags.TANKER_GAS, AccessTags.TANKER_OIL, AccessTags.TANKER_CHEMICAL, 468 AccessTags.TANKER_SINGLEHULL); 469 470 // Rail 471 addModes(AccessTags.ACCESS_KEY, AccessTags.TRAIN); 472 } 473 474 /** 475 * Add multiple modes with a common parent 476 * 477 * @param parent The parent of all the modes 478 * @param modes The modes to add 479 */ 480 public static void addModes(AccessTags parent, AccessTags... modes) { 481 for (AccessTags mode : modes) { 482 addMode(parent, mode); 483 } 484 } 485 486 /** 487 * Add modes to a list, modifying parents as needed 488 * 489 * @param mode The mode to be added 490 * @param parent The parent of the mode 491 */ 492 public static void addMode(AccessTags parent, AccessTags mode) { 493 Objects.requireNonNull(mode, "Mode must not be null"); 494 if (parent != null) { 495 Map<String, List<String>> parentMap = accessMethods.getOrDefault(parent.getKey(), new HashMap<>()); 496 accessMethods.putIfAbsent(parent.getKey(), parentMap); 497 List<String> parentChildren = parentMap.getOrDefault(CHILDREN, new ArrayList<>()); 498 if (!parentChildren.contains(mode.getKey())) 499 parentChildren.add(mode.getKey()); 500 parentMap.putIfAbsent(CHILDREN, parentChildren); 501 } 502 Map<String, List<String>> modeMap = accessMethods.getOrDefault(mode.getKey(), new HashMap<>()); 503 accessMethods.putIfAbsent(mode.getKey(), modeMap); 504 List<String> modeParent = modeMap.getOrDefault(PARENT, 505 Collections.singletonList(parent == null ? null : parent.getKey())); 506 modeMap.putIfAbsent(PARENT, modeParent); 507 } 508 509 /** 510 * Get the number of parents a mode has 511 * 512 * @param mode The mode with parents 513 * @return The number of parents the mode has 514 */ 515 public static int depth(String mode) { 516 String tempMode = mode; 517 int maxCount = accessMethods.size(); 518 while (tempMode != null && maxCount > 0) { 519 tempMode = accessMethods.getOrDefault(tempMode, Collections.emptyMap()) 520 .getOrDefault(PARENT, Collections.emptyList()).get(0); 521 if (tempMode != null) 522 maxCount--; 523 } 524 return accessMethods.size() - maxCount; 525 } 526 527 /** 528 * Expand access modes to cover the children of that access mode (currently only 529 * supports the default hierarchy) 530 * 531 * @param mode The transport mode 532 * @param access The access value (the children transport modes inherit this 533 * value) 534 * @return A map of the mode and its children (does not include parents) 535 */ 536 public static Map<String, String> expandAccessMode(String mode, String access) { 537 return expandAccessMode(mode, access, AccessTags.ALL_TRANSPORT_TYPE); 538 } 539 540 /** 541 * Expand access modes to cover the children of that access mode (currently only 542 * supports the default hierarchy) 543 * 544 * @param mode The transport mode 545 * @param access The access value (the children transport modes inherit 546 * this value) 547 * @param transportType {@link AccessTags#ALL_TRANSPORT_TYPE}, 548 * {@link AccessTags#LAND_TRANSPORT_TYPE}, 549 * {@link AccessTags#WATER_TRANSPORT_TYPE}, 550 * {@link AccessTags#RAIL_TRANSPORT_TYPE} 551 * @return A map of the mode and its children (does not include parents) 552 */ 553 public static Map<String, String> expandAccessMode(String mode, String access, AccessTags transportType) { 554 Map<String, String> accessModes = new HashMap<>(); 555 accessModes.put(mode, access); 556 if (accessMethods.containsKey(mode)) { 557 for (String childrenMode : accessMethods.getOrDefault(mode, Collections.emptyMap()).getOrDefault(CHILDREN, 558 Collections.emptyList())) { 559 if (transportType.parentOf(AccessTags.get(childrenMode))) 560 accessModes.putAll(expandAccessMode(childrenMode, access, transportType)); 561 } 562 } 563 return accessModes; 564 } 565 566 /** 567 * Merge two access maps (more specific wins) 568 * 569 * @param map1 A map with access values (see {@link Access#expandAccessMode}) 570 * @param map2 A map with access values (see {@link Access#expandAccessMode}) 571 * @return The merged map 572 */ 573 public static Map<String, String> mergeMaps(Map<String, String> map1, Map<String, String> map2) { 574 Map<String, String> merged; 575 if (map1.keySet().containsAll(map2.keySet())) { 576 merged = new HashMap<>(map1); 577 merged.putAll(map2); 578 } else { // if they don't overlap or if map2 contains all of map1 579 merged = new HashMap<>(map2); 580 merged.putAll(map1); 581 } 582 return merged; 583 } 584 585 /** 586 * Get the set of values that can generally be considered to be accessible 587 * 588 * @return A set of values that can be used for routing purposes (unmodifiable) 589 */ 590 public static Set<String> getPositiveAccessValues() { 591 return Collections.unmodifiableSet(POSITIVE_ACCESS); 592 } 593 594 /** 595 * Get the valid restriction values ({@code unknown} is not included). See 596 * 597 * @return Valid values for restrictions (unmodifiable) 598 * @see <a href= 599 * "https://wiki.openstreetmap.org/wiki/Key:access#List_of_possible_values">Key:access#List_of_possible_values</a> 600 */ 601 public static Set<String> getRestrictionValues() { 602 return Collections.unmodifiableSet(RESTRICTION_VALUES); 603 } 604 605 /** 606 * Get the valid transport modes. See 607 * 608 * @return Value transport modes (unmodifiable) 609 * @see <a href= 610 * "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">Key:access#Transport_mode_restrictions</a> 611 */ 612 public static Set<String> getTransportModes() { 613 return Collections.unmodifiableSet(TRANSPORT_MODES); 614 } 615 616 /** 617 * Get the access method hierarchy. 618 * 619 * @return The hierarchy for access modes (unmodifiable) 620 * @see <a href= 621 * "https://wiki.openstreetmap.org/wiki/Key:access#Transport_mode_restrictions">Key:access#Transport_mode_restrictions</a> 622 */ 623 public static Map<String, Map<String, List<String>>> getAccessMethods() { 624 Map<String, Map<String, List<String>>> map = new HashMap<>(); 625 for (Entry<String, Map<String, List<String>>> entry : map.entrySet()) { 626 Map<String, List<String>> tMap = new HashMap<>(); 627 entry.getValue().forEach((key, list) -> tMap.put(key, Collections.unmodifiableList(list))); 628 map.put(entry.getKey(), Collections.unmodifiableMap(tMap)); 629 } 630 return Collections.unmodifiableMap(map); 631 } 632 633 /** 634 * Get the implied access values for a primitive 635 * 636 * @param primitive A primitive with access values 637 * @param transportType {@link AccessTags#ALL_TRANSPORT_TYPE}, 638 * {@link AccessTags#LAND_TRANSPORT_TYPE}, 639 * {@link AccessTags#WATER_TRANSPORT_TYPE}, 640 * {@link AccessTags#RAIL_TRANSPORT_TYPE} 641 * @return The implied access values (for example, "hgv=designated" adds 642 * "hgv_articulated=designated") 643 */ 644 public static Map<String, String> getAccessValues(OsmPrimitive primitive, AccessTags transportType) { 645 Map<String, String> accessValues = new HashMap<>(); 646 TRANSPORT_MODES.stream().filter(primitive::hasKey) 647 .map(mode -> expandAccessMode(mode, primitive.get(mode), transportType)) 648 .forEach(modeAccess -> { 649 Map<String, String> tMap = mergeMaps(accessValues, modeAccess); 650 accessValues.clear(); 651 accessValues.putAll(tMap); 652 }); 653 return accessValues; 654 } 655 656 /** 657 * Expand a map of access values 658 * 659 * @param accessValues A map of mode, access type values 660 * @return The expanded access values 661 */ 662 public static Map<String, String> expandAccessValues(Map<String, String> accessValues) { 663 Map<String, String> modes = new HashMap<>(); 664 List<Map<String, String>> list = accessValues.entrySet().stream() 665 .map(entry -> expandAccessMode(entry.getKey(), entry.getValue())) 666 .sorted(Comparator.comparingInt(Map::size)) 667 .collect(Collectors.toCollection(ArrayList::new)); 668 Collections.reverse(list); 669 for (Map<String, String> access : list) { 670 modes = mergeMaps(modes, access); 671 } 672 return modes; 673 } 674 } -
test/unit/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTestTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertFalse; 6 import static org.junit.Assert.assertNull; 7 import static org.junit.Assert.assertSame; 8 import static org.junit.Assert.assertTrue; 9 10 import java.util.Arrays; 11 import java.util.Collections; 12 import java.util.HashSet; 13 import java.util.Set; 14 import java.util.stream.Collectors; 15 16 import org.junit.Rule; 17 import org.junit.Test; 18 import org.openstreetmap.josm.TestUtils; 19 import org.openstreetmap.josm.data.Bounds; 20 import org.openstreetmap.josm.data.DataSource; 21 import org.openstreetmap.josm.data.coor.LatLon; 22 import org.openstreetmap.josm.data.osm.DataSet; 23 import org.openstreetmap.josm.data.osm.Node; 24 import org.openstreetmap.josm.data.osm.OsmPrimitive; 25 import org.openstreetmap.josm.data.osm.Way; 26 import org.openstreetmap.josm.data.validation.Severity; 27 import org.openstreetmap.josm.spi.preferences.Config; 28 import org.openstreetmap.josm.testutils.JOSMTestRules; 29 30 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 31 32 /** 33 * Test class for {@link RoutingIslandsTest} 34 * 35 * @author Taylor Smock 36 * @since xxx 37 */ 38 public class RoutingIslandsTestTest { 39 /** 40 * Setup test. 41 */ 42 @Rule 43 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 44 public JOSMTestRules rule = new JOSMTestRules().projection().preferences(); 45 46 /** 47 * Test method for {@link RoutingIslandsTest#RoutingIslandsTest()} and the 48 * testing apparatus 49 */ 50 @Test 51 public void testRoutingIslandsTest() { 52 RoutingIslandsTest.setErrorLevel(RoutingIslandsTest.ROUTING_ISLAND, Severity.WARNING); 53 RoutingIslandsTest test = new RoutingIslandsTest(); 54 test.startTest(null); 55 test.endTest(); 56 assertTrue(test.getErrors().isEmpty()); 57 58 DataSet ds = new DataSet(); 59 60 Way way1 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1))); 61 Way way2 = TestUtils.newWay("highway=residential", new Node(new LatLon(-1, 0)), way1.firstNode()); 62 addToDataSet(ds, way1); 63 addToDataSet(ds, way2); 64 65 ds.addDataSource(new DataSource(new Bounds(0, 0, 1, 1), "openstreetmap.org")); 66 67 test.clear(); 68 test.startTest(null); 69 test.visit(ds.allPrimitives()); 70 test.endTest(); 71 assertTrue(test.getErrors().isEmpty()); 72 73 ds.addDataSource(new DataSource(new Bounds(-5, -5, 5, 5), "openstreetmap.org")); 74 test.clear(); 75 test.startTest(null); 76 test.visit(ds.allPrimitives()); 77 test.endTest(); 78 assertEquals(1, test.getErrors().size()); 79 assertEquals(2, test.getErrors().get(0).getPrimitives().size()); 80 81 ds.clear(); 82 way1 = TestUtils.newWay("highway=motorway oneway=yes", new Node(new LatLon(39.1156655, -108.5465434)), 83 new Node(new LatLon(39.1157251, -108.5496874)), new Node(new LatLon(39.11592, -108.5566841))); 84 way2 = TestUtils.newWay("highway=motorway oneway=yes", new Node(new LatLon(39.1157244, -108.55674)), 85 new Node(new LatLon(39.1155548, -108.5496901)), new Node(new LatLon(39.1154827, -108.5462431))); 86 addToDataSet(ds, way1); 87 addToDataSet(ds, way2); 88 ds.addDataSource( 89 new DataSource(new Bounds(new LatLon(39.1136949, -108.558445), new LatLon(39.117242, -108.5489166)), 90 "openstreetmap.org")); 91 test.clear(); 92 test.startTest(null); 93 test.visit(ds.allPrimitives()); 94 test.endTest(); 95 assertEquals(2, test.getErrors().size()); 96 Way way3 = TestUtils.newWay("highway=motorway oneway=no", way1.getNode(1), way2.getNode(1)); 97 addToDataSet(ds, way3); 98 test.clear(); 99 test.startTest(null); 100 test.visit(ds.allPrimitives()); 101 test.endTest(); 102 assertEquals(2, test.getErrors().size()); 103 104 Node tNode = new Node(new LatLon(39.1158845, -108.5599312)); 105 addToDataSet(ds, tNode); 106 way1.addNode(tNode); 107 tNode = new Node(new LatLon(39.115723, -108.5599239)); 108 addToDataSet(ds, tNode); 109 way2.addNode(0, tNode); 110 test.clear(); 111 test.startTest(null); 112 test.visit(ds.allPrimitives()); 113 test.endTest(); 114 assertTrue(test.getErrors().isEmpty()); 115 } 116 117 /** 118 * Test roundabouts 119 */ 120 @Test 121 public void testRoundabouts() { 122 RoutingIslandsTest test = new RoutingIslandsTest(); 123 Way roundabout = TestUtils.newWay("highway=residential junction=roundabout oneway=yes", 124 new Node(new LatLon(39.119582, -108.5262686)), new Node(new LatLon(39.1196494, -108.5260935)), 125 new Node(new LatLon(39.1197572, -108.5260784)), new Node(new LatLon(39.1197929, -108.526391)), 126 new Node(new LatLon(39.1196595, -108.5264047))); 127 roundabout.addNode(roundabout.firstNode()); // close it up 128 DataSet ds = new DataSet(); 129 addToDataSet(ds, roundabout); 130 ds.addDataSource( 131 new DataSource(new Bounds(new LatLon(39.1182025, -108.527574), new LatLon(39.1210588, -108.5251112)), 132 "openstreetmap.org")); 133 Way incomingFlare = TestUtils.newWay("highway=residential oneway=yes", 134 new Node(new LatLon(39.1196377, -108.5257567)), roundabout.getNode(3)); 135 addToDataSet(ds, incomingFlare); 136 Way outgoingFlare = TestUtils.newWay("highway=residential oneway=yes", roundabout.getNode(2), 137 incomingFlare.firstNode()); 138 addToDataSet(ds, outgoingFlare); 139 140 Way outgoingRoad = TestUtils.newWay("highway=residential", incomingFlare.firstNode(), 141 new Node(new LatLon(39.1175184, -108.5219623))); 142 addToDataSet(ds, outgoingRoad); 143 144 test.startTest(null); 145 test.visit(ds.allPrimitives()); 146 test.endTest(); 147 assertTrue(test.getErrors().isEmpty()); 148 } 149 150 /** 151 * Test method for {@link RoutingIslandsTest#checkForUnconnectedWays}. 152 */ 153 @Test 154 public void testCheckForUnconnectedWaysIncoming() { 155 RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), Collections.emptySet(), null); 156 Way way1 = TestUtils.newWay("highway=residential oneway=yes", new Node(new LatLon(0, 0)), 157 new Node(new LatLon(1, 1))); 158 Set<Way> incomingSet = new HashSet<>(); 159 DataSet ds = new DataSet(); 160 way1.getNodes().forEach(ds::addPrimitive); 161 ds.addPrimitive(way1); 162 incomingSet.add(way1); 163 RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null); 164 assertEquals(1, incomingSet.size()); 165 assertSame(way1, incomingSet.iterator().next()); 166 167 Way way2 = TestUtils.newWay("highway=residential", way1.firstNode(), new Node(new LatLon(-1, -2))); 168 way2.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive); 169 ds.addPrimitive(way2); 170 171 RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null); 172 assertEquals(2, incomingSet.size()); 173 assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way))); 174 175 Way way3 = TestUtils.newWay("highway=residential", way2.lastNode(), new Node(new LatLon(-2, -1))); 176 way3.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive); 177 ds.addPrimitive(way3); 178 179 incomingSet.clear(); 180 incomingSet.add(way1); 181 RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null); 182 assertEquals(3, incomingSet.size()); 183 assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2, way3).contains(way))); 184 185 Config.getPref().putInt("validator.routingislands.maxrecursion", 1); 186 incomingSet.clear(); 187 incomingSet.add(way1); 188 RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null); 189 assertEquals(2, incomingSet.size()); 190 assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way))); 191 } 192 193 /** 194 * Test method for {@link RoutingIslandsTest#checkForUnconnectedWays}. 195 */ 196 @Test 197 public void testCheckForUnconnectedWaysOutgoing() { 198 RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), Collections.emptySet(), null); 199 Way way1 = TestUtils.newWay("highway=residential oneway=yes", new Node(new LatLon(0, 0)), 200 new Node(new LatLon(1, 1))); 201 Set<Way> outgoingSet = new HashSet<>(); 202 DataSet ds = new DataSet(); 203 way1.getNodes().forEach(ds::addPrimitive); 204 ds.addPrimitive(way1); 205 outgoingSet.add(way1); 206 RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null); 207 assertEquals(1, outgoingSet.size()); 208 assertSame(way1, outgoingSet.iterator().next()); 209 210 Way way2 = TestUtils.newWay("highway=residential", way1.firstNode(), new Node(new LatLon(-1, -2))); 211 way2.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive); 212 ds.addPrimitive(way2); 213 214 RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null); 215 assertEquals(2, outgoingSet.size()); 216 assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way))); 217 218 Way way3 = TestUtils.newWay("highway=residential", way2.lastNode(), new Node(new LatLon(-2, -1))); 219 way3.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive); 220 ds.addPrimitive(way3); 221 222 outgoingSet.clear(); 223 outgoingSet.add(way1); 224 RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null); 225 assertEquals(3, outgoingSet.size()); 226 assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2, way3).contains(way))); 227 228 Config.getPref().putInt("validator.routingislands.maxrecursion", 1); 229 outgoingSet.clear(); 230 outgoingSet.add(way1); 231 RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null); 232 assertEquals(2, outgoingSet.size()); 233 assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way))); 234 } 235 236 /** 237 * Test method for {@link RoutingIslandsTest#outsideConnections(Node)}. 238 */ 239 @Test 240 public void testOutsideConnections() { 241 Node node = new Node(new LatLon(0, 0)); 242 DataSet ds = new DataSet(node); 243 ds.addDataSource(new DataSource(new Bounds(-0.1, -0.1, -0.01, -0.01), "Test bounds")); 244 node.setOsmId(1, 1); 245 assertTrue(RoutingIslandsTest.outsideConnections(node)); 246 ds.addDataSource(new DataSource(new Bounds(-0.1, -0.1, 0.1, 0.1), "Test bounds")); 247 assertFalse(RoutingIslandsTest.outsideConnections(node)); 248 node.put("amenity", "parking_entrance"); 249 assertTrue(RoutingIslandsTest.outsideConnections(node)); 250 } 251 252 /** 253 * Test method for {@link RoutingIslandsTest#isOneway(Way, String)}. 254 */ 255 @Test 256 public void testIsOneway() { 257 Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1))); 258 assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, null)); 259 assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, " ")); 260 way.put("oneway", "yes"); 261 assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, null)); 262 assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, " ")); 263 way.put("oneway", "-1"); 264 assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, null)); 265 assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, " ")); 266 267 way.put("vehicle:forward", "yes"); 268 assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, "vehicle")); 269 way.put("vehicle:backward", "no"); 270 assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, "vehicle")); 271 way.put("vehicle:forward", "no"); 272 assertNull(RoutingIslandsTest.isOneway(way, "vehicle")); 273 way.put("vehicle:backward", "yes"); 274 assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, "vehicle")); 275 276 way.put("oneway", "yes"); 277 way.remove("vehicle:backward"); 278 way.remove("vehicle:forward"); 279 assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, "vehicle")); 280 way.remove("oneway"); 281 assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, "vehicle")); 282 283 way.put("oneway", "-1"); 284 assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, "vehicle")); 285 } 286 287 /** 288 * Test method for {@link RoutingIslandsTest#firstNode(Way, String)}. 289 */ 290 @Test 291 public void testFirstNode() { 292 Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1))); 293 assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, null)); 294 way.put("oneway", "yes"); 295 assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, null)); 296 way.put("oneway", "-1"); 297 assertEquals(way.lastNode(), RoutingIslandsTest.firstNode(way, null)); 298 299 way.put("vehicle:forward", "yes"); 300 assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle")); 301 way.put("vehicle:backward", "no"); 302 assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle")); 303 way.put("vehicle:forward", "no"); 304 assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle")); 305 way.put("vehicle:backward", "yes"); 306 assertEquals(way.lastNode(), RoutingIslandsTest.firstNode(way, "vehicle")); 307 } 308 309 /** 310 * Test method for {@link RoutingIslandsTest#lastNode(Way, String)}. 311 */ 312 @Test 313 public void testLastNode() { 314 Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1))); 315 assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, null)); 316 way.put("oneway", "yes"); 317 assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, null)); 318 way.put("oneway", "-1"); 319 assertEquals(way.firstNode(), RoutingIslandsTest.lastNode(way, null)); 320 321 way.put("vehicle:forward", "yes"); 322 assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle")); 323 way.put("vehicle:backward", "no"); 324 assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle")); 325 way.put("vehicle:forward", "no"); 326 assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle")); 327 way.put("vehicle:backward", "yes"); 328 assertEquals(way.firstNode(), RoutingIslandsTest.lastNode(way, "vehicle")); 329 } 330 331 /** 332 * Test with a way that by default does not give access to motor vehicles 333 */ 334 @Test 335 public void testNoAccessWay() { 336 Way i70w = TestUtils.newWay("highway=motorway hgv=designated", new Node(new LatLon(39.1058104, -108.5258586)), 337 new Node(new LatLon(39.1052235, -108.5293733))); 338 Way i70e = TestUtils.newWay("highway=motorway hgv=designated", new Node(new LatLon(39.1049905, -108.5293074)), 339 new Node(new LatLon(39.1055829, -108.5257975))); 340 Way testPath = TestUtils.newWay("highway=footway", i70w.lastNode(true), i70e.firstNode(true)); 341 DataSet ds = new DataSet(); 342 Arrays.asList(i70w, i70e, testPath).forEach(way -> addToDataSet(ds, way)); 343 344 RoutingIslandsTest test = new RoutingIslandsTest(); 345 test.startTest(null); 346 test.visit(ds.allPrimitives()); 347 test.endTest(); 348 } 349 350 private static void addToDataSet(DataSet ds, OsmPrimitive primitive) { 351 if (primitive instanceof Way) { 352 ((Way) primitive).getNodes().parallelStream().distinct().filter(node -> node.getDataSet() == null) 353 .forEach(ds::addPrimitive); 354 } 355 if (primitive.getDataSet() == null) 356 ds.addPrimitive(primitive); 357 Long id = Math.max(ds.allPrimitives().parallelStream().mapToLong(prim -> prim.getId()).max().orElse(0L), 0L); 358 for (OsmPrimitive osm : ds.allPrimitives().parallelStream().filter(prim -> prim.getUniqueId() < 0) 359 .collect(Collectors.toList())) { 360 id++; 361 osm.setOsmId(id, 1); 362 } 363 } 364 }