source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/Highways.java@ 10179

Last change on this file since 10179 was 10179, checked in by Don-vip, 8 years ago

sonar - squid:AssignmentInSubExpressionCheck - Assignments should not be made from within sub-expressions

  • Property svn:eol-style set to native
File size: 11.4 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.HashMap;
9import java.util.HashSet;
10import java.util.Iterator;
11import java.util.List;
12import java.util.Locale;
13import java.util.Map;
14import java.util.Set;
15
16import org.openstreetmap.josm.command.ChangePropertyCommand;
17import org.openstreetmap.josm.command.Command;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.OsmUtils;
21import org.openstreetmap.josm.data.osm.Way;
22import org.openstreetmap.josm.data.validation.FixableTestError;
23import org.openstreetmap.josm.data.validation.Severity;
24import org.openstreetmap.josm.data.validation.Test;
25import org.openstreetmap.josm.data.validation.TestError;
26import org.openstreetmap.josm.tools.Predicate;
27import org.openstreetmap.josm.tools.Utils;
28
29/**
30 * Test that performs semantic checks on highways.
31 * @since 5902
32 */
33public class Highways extends Test {
34
35 protected static final int WRONG_ROUNDABOUT_HIGHWAY = 2701;
36 protected static final int MISSING_PEDESTRIAN_CROSSING = 2702;
37 protected static final int SOURCE_MAXSPEED_UNKNOWN_COUNTRY_CODE = 2703;
38 protected static final int SOURCE_MAXSPEED_UNKNOWN_CONTEXT = 2704;
39 protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_MAXSPEED = 2705;
40 protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_HIGHWAY = 2706;
41 protected static final int SOURCE_WRONG_LINK = 2707;
42
43 protected static final String SOURCE_MAXSPEED = "source:maxspeed";
44
45 /**
46 * Classified highways in order of importance
47 */
48 private static final List<String> CLASSIFIED_HIGHWAYS = Arrays.asList(
49 "motorway", "motorway_link",
50 "trunk", "trunk_link",
51 "primary", "primary_link",
52 "secondary", "secondary_link",
53 "tertiary", "tertiary_link",
54 "unclassified",
55 "residential",
56 "living_street");
57
58 private static final Set<String> KNOWN_SOURCE_MAXSPEED_CONTEXTS = new HashSet<>(Arrays.asList(
59 "urban", "rural", "zone", "zone30", "zone:30", "nsl_single", "nsl_dual", "motorway", "trunk", "living_street", "bicycle_road"));
60
61 private static final Set<String> ISO_COUNTRIES = new HashSet<>(Arrays.asList(Locale.getISOCountries()));
62
63 private boolean leftByPedestrians;
64 private boolean leftByCyclists;
65 private boolean leftByCars;
66 private int pedestrianWays;
67 private int cyclistWays;
68 private int carsWays;
69
70 /**
71 * Constructs a new {@code Highways} test.
72 */
73 public Highways() {
74 super(tr("Highways"), tr("Performs semantic checks on highways."));
75 }
76
77 protected class WrongRoundaboutHighway extends TestError {
78
79 public final String correctValue;
80
81 public WrongRoundaboutHighway(Way w, String key) {
82 super(Highways.this, Severity.WARNING,
83 tr("Incorrect roundabout (highway: {0} instead of {1})", w.get("highway"), key),
84 WRONG_ROUNDABOUT_HIGHWAY, w);
85 this.correctValue = key;
86 }
87 }
88
89 @Override
90 public void visit(Node n) {
91 if (n.isUsable()) {
92 if (!n.hasTag("crossing", "no")
93 && !(n.hasKey("crossing") && (n.hasTag("highway", "crossing") || n.hasTag("highway", "traffic_signals")))
94 && n.isReferredByWays(2)) {
95 testMissingPedestrianCrossing(n);
96 }
97 if (n.hasKey(SOURCE_MAXSPEED)) {
98 // Check maxspeed but not context against highway for nodes
99 // as maxspeed is not set on highways here but on signs, speed cameras, etc.
100 testSourceMaxspeed(n, false);
101 }
102 }
103 }
104
105 @Override
106 public void visit(Way w) {
107 if (w.isUsable()) {
108 if (w.hasKey("highway") && CLASSIFIED_HIGHWAYS.contains(w.get("highway"))
109 && w.hasKey("junction") && "roundabout".equals(w.get("junction"))) {
110 testWrongRoundabout(w);
111 }
112 if (w.hasKey(SOURCE_MAXSPEED)) {
113 // Check maxspeed, including context against highway
114 testSourceMaxspeed(w, true);
115 }
116 testHighwayLink(w);
117 }
118 }
119
120 private void testWrongRoundabout(Way w) {
121 Map<String, List<Way>> map = new HashMap<>();
122 // Count all highways (per type) connected to this roundabout, except links
123 // As roundabouts are closed ways, take care of not processing the first/last node twice
124 for (Node n : new HashSet<>(w.getNodes())) {
125 for (Way h : Utils.filteredCollection(n.getReferrers(), Way.class)) {
126 String value = h.get("highway");
127 if (h != w && value != null && !value.endsWith("_link")) {
128 List<Way> list = map.get(value);
129 if (list == null) {
130 list = new ArrayList<>();
131 map.put(value, list);
132 }
133 list.add(h);
134 }
135 }
136 }
137 // The roundabout should carry the highway tag of its two biggest highways
138 for (String s : CLASSIFIED_HIGHWAYS) {
139 List<Way> list = map.get(s);
140 if (list != null && list.size() >= 2) {
141 // Except when a single road is connected, but with two oneway segments
142 Boolean oneway1 = OsmUtils.getOsmBoolean(list.get(0).get("oneway"));
143 Boolean oneway2 = OsmUtils.getOsmBoolean(list.get(1).get("oneway"));
144 if (list.size() > 2 || oneway1 == null || oneway2 == null || !oneway1 || !oneway2) {
145 // Error when the highway tags do not match
146 if (!w.get("highway").equals(s)) {
147 errors.add(new WrongRoundaboutHighway(w, s));
148 }
149 break;
150 }
151 }
152 }
153 }
154
155 public static boolean isHighwayLinkOkay(final Way way) {
156 final String highway = way.get("highway");
157 if (highway == null || !highway.endsWith("_link")
158 || !IN_DOWNLOADED_AREA.evaluate(way.getNode(0)) || !IN_DOWNLOADED_AREA.evaluate(way.getNode(way.getNodesCount()-1))) {
159 return true;
160 }
161
162 final Set<OsmPrimitive> referrers = new HashSet<>();
163
164 if (way.isClosed()) {
165 // for closed way we need to check all adjacent ways
166 for (Node n: way.getNodes()) {
167 referrers.addAll(n.getReferrers());
168 }
169 } else {
170 referrers.addAll(way.firstNode().getReferrers());
171 referrers.addAll(way.lastNode().getReferrers());
172 }
173
174 return Utils.exists(Utils.filteredCollection(referrers, Way.class), new Predicate<Way>() {
175 @Override
176 public boolean evaluate(final Way otherWay) {
177 return !way.equals(otherWay) && otherWay.hasTag("highway", highway, highway.replaceAll("_link$", ""));
178 }
179 });
180 }
181
182 private void testHighwayLink(final Way way) {
183 if (!isHighwayLinkOkay(way)) {
184 errors.add(new TestError(this, Severity.WARNING,
185 tr("Highway link is not linked to adequate highway/link"), SOURCE_WRONG_LINK, way));
186 }
187 }
188
189 private void testMissingPedestrianCrossing(Node n) {
190 leftByPedestrians = false;
191 leftByCyclists = false;
192 leftByCars = false;
193 pedestrianWays = 0;
194 cyclistWays = 0;
195 carsWays = 0;
196
197 for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
198 String highway = w.get("highway");
199 if (highway != null) {
200 if ("footway".equals(highway) || "path".equals(highway)) {
201 handlePedestrianWay(n, w);
202 if (w.hasTag("bicycle", "yes", "designated")) {
203 handleCyclistWay(n, w);
204 }
205 } else if ("cycleway".equals(highway)) {
206 handleCyclistWay(n, w);
207 if (w.hasTag("foot", "yes", "designated")) {
208 handlePedestrianWay(n, w);
209 }
210 } else if (CLASSIFIED_HIGHWAYS.contains(highway)) {
211 // Only look at classified highways for now:
212 // - service highways support is TBD (see #9141 comments)
213 // - roads should be determined first. Another warning is raised anyway
214 handleCarWay(n, w);
215 }
216 if ((leftByPedestrians || leftByCyclists) && leftByCars) {
217 errors.add(new TestError(this, Severity.OTHER, tr("Missing pedestrian crossing information"),
218 MISSING_PEDESTRIAN_CROSSING, n));
219 return;
220 }
221 }
222 }
223 }
224
225 private void handleCarWay(Node n, Way w) {
226 carsWays++;
227 if (!w.isFirstLastNode(n) || carsWays > 1) {
228 leftByCars = true;
229 }
230 }
231
232 private void handleCyclistWay(Node n, Way w) {
233 cyclistWays++;
234 if (!w.isFirstLastNode(n) || cyclistWays > 1) {
235 leftByCyclists = true;
236 }
237 }
238
239 private void handlePedestrianWay(Node n, Way w) {
240 pedestrianWays++;
241 if (!w.isFirstLastNode(n) || pedestrianWays > 1) {
242 leftByPedestrians = true;
243 }
244 }
245
246 private void testSourceMaxspeed(OsmPrimitive p, boolean testContextHighway) {
247 String value = p.get(SOURCE_MAXSPEED);
248 if (value.matches("[A-Z]{2}:.+")) {
249 int index = value.indexOf(':');
250 // Check country
251 String country = value.substring(0, index);
252 if (!ISO_COUNTRIES.contains(country)) {
253 if ("UK".equals(country)) {
254 errors.add(new FixableTestError(this, Severity.WARNING,
255 tr("Unknown country code: {0}", country), SOURCE_MAXSPEED_UNKNOWN_COUNTRY_CODE, p,
256 new ChangePropertyCommand(p, SOURCE_MAXSPEED, value.replace("UK:", "GB:"))));
257 } else {
258 errors.add(new TestError(this, Severity.WARNING,
259 tr("Unknown country code: {0}", country), SOURCE_MAXSPEED_UNKNOWN_COUNTRY_CODE, p));
260 }
261 }
262 // Check context
263 String context = value.substring(index+1);
264 if (!KNOWN_SOURCE_MAXSPEED_CONTEXTS.contains(context)) {
265 errors.add(new TestError(this, Severity.WARNING,
266 tr("Unknown source:maxspeed context: {0}", context), SOURCE_MAXSPEED_UNKNOWN_CONTEXT, p));
267 }
268 // TODO: Check coherence of context against maxspeed
269 // TODO: Check coherence of context against highway
270 }
271 }
272
273 @Override
274 public boolean isFixable(TestError testError) {
275 return testError instanceof WrongRoundaboutHighway;
276 }
277
278 @Override
279 public Command fixError(TestError testError) {
280 if (testError instanceof WrongRoundaboutHighway) {
281 // primitives list can be empty if all primitives have been purged
282 Iterator<? extends OsmPrimitive> it = testError.getPrimitives().iterator();
283 if (it.hasNext()) {
284 return new ChangePropertyCommand(it.next(),
285 "highway", ((WrongRoundaboutHighway) testError).correctValue);
286 }
287 }
288 return null;
289 }
290}
Note: See TracBrowser for help on using the repository browser.