Ticket #18127: 18127.patch

File 18127.patch, 6.7 KB (added by taylor.smock, 7 months ago)

Add check for sharp angles in roads, no unit tests yet

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