Ticket #18127: 18127.2.patch

File 18127.2.patch, 7.7 KB (added by taylor.smock, 7 months ago)

Ignore some special highway cases, find sharp angles at start/end of closed way. Still no tests, and only highlights the shortest WaySegment.

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