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

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

sonar - squid:S1192 - String literals should not be duplicated

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