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

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

fix #17057

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