Ticket #18364: 18364.patch

File 18364.patch, 29.3 KB (added by taylor.smock, 6 years ago)

Initial patch

  • src/org/openstreetmap/josm/data/validation/OsmValidator.java

     
    6060import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
    6161import org.openstreetmap.josm.data.validation.tests.RelationChecker;
    6262import org.openstreetmap.josm.data.validation.tests.RightAngleBuildingTest;
     63import org.openstreetmap.josm.data.validation.tests.RoutingIslandsTest;
    6364import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
    6465import org.openstreetmap.josm.data.validation.tests.SharpAngles;
    6566import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
     
    150151        PublicTransportRouteTest.class, // 3600 .. 3699
    151152        RightAngleBuildingTest.class, // 3700 .. 3799
    152153        SharpAngles.class, // 3800 .. 3899
     154        RoutingIslandsTest.class, // 3900 .. 3999
    153155    };
    154156
    155157    /**
  • src/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.validation.tests;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.ArrayList;
     7import java.util.Arrays;
     8import java.util.Collection;
     9import java.util.Collections;
     10import java.util.HashSet;
     11import java.util.List;
     12import java.util.Objects;
     13import java.util.Set;
     14import java.util.function.BiPredicate;
     15import java.util.stream.Collectors;
     16
     17import org.openstreetmap.josm.data.osm.Node;
     18import org.openstreetmap.josm.data.osm.Relation;
     19import org.openstreetmap.josm.data.osm.Way;
     20import org.openstreetmap.josm.data.validation.Severity;
     21import org.openstreetmap.josm.data.validation.Test;
     22import org.openstreetmap.josm.data.validation.TestError;
     23import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     24import org.openstreetmap.josm.spi.preferences.Config;
     25
     26/**
     27 * A test for routing islands
     28 *
     29 * @author Taylor Smock
     30 * @since xxx
     31 */
     32public class RoutingIslandsTest extends Test {
     33
     34    private static final int ROUTING_ISLAND = 3900;
     35    /**
     36     * This is mostly as a sanity check, and to avoid infinite recursion (shouldn't
     37     * happen, but still)
     38     */
     39    private static final int MAX_LOOPS = 1000;
     40    private Set<Way> potentialWays;
     41
     42    // Duplicates from ConditionalKeys (should probably change them to public,
     43    // possibly use Collections.unmodifiableSet TODO iterate through the transport
     44    // modes
     45    private static final Set<String> RESTRICTION_VALUES = new HashSet<>(Arrays.asList("yes", "official", "designated",
     46            "destination", "delivery", "customers", "permissive", "private", "agricultural", "forestry", "no"));
     47    private static final Set<String> TRANSPORT_MODES = new HashSet<>(
     48            Arrays.asList("access", "foot", "ski", "inline_skates", "ice_skates", "horse", "vehicle", "bicycle",
     49                    "carriage", "trailer", "caravan", "motor_vehicle", "motorcycle", "moped", "mofa", "motorcar",
     50                    "motorhome", "psv", "bus", "taxi", "tourist_bus", "goods", "hgv", "agricultural", "atv",
     51                    "snowmobile", "hgv_articulated", "ski:nordic", "ski:alpine", "ski:telemark", "coach", "golf_cart"
     52            /*
     53             * ,"minibus","share_taxi","hov","car_sharing","emergency","hazmat","disabled"
     54             */));
     55
     56    /**
     57     * Constructs a new {@code RightAngleBuildingTest} test.
     58     */
     59    public RoutingIslandsTest() {
     60        super(tr("Routing islands"), tr("Checks for roads that cannot be reached or left."));
     61    }
     62
     63    @Override
     64    public void startTest(ProgressMonitor monitor) {
     65        super.startTest(monitor);
     66        potentialWays = new HashSet<>();
     67    }
     68
     69    @Override
     70    public void endTest() {
     71        runTest(null);
     72        super.endTest();
     73    }
     74
     75    /**
     76     * TODO Turn restrictions, loop through transport types
     77     */
     78
     79    /**
     80     * May connect to: 1) A road leaving the fully downloaded BBox 2) A
     81     * dock/ferry/other waterway loading point 3) An aeroport
     82     */
     83
     84    @Override
     85    public void visit(Way way) {
     86        if (way.hasKey("highway") && way.isUsable()
     87                && way.getDataSet().getDataSources().parallelStream()
     88                        .filter(source -> source.bounds.toBBox().intersects(way.getBBox())).map(bound -> bound.origin)
     89                        .filter(Objects::nonNull).filter(string -> !string.trim().isEmpty())
     90                        .anyMatch(string -> string.contains("openstreetmap.org")))
     91            potentialWays.add(way);
     92    }
     93
     94    private void runTest(String currentTransportMode) {
     95        Set<Way> incomingWays = new HashSet<>();
     96        Set<Way> outgoingWays = new HashSet<>();
     97        for (Way way : potentialWays) {
     98            if (way.isUsable() && way.isOutsideDownloadArea()) {
     99                if (isOneway(way, currentTransportMode) != 0
     100                        && firstNode(way, currentTransportMode).isOutsideDownloadArea())
     101                    incomingWays.add(way);
     102                if (isOneway(way, currentTransportMode) != 0
     103                        && lastNode(way, currentTransportMode).isOutsideDownloadArea()) {
     104                    outgoingWays.add(way);
     105                }
     106                if (isOneway(way, currentTransportMode) == 0
     107                        && (way.firstNode().isOutsideDownloadArea() || way.lastNode().isOutsideDownloadArea())) {
     108                    incomingWays.add(way);
     109                    outgoingWays.add(way);
     110                }
     111            }
     112        }
     113        Set<Way> toCheck = potentialWays.parallelStream()
     114                .filter(way -> !incomingWays.contains(way) && !outgoingWays.contains(way)).collect(Collectors.toSet());
     115        checkForUnconnectedWays(incomingWays, outgoingWays, currentTransportMode);
     116        List<Set<Way>> problematic = collectConnected(toCheck.parallelStream()
     117                .filter(way -> !incomingWays.contains(way) || !outgoingWays.contains(way)).collect(Collectors.toSet()));
     118        createErrors(problematic);
     119    }
     120
     121    private static List<Set<Way>> collectConnected(Collection<Way> ways) {
     122        ArrayList<Set<Way>> collected = new ArrayList<>();
     123        ArrayList<Way> listOfWays = new ArrayList<>(ways);
     124        final int maxLoop = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
     125        for (int i = 0; i < listOfWays.size(); i++) {
     126            Way initial = listOfWays.get(i);
     127            Set<Way> connected = new HashSet<>();
     128            connected.add(initial);
     129            int loopCounter = 0;
     130            while (!getConnected(connected) && loopCounter < maxLoop) {
     131                loopCounter++;
     132            }
     133            if (listOfWays.removeAll(connected))
     134                i--; // not an issue -- this ensures that everything is accounted for, only triggers
     135                     // when ways removed
     136            collected.add(connected);
     137        }
     138        return collected;
     139    }
     140
     141    private static boolean getConnected(Collection<Way> ways) {
     142        return ways.addAll(ways.parallelStream().flatMap(way -> way.getNodes().parallelStream())
     143                .flatMap(node -> node.getReferrers().parallelStream()).filter(Way.class::isInstance)
     144                .map(Way.class::cast).collect(Collectors.toSet()));
     145    }
     146
     147    private void createErrors(List<Set<Way>> problematic) {
     148        problematic.forEach(
     149                way -> errors.add(TestError.builder(this, Severity.OTHER, ROUTING_ISLAND).message(tr("Routing island"))
     150                        .primitives(way).build()));
     151    }
     152
     153    /**
     154     * Check for unconnected ways
     155     *
     156     * @param incoming             The current incoming ways (will be modified)
     157     * @param outgoing             The current outgoing ways (will be modified)
     158     * @param currentTransportMode The transport mode we are investigating (may be
     159     *                             {@code null})
     160     */
     161    public static void checkForUnconnectedWays(Collection<Way> incoming,
     162            Collection<Way> outgoing, String currentTransportMode) {
     163        int loopCount = 0;
     164        int maxLoops = Config.getPref().getInt("validator.routingislands.maxrecursion", MAX_LOOPS);
     165        do {
     166            loopCount++;
     167        } while (loopCount <= maxLoops && getWaysFor(incoming, currentTransportMode,
     168                (way, oldWay) -> oldWay.containsNode(firstNode(way, currentTransportMode))));
     169        loopCount = 0;
     170        do {
     171            loopCount++;
     172        } while (loopCount <= maxLoops && getWaysFor(outgoing, currentTransportMode,
     173                (way, oldWay) -> oldWay.containsNode(lastNode(way, currentTransportMode))));
     174    }
     175
     176    private static boolean getWaysFor(Collection<Way> directional, String currentTransportMode,
     177            BiPredicate<Way, Way> predicate) {
     178        Set<Way> toAdd = new HashSet<>();
     179        for (Way way : directional) {
     180            for (Node node : way.getNodes()) {
     181                Set<Way> referrers = node.getReferrers(true).parallelStream().filter(Way.class::isInstance)
     182                        .map(Way.class::cast).filter(tWay -> !directional.contains(tWay)).collect(Collectors.toSet());
     183                for (Way tWay : referrers) {
     184                    if (isOneway(tWay, currentTransportMode) == 0 || predicate.test(tWay, way)
     185                            || tWay.hasKey("junction")) {
     186                        toAdd.add(tWay);
     187                    }
     188                }
     189            }
     190        }
     191        return directional.addAll(toAdd);
     192    }
     193
     194    /**
     195     * Check if I can get to way to from way from (currently doesn't work with via
     196     * ways)
     197     *
     198     * @param from                 The from way
     199     * @param to                   The to way
     200     * @param currentTransportMode The specific transport mode to check
     201     * @return {@code true} if the to way can be accessed from the from way TODO
     202     *         clean up and work with via ways
     203     */
     204    public static boolean checkAccessibility(Way from, Way to, String currentTransportMode) {
     205        boolean isAccessible = true;
     206
     207        List<Relation> relations = from.getReferrers().parallelStream().distinct().filter(Relation.class::isInstance)
     208                .map(Relation.class::cast).filter(relation -> "restriction".equals(relation.get("type")))
     209                .collect(Collectors.toList());
     210        for (Relation relation : relations) {
     211            if ((relation.hasKey("except") && relation.get("except").contains(currentTransportMode)
     212                    || (currentTransportMode == null || currentTransportMode.trim().isEmpty()))
     213                    && relation.getMembersFor(Collections.singleton(from)).parallelStream()
     214                    .anyMatch(member -> "from".equals(member.getRole()))
     215                    && relation.getMembersFor(Collections.singleton(to)).parallelStream()
     216                            .anyMatch(member -> "to".equals(member.getRole()))) {
     217                isAccessible = false;
     218            }
     219        }
     220
     221        return isAccessible;
     222    }
     223
     224    /**
     225     * Check if a node connects to the outside world
     226     *
     227     * @param node The node to check
     228     * @return true if outside download area, connects to an aeroport, or a water
     229     *         transport
     230     */
     231    public static Boolean outsideConnections(Node node) {
     232        boolean outsideConnections = false;
     233        if (node.isOutsideDownloadArea() || node.hasTag("amenity", "parking_entrance", "parking", "parking_space",
     234                "motorcycle_parking", "ferry_terminal"))
     235            outsideConnections = true;
     236        return outsideConnections;
     237    }
     238
     239    /**
     240     * Check if a way is oneway for a specific transport type
     241     *
     242     * @param way           The way to look at
     243     * @param transportType The specific transport type
     244     * @return See {@link Way#isOneway} (but may additionally return {@code null} if
     245     *         the transport type cannot route down that way)
     246     */
     247    public static Integer isOneway(Way way, String transportType) {
     248        if (transportType == null || transportType.trim().isEmpty()) {
     249            return way.isOneway();
     250        }
     251        String forward = transportType.concat(":forward");
     252        String backward = transportType.concat(":backward");
     253        boolean possibleForward = "yes".equals(way.get(forward))
     254                || (!way.hasKey(forward) && way.isOneway() != -1);
     255        boolean possibleBackward = "yes".equals(way.get(backward))
     256                || (!way.hasKey(backward) && way.isOneway() != 1);
     257        if (possibleForward && !possibleBackward) {
     258            return 1;
     259        } else if (!possibleForward && possibleBackward) {
     260            return -1;
     261        } else if (!possibleBackward) {
     262            return null;
     263        }
     264        return 0;
     265    }
     266
     267    /**
     268     * Get the first node of a way respecting the oneway for a transport type
     269     *
     270     * @param way           The way to get the node from
     271     * @param transportType The transport type
     272     * @return The first node for the specified transport type, or the first node of
     273     *         the way
     274     */
     275    public static Node firstNode(Way way, String transportType) {
     276        Integer oneway = isOneway(way, transportType);
     277        return (Integer.valueOf(-1).equals(oneway)) ? way.lastNode() : way.firstNode();
     278    }
     279
     280    /**
     281     * Get the last node of a way respecting the oneway for a transport type
     282     *
     283     * @param way           The way to get the node from
     284     * @param transportType The transport type
     285     * @return The last node for the specified transport type, or the last node of
     286     *         the way
     287     */
     288    public static Node lastNode(Way way, String transportType) {
     289        Integer oneway = isOneway(way, transportType);
     290        return (Integer.valueOf(-1).equals(oneway)) ? way.firstNode() : way.lastNode();
     291    }
     292}
  • test/unit/org/openstreetmap/josm/data/validation/tests/RoutingIslandsTestTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.validation.tests;
     3
     4import static org.junit.Assert.assertEquals;
     5import static org.junit.Assert.assertFalse;
     6import static org.junit.Assert.assertNull;
     7import static org.junit.Assert.assertSame;
     8import static org.junit.Assert.assertTrue;
     9
     10import java.util.Arrays;
     11import java.util.Collection;
     12import java.util.Collections;
     13import java.util.HashSet;
     14import java.util.Set;
     15
     16import org.junit.Rule;
     17import org.junit.Test;
     18import org.openstreetmap.josm.TestUtils;
     19import org.openstreetmap.josm.data.Bounds;
     20import org.openstreetmap.josm.data.DataSource;
     21import org.openstreetmap.josm.data.coor.LatLon;
     22import org.openstreetmap.josm.data.osm.DataSet;
     23import org.openstreetmap.josm.data.osm.Node;
     24import org.openstreetmap.josm.data.osm.OsmPrimitive;
     25import org.openstreetmap.josm.data.osm.Way;
     26import org.openstreetmap.josm.spi.preferences.Config;
     27import org.openstreetmap.josm.testutils.JOSMTestRules;
     28
     29import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     30
     31/**
     32 * Test class for {@link RoutingIslandsTest}
     33 *
     34 * @author Taylor Smock
     35 * @since xxx
     36 */
     37public class RoutingIslandsTestTest {
     38    /**
     39     * Setup test.
     40     */
     41    @Rule
     42    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     43    public JOSMTestRules rule = new JOSMTestRules().projection().preferences();
     44
     45    /**
     46     * Test method for {@link RoutingIslandsTest#RoutingIslandsTest()} and the
     47     * testing apparatus
     48     */
     49    @Test
     50    public void testRoutingIslandsTest() {
     51        RoutingIslandsTest test = new RoutingIslandsTest();
     52        test.startTest(null);
     53        test.endTest();
     54        assertTrue(test.getErrors().isEmpty());
     55
     56        DataSet ds = new DataSet();
     57
     58        Way way1 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
     59        Way way2 = TestUtils.newWay("highway=residential", new Node(new LatLon(-1, 0)), way1.firstNode());
     60        addToDataSet(ds, way1);
     61        addToDataSet(ds, way2);
     62
     63        ds.addDataSource(new DataSource(new Bounds(0, 0, 1, 1), "openstreetmap.org"));
     64
     65        test.clear();
     66        test.startTest(null);
     67        test.visit(ds.allPrimitives());
     68        test.endTest();
     69        assertTrue(test.getErrors().isEmpty());
     70
     71        ds.addDataSource(new DataSource(new Bounds(-5, -5, 5, 5), "openstreetmap.org"));
     72        test.clear();
     73        test.startTest(null);
     74        test.visit(ds.allPrimitives());
     75        test.endTest();
     76        assertEquals(1, test.getErrors().size());
     77        assertEquals(2, test.getErrors().get(0).getPrimitives().size());
     78
     79        ds.clear();
     80        way1 = TestUtils.newWay("highway=motorway oneway=yes", new Node(new LatLon(39.1156655, -108.5465434)),
     81                new Node(new LatLon(39.1157251, -108.5496874)), new Node(new LatLon(39.11592, -108.5566841)));
     82        way2 = TestUtils.newWay("highway=motorway oneway=yes", new Node(new LatLon(39.1157244, -108.55674)),
     83                new Node(new LatLon(39.1155548, -108.5496901)), new Node(new LatLon(39.1154827, -108.5462431)));
     84        addToDataSet(ds, way1);
     85        addToDataSet(ds, way2);
     86        ds.addDataSource(
     87                new DataSource(new Bounds(new LatLon(39.1161391, -108.5516083), new LatLon(39.1136949, -108.5489166)),
     88                        "openstreetmap.org"));
     89        test.clear();
     90        test.startTest(null);
     91        test.visit(ds.allPrimitives());
     92        test.endTest();
     93        assertTrue(test.getErrors().isEmpty());
     94        Way way3 = TestUtils.newWay("highway=service", way1.getNode(1), way2.getNode(2));
     95        addToDataSet(ds, way3);
     96        test.startTest(null);
     97        test.visit(ds.allPrimitives());
     98        test.endTest();
     99        assertTrue(test.getErrors().isEmpty());
     100    }
     101
     102    /**
     103     * Test roundabouts
     104     */
     105    @Test
     106    public void testRoundabouts() {
     107        RoutingIslandsTest test = new RoutingIslandsTest();
     108        Way roundabout = TestUtils.newWay("highway=residential junction=roundabout oneway=yes",
     109                new Node(new LatLon(39.119582, -108.5262686)), new Node(new LatLon(39.1196494, -108.5260935)),
     110                new Node(new LatLon(39.1197572, -108.5260784)), new Node(new LatLon(39.1197929, -108.526391)),
     111                new Node(new LatLon(39.1196595, -108.5264047)));
     112        roundabout.addNode(roundabout.firstNode()); // close it up
     113        DataSet ds = new DataSet();
     114        addToDataSet(ds, roundabout);
     115        ds.addDataSource(
     116                new DataSource(new Bounds(new LatLon(39.1182025, -108.527574), new LatLon(39.1210588, -108.5251112)),
     117                        "openstreetmap.org"));
     118        Way incomingFlare = TestUtils.newWay("highway=residential oneway=yes",
     119                new Node(new LatLon(39.1196377, -108.5257567)), roundabout.getNode(3));
     120        addToDataSet(ds, incomingFlare);
     121        Way outgoingFlare = TestUtils.newWay("highway=residential oneway=yes", roundabout.getNode(2),
     122                incomingFlare.firstNode());
     123        addToDataSet(ds, outgoingFlare);
     124
     125        Way outgoingRoad = TestUtils.newWay("highway=residential", incomingFlare.firstNode(),
     126                new Node(new LatLon(39.1175184, -108.5219623)));
     127        addToDataSet(ds, outgoingRoad);
     128
     129        test.startTest(null);
     130        test.visit(ds.allPrimitives());
     131        test.endTest();
     132        assertTrue(test.getErrors().isEmpty());
     133    }
     134
     135    private static void addToDataSet(DataSet ds, Way way) {
     136        way.getNodes().parallelStream().distinct().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
     137        if (way.getDataSet() == null)
     138            ds.addPrimitive(way);
     139        Long id = Math.max(ds.allPrimitives().parallelStream().mapToLong(prim -> prim.getId()).max().orElse(0L), 0L);
     140        for (OsmPrimitive osm : ds.allPrimitives()) {
     141            id++;
     142            osm.setOsmId(id, 1);
     143        }
     144    }
     145
     146    /**
     147     * Test method for
     148     * {@link RoutingIslandsTest#checkForUnconnectedWays(Collection, Collection, String)}.
     149     */
     150    @Test
     151    public void testCheckForUnconnectedWaysIncoming() {
     152        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), Collections.emptySet(), null);
     153        Way way1 = TestUtils.newWay("highway=residential oneway=yes", new Node(new LatLon(0, 0)),
     154                new Node(new LatLon(1, 1)));
     155        Set<Way> incomingSet = new HashSet<>();
     156        DataSet ds = new DataSet();
     157        way1.getNodes().forEach(ds::addPrimitive);
     158        ds.addPrimitive(way1);
     159        incomingSet.add(way1);
     160        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
     161        assertEquals(1, incomingSet.size());
     162        assertSame(way1, incomingSet.iterator().next());
     163
     164        Way way2 = TestUtils.newWay("highway=residential", way1.firstNode(), new Node(new LatLon(-1, -2)));
     165        way2.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
     166        ds.addPrimitive(way2);
     167
     168        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
     169        assertEquals(2, incomingSet.size());
     170        assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
     171
     172        Way way3 = TestUtils.newWay("highway=residential", way2.lastNode(), new Node(new LatLon(-2, -1)));
     173        way3.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
     174        ds.addPrimitive(way3);
     175
     176        incomingSet.clear();
     177        incomingSet.add(way1);
     178        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
     179        assertEquals(3, incomingSet.size());
     180        assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2, way3).contains(way)));
     181
     182        Config.getPref().putInt("validator.routingislands.maxrecursion", 1);
     183        incomingSet.clear();
     184        incomingSet.add(way1);
     185        RoutingIslandsTest.checkForUnconnectedWays(incomingSet, Collections.emptySet(), null);
     186        assertEquals(2, incomingSet.size());
     187        assertTrue(incomingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
     188    }
     189
     190    @Test
     191    public void testCheckForUnconnectedWaysOutgoing() {
     192        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), Collections.emptySet(), null);
     193        Way way1 = TestUtils.newWay("highway=residential oneway=yes", new Node(new LatLon(0, 0)),
     194                new Node(new LatLon(1, 1)));
     195        Set<Way> outgoingSet = new HashSet<>();
     196        DataSet ds = new DataSet();
     197        way1.getNodes().forEach(ds::addPrimitive);
     198        ds.addPrimitive(way1);
     199        outgoingSet.add(way1);
     200        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
     201        assertEquals(1, outgoingSet.size());
     202        assertSame(way1, outgoingSet.iterator().next());
     203
     204        Way way2 = TestUtils.newWay("highway=residential", way1.firstNode(), new Node(new LatLon(-1, -2)));
     205        way2.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
     206        ds.addPrimitive(way2);
     207
     208        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
     209        assertEquals(2, outgoingSet.size());
     210        assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
     211
     212        Way way3 = TestUtils.newWay("highway=residential", way2.lastNode(), new Node(new LatLon(-2, -1)));
     213        way3.getNodes().parallelStream().filter(node -> node.getDataSet() == null).forEach(ds::addPrimitive);
     214        ds.addPrimitive(way3);
     215
     216        outgoingSet.clear();
     217        outgoingSet.add(way1);
     218        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
     219        assertEquals(3, outgoingSet.size());
     220        assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2, way3).contains(way)));
     221
     222        Config.getPref().putInt("validator.routingislands.maxrecursion", 1);
     223        outgoingSet.clear();
     224        outgoingSet.add(way1);
     225        RoutingIslandsTest.checkForUnconnectedWays(Collections.emptySet(), outgoingSet, null);
     226        assertEquals(2, outgoingSet.size());
     227        assertTrue(outgoingSet.parallelStream().allMatch(way -> Arrays.asList(way1, way2).contains(way)));
     228    }
     229
     230    /**
     231     * Test method for {@link RoutingIslandsTest#outsideConnections(Node)}.
     232     */
     233    @Test
     234    public void testOutsideConnections() {
     235        Node node = new Node(new LatLon(0, 0));
     236        DataSet ds = new DataSet(node);
     237        ds.addDataSource(new DataSource(new Bounds(-0.1, -0.1, -0.01, -0.01), "Test bounds"));
     238        node.setOsmId(1, 1);
     239        assertTrue(RoutingIslandsTest.outsideConnections(node));
     240        ds.addDataSource(new DataSource(new Bounds(-0.1, -0.1, 0.1, 0.1), "Test bounds"));
     241        assertFalse(RoutingIslandsTest.outsideConnections(node));
     242        node.put("amenity", "parking_entrance");
     243        assertTrue(RoutingIslandsTest.outsideConnections(node));
     244    }
     245
     246    /**
     247     * Test method for {@link RoutingIslandsTest#isOneway(Way, String)}.
     248     */
     249    @Test
     250    public void testIsOneway() {
     251        Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
     252        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, null));
     253        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, " "));
     254        way.put("oneway", "yes");
     255        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, null));
     256        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, " "));
     257        way.put("oneway", "-1");
     258        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, null));
     259        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, " "));
     260
     261        way.put("vehicle:forward", "yes");
     262        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, "vehicle"));
     263        way.put("vehicle:backward", "no");
     264        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, "vehicle"));
     265        way.put("vehicle:forward", "no");
     266        assertNull(RoutingIslandsTest.isOneway(way, "vehicle"));
     267        way.put("vehicle:backward", "yes");
     268        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, "vehicle"));
     269
     270        way.put("oneway", "yes");
     271        way.remove("vehicle:backward");
     272        way.remove("vehicle:forward");
     273        assertEquals(Integer.valueOf(1), RoutingIslandsTest.isOneway(way, "vehicle"));
     274        way.remove("oneway");
     275        assertEquals(Integer.valueOf(0), RoutingIslandsTest.isOneway(way, "vehicle"));
     276
     277        way.put("oneway", "-1");
     278        assertEquals(Integer.valueOf(-1), RoutingIslandsTest.isOneway(way, "vehicle"));
     279    }
     280
     281    /**
     282     * Test method for {@link RoutingIslandsTest#firstNode(Way, String)}.
     283     */
     284    @Test
     285    public void testFirstNode() {
     286        Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
     287        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, null));
     288        way.put("oneway", "yes");
     289        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, null));
     290        way.put("oneway", "-1");
     291        assertEquals(way.lastNode(), RoutingIslandsTest.firstNode(way, null));
     292
     293        way.put("vehicle:forward", "yes");
     294        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
     295        way.put("vehicle:backward", "no");
     296        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
     297        way.put("vehicle:forward", "no");
     298        assertEquals(way.firstNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
     299        way.put("vehicle:backward", "yes");
     300        assertEquals(way.lastNode(), RoutingIslandsTest.firstNode(way, "vehicle"));
     301    }
     302
     303    /**
     304     * Test method for {@link RoutingIslandsTest#lastNode(Way, String)}.
     305     */
     306    @Test
     307    public void testLastNode() {
     308        Way way = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(1, 1)));
     309        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, null));
     310        way.put("oneway", "yes");
     311        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, null));
     312        way.put("oneway", "-1");
     313        assertEquals(way.firstNode(), RoutingIslandsTest.lastNode(way, null));
     314
     315        way.put("vehicle:forward", "yes");
     316        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
     317        way.put("vehicle:backward", "no");
     318        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
     319        way.put("vehicle:forward", "no");
     320        assertEquals(way.lastNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
     321        way.put("vehicle:backward", "yes");
     322        assertEquals(way.firstNode(), RoutingIslandsTest.lastNode(way, "vehicle"));
     323    }
     324
     325}