Ticket #18127: 18127.5.patch

File 18127.5.patch, 14.0 KB (added by taylor.smock, 7 months ago)

Set a default maximum length that can be overriden (mostly for tests) and add methods for other classes to add/modify data (static, so it carries over between classes).

  • 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;
     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 double MAX_ANGLE = 45.0; // degrees
     40    /** The length that at least one way segment must be shorter than */
     41    protected static double MAX_LENGTH = 10.0; // meters
     42    /** The stepping points for severity */
     43    protected static Map<Double, Severity> severityBreakPoints = new LinkedHashMap<>();
     44    /** Specific highway types to ignore */
     45    protected static Collection<String> ignoreHighways = new TreeSet<>();
     46    static {
     47        setBreakPoints();
     48        addIgnoredHighway("rest_area");
     49        addIgnoredHighway("platform");
     50        addIgnoredHighway("services");
     51        addIgnoredHighway("via_ferrata"); // mountainside paths
     52    }
     53
     54    ArrayList<Way> allWays;
     55    /**
     56     * Construct a new {@code IntersectionIssues} object
     57     */
     58    public SharpAngles() {
     59        super(tr("Sharp angles"), tr("Check for sharp angles on roads"));
     60    }
     61
     62    @Override
     63    public void startTest(ProgressMonitor monitor) {
     64        super.startTest(monitor);
     65        allWays = new ArrayList<>();
     66    }
     67
     68    @Override
     69    public void endTest() {
     70        Way pWay = null;
     71        try {
     72            for (Way way : allWays) {
     73                pWay = way;
     74                checkWayForSharpAngles(way);
     75            }
     76        } catch (Exception e) {
     77            if (pWay != null) {
     78                Logging.debug("Way https://osm.org/way/{0} caused an error ({1})", pWay.getOsmId(), e);
     79            }
     80            Logging.warn(e);
     81        }
     82        allWays = null;
     83        super.endTest();
     84    }
     85
     86    @Override
     87    public void visit(Way way) {
     88        if (!way.isUsable()) return;
     89        if (way.hasKey("highway")) {
     90            if ((way.hasKey("area") && way.get("area").equalsIgnoreCase("yes")) ||
     91                    ignoreHighways.contains(way.get("highway"))) {
     92                return;
     93            }
     94            allWays.add(way);
     95        }
     96    }
     97
     98    /**
     99     * Check nodes in a way for sharp angles
     100     * @param way A way to check for sharp angles
     101     */
     102    public void checkWayForSharpAngles(Way way) {
     103        Node node1 = null;
     104        Node node2 = null;
     105        Node node3 = null;
     106        int i = -2;
     107        for (Node node : way.getNodes()) {
     108            node1 = node2;
     109            node2 = node3;
     110            node3 = node;
     111            checkAngle(node1, node2, node3, i, way, false);
     112            i++;
     113        }
     114        if (way.isClosed() && way.getNodesCount() > 2) {
     115            node1 = node2;
     116            node2 = node3;
     117            // Get the second node, not the first node, since a closed way has first node == second node
     118            node3 = way.getNode(1);
     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() && possibleShortSegment.get().toWay().getLength() < MAX_LENGTH) {
     145            createNearlyOverlappingError(angle, Collections.singleton(way), waysegmentList);
     146        }
     147    }
     148
     149    private void createNearlyOverlappingError(Double angle,
     150            Collection<? extends OsmPrimitive> primitiveIssues, Collection<WaySegment> waysegments) {
     151        TestError.Builder testError = TestError.builder(this, getSeverity(angle), SHARP_ANGLES)
     152                .primitives(primitiveIssues)
     153                .highlightWaySegments(waysegments)
     154                .message(tr("Sharp angle"));
     155        errors.add(testError.build());
     156    }
     157
     158    private Severity getSeverity(Double angle) {
     159        Severity rSeverity = Severity.OTHER;
     160        for (Entry<Double, Severity> entry : severityBreakPoints.entrySet()) {
     161            if (angle < entry.getKey()) {
     162                rSeverity = entry.getValue();
     163            }
     164        }
     165        return rSeverity;
     166    }
     167
     168    /**
     169     * Set the maximum length for the shortest segment
     170     * @param length The max length in meters
     171     */
     172    public static void setMaxLength(double length) {
     173        MAX_LENGTH = length;
     174    }
     175
     176    /**
     177     * Add a highway to ignore
     178     * @param highway
     179     */
     180    public static void addIgnoredHighway(String highway) {
     181        ignoreHighways.add(highway);
     182    }
     183
     184    /**
     185     * Set the maximum angle
     186     * @param angle The maximum angle in degrees.
     187     */
     188    public static void setMaxAngle(double angle) {
     189        MAX_ANGLE = angle;
     190        setBreakPoints();
     191    }
     192
     193    /**
     194     * Set the breakpoints for the test
     195     */
     196    private static void setBreakPoints() {
     197        severityBreakPoints.put(MAX_ANGLE, Severity.OTHER);
     198        severityBreakPoints.put(MAX_ANGLE * 2 / 3, Severity.WARNING);
     199        severityBreakPoints.put(MAX_ANGLE / 3, Severity.ERROR);
     200    }
     201
     202    @Override
     203    public boolean equals(Object other) {
     204        if (other instanceof SharpAngles) {
     205            SharpAngles otherReal = (SharpAngles) other;
     206            if (otherReal.allWays.equals(allWays)) {
     207                return true;
     208            }
     209        }
     210        return false;
     211    }
     212
     213    @Override
     214    public int hashCode() {
     215        return super.hashCode();
     216    }
     217}
  • 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        SharpAngles.setMaxLength(Double.MAX_VALUE);
     56        angles.visit(way);
     57        angles.endTest();
     58        Assert.assertEquals(1, angles.getErrors().size());
     59    }
     60
     61    /**
     62     * Check a way for multiple sharp angles
     63     */
     64    @Test
     65    public void testMultipleSharpAngles() {
     66        Way way = TestUtils.newWay("highway=residential",
     67                new Node(new LatLon(0.005069377713748322, -0.0014832642674429382)),
     68                new Node(new LatLon(0.005021097951663415, 0.0008636686205880686)),
     69                new Node(new LatLon(0.005085470967776624, -0.00013411313295197088)),
     70                new Node(new LatLon(0.005031826787678042, 0.0020116540789620915)));
     71        SharpAngles.setMaxLength(Double.MAX_VALUE);
     72        angles.visit(way);
     73        angles.endTest();
     74        Assert.assertEquals(2,  angles.getErrors().size());
     75    }
     76
     77    /**
     78     * Check for no sharp angles
     79     */
     80    @Test
     81    public void testNoSharpAngles() {
     82        Way way = TestUtils.newWay("highway=residential",
     83                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     84                new Node(new LatLon(0.2, 0.3)), new Node(new LatLon(0.3, 0.1)));
     85        angles.visit(way);
     86        angles.endTest();
     87        Assert.assertEquals(0, angles.getErrors().size());
     88    }
     89
     90    /**
     91     * Ensure that we aren't accidentally using the same node twice.
     92     * This was found during initial testing. See way 10041221 (on 20190914)
     93     */
     94    @Test
     95    public void testCheckBadAnglesFromSameNodeTwice() {
     96        Way way = TestUtils.newWay("highway=service oneway=yes",
     97                new Node(new LatLon(52.8903308, 8.4302322)),
     98                new Node(new LatLon(52.8902468, 8.4302138)),
     99                new Node(new LatLon(52.8902191, 8.4302282)),
     100                new Node(new LatLon(52.8901155, 8.4304753)),
     101                new Node(new LatLon(52.8900669, 8.430763)),
     102                new Node(new LatLon(52.8901138, 8.4308262)),
     103                new Node(new LatLon(52.8902482, 8.4307568)));
     104        way.addNode(way.firstNode());
     105        angles.visit(way);
     106        angles.endTest();
     107        Assert.assertEquals(0, angles.getErrors().size());
     108    }
     109
     110    /**
     111     * Check that special cases are ignored
     112     */
     113    @Test
     114    public void testIgnoredCases() {
     115        Way way = TestUtils.newWay("highway=residential",
     116                new Node(new LatLon(0, 0)), new Node(new LatLon(0.1, 0.1)),
     117                new Node(new LatLon(0, 0.01)));
     118        angles.visit(way);
     119        angles.endTest();
     120        Assert.assertEquals(1, angles.getErrors().size());
     121
     122        way.put("highway", "rest_area");
     123        angles.startTest(null);
     124        angles.visit(way);
     125        angles.endTest();
     126        Assert.assertEquals(0, angles.getErrors().size());
     127
     128        way.put("highway", "residential");
     129        angles.startTest(null);
     130        angles.visit(way);
     131        angles.endTest();
     132        Assert.assertEquals(1, angles.getErrors().size());
     133        way.put("area", "yes");
     134        angles.startTest(null);
     135        angles.visit(way);
     136        angles.endTest();
     137        Assert.assertEquals(0, angles.getErrors().size());
     138        way.put("area", "no");
     139        angles.startTest(null);
     140        angles.visit(way);
     141        angles.endTest();
     142        Assert.assertEquals(1, angles.getErrors().size());
     143    }
     144}
     145 No newline at end of file