source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/SharpAngles.java@ 19162

Last change on this file since 19162 was 19162, checked in by taylor.smock, 6 months ago

Fix #21333: Extend SharpAngles test to railways

File size: 7.2 KB
Line 
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.Arrays;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.TreeSet;
10
11import org.openstreetmap.josm.data.coor.EastNorth;
12import org.openstreetmap.josm.data.osm.Node;
13import org.openstreetmap.josm.data.osm.OsmPrimitive;
14import org.openstreetmap.josm.data.osm.Way;
15import org.openstreetmap.josm.data.osm.WaySegment;
16import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
17import org.openstreetmap.josm.data.validation.Severity;
18import org.openstreetmap.josm.data.validation.Test;
19import org.openstreetmap.josm.data.validation.TestError;
20import org.openstreetmap.josm.gui.progress.ProgressMonitor;
21import org.openstreetmap.josm.spi.preferences.Config;
22import org.openstreetmap.josm.tools.Geometry;
23import org.openstreetmap.josm.tools.bugreport.BugReport;
24
25/**
26 * Find highways that have sharp angles
27 * @author Taylor Smock
28 * @since 15406
29 */
30public class SharpAngles extends Test {
31 private static final int SHARPANGLESCODE = 3800;
32 /** The code for a sharp angle */
33 private static final int SHARP_ANGLES = SHARPANGLESCODE;
34 /** The maximum angle for sharp angles (degrees) */
35 private double maxAngle;
36 /** The length that at least one way segment must be shorter than (meters) */
37 private double maxLength;
38 /** Specific highway types to ignore */
39 private Collection<String> ignoreHighways = Collections.emptyList();
40 /** Specific railway types to ignore */
41 private Collection<String> ignoreRailway = Collections.emptyList();
42
43 /**
44 * Construct a new {@code IntersectionIssues} object
45 */
46 public SharpAngles() {
47 super(tr("Sharp angles"), tr("Check for sharp angles on man made transportation ways"));
48 }
49
50 @Override
51 public void startTest(ProgressMonitor progressMonitor) {
52 super.startTest(progressMonitor);
53 this.maxLength = Config.getPref().getDouble("validator.sharpangles.maxlength", 10.0); // meters
54 this.maxAngle = Config.getPref().getDouble("validator.sharpangles.maxangle", 45.0); // degrees
55 this.ignoreRailway = Collections.unmodifiableCollection(new TreeSet<>(
56 Config.getPref().getList("validator.sharpangles.ignorerailway",
57 Arrays.asList("crossing_box", "loading_ramp", "platform", "roundhouse", "signal_box", "station",
58 "traverser", "wash", "workshop"))));
59 // TODO make immutable when addIgnoredHighway is removed
60 this.ignoreHighways = new TreeSet<>(
61 Config.getPref().getList("validator.sharpangles.ignorehighway",
62 Arrays.asList("platform", "rest_area", "services", "via_ferrata"))
63 );
64 }
65
66 @Override
67 public void visit(Way way) {
68 if (!way.isUsable()) return;
69 if (shouldBeTestedForSharpAngles(way)) {
70 try {
71 checkWayForSharpAngles(way);
72 } catch (RuntimeException e) {
73 throw BugReport.intercept(e).put("way", way);
74 }
75 }
76 }
77
78 /**
79 * Check whether a way should be checked for sharp angles
80 * @param way The way that needs to be checked
81 * @return {@code true} if the way should be checked.
82 */
83 public boolean shouldBeTestedForSharpAngles(Way way) {
84 return !way.hasTag("area", "yes") &&
85 ((way.hasKey("highway") && !way.hasKey("via_ferrata_scale") && !ignoreHighways.contains(way.get("highway")))
86 || (way.hasKey("railway") && !ignoreRailway.contains(way.get("railway"))));
87 }
88
89 /**
90 * Check nodes in a way for sharp angles
91 * @param way A way to check for sharp angles
92 */
93 public void checkWayForSharpAngles(Way way) {
94 Node node1 = null;
95 Node node2 = null;
96 Node node3 = null;
97 int i = -2;
98 for (Node node : way.getNodes()) {
99 node1 = node2;
100 node2 = node3;
101 node3 = node;
102 checkAngle(node1, node2, node3, i, way, false);
103 i++;
104 }
105 if (way.isClosed() && way.getNodesCount() > 2) {
106 node1 = node2;
107 node2 = node3;
108 // Get the second node, not the first node, since a closed way has first node == last node
109 node3 = way.getNode(1);
110 checkAngle(node1, node2, node3, i, way, true);
111 }
112 }
113
114 private void checkAngle(Node node1, Node node2, Node node3, int i, Way way, boolean last) {
115 if (node1 == null || !node1.isLatLonKnown()
116 || node2 == null || !node2.isLatLonKnown()
117 || node3 == null || !node3.isLatLonKnown()) {
118 return;
119 }
120 EastNorth n1 = node1.getEastNorth();
121 EastNorth n2 = node2.getEastNorth();
122 EastNorth n3 = node3.getEastNorth();
123 double angle = Math.toDegrees(Math.abs(Geometry.getCornerAngle(n1, n2, n3)));
124 if (angle < maxAngle) {
125 processSharpAngleForErrorCreation(angle, i, way, last, node2);
126 }
127 }
128
129 private void processSharpAngleForErrorCreation(double angle, int i, Way way, boolean last, Node pointNode) {
130 WaySegment ws1 = new WaySegment(way, i);
131 WaySegment ws2 = new WaySegment(way, last ? 0 : i + 1);
132 double d1 = ws1.getFirstNode().getEastNorth().distance(ws1.getSecondNode().getEastNorth());
133 double d2 = ws2.getFirstNode().getEastNorth().distance(ws2.getSecondNode().getEastNorth());
134 double shorterLen = Math.min(d1, d2);
135 if (shorterLen < maxLength) {
136 createNearlyOverlappingError(angle, way, pointNode);
137 }
138 }
139
140 private void createNearlyOverlappingError(double angle, Way way, OsmPrimitive primitive) {
141 Severity severity = getSeverity(angle);
142 if (severity != Severity.OTHER || (ValidatorPrefHelper.PREF_OTHER.get() || ValidatorPrefHelper.PREF_OTHER_UPLOAD.get())) {
143 int addCode = severity == Severity.OTHER ? 1 : 0;
144 TestError.Builder testError = TestError.builder(this, severity, SHARP_ANGLES + addCode)
145 .primitives(way)
146 .highlight(primitive)
147 .message(tr("Sharp angle"));
148 errors.add(testError.build());
149 }
150 }
151
152 private Severity getSeverity(double angle) {
153 return angle < maxAngle * 2 / 3 ? Severity.WARNING : Severity.OTHER;
154 }
155
156 /**
157 * Set the maximum length for the shortest segment
158 * @param length The max length in meters
159 */
160 public void setMaxLength(double length) {
161 maxLength = length;
162 }
163
164 /**
165 * Add a highway to ignore
166 * @param highway The highway type to ignore (e.g., if you want to ignore residential roads, use "residential")
167 * @since 19162 (deprecated)
168 * @deprecated Not known to be used. Please use config preference "validator.sharpangles.ignorehighway" instead.
169 */
170 @Deprecated(since = "19162", forRemoval = true)
171 public void addIgnoredHighway(String highway) {
172 // Don't forget to make ignoreHighways immutable when this method is removed
173 ignoreHighways.add(highway);
174 }
175
176 /**
177 * Set the maximum angle
178 * @param angle The maximum angle in degrees.
179 */
180 public void setMaxAngle(double angle) {
181 maxAngle = angle;
182 }
183
184}
Note: See TracBrowser for help on using the repository browser.