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

Last change on this file was 17098, checked in by GerdP, 5 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
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[3669]2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
[14966]7import java.util.Arrays;
[4370]8import java.util.List;
[3669]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;
[15071]18import org.openstreetmap.josm.data.validation.tests.ConditionalKeys.ConditionalParsingException;
19import org.openstreetmap.josm.data.validation.tests.ConditionalKeys.ConditionalValue;
20import org.openstreetmap.josm.tools.Logging;
[3669]21
[6241]22/**
[15019]23 * Checks if turn restrictions are valid
[6241]24 * @since 3669
25 */
[3669]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;
[15003]34 protected static final int UNEXPECTED_ROLE = 1807;
35 protected static final int UNEXPECTED_TYPE = 1808;
[3669]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;
[4370]40 protected static final int MIX_VIA = 1813;
41 protected static final int UNCONNECTED_VIA = 1814;
[5199]42 protected static final int SUPERFLUOUS = 1815;
[9236]43 protected static final int FROM_EQUALS_TO = 1816;
[14966]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;
[3669]47
[14966]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
[6241]54 /**
55 * Constructs a new {@code TurnrestrictionTest}.
56 */
[3669]57 public TurnrestrictionTest() {
[15019]58 super(tr("Turn restrictions"), tr("This test checks if turn restrictions are valid."));
[3669]59 }
60
[15071]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
[3669]76 @Override
77 public void visit(Relation r) {
[11608]78 if (!r.hasTag("type", "restriction"))
[3669]79 return;
80
[15071]81 if (!hasSupportedRestrictionTag(r)) {
[14966]82 errors.add(TestError.builder(this, Severity.ERROR, UNKNOWN_RESTRICTION)
[15019]83 .message(tr("Unknown turn restriction"))
[14966]84 .primitives(r)
85 .build());
86 return;
87 }
88
[3669]89 Way fromWay = null;
90 Way toWay = null;
[7005]91 List<OsmPrimitive> via = new ArrayList<>();
[3669]92
93 boolean morefrom = false;
94 boolean moreto = false;
95 boolean morevia = false;
[4370]96 boolean mixvia = false;
[3669]97
98 /* find the "from", "via" and "to" elements */
[3671]99 for (RelationMember m : r.getMembers()) {
100 if (m.getMember().isIncomplete())
[3669]101 return;
102
[7005]103 List<OsmPrimitive> l = new ArrayList<>();
[3671]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
[7012]112 switch (m.getRole()) {
113 case "from":
[3671]114 if (fromWay != null) {
115 morefrom = true;
[3669]116 } else {
[3671]117 fromWay = w;
[3669]118 }
[7012]119 break;
120 case "to":
[3671]121 if (toWay != null) {
122 moreto = true;
123 } else {
124 toWay = w;
125 }
[7012]126 break;
127 case "via":
[4370]128 if (!via.isEmpty() && via.get(0) instanceof Node) {
129 mixvia = true;
[3671]130 } else {
[4370]131 via.add(w);
[3671]132 }
[7012]133 break;
134 default:
[15003]135 errors.add(TestError.builder(this, Severity.WARNING, UNEXPECTED_ROLE)
[15019]136 .message(tr("Unexpected role ''{0}'' in turn restriction", m.getRole()))
[11129]137 .primitives(l)
138 .highlight(m.getMember())
139 .build());
[3669]140 }
[3671]141 } else if (m.isNode()) {
142 Node n = m.getNode();
[15003]143 if ("via".equals(m.getRole())) {
[4370]144 if (!via.isEmpty()) {
145 if (via.get(0) instanceof Node) {
146 morevia = true;
147 } else {
148 mixvia = true;
149 }
[3669]150 } else {
[4370]151 via.add(n);
[3669]152 }
[15003]153 } else {
154 errors.add(TestError.builder(this, Severity.WARNING, UNEXPECTED_ROLE)
[15019]155 .message(tr("Unexpected role ''{0}'' in turn restriction", m.getRole()))
[11129]156 .primitives(l)
157 .highlight(m.getMember())
158 .build());
[3669]159 }
[3671]160 } else {
[15003]161 errors.add(TestError.builder(this, Severity.WARNING, UNEXPECTED_TYPE)
[15019]162 .message(tr("Unexpected member type in turn restriction"))
[11129]163 .primitives(l)
164 .highlight(m.getMember())
165 .build());
[3669]166 }
167 }
[3671]168 if (morefrom) {
[14501]169 errors.add(TestError.builder(this, Severity.ERROR, MORE_FROM)
[11129]170 .message(tr("More than one \"from\" way found"))
171 .primitives(r)
172 .build());
[14966]173 return;
[3671]174 }
175 if (moreto) {
[14501]176 errors.add(TestError.builder(this, Severity.ERROR, MORE_TO)
[11129]177 .message(tr("More than one \"to\" way found"))
178 .primitives(r)
179 .build());
[14966]180 return;
[3671]181 }
182 if (morevia) {
[11129]183 errors.add(TestError.builder(this, Severity.ERROR, MORE_VIA)
184 .message(tr("More than one \"via\" node found"))
185 .primitives(r)
186 .build());
[14966]187 return;
[3671]188 }
[4370]189 if (mixvia) {
[11129]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());
[14966]194 return;
[4370]195 }
[3669]196
197 if (fromWay == null) {
[11129]198 errors.add(TestError.builder(this, Severity.ERROR, NO_FROM)
199 .message(tr("No \"from\" way found"))
200 .primitives(r)
201 .build());
[3669]202 return;
[14966]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;
[3669]210 }
[14966]211
[3669]212 if (toWay == null) {
[11129]213 errors.add(TestError.builder(this, Severity.ERROR, NO_TO)
214 .message(tr("No \"to\" way found"))
215 .primitives(r)
216 .build());
[3669]217 return;
[14966]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;
[3669]225 }
[9244]226 if (fromWay.equals(toWay)) {
[14501]227 Severity severity = r.hasTag("restriction", "no_u_turn") ? Severity.OTHER : Severity.WARNING;
[11129]228 errors.add(TestError.builder(this, severity, FROM_EQUALS_TO)
229 .message(tr("\"from\" way equals \"to\" way"))
230 .primitives(r)
231 .build());
[9236]232 }
[4370]233 if (via.isEmpty()) {
[11129]234 errors.add(TestError.builder(this, Severity.ERROR, NO_VIA)
235 .message(tr("No \"via\" node or way found"))
236 .primitives(r)
237 .build());
[3669]238 return;
239 }
240
[4370]241 if (via.get(0) instanceof Node) {
[5199]242 final Node viaNode = (Node) via.get(0);
[14966]243 if (isFullOneway(toWay) && viaNode.equals(toWay.lastNode(true))) {
244 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
[15019]245 .message(tr("Superfluous turn restriction as \"to\" way is oneway"))
[14966]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)
[15019]253 .message(tr("Superfluous turn restriction as \"from\" way is oneway"))
[14966]254 .primitives(r)
255 .highlight(fromWay)
256 .build());
257 return;
258 }
[17098]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 }
[14966]273 } else {
274 if (isFullOneway(toWay) && ((Way) via.get(via.size() - 1)).isFirstLastNode(toWay.lastNode(true))) {
[11129]275 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
[15019]276 .message(tr("Superfluous turn restriction as \"to\" way is oneway"))
[11129]277 .primitives(r)
[14966]278 .highlight(toWay)
[11129]279 .build());
[5199]280 return;
281 }
[14966]282 if (isFullOneway(fromWay) && ((Way) via.get(0)).isFirstLastNode(fromWay.firstNode(true))) {
283 errors.add(TestError.builder(this, Severity.WARNING, SUPERFLUOUS)
[15019]284 .message(tr("Superfluous turn restriction as \"from\" way is oneway"))
[14966]285 .primitives(r)
286 .highlight(fromWay)
287 .build());
288 return;
289 }
[4370]290 // check if consecutive ways are connected: from/via[0], via[i-1]/via[i], via[last]/to
[14966]291 checkIfConnected(r, fromWay, (Way) via.get(0),
[4370]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);
[14966]297 checkIfConnected(r, previous, current,
[4370]298 tr("The \"via\" ways are not connected."), UNCONNECTED_VIA);
[3669]299 }
300 }
[14966]301 checkIfConnected(r, (Way) via.get(via.size() - 1), toWay,
[4370]302 tr("The last \"via\" and the \"to\" way are not connected."), TO_VIA_WAY);
303 }
304 }
305
[8452]306 private static boolean isFullOneway(Way w) {
[11608]307 return w.isOneway() != 0 && !w.hasTag("oneway:bicycle", "no");
[8452]308 }
309
[14966]310 private void checkIfConnected(Relation r, Way previous, Way current, String msg, int code) {
[4370]311 boolean c;
[8452]312 if (isFullOneway(previous) && isFullOneway(current)) {
[4370]313 // both oneways: end/start node must be equal
[5199]314 c = previous.lastNode(true).equals(current.firstNode(true));
[8452]315 } else if (isFullOneway(previous)) {
[4370]316 // previous way is oneway: end of previous must be start/end of current
[5199]317 c = current.isFirstLastNode(previous.lastNode(true));
[8452]318 } else if (isFullOneway(current)) {
[4370]319 // current way is oneway: start of current must be start/end of previous
[5199]320 c = previous.isFirstLastNode(current.firstNode(true));
[4370]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) {
[11129]326 errors.add(TestError.builder(this, Severity.ERROR, code)
327 .message(msg)
[17098]328 .primitives(r, previous, current)
329 .highlight(previous, current)
[11129]330 .build());
[4370]331 }
332 }
[3669]333}
Note: See TracBrowser for help on using the repository browser.