source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/TurnrestrictionTest.java@ 14966

Last change on this file since 14966 was 14966, checked in by GerdP, 6 years ago

fix #17561 Confusing error message for turn restriction
fix #17567 rephrase warning for role location_hint in restriction relation

  • improve validator messages for restriction relations
  • suppress duplicate warnings for wrong roles from RelationChecker when TurnrestrictionTest is enabled
  • add unit test for TurnrestrictionTest similar to the one for MultipolygonTest
  • Property svn:eol-style set to native
File size: 13.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.ArrayList;
7import java.util.Arrays;
8import java.util.List;
9
10import org.openstreetmap.josm.data.osm.Node;
11import org.openstreetmap.josm.data.osm.OsmPrimitive;
12import org.openstreetmap.josm.data.osm.Relation;
13import org.openstreetmap.josm.data.osm.RelationMember;
14import org.openstreetmap.josm.data.osm.Way;
15import org.openstreetmap.josm.data.validation.Severity;
16import org.openstreetmap.josm.data.validation.Test;
17import org.openstreetmap.josm.data.validation.TestError;
18
19/**
20 * Checks if turnrestrictions are valid
21 * @since 3669
22 */
23public class TurnrestrictionTest extends Test {
24
25 protected static final int NO_VIA = 1801;
26 protected static final int NO_FROM = 1802;
27 protected static final int NO_TO = 1803;
28 protected static final int MORE_VIA = 1804;
29 protected static final int MORE_FROM = 1805;
30 protected static final int MORE_TO = 1806;
31 protected static final int UNKNOWN_ROLE = 1807;
32 protected static final int UNKNOWN_TYPE = 1808;
33 protected static final int FROM_VIA_NODE = 1809;
34 protected static final int TO_VIA_NODE = 1810;
35 protected static final int FROM_VIA_WAY = 1811;
36 protected static final int TO_VIA_WAY = 1812;
37 protected static final int MIX_VIA = 1813;
38 protected static final int UNCONNECTED_VIA = 1814;
39 protected static final int SUPERFLUOUS = 1815;
40 protected static final int FROM_EQUALS_TO = 1816;
41 protected static final int UNKNOWN_RESTRICTION = 1817;
42 protected static final int TO_CLOSED_WAY = 1818;
43 protected static final int FROM_CLOSED_WAY = 1819;
44
45 private static final List<String> SUPPORTED_RESTRICTIONS = Arrays.asList(
46 "no_right_turn", "no_left_turn", "no_u_turn", "no_straight_on",
47 "only_right_turn", "only_left_turn", "only_straight_on",
48 "no_entry", "no_exit"
49 );
50
51 /**
52 * Constructs a new {@code TurnrestrictionTest}.
53 */
54 public TurnrestrictionTest() {
55 super(tr("Turnrestrictions"), tr("This test checks if turnrestrictions are valid."));
56 }
57
58 @Override
59 public void visit(Relation r) {
60 if (!r.hasTag("type", "restriction"))
61 return;
62
63 if (!r.hasTag("restriction", SUPPORTED_RESTRICTIONS)) {
64 errors.add(TestError.builder(this, Severity.ERROR, UNKNOWN_RESTRICTION)
65 .message(tr("Unknown restriction"))
66 .primitives(r)
67 .build());
68 return;
69 }
70
71 Way fromWay = null;
72 Way toWay = null;
73 List<OsmPrimitive> via = new ArrayList<>();
74
75 boolean morefrom = false;
76 boolean moreto = false;
77 boolean morevia = false;
78 boolean mixvia = false;
79
80 /* find the "from", "via" and "to" elements */
81 for (RelationMember m : r.getMembers()) {
82 if (m.getMember().isIncomplete())
83 return;
84
85 List<OsmPrimitive> l = new ArrayList<>();
86 l.add(r);
87 l.add(m.getMember());
88 if (m.isWay()) {
89 Way w = m.getWay();
90 if (w.getNodesCount() < 2) {
91 continue;
92 }
93
94 switch (m.getRole()) {
95 case "from":
96 if (fromWay != null) {
97 morefrom = true;
98 } else {
99 fromWay = w;
100 }
101 break;
102 case "to":
103 if (toWay != null) {
104 moreto = true;
105 } else {
106 toWay = w;
107 }
108 break;
109 case "via":
110 if (!via.isEmpty() && via.get(0) instanceof Node) {
111 mixvia = true;
112 } else {
113 via.add(w);
114 }
115 break;
116 default:
117 errors.add(TestError.builder(this, Severity.WARNING, UNKNOWN_ROLE)
118 .message(tr("Unknown role in restriction"))
119 .primitives(l)
120 .highlight(m.getMember())
121 .build());
122 }
123 } else if (m.isNode()) {
124 Node n = m.getNode();
125 switch (m.getRole()) {
126 case "via":
127 if (!via.isEmpty()) {
128 if (via.get(0) instanceof Node) {
129 morevia = true;
130 } else {
131 mixvia = true;
132 }
133 } else {
134 via.add(n);
135 }
136 break;
137 case "location_hint":
138 errors.add(TestError.builder(this, Severity.WARNING, UNKNOWN_ROLE)
139 .message(tr("Role location_hint in not in templates"))
140 .primitives(l)
141 .highlight(m.getMember())
142 .build());
143 break;
144 default:
145 errors.add(TestError.builder(this, Severity.WARNING, UNKNOWN_ROLE)
146 .message(tr("Unknown role in restriction"))
147 .primitives(l)
148 .highlight(m.getMember())
149 .build());
150 }
151 } else {
152 errors.add(TestError.builder(this, Severity.WARNING, UNKNOWN_TYPE)
153 .message(tr("Unknown member type"))
154 .primitives(l)
155 .highlight(m.getMember())
156 .build());
157 }
158 }
159 if (morefrom) {
160 errors.add(TestError.builder(this, Severity.ERROR, MORE_FROM)
161 .message(tr("More than one \"from\" way found"))
162 .primitives(r)
163 .build());
164 return;
165 }
166 if (moreto) {
167 errors.add(TestError.builder(this, Severity.ERROR, MORE_TO)
168 .message(tr("More than one \"to\" way found"))
169 .primitives(r)
170 .build());
171 return;
172 }
173 if (morevia) {
174 errors.add(TestError.builder(this, Severity.ERROR, MORE_VIA)
175 .message(tr("More than one \"via\" node found"))
176 .primitives(r)
177 .build());
178 return;
179 }
180 if (mixvia) {
181 errors.add(TestError.builder(this, Severity.ERROR, MIX_VIA)
182 .message(tr("Cannot mix node and way for role \"via\""))
183 .primitives(r)
184 .build());
185 return;
186 }
187
188 if (fromWay == null) {
189 errors.add(TestError.builder(this, Severity.ERROR, NO_FROM)
190 .message(tr("No \"from\" way found"))
191 .primitives(r)
192 .build());
193 return;
194 } else if (fromWay.isClosed()) {
195 errors.add(TestError.builder(this, Severity.ERROR, FROM_CLOSED_WAY)
196 .message(tr("\"from\" way is a closed way"))
197 .primitives(r)
198 .highlight(fromWay)
199 .build());
200 return;
201 }
202
203 if (toWay == null) {
204 errors.add(TestError.builder(this, Severity.ERROR, NO_TO)
205 .message(tr("No \"to\" way found"))
206 .primitives(r)
207 .build());
208 return;
209 } else if (toWay.isClosed()) {
210 errors.add(TestError.builder(this, Severity.ERROR, TO_CLOSED_WAY)
211 .message(tr("\"to\" way is a closed way"))
212 .primitives(r)
213 .highlight(toWay)
214 .build());
215 return;
216 }
217 if (fromWay.equals(toWay)) {
218 Severity severity = r.hasTag("restriction", "no_u_turn") ? Severity.OTHER : Severity.WARNING;
219 errors.add(TestError.builder(this, severity, FROM_EQUALS_TO)
220 .message(tr("\"from\" way equals \"to\" way"))
221 .primitives(r)
222 .build());
223 }
224 if (via.isEmpty()) {
225 errors.add(TestError.builder(this, Severity.ERROR, NO_VIA)
226 .message(tr("No \"via\" node or way found"))
227 .primitives(r)
228 .build());
229 return;
230 }
231
232 if (via.get(0) instanceof Node) {
233 final Node viaNode = (Node) via.get(0);
234 if (isFullOneway(toWay) && viaNode.equals(toWay.lastNode(true))) {
235 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
236 .message(tr("Superfluous turnrestriction as \"to\" way is oneway"))
237 .primitives(r)
238 .highlight(toWay)
239 .build());
240 return;
241 }
242 if (isFullOneway(fromWay) && viaNode.equals(fromWay.firstNode(true))) {
243 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
244 .message(tr("Superfluous turnrestriction as \"from\" way is oneway"))
245 .primitives(r)
246 .highlight(fromWay)
247 .build());
248 return;
249 }
250 final Way viaPseudoWay = new Way();
251 viaPseudoWay.addNode(viaNode);
252 checkIfConnected(r, fromWay, viaPseudoWay,
253 tr("The \"from\" way does not start or end at a \"via\" node."), FROM_VIA_NODE);
254 checkIfConnected(r, viaPseudoWay, toWay,
255 tr("The \"to\" way does not start or end at a \"via\" node."), TO_VIA_NODE);
256 } else {
257 if (isFullOneway(toWay) && ((Way) via.get(via.size() - 1)).isFirstLastNode(toWay.lastNode(true))) {
258 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
259 .message(tr("Superfluous turnrestriction as \"to\" way is oneway"))
260 .primitives(r)
261 .highlight(toWay)
262 .build());
263 return;
264 }
265 if (isFullOneway(fromWay) && ((Way) via.get(0)).isFirstLastNode(fromWay.firstNode(true))) {
266 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
267 .message(tr("Superfluous turnrestriction as \"from\" way is oneway"))
268 .primitives(r)
269 .highlight(fromWay)
270 .build());
271 return;
272 }
273 // check if consecutive ways are connected: from/via[0], via[i-1]/via[i], via[last]/to
274 checkIfConnected(r, fromWay, (Way) via.get(0),
275 tr("The \"from\" and the first \"via\" way are not connected."), FROM_VIA_WAY);
276 if (via.size() > 1) {
277 for (int i = 1; i < via.size(); i++) {
278 Way previous = (Way) via.get(i - 1);
279 Way current = (Way) via.get(i);
280 checkIfConnected(r, previous, current,
281 tr("The \"via\" ways are not connected."), UNCONNECTED_VIA);
282 }
283 }
284 checkIfConnected(r, (Way) via.get(via.size() - 1), toWay,
285 tr("The last \"via\" and the \"to\" way are not connected."), TO_VIA_WAY);
286 }
287 }
288
289 private static boolean isFullOneway(Way w) {
290 return w.isOneway() != 0 && !w.hasTag("oneway:bicycle", "no");
291 }
292
293 private void checkIfConnected(Relation r, Way previous, Way current, String msg, int code) {
294 boolean c;
295 if (isFullOneway(previous) && isFullOneway(current)) {
296 // both oneways: end/start node must be equal
297 c = previous.lastNode(true).equals(current.firstNode(true));
298 } else if (isFullOneway(previous)) {
299 // previous way is oneway: end of previous must be start/end of current
300 c = current.isFirstLastNode(previous.lastNode(true));
301 } else if (isFullOneway(current)) {
302 // current way is oneway: start of current must be start/end of previous
303 c = previous.isFirstLastNode(current.firstNode(true));
304 } else {
305 // otherwise: start/end of previous must be start/end of current
306 c = current.isFirstLastNode(previous.firstNode()) || current.isFirstLastNode(previous.lastNode());
307 }
308 if (!c) {
309 List<OsmPrimitive> hilite = new ArrayList<>();
310 if (previous.getNodesCount() == 1 && previous.isNew())
311 hilite.add(previous.firstNode());
312 else
313 hilite.add(previous);
314 if (current.getNodesCount() == 1 && current.isNew())
315 hilite.add(current.firstNode());
316 else
317 hilite.add(current);
318 List<OsmPrimitive> primitives = new ArrayList<>();
319 primitives.add(r);
320 primitives.addAll(hilite);
321 errors.add(TestError.builder(this, Severity.ERROR, code)
322 .message(msg)
323 .primitives(primitives)
324 .highlight(hilite)
325 .build());
326 }
327 }
328}
Note: See TracBrowser for help on using the repository browser.