| | 45 | /** threshold value for angles between two highway segments. */ |
| | 46 | private static final int MIN_ANGLE_NOT_SHARP = 60; |
| | 47 | |
| | 48 | // CHECKSTYLE.OFF: SingleSpaceSeparator |
| | 49 | private static final Set<String> LINK_TO_HIGHWAYS = new HashSet<>(Arrays.asList( |
| | 50 | "motorway", "motorway_link", |
| | 51 | "trunk", "trunk_link", |
| | 52 | "primary", "primary_link", |
| | 53 | "secondary", "secondary_link", |
| | 54 | "tertiary", "tertiary_link" |
| | 55 | )); |
| | 56 | |
| 59 | 72 | private static final Set<String> KNOWN_SOURCE_MAXSPEED_CONTEXTS = new HashSet<>(Arrays.asList( |
| 60 | 73 | "urban", "rural", "zone", "zone20", "zone:20", "zone30", "zone:30", "zone40", |
| 61 | 74 | "nsl_single", "nsl_dual", "motorway", "trunk", "living_street", "bicycle_road")); |
| 172 | | final Set<OsmPrimitive> referrers = new HashSet<>(); |
| | 184 | // check if connected to a high class road where the link must match the higher class |
| | 185 | String highClass = null; |
| | 186 | for (int i = 0; i < way.getNodesCount(); i++) { |
| | 187 | Node n = way.getNode(i); |
| | 188 | if (!IN_DOWNLOADED_AREA.test(n)) |
| | 189 | return true; |
| | 190 | Set<Way> otherWays = new HashSet<>(); |
| | 191 | otherWays.addAll(Utils.filteredCollection(n.getReferrers(), Way.class)); |
| | 192 | if (otherWays.size() == 1) |
| | 193 | continue; |
| | 194 | Iterator<Way> iter = otherWays.iterator(); |
| | 195 | while (iter.hasNext()) { |
| | 196 | Way w = iter.next(); |
| | 197 | final String hw2 = w.get(HIGHWAY); |
| | 198 | if (way == w || w.getNodesCount() < 2 || !w.isUsable() || hw2 == null) |
| | 199 | iter.remove(); |
| | 200 | else { |
| | 201 | if ("motorway".equals(hw2)) { |
| | 202 | highClass = "motorway"; |
| | 203 | break; |
| | 204 | } else if ("trunk".equals(hw2)) |
| | 205 | highClass = "trunk"; |
| | 206 | } |
| | 207 | } |
| | 208 | } |
| 174 | | if (way.isClosed()) { |
| 175 | | // for closed way we need to check all adjacent ways |
| 176 | | for (Node n: way.getNodes()) { |
| 177 | | referrers.addAll(n.getReferrers()); |
| | 210 | if (highClass != null && !highway.equals(highClass + "_link")) { |
| | 211 | return false; |
| | 212 | } |
| | 213 | |
| | 214 | for (int i = 0; i < way.getNodesCount(); i++) { |
| | 215 | Node n = way.getNode(i); |
| | 216 | Set<Way> otherWays = new HashSet<>(); |
| | 217 | otherWays.addAll(Utils.filteredCollection(n.getReferrers(), Way.class)); |
| | 218 | if (otherWays.size() == 1) |
| | 219 | continue; |
| | 220 | otherWays.removeIf(w -> w == way || !highway.startsWith(w.get(HIGHWAY)) || !LINK_TO_HIGHWAYS.contains(w.get(HIGHWAY))); |
| | 221 | if (otherWays.isEmpty()) |
| | 222 | continue; |
| | 223 | |
| | 224 | //TODO: ignore ways which are not allowed because of turn restrictions, oneway attributes or access rules? |
| | 225 | HashSet<Way> sameTag = new HashSet<>(); |
| | 226 | for (Way ow : otherWays) { |
| | 227 | if (highway.equals(ow.get(HIGHWAY))) |
| | 228 | sameTag.add(ow); |
| | 229 | else |
| | 230 | return true; |
| | 232 | // we have way(s) with the same _link tag, ignore those with a sharp angle |
| | 233 | final int pos = i; |
| | 234 | sameTag.removeIf(w -> isSharpAngle(way, pos, w)); |
| | 235 | if (!sameTag.isEmpty()) |
| | 236 | return true; |
| | 237 | } |
| | 238 | return false; |
| | 239 | |
| | 240 | } |
| | 241 | |
| | 242 | /** |
| | 243 | * Check if the two given connected ways form a sharp angle. |
| | 244 | * @param way 1st way |
| | 245 | * @param nodePos node position of connecting node in 1st way |
| | 246 | * @param otherWay the 2nd way |
| | 247 | * @return true if angle is sharp or way cannot be travelled because of oneway attributes |
| | 248 | */ |
| | 249 | private static boolean isSharpAngle(Way way, int nodePos, Way otherWay) { |
| | 250 | Node n = way.getNode(nodePos); |
| | 251 | int oneway = way.isOneway(); |
| | 252 | if (oneway == 0) { |
| | 253 | if ("roundabout".equals(way.get("junction"))) { |
| | 254 | oneway = 1; |
| | 255 | } |
| | 256 | } |
| | 257 | |
| | 258 | if (oneway != 1) { |
| | 259 | Node prev = getPrevNode(way, nodePos); |
| | 260 | if (prev != null && !onlySharpAngle(n, prev, otherWay)) |
| | 261 | return false; |
| | 262 | } |
| | 263 | if (oneway != -1) { |
| | 264 | Node next = getNextNode(way, nodePos); |
| | 265 | if (next != null && !onlySharpAngle(n, next, otherWay)) |
| | 266 | return false; |
| | 267 | } |
| | 268 | return true; |
| | 269 | } |
| | 270 | |
| | 271 | private static Node getNextNode(Way way, int nodePos) { |
| | 272 | if (nodePos + 1 >= way.getNodesCount()) { |
| | 273 | if (way.isClosed()) |
| | 274 | return way.getNode(1); |
| | 275 | return null; |
| 184 | | // Find ways of same class (exact class of class_link) |
| 185 | | List<Way> sameClass = Utils.filteredCollection(referrers, Way.class).stream().filter( |
| 186 | | otherWay -> !way.equals(otherWay) && otherWay.hasTag(HIGHWAY, highway, highway.replaceAll("_link$", ""))) |
| 187 | | .collect(Collectors.toList()); |
| 188 | | if (sameClass.size() > 1) { |
| 189 | | // It is possible to have a class_link between 2 segments of same class |
| 190 | | // in roundabout designs that physically separate a specific turn from the main roundabout |
| 191 | | // But if we have more than a single adjacent class, and one of them is a roundabout, that's an error |
| 192 | | for (Way w : sameClass) { |
| 193 | | if (w.hasTag("junction", "roundabout")) { |
| 194 | | return false; |
| | 281 | private static Node getPrevNode(Way way, int nodePos) { |
| | 282 | if (nodePos == 0) { |
| | 283 | if (way.isClosed()) |
| | 284 | return way.getNode(way.getNodesCount() - 2); |
| | 285 | return null; |
| | 286 | } else { |
| | 287 | return way.getNode(nodePos - 1); |
| | 288 | } |
| | 289 | } |
| | 290 | |
| | 291 | private static boolean onlySharpAngle(Node common, Node from, Way toWay) { |
| | 292 | int oneway = toWay.isOneway(); |
| | 293 | if (oneway == 0) { |
| | 294 | if ("roundabout".equals(toWay.get("junction"))) { |
| | 295 | oneway = 1; |
| | 296 | } |
| | 297 | } |
| | 298 | |
| | 299 | for (int i = 0; i < toWay.getNodesCount(); i++) { |
| | 300 | if (common == toWay.getNode(i)) { |
| | 301 | |
| | 302 | if (oneway != 1) { |
| | 303 | Node to = getNextNode(toWay, i); |
| | 304 | if (to != null && !isSharpAngle(from, common, to)) |
| | 305 | return false; |