Ticket #17528: intersectionissues.3.patch

File intersectionissues.3.patch, 15.3 KB (added by taylor.smock, 5 years ago)

Implement methods to ensure a test occurs after another test. The code that attempts to prevent duplication of overlapping ways is not currently working.

  • src/org/openstreetmap/josm/actions/ValidateAction.java

     
    116116        private boolean canceled;
    117117        private List<TestError> errors;
    118118
     119        private List<Class<? extends Test>> runTests;
     120
    119121        /**
    120122         * Constructs a new {@code ValidationTask}
    121123         * @param tests  the tests to run
     
    153155        @Override
    154156        protected void realRun() throws SAXException, IOException,
    155157        OsmTransferException {
     158            runTests = new ArrayList<>();
    156159            if (tests == null || tests.isEmpty())
    157160                return;
    158161            errors = new ArrayList<>(200);
    159162            getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size());
    160             int testCounter = 0;
     163            runTests(tests, 0);
     164            tests = null;
     165            if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) {
     166                getProgressMonitor().setCustomText("");
     167                getProgressMonitor().subTask(tr("Updating ignored errors ..."));
     168                for (TestError error : errors) {
     169                    if (canceled) return;
     170                    error.updateIgnored();
     171                }
     172            }
     173        }
     174
     175        protected int runTests(Collection<Test> tests, int testCounter) {
     176            ArrayList<Test> remaining = new ArrayList<>();
    161177            for (Test test : tests) {
    162178                if (canceled)
    163                     return;
     179                    return testCounter;
     180                if (test.getAfterClass() != null && !runTests.contains(test.getAfterClass())) {
     181                    remaining.add(test);
     182                    continue;
     183                }
    164184                testCounter++;
    165                 getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(), test.getName()));
     185                getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, this.tests.size(), test.getName()));
    166186                test.setPartialSelection(formerValidatedPrimitives != null);
    167187                test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false));
    168188                test.visit(validatedPrimitives);
     
    169189                test.endTest();
    170190                errors.addAll(test.getErrors());
    171191                test.clear();
     192                runTests.add(test.getClass());
    172193            }
    173             tests = null;
    174             if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) {
    175                 getProgressMonitor().setCustomText("");
    176                 getProgressMonitor().subTask(tr("Updating ignored errors ..."));
    177                 for (TestError error : errors) {
    178                     if (canceled) return;
    179                     error.updateIgnored();
    180                 }
     194            if (!remaining.isEmpty()) {
     195                testCounter = runTests(remaining, testCounter);
    181196            }
     197            return testCounter;
    182198        }
    183199    }
    184200}
  • src/org/openstreetmap/josm/data/validation/OsmValidator.java

     
    4949import org.openstreetmap.josm.data.validation.tests.DuplicatedWayNodes;
    5050import org.openstreetmap.josm.data.validation.tests.Highways;
    5151import org.openstreetmap.josm.data.validation.tests.InternetTags;
     52import org.openstreetmap.josm.data.validation.tests.IntersectionIssues;
    5253import org.openstreetmap.josm.data.validation.tests.Lanes;
    5354import org.openstreetmap.josm.data.validation.tests.LongSegment;
    5455import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
     
    148149        LongSegment.class, // 3500 .. 3599
    149150        PublicTransportRouteTest.class, // 3600 .. 3699
    150151        RightAngleBuildingTest.class, // 3700 .. 3799
     152        IntersectionIssues.class, // 3800 .. 3899
    151153    };
    152154
    153155    /**
  • src/org/openstreetmap/josm/data/validation/Test.java

     
    4646    /** Name of the test */
    4747    protected final String name;
    4848
     49    /** Test to run after */
     50    protected Class<? extends Test> afterTest;
     51
    4952    /** Description of the test */
    5053    protected final String description;
    5154
     
    8487     * @param description Description of the test
    8588     */
    8689    public Test(String name, String description) {
     90        this(name, description, null);
     91    }
     92
     93    /**
     94     * Constructor
     95     * @param name Name of the test
     96     * @param description Description of the test
     97     * @param afterTest Ensure the test is run after a test with this name
     98     *
     99     * @since xxx
     100     */
     101    public Test(String name, String description, Class<? extends Test> afterTest) {
    87102        this.name = name;
    88103        this.description = description;
     104        this.afterTest = afterTest;
    89105    }
    90106
    91107    /**
     
    319335    }
    320336
    321337    /**
     338     * Get the class that the test must run after
     339     * @return A class that extends {@code Test}
     340     *
     341     * @since xxx
     342     */
     343    public Class<? extends Test> getAfterClass() {
     344        return afterTest;
     345    }
     346
     347    /**
    322348     * Determines if the test has been canceled.
    323349     * @return {@code true} if the test has been canceled, {@code false} otherwise
    324350     */
  • src/org/openstreetmap/josm/data/validation/tests/IntersectionIssues.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.HashMap;
     8import java.util.List;
     9import java.util.Set;
     10
     11import org.openstreetmap.josm.data.coor.EastNorth;
     12import org.openstreetmap.josm.data.coor.LatLon;
     13import org.openstreetmap.josm.data.gpx.GpxDistance;
     14import org.openstreetmap.josm.data.gpx.WayPoint;
     15import org.openstreetmap.josm.data.osm.Node;
     16import org.openstreetmap.josm.data.osm.Way;
     17import org.openstreetmap.josm.data.validation.Severity;
     18import org.openstreetmap.josm.data.validation.Test;
     19import org.openstreetmap.josm.data.validation.TestError;
     20import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     21import org.openstreetmap.josm.tools.Geometry;
     22import org.openstreetmap.josm.tools.Logging;
     23
     24/**
     25 * Finds issues with highway intersections
     26 * @author Taylor Smock
     27 * @since xxx
     28 */
     29public class IntersectionIssues extends Test {
     30    private static final int INTERSECTIONISSUESCODE = 3800;
     31    /** The code for an intersection which briefly interrupts a road */
     32    public static final int SHORT_DISCONNECT = INTERSECTIONISSUESCODE + 0;
     33    /** The code for a node that is almost on a way */
     34    public static final int NEARBY_NODE = INTERSECTIONISSUESCODE + 1;
     35    /** The distance to consider for nearby nodes/short disconnects */
     36    public static final double maxDistance = 5.0; // meters
     37    /** The distance to consider for nearby nodes with tags */
     38    public static final double maxDistanceNodeInformation = maxDistance / 5.0; // meters
     39    /** The maximum angle for almost overlapping ways */
     40    public static final double maxAngle = 15.0;
     41
     42    private HashMap<String, ArrayList<Way>> ways;
     43    ArrayList<Way> allWays;
     44
     45    /**
     46     * Construct a new {@code IntersectionIssues} object
     47     */
     48    public IntersectionIssues() {
     49        super(tr("Intersection Issues"), tr("Check for issues at intersections"), OverlappingWays.class);
     50    }
     51
     52    @Override
     53    public void startTest(ProgressMonitor monitor) {
     54        super.startTest(monitor);
     55        ways = new HashMap<>();
     56        allWays = new ArrayList<>();
     57    }
     58
     59    @Override
     60    public void endTest() {
     61        Way pWay = null;
     62        try {
     63            for (String key : ways.keySet()) {
     64                ArrayList<Way> comparison = ways.get(key);
     65                pWay = comparison.get(0);
     66                checkNearbyEnds(comparison);
     67            }
     68            for (Way way : allWays) {
     69                pWay = way;
     70                for (Way way2 : allWays) {
     71                    if (way2.equals(way)) continue;
     72                    pWay = way2;
     73                    if (way.getBBox().intersects(way2.getBBox())) {
     74                        checkNearbyNodes(way, way2);
     75                    }
     76                }
     77            }
     78        } catch (Exception e) {
     79            if (pWay != null) {
     80                System.out.printf("Way https://osm.org/way/%d caused an error".concat(System.lineSeparator()), pWay.getOsmId());
     81            }
     82            e.printStackTrace();
     83        }
     84        ways = null;
     85        allWays = null;
     86        super.endTest();
     87    }
     88
     89    @Override
     90    public void visit(Way way) {
     91        if (!way.isUsable()) return;
     92        if (way.hasKey("highway")) {
     93            String[] identityTags = new String[] {"name", "ref"};
     94            for (String tag : identityTags) {
     95                if (way.hasKey(tag)) {
     96                    ArrayList<Way> similar = new ArrayList<>();
     97                    if (ways.containsKey(way.get(tag))) similar = ways.get(way.get(tag));
     98
     99                    if (!similar.contains(way)) similar.add(way);
     100                    ways.put(way.get(tag), similar);
     101                }
     102            }
     103            if (!allWays.contains(way)) allWays.add(way);
     104        }
     105    }
     106
     107    /**
     108     * Check for ends that are nearby but not directly connected
     109     * @param comparison Ways to look at
     110     */
     111    public void checkNearbyEnds(ArrayList<Way> comparison) {
     112        ArrayList<Way> errored = new ArrayList<>();
     113        for (Way one : comparison) {
     114            LatLon oneLast = one.lastNode().getCoor();
     115            LatLon oneFirst = one.firstNode().getCoor();
     116            for (Way two : comparison) {
     117                if (one.isFirstLastNode(two.firstNode()) || one.isFirstLastNode(two.lastNode()) ||
     118                        (errored.contains(one) && errored.contains(two))) continue;
     119                LatLon twoLast = two.lastNode().getCoor();
     120                LatLon twoFirst = two.firstNode().getCoor();
     121                if (twoLast.greatCircleDistance(oneLast) <= maxDistance ||
     122                        twoLast.greatCircleDistance(oneFirst) <= maxDistance ||
     123                        twoFirst.greatCircleDistance(oneLast) <= maxDistance ||
     124                        twoFirst.greatCircleDistance(oneFirst) <= maxDistance) {
     125                    List<Way> nearby = new ArrayList<>();
     126                    nearby.add(one);
     127                    nearby.add(two);
     128                    errored.addAll(nearby);
     129                    allWays.removeAll(errored);
     130                    TestError.Builder testError = TestError.builder(this, Severity.WARNING, SHORT_DISCONNECT)
     131                            .primitives(nearby)
     132                            .message(tr("Disconnected road"));
     133                    errors.add(testError.build());
     134                }
     135            }
     136        }
     137    }
     138
     139    /**
     140     * Check nearby nodes to an intersection of two ways
     141     * @param way1 A way to check an almost intersection with
     142     * @param way2 A way to check an almost intersection with
     143     */
     144    public void checkNearbyNodes(Way way1, Way way2) {
     145        Node intersectingNode = getIntersectingNode(way1, way2);
     146        if (intersectingNode == null) return;
     147        checkNearbyNodes(way1, way2, intersectingNode);
     148        checkNearbyNodes(way2, way1, intersectingNode);
     149    }
     150
     151    private void checkNearbyNodes(Way way1, Way way2, Node nearby) {
     152        for (Node node : way1.getNeighbours(nearby)) {
     153            if (node.equals(nearby)) continue;
     154            WayPoint waypoint = new WayPoint(node.getCoor());
     155            double distance = GpxDistance.getDistance(way2, waypoint);
     156            if (((distance < maxDistance && !node.isTagged())
     157                    || (distance < maxDistanceNodeInformation && node.isTagged()))
     158                    && getSmallestAngle(way2, nearby, node) < maxAngle) {
     159                List<Way> primitiveIssues = new ArrayList<>();
     160                primitiveIssues.add(way1);
     161                primitiveIssues.add(way2);
     162                for (TestError error : getErrors()) {
     163                    int code = error.getCode();
     164                    if ((code == SHORT_DISCONNECT || code == NEARBY_NODE
     165                            || code == OverlappingWays.OVERLAPPING_HIGHWAY
     166                            || code == OverlappingWays.DUPLICATE_WAY_SEGMENT
     167                            || code == OverlappingWays.OVERLAPPING_HIGHWAY_AREA
     168                            || code == OverlappingWays.OVERLAPPING_WAY
     169                            || code == OverlappingWays.OVERLAPPING_WAY_AREA
     170                            || code == OverlappingWays.OVERLAPPING_RAILWAY
     171                            || code == OverlappingWays.OVERLAPPING_RAILWAY_AREA)
     172                            && primitiveIssues.containsAll(error.getPrimitives())) {
     173                        Logging.info("{0}: Found duplicate error", getName());
     174                        return;
     175                    }
     176                }
     177                TestError.Builder testError = TestError.builder(this, Severity.WARNING, NEARBY_NODE)
     178                        .primitives(primitiveIssues)
     179                        .message(tr("Almost overlapping highways"));
     180                errors.add(testError.build());
     181            }
     182        }
     183    }
     184
     185    /**
     186     * Get the intersecting node of two ways
     187     * @param way1 A way that (hopefully) intersects with way2
     188     * @param way2 A way to find an intersection with
     189     * @return {@code Node} if there is an intersecting node, {@code null} otherwise
     190     */
     191    public Node getIntersectingNode(Way way1, Way way2) {
     192        for (Node node : way1.getNodes()) {
     193            if (way2.containsNode(node)) {
     194                return node;
     195            }
     196        }
     197        return null;
     198    }
     199
     200    /**
     201     * Get the corner angle between nodes
     202     * @param way The way with additional nodes
     203     * @param intersection The node to get angles around
     204     * @param comparison The node to get angles from
     205     * @return The angle for comparison->intersection->(additional node) (normalized degrees)
     206     */
     207    public double getSmallestAngle(Way way, Node intersection, Node comparison) {
     208        Set<Node> neighbours = way.getNeighbours(intersection);
     209        double angle = Double.MAX_VALUE;
     210        EastNorth eastNorthIntersection = intersection.getEastNorth();
     211        EastNorth eastNorthComparison = comparison.getEastNorth();
     212        for (Node node : neighbours) {
     213            EastNorth eastNorthNode = node.getEastNorth();
     214            double tAngle = Geometry.getCornerAngle(eastNorthComparison, eastNorthIntersection, eastNorthNode);
     215            if (Math.abs(tAngle) < angle) angle = Math.abs(tAngle);
     216        }
     217        return Geometry.getNormalizedAngleInDegrees(angle);
     218    }
     219}