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.Collections;
|
---|
9 | import java.util.TreeSet;
|
---|
10 |
|
---|
11 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
12 | import org.openstreetmap.josm.data.osm.Node;
|
---|
13 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
14 | import org.openstreetmap.josm.data.osm.Way;
|
---|
15 | import org.openstreetmap.josm.data.osm.WaySegment;
|
---|
16 | import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
|
---|
17 | import org.openstreetmap.josm.data.validation.Severity;
|
---|
18 | import org.openstreetmap.josm.data.validation.Test;
|
---|
19 | import org.openstreetmap.josm.data.validation.TestError;
|
---|
20 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
---|
21 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
22 | import org.openstreetmap.josm.tools.Geometry;
|
---|
23 | import org.openstreetmap.josm.tools.bugreport.BugReport;
|
---|
24 |
|
---|
25 | /**
|
---|
26 | * Find highways that have sharp angles
|
---|
27 | * @author Taylor Smock
|
---|
28 | * @since 15406
|
---|
29 | */
|
---|
30 | public 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 | }
|
---|