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

Last change on this file was 17098, checked in by GerdP, 4 years ago

fix #19871: TurnrestrictionTest cannot cope with read-only datasets.
Don't add temporary way for test

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