Ticket #18127: 18127.11.patch

File 18127.11.patch, 12.1 KB (added by taylor.smock, 7 months ago)

Remove startTest and endTest. The tests now run as the ways are visited.

  • 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.Arrays;
     7import java.util.Collection;
     8import java.util.LinkedHashMap;
     9import java.util.Map;
     10import java.util.Map.Entry;
     11import java.util.TreeSet;
     12
     13import org.openstreetmap.josm.data.coor.EastNorth;
     14import org.openstreetmap.josm.data.osm.Node;
     15import org.openstreetmap.josm.data.osm.OsmPrimitive;
     16import org.openstreetmap.josm.data.osm.Way;
     17import org.openstreetmap.josm.data.osm.WaySegment;
     18import org.openstreetmap.josm.data.validation.Severity;
     19import org.openstreetmap.josm.data.validation.Test;
     20import org.openstreetmap.josm.data.validation.TestError;
     21import org.openstreetmap.josm.tools.Geometry;
     22import org.openstreetmap.josm.tools.Logging;
     23
     24/**
     25 * Find highways that have sharp angles
     26 * @author Taylor Smock
     27 * @since xxx
     28 */
     29public class SharpAngles extends Test {
     30    private static final int SHARPANGLESCODE = 3800;
     31    /** The code for a sharp angle */
     32    private static final int SHARP_ANGLES = SHARPANGLESCODE + 0;
     33    /** The maximum angle for sharp angles */
     34    private double maxAngle = 45.0; // degrees
     35    /** The length that at least one way segment must be shorter than */
     36    private double maxLength = 10.0; // meters
     37    /** The stepping points for severity */
     38    private Map<Double, Severity> severityBreakPoints = new LinkedHashMap<>();
     39    /** Specific highway types to ignore */
     40    private Collection<String> ignoreHighways = new TreeSet<>(
     41            Arrays.asList("platform", "rest_area", "services", "via_ferrata"));
     42
     43    /**
     44     * Construct a new {@code IntersectionIssues} object
     45     */
     46    public SharpAngles() {
     47        super(tr("Sharp angles"), tr("Check for sharp angles on roads"));
     48        setBreakPoints();
     49    }
     50
     51    @Override
     52    public void visit(Way way) {
     53        if (!way.isUsable()) return;
     54        if (way.hasKey("highway") && !way.hasTag("area", "yes") &&
     55                    !ignoreHighways.contains(way.get("highway"))) {
     56            try {
     57                checkWayForSharpAngles(way);
     58            } catch (Exception e) {
     59                Logging.error("Way https://osm.org/way/{0} caused an error ({1})", way.getUniqueId(), e);
     60                throw e;
     61            }
     62        }
     63    }
     64
     65    /**
     66     * Check nodes in a way for sharp angles
     67     * @param way A way to check for sharp angles
     68     */
     69    public void checkWayForSharpAngles(Way way) {
     70        Node node1 = null;
     71        Node node2 = null;
     72        Node node3 = null;
     73        int i = -2;
     74        for (Node node : way.getNodes()) {
     75            node1 = node2;
     76            node2 = node3;
     77            node3 = node;
     78            checkAngle(node1, node2, node3, i, way, false);
     79            i++;
     80        }
     81        if (way.isClosed() && way.getNodesCount() > 2) {
     82            node1 = node2;
     83            node2 = node3;
     84            // Get the second node, not the first node, since a closed way has first node == last node
     85            node3 = way.getNode(1);
     86            checkAngle(node1, node2, node3, i, way, true);
     87        }
     88    }
     89
     90    private void checkAngle(Node node1, Node node2, Node node3, int i, Way way, boolean last) {
     91        if (node1 == null || node2 == null || node3 == null) return;
     92        EastNorth n1 = node1.getEastNorth();
     93        EastNorth n2 = node2.getEastNorth();
     94        EastNorth n3 = node3.getEastNorth();
     95        double angle = Math.toDegrees(Math.abs(Geometry.getCornerAngle(n1, n2, n3)));
     96        if (angle < maxAngle) {
     97            processSharpAngleForErrorCreation(angle, i, way, last, node2);
     98        }
     99    }
     100
     101    private void processSharpAngleForErrorCreation(double angle, int i, Way way, boolean last, Node pointNode) {
     102        WaySegment ws1 = new WaySegment(way, i);
     103        WaySegment ws2 = new WaySegment(way, last ? 0 : i + 1);
     104        double shorterLen = Math.min(ws1.toWay().getLength(), ws2.toWay().getLength());
     105        if (shorterLen < maxLength) {
     106            createNearlyOverlappingError(angle, way, pointNode);
     107        }
     108    }
     109
     110    private void createNearlyOverlappingError(double angle, Way way, OsmPrimitive primitive) {
     111        TestError.Builder testError = TestError.builder(this, getSeverity(angle), SHARP_ANGLES)
     112                .primitives(way)
     113                .highlight(primitive)
     114                .message(tr("Sharp angle"));
     115        errors.add(testError.build());
     116    }
     117
     118    private Severity getSeverity(double angle) {
     119        Severity rSeverity = Severity.OTHER;
     120        for (Entry<Double, Severity> entry : severityBreakPoints.entrySet()) {
     121            if (angle < entry.getKey()) {
     122                rSeverity = entry.getValue();
     123            }
     124        }
     125        return rSeverity;
     126    }
     127
     128    /**
     129     * Set the maximum length for the shortest segment
     130     * @param length The max length in meters
     131     */
     132    public void setMaxLength(double length) {
     133        maxLength = length;
     134    }
     135
     136    /**
     137     * Add a highway to ignore
     138     * @param highway The highway type to ignore (e.g., if you want to ignore residential roads, use "residential")
     139     */
     140    public void addIgnoredHighway(String highway) {
     141        ignoreHighways.add(highway);
     142    }
     143
     144    /**
     145     * Set the maximum angle
     146     * @param angle The maximum angle in degrees.
     147     */
     148    public void setMaxAngle(double angle) {
     149        maxAngle = angle;
     150        setBreakPoints();
     151    }
     152
     153    /**
     154     * Set the breakpoints for the test
     155     */
     156    private void setBreakPoints() {
     157        severityBreakPoints.clear();
     158        severityBreakPoints.put(maxAngle, Severity.OTHER);
     159        severityBreakPoints.put(maxAngle * 2 / 3, Severity.WARNING);
     160        severityBreakPoints.put(maxAngle / 3, Severity.ERROR);
     161    }
     162}
     163
  • 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    }
     30
     31    /**
     32     * Check a closed loop with no sharp angles
     33     */
     34    @Test
     35    public void closedLoopNoSharpAngles() {
     36        Way way = TestUtils.newWay("highway=residential",
     37                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     38                new Node(new LatLon(0.1, -0.2)), new Node(new LatLon(-0.1, -0.1)));
     39        way.addNode(way.firstNode());
     40        angles.visit(way);
     41        Assert.assertEquals(0, angles.getErrors().size());
     42    }
     43
     44    /**
     45     * Check a closed loop with a sharp angle
     46     */
     47    @Test
     48    public void closedLoopSharpAngles() {
     49        Way way = TestUtils.newWay("highway=residential",
     50                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     51                new Node(new LatLon(0.1, -0.2)));
     52        way.addNode(way.firstNode());
     53        angles.setMaxLength(Double.MAX_VALUE);
     54        angles.visit(way);
     55        Assert.assertEquals(1, angles.getErrors().size());
     56    }
     57
     58    /**
     59     * Check a way for multiple sharp angles
     60     */
     61    @Test
     62    public void testMultipleSharpAngles() {
     63        Way way = TestUtils.newWay("highway=residential",
     64                new Node(new LatLon(0.005069377713748322, -0.0014832642674429382)),
     65                new Node(new LatLon(0.005021097951663415, 0.0008636686205880686)),
     66                new Node(new LatLon(0.005085470967776624, -0.00013411313295197088)),
     67                new Node(new LatLon(0.005031826787678042, 0.0020116540789620915)));
     68        angles.setMaxLength(Double.MAX_VALUE);
     69        angles.visit(way);
     70        Assert.assertEquals(2, angles.getErrors().size());
     71    }
     72
     73    /**
     74     * Check for no sharp angles
     75     */
     76    @Test
     77    public void testNoSharpAngles() {
     78        Way way = TestUtils.newWay("highway=residential",
     79                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     80                new Node(new LatLon(0.2, 0.3)), new Node(new LatLon(0.3, 0.1)));
     81        angles.visit(way);
     82        Assert.assertEquals(0, angles.getErrors().size());
     83    }
     84
     85    /**
     86     * Ensure that we aren't accidentally using the same node twice.
     87     * This was found during initial testing. See way 10041221 (on 20190914)
     88     */
     89    @Test
     90    public void testCheckBadAnglesFromSameNodeTwice() {
     91        Way way = TestUtils.newWay("highway=service oneway=yes",
     92                new Node(new LatLon(52.8903308, 8.4302322)),
     93                new Node(new LatLon(52.8902468, 8.4302138)),
     94                new Node(new LatLon(52.8902191, 8.4302282)),
     95                new Node(new LatLon(52.8901155, 8.4304753)),
     96                new Node(new LatLon(52.8900669, 8.430763)),
     97                new Node(new LatLon(52.8901138, 8.4308262)),
     98                new Node(new LatLon(52.8902482, 8.4307568)));
     99        way.addNode(way.firstNode());
     100        angles.visit(way);
     101        Assert.assertEquals(0, angles.getErrors().size());
     102    }
     103
     104    /**
     105     * Check that special cases are ignored
     106     */
     107    @Test
     108    public void testIgnoredCases() {
     109        Way way = TestUtils.newWay("highway=residential",
     110                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     111                new Node(new LatLon(0, 0.01)));
     112        angles.setMaxLength(Double.MAX_VALUE);
     113        angles.visit(way);
     114        Assert.assertEquals(1, angles.getErrors().size());
     115        angles.getErrors().clear();
     116
     117        way.put("highway", "rest_area");
     118        angles.visit(way);
     119        Assert.assertEquals(0, angles.getErrors().size());
     120
     121        way.put("highway", "residential");
     122        angles.visit(way);
     123        Assert.assertEquals(1, angles.getErrors().size());
     124        angles.getErrors().clear();
     125        way.put("area", "yes");
     126        angles.visit(way);
     127        Assert.assertEquals(0, angles.getErrors().size());
     128        way.put("area", "no");
     129        angles.visit(way);
     130        Assert.assertEquals(1, angles.getErrors().size());
     131    }
     132}