Ticket #18127: 18127.4.patch

File 18127.4.patch, 13.2 KB (added by taylor.smock, 7 months ago)

Fix an issue where closed ways got the angle for the first node, the last node, and the second to last node (first node == last node) by getting the second node instead of the first node. Also more tests.

  • 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() && way.getNodesCount() > 2) {
     116            node1 = node2;
     117            node2 = node3;
     118            // Get the second node, not the first node, since a closed way has first node == second node
     119            node3 = way.getNode(1);
     120            checkAngle(node1, node2, node3, i, way, true);
     121        }
     122    }
     123
     124    private void checkAngle(Node node1, Node node2, Node node3, Integer i, Way way, Boolean last) {
     125        if (node1 == null || node2 == null || node3 == null) return;
     126        EastNorth n1 = node1.getEastNorth();
     127        EastNorth n2 = node2.getEastNorth();
     128        EastNorth n3 = node3.getEastNorth();
     129        Double angle = Math.toDegrees(Math.abs(Geometry.getCornerAngle(n1, n2, n3)));
     130        if (angle < MAX_ANGLE) {
     131            processSharpAngleForErrorCreation(angle, i, way, last);
     132        }
     133    }
     134
     135    private void processSharpAngleForErrorCreation(Double angle, Integer i, Way way, Boolean last) {
     136        List<WaySegment> waysegmentList = new ArrayList<>();
     137        waysegmentList.add(new WaySegment(way, i));
     138        if (last) {
     139            waysegmentList.add(new WaySegment(way, 0));
     140        } else {
     141            waysegmentList.add(new WaySegment(way, i+1));
     142        }
     143        Optional<WaySegment> possibleShortSegment = waysegmentList.stream()
     144                .min(Comparator.comparing(segment -> segment.toWay().getLength()));
     145        if (possibleShortSegment.isPresent() && possibleShortSegment.get().toWay().getLength() < 1) {
     146            createNearlyOverlappingError(angle,
     147                    Collections.singleton(way), Collections.singleton(possibleShortSegment.get()));
     148        } else {
     149            createNearlyOverlappingError(angle, Collections.singleton(way), waysegmentList);
     150        }
     151    }
     152
     153    private void createNearlyOverlappingError(Double angle,
     154            Collection<? extends OsmPrimitive> primitiveIssues, Collection<WaySegment> waysegments) {
     155        TestError.Builder testError = TestError.builder(this, getSeverity(angle), SHARP_ANGLES)
     156                .primitives(primitiveIssues)
     157                .highlightWaySegments(waysegments)
     158                .message(tr("Sharp angle"));
     159        errors.add(testError.build());
     160    }
     161
     162    private Severity getSeverity(Double angle) {
     163        Severity rSeverity = Severity.OTHER;
     164        for (Entry<Double, Severity> entry : severityBreakPoints.entrySet()) {
     165            if (angle < entry.getKey()) {
     166                rSeverity = entry.getValue();
     167            }
     168        }
     169        return rSeverity;
     170    }
     171
     172    @Override
     173    public boolean equals(Object other) {
     174        if (other instanceof SharpAngles) {
     175            SharpAngles otherReal = (SharpAngles) other;
     176            if (otherReal.allWays.equals(allWays)) {
     177                return true;
     178            }
     179        }
     180        return false;
     181    }
     182
     183    @Override
     184    public int hashCode() {
     185        return super.hashCode();
     186    }
     187}
  • 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     * Ensure that we aren't accidentally using the same node twice.
     90     * This was found during initial testing. See way 10041221 (on 20190914)
     91     */
     92    @Test
     93    public void testCheckBadAnglesFromSameNodeTwice() {
     94        Way way = TestUtils.newWay("highway=service oneway=yes",
     95                new Node(new LatLon(52.8903308, 8.4302322)),
     96                new Node(new LatLon(52.8902468, 8.4302138)),
     97                new Node(new LatLon(52.8902191, 8.4302282)),
     98                new Node(new LatLon(52.8901155, 8.4304753)),
     99                new Node(new LatLon(52.8900669, 8.430763)),
     100                new Node(new LatLon(52.8901138, 8.4308262)),
     101                new Node(new LatLon(52.8902482, 8.4307568)));
     102        way.addNode(way.firstNode());
     103        angles.visit(way);
     104        angles.endTest();
     105        Assert.assertEquals(0, angles.getErrors().size());
     106    }
     107
     108    /**
     109     * Check that special cases are ignored
     110     */
     111    @Test
     112    public void testIgnoredCases() {
     113        Way way = TestUtils.newWay("highway=residential",
     114                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     115                new Node(new LatLon(0, 0.01)));
     116        angles.visit(way);
     117        angles.endTest();
     118        Assert.assertEquals(1, angles.getErrors().size());
     119
     120        way.put("highway", "rest_area");
     121        angles.startTest(null);
     122        angles.visit(way);
     123        angles.endTest();
     124        Assert.assertEquals(0, angles.getErrors().size());
     125
     126        way.put("highway", "residential");
     127        angles.startTest(null);
     128        angles.visit(way);
     129        angles.endTest();
     130        Assert.assertEquals(1, angles.getErrors().size());
     131        way.put("area", "yes");
     132        angles.startTest(null);
     133        angles.visit(way);
     134        angles.endTest();
     135        Assert.assertEquals(0, angles.getErrors().size());
     136        way.put("area", "no");
     137        angles.startTest(null);
     138        angles.visit(way);
     139        angles.endTest();
     140        Assert.assertEquals(1, angles.getErrors().size());
     141    }
     142}
     143 No newline at end of file