1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.validation.tests;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 |
|
---|
6 | import java.util.Arrays;
|
---|
7 | import java.util.Collection;
|
---|
8 | import java.util.LinkedHashMap;
|
---|
9 | import java.util.Map;
|
---|
10 | import java.util.Map.Entry;
|
---|
11 | import java.util.TreeSet;
|
---|
12 |
|
---|
13 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
14 | import org.openstreetmap.josm.data.osm.Node;
|
---|
15 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
16 | import org.openstreetmap.josm.data.osm.Way;
|
---|
17 | import org.openstreetmap.josm.data.osm.WaySegment;
|
---|
18 | import org.openstreetmap.josm.data.validation.Severity;
|
---|
19 | import org.openstreetmap.josm.data.validation.Test;
|
---|
20 | import org.openstreetmap.josm.data.validation.TestError;
|
---|
21 | import org.openstreetmap.josm.tools.Geometry;
|
---|
22 | import org.openstreetmap.josm.tools.Logging;
|
---|
23 |
|
---|
24 | /**
|
---|
25 | * Find highways that have sharp angles
|
---|
26 | * @author Taylor Smock
|
---|
27 | * @since xxx
|
---|
28 | */
|
---|
29 | public 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 | }
|
---|
161 | }
|
---|
162 |
|
---|