Changeset 14772 in josm


Ignore:
Timestamp:
2019-02-09T11:01:20+01:00 (7 days ago)
Author:
GerdP
Message:

fix #16803 Validator: Wrong warning Highway link is not linked to adequate highway/link
(16803-v4.patch)

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/Highways.java

    r14654 r14772  
    99import java.util.HashMap;
    1010import java.util.HashSet;
     11import java.util.Iterator;
    1112import java.util.List;
    1213import java.util.Locale;
    1314import java.util.Map;
    1415import java.util.Set;
    15 import java.util.stream.Collectors;
    1616
    1717import org.openstreetmap.josm.command.ChangePropertyCommand;
     
    2323import org.openstreetmap.josm.data.validation.Test;
    2424import org.openstreetmap.josm.data.validation.TestError;
     25import org.openstreetmap.josm.tools.Geometry;
    2526import org.openstreetmap.josm.tools.Logging;
    2627import org.openstreetmap.josm.tools.Utils;
     
    4243    protected static final String SOURCE_MAXSPEED = "source:maxspeed";
    4344
     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
    4457    /**
    4558     * Classified highways in order of importance
    4659     */
    47     // CHECKSTYLE.OFF: SingleSpaceSeparator
    4860    private static final List<String> CLASSIFIED_HIGHWAYS = Arrays.asList(
    4961            "motorway",  "motorway_link",
     
    5769    // CHECKSTYLE.ON: SingleSpaceSeparator
    5870
     71
    5972    private static final Set<String> KNOWN_SOURCE_MAXSPEED_CONTEXTS = new HashSet<>(Arrays.asList(
    6073            "urban", "rural", "zone", "zone20", "zone:20", "zone30", "zone:30", "zone40",
     
    99112            if (w.isClosed() && w.hasTag(HIGHWAY, CLASSIFIED_HIGHWAYS) && w.hasTag("junction", "roundabout")
    100113                    && IN_DOWNLOADED_AREA_STRICT.test(w)) {
    101                 // TODO: find out how to handle splitted roundabouts (see #12841)
     114                // TODO: find out how to handle split roundabouts (see #12841)
    102115                testWrongRoundabout(w);
    103116            }
     
    165178    public static boolean isHighwayLinkOkay(final Way way) {
    166179        final String highway = way.get(HIGHWAY);
    167         if (highway == null || !highway.endsWith("_link")
    168                 || !IN_DOWNLOADED_AREA.test(way.getNode(0)) || !IN_DOWNLOADED_AREA.test(way.getNode(way.getNodesCount()-1))) {
     180        if (highway == null || !highway.endsWith("_link")) {
    169181            return true;
    170182        }
    171183
    172         final Set<OsmPrimitive> referrers = new HashSet<>();
    173 
    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());
    178             }
     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        }
     209
     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;
     231            }
     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;
    179276        } else {
    180             referrers.addAll(way.firstNode().getReferrers());
    181             referrers.addAll(way.lastNode().getReferrers());
    182         }
    183 
    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;
    195                 }
    196             }
    197         }
    198         // Link roads should always at least one adjacent segment of same class
    199         return !sameClass.isEmpty();
     277            return way.getNode(nodePos + 1);
     278        }
     279    }
     280
     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;
     306                }
     307                if (oneway != -1) {
     308                    Node to = getPrevNode(toWay, i);
     309                    if (to != null && !isSharpAngle(from, common, to))
     310                        return false;
     311                }
     312                break;
     313            }
     314        }
     315        return true;
     316    }
     317
     318    /**
     319     * Returns true if angle of a corner defined with 3 point coordinates is &lt; MIN_ANGLE_NOT_SHARP
     320     *
     321     * @param n1 first node
     322     * @param n2 Common node
     323     * @param n3 third node
     324     * @return true if angle is below value given in MIN_ANGLE_NOT_SHARP
     325     */
     326
     327    private static boolean isSharpAngle(Node n1, Node n2, Node n3) {
     328        double angle = Geometry.getNormalizedAngleInDegrees(
     329                Geometry.getCornerAngle(n1.getEastNorth(), n2.getEastNorth(), n3.getEastNorth()));
     330        return angle < MIN_ANGLE_NOT_SHARP;
    200331    }
    201332
  • trunk/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java

    r12312 r14772  
    117117                fail(test.getErrors().get(0).getMessage());
    118118            }
    119             Way w1 = ways.stream().filter(w -> 28508494 == w.getId()).findFirst().get();
    120             Way w2 = ways.stream().filter(w -> 28508493 == w.getId()).findFirst().get();
    121             test.visit(w1);
    122             test.visit(w2);
    123             assertEquals(2, test.getErrors().size());
    124119        }
    125120    }
Note: See TracChangeset for help on using the changeset viewer.