Ticket #18127: 18127.3.patch

File 18127.3.patch, 11.9 KB (added by taylor.smock, 7 months ago)

Add tests and fix issue with closed ways where an NPE would occur

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

     
    6161import org.openstreetmap.josm.data.validation.tests.RelationChecker;
    6262import org.openstreetmap.josm.data.validation.tests.RightAngleBuildingTest;
    6363import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
     64import org.openstreetmap.josm.data.validation.tests.SharpAngles;
    6465import org.openstreetmap.josm.data.validation.tests.SimilarNamedWays;
    6566import org.openstreetmap.josm.data.validation.tests.TagChecker;
    6667import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest;
     
    148149        LongSegment.class, // 3500 .. 3599
    149150        PublicTransportRouteTest.class, // 3600 .. 3699
    150151        RightAngleBuildingTest.class, // 3700 .. 3799
     152        SharpAngles.class, // 3800 .. 3899
    151153    };
    152154
    153155    /**
  • src/org/openstreetmap/josm/data/validation/tests/SharpAngles.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.Collection;
     8import java.util.Collections;
     9import java.util.Comparator;
     10import java.util.LinkedHashMap;
     11import java.util.List;
     12import java.util.Map;
     13import java.util.Map.Entry;
     14import java.util.Optional;
     15import java.util.TreeSet;
     16
     17import org.openstreetmap.josm.data.coor.EastNorth;
     18import org.openstreetmap.josm.data.osm.Node;
     19import org.openstreetmap.josm.data.osm.OsmPrimitive;
     20import org.openstreetmap.josm.data.osm.Way;
     21import org.openstreetmap.josm.data.osm.WaySegment;
     22import org.openstreetmap.josm.data.validation.Severity;
     23import org.openstreetmap.josm.data.validation.Test;
     24import org.openstreetmap.josm.data.validation.TestError;
     25import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     26import org.openstreetmap.josm.tools.Geometry;
     27import org.openstreetmap.josm.tools.Logging;
     28
     29/**
     30 * Find highways that have sharp angles
     31 * @author Taylor Smock
     32 *
     33 */
     34public class SharpAngles extends Test {
     35    private static final int SHARPANGLESCODE = 3800; // TODO change this before commit
     36    /** The code for a sharp angle */
     37    protected static final int SHARP_ANGLES = SHARPANGLESCODE + 0;
     38    /** The maximum angle for sharp angles */
     39    protected static final double MAX_ANGLE = 45.0; // degrees
     40    /** The stepping points for severity */
     41    protected static final Map<Double, Severity> severityBreakPoints = new LinkedHashMap<>();
     42    static {
     43        severityBreakPoints.put(MAX_ANGLE, Severity.OTHER);
     44        severityBreakPoints.put(MAX_ANGLE * 2 / 3, Severity.WARNING);
     45        severityBreakPoints.put(MAX_ANGLE / 3, Severity.ERROR);
     46    }
     47    /** Specific highway types to ignore */
     48    protected static final Collection<String> ignoreHighways = new TreeSet<>();
     49    static {
     50        ignoreHighways.add("rest_area");
     51        ignoreHighways.add("platform");
     52        ignoreHighways.add("services");
     53    }
     54
     55    ArrayList<Way> allWays;
     56    /**
     57     * Construct a new {@code IntersectionIssues} object
     58     */
     59    public SharpAngles() {
     60        super(tr("Sharp angles"), tr("Check for sharp angles on roads"));
     61    }
     62
     63    @Override
     64    public void startTest(ProgressMonitor monitor) {
     65        super.startTest(monitor);
     66        allWays = new ArrayList<>();
     67    }
     68
     69    @Override
     70    public void endTest() {
     71        Way pWay = null;
     72        try {
     73            for (Way way : allWays) {
     74                pWay = way;
     75                checkWayForSharpAngles(way);
     76            }
     77        } catch (Exception e) {
     78            if (pWay != null) {
     79                Logging.debug("Way https://osm.org/way/{0} caused an error ({1})", pWay.getOsmId(), e);
     80            }
     81            Logging.warn(e);
     82        }
     83        allWays = null;
     84        super.endTest();
     85    }
     86
     87    @Override
     88    public void visit(Way way) {
     89        if (!way.isUsable()) return;
     90        if (way.hasKey("highway")) {
     91            if (way.hasKey("area") && way.get("area").equalsIgnoreCase("yes") ||
     92                    ignoreHighways.contains(way.get("highway"))) {
     93                return;
     94            }
     95            allWays.add(way);
     96        }
     97    }
     98
     99    /**
     100     * Check nodes in a way for sharp angles
     101     * @param way A way to check for sharp angles
     102     */
     103    public void checkWayForSharpAngles(Way way) {
     104        Node node1 = null;
     105        Node node2 = null;
     106        Node node3 = null;
     107        int i = -2;
     108        for (Node node : way.getNodes()) {
     109            node1 = node2;
     110            node2 = node3;
     111            node3 = node;
     112            checkAngle(node1, node2, node3, i, way, false);
     113            i++;
     114        }
     115        if (way.isClosed()) {
     116            node1 = node2;
     117            node2 = node3;
     118            node3 = way.firstNode();
     119            checkAngle(node1, node2, node3, i, way, true);
     120        }
     121    }
     122
     123    private void checkAngle(Node node1, Node node2, Node node3, Integer i, Way way, Boolean last) {
     124        if (node1 == null || node2 == null || node3 == null) return;
     125        EastNorth n1 = node1.getEastNorth();
     126        EastNorth n2 = node2.getEastNorth();
     127        EastNorth n3 = node3.getEastNorth();
     128        Double angle = Math.toDegrees(Math.abs(Geometry.getCornerAngle(n1, n2, n3)));
     129        if (angle < MAX_ANGLE) {
     130            processSharpAngleForErrorCreation(angle, i, way, last);
     131        }
     132    }
     133
     134    private void processSharpAngleForErrorCreation(Double angle, Integer i, Way way, Boolean last) {
     135        List<WaySegment> waysegmentList = new ArrayList<>();
     136        waysegmentList.add(new WaySegment(way, i));
     137        if (last) {
     138            waysegmentList.add(new WaySegment(way, 0));
     139        } else {
     140            waysegmentList.add(new WaySegment(way, i+1));
     141        }
     142        Optional<WaySegment> possibleShortSegment = waysegmentList.stream()
     143                .min(Comparator.comparing(segment -> segment.toWay().getLength()));
     144        if (possibleShortSegment.isPresent()) {
     145            createNearlyOverlappingError(angle,
     146                    Collections.singleton(way), Collections.singleton(possibleShortSegment.get()));
     147        } else {
     148            createNearlyOverlappingError(angle, Collections.singleton(way), waysegmentList);
     149        }
     150    }
     151
     152    private void createNearlyOverlappingError(Double angle,
     153            Collection<? extends OsmPrimitive> primitiveIssues, Collection<WaySegment> waysegments) {
     154        TestError.Builder testError = TestError.builder(this, getSeverity(angle), SHARP_ANGLES)
     155                .primitives(primitiveIssues)
     156                .highlightWaySegments(waysegments)
     157                .message(tr("Sharp angle"));
     158        errors.add(testError.build());
     159    }
     160
     161    private Severity getSeverity(Double angle) {
     162        Severity rSeverity = Severity.OTHER;
     163        for (Entry<Double, Severity> entry : severityBreakPoints.entrySet()) {
     164            if (angle < entry.getKey()) {
     165                rSeverity = entry.getValue();
     166            }
     167        }
     168        return rSeverity;
     169    }
     170
     171    @Override
     172    public boolean equals(Object other) {
     173        if (other instanceof SharpAngles) {
     174            SharpAngles otherReal = (SharpAngles) other;
     175            if (otherReal.allWays.equals(allWays)) {
     176                return true;
     177            }
     178        }
     179        return false;
     180    }
     181
     182    @Override
     183    public int hashCode() {
     184        return super.hashCode();
     185    }
     186}
  • test/unit/org/openstreetmap/josm/data/validation/tests/SharpAnglesTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.validation.tests;
     3
     4import org.junit.Assert;
     5import org.junit.Before;
     6import org.junit.Test;
     7import org.openstreetmap.josm.JOSMFixture;
     8import org.openstreetmap.josm.TestUtils;
     9import org.openstreetmap.josm.data.coor.LatLon;
     10import org.openstreetmap.josm.data.osm.Node;
     11import org.openstreetmap.josm.data.osm.Way;
     12
     13/**
     14 * JUnit Test of the Sharp Angles validation test.
     15 */
     16
     17public class SharpAnglesTest {
     18    private SharpAngles angles;
     19
     20    /**
     21     * Setup test.
     22     * @throws Exception if an error occurs
     23     */
     24    @Before
     25    public void setUp() throws Exception {
     26        JOSMFixture.createUnitTestFixture().init();
     27        angles = new SharpAngles();
     28        angles.initialize();
     29        angles.startTest(null);
     30    }
     31
     32    /**
     33     * Check a closed loop with no sharp angles
     34     */
     35    @Test
     36    public void closedLoopNoSharpAngles() {
     37        Way way = TestUtils.newWay("highway=residential",
     38                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     39                new Node(new LatLon(0.1, -0.2)), new Node(new LatLon(-0.1, -0.1)));
     40        way.addNode(way.firstNode());
     41        angles.visit(way);
     42        angles.endTest();
     43        Assert.assertEquals(0, angles.getErrors().size());
     44    }
     45
     46    /**
     47     * Check a closed loop with a sharp angle
     48     */
     49    @Test
     50    public void closedLoopSharpAngles() {
     51        Way way = TestUtils.newWay("highway=residential",
     52                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     53                new Node(new LatLon(0.1, -0.2)));
     54        way.addNode(way.firstNode());
     55        angles.visit(way);
     56        angles.endTest();
     57        Assert.assertEquals(1, angles.getErrors().size());
     58    }
     59
     60    /**
     61     * Check a way for multiple sharp angles
     62     */
     63    @Test
     64    public void testMultipleSharpAngles() {
     65        Way way = TestUtils.newWay("highway=residential",
     66                new Node(new LatLon(0.005069377713748322, -0.0014832642674429382)),
     67                new Node(new LatLon(0.005021097951663415, 0.0008636686205880686)),
     68                new Node(new LatLon(0.005085470967776624, -0.00013411313295197088)),
     69                new Node(new LatLon(0.005031826787678042, 0.0020116540789620915)));
     70        angles.visit(way);
     71        angles.endTest();
     72        Assert.assertEquals(2,  angles.getErrors().size());
     73    }
     74
     75    /**
     76     * Check for no sharp angles
     77     */
     78    @Test
     79    public void testNoSharpAngles() {
     80        Way way = TestUtils.newWay("highway=residential",
     81                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     82                new Node(new LatLon(0.2, 0.3)), new Node(new LatLon(0.3, 0.1)));
     83        angles.visit(way);
     84        angles.endTest();
     85        Assert.assertEquals(0, angles.getErrors().size());
     86    }
     87
     88    /**
     89     * Check that special cases are ignored
     90     */
     91    public void testIgnoredCases() {
     92        Way way = TestUtils.newWay("highway=residential",
     93                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     94                new Node(new LatLon(0.1, -0.2)));
     95        angles.visit(way);
     96        angles.endTest();
     97        Assert.assertEquals(1, angles.getErrors().size());
     98
     99        way.put("highway", "rest_area");
     100        angles.endTest();
     101        Assert.assertEquals(0, angles.getErrors().size());
     102
     103        way.put("highway", "residential");
     104        angles.endTest();
     105        Assert.assertEquals(1, angles.getErrors().size());
     106        way.put("area", "yes");
     107        angles.endTest();
     108        Assert.assertEquals(0, angles.getErrors().size());
     109        way.put("area", "no");
     110        angles.endTest();
     111        Assert.assertEquals(1, angles.getErrors().size());
     112    }
     113}
     114 No newline at end of file