source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java@ 14468

Last change on this file since 14468 was 14468, checked in by GerdP, 5 years ago

fix #16978 Use distinct code number for each test group in CrossingWays and UnconnectedWays

  • Property svn:eol-style set to native
File size: 15.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.awt.geom.Point2D;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12import java.util.Objects;
13
14import org.openstreetmap.josm.data.coor.EastNorth;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.OsmUtils;
17import org.openstreetmap.josm.data.osm.Relation;
18import org.openstreetmap.josm.data.osm.Way;
19import org.openstreetmap.josm.data.osm.WaySegment;
20import org.openstreetmap.josm.data.validation.OsmValidator;
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.data.validation.util.ValUtil;
25import org.openstreetmap.josm.gui.progress.ProgressMonitor;
26import org.openstreetmap.josm.tools.Logging;
27
28/**
29 * Tests if there are segments that crosses in the same layer
30 *
31 * @author frsantos
32 */
33public abstract class CrossingWays extends Test {
34
35 static final String HIGHWAY = "highway";
36 static final String RAILWAY = "railway";
37 static final String WATERWAY = "waterway";
38 static final String LANDUSE = "landuse";
39
40 private static final class MessageHelper {
41 final String message;
42 final int code;
43
44 MessageHelper(String message, int code) {
45 this.message = message;
46 this.code = code;
47 }
48 }
49
50 /**
51 * Type of way. Entries have to be declared in alphabetical order, see sort below.
52 */
53 private enum WayType {
54 BUILDING, HIGHWAY, RAILWAY, RESIDENTIAL_AREA, WATERWAY, WAY;
55
56 static WayType of(Way w) {
57 if (isBuilding(w))
58 return BUILDING;
59 else if (w.hasKey(CrossingWays.HIGHWAY))
60 return HIGHWAY;
61 else if (isRailway(w))
62 return RAILWAY;
63 else if (isResidentialArea(w))
64 return RESIDENTIAL_AREA;
65 else if (w.hasKey(CrossingWays.WATERWAY))
66 return WATERWAY;
67 else
68 return WAY;
69 }
70 }
71
72 /** All way segments, grouped by cells */
73 private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000);
74 /** The already detected ways in error */
75 private final Map<List<Way>, List<WaySegment>> seenWays = new HashMap<>(50);
76
77 protected final int code;
78
79 /**
80 * General crossing ways test.
81 */
82 public static class Ways extends CrossingWays {
83
84 protected static final int CROSSING_WAYS = 601;
85
86 /**
87 * Constructs a new crossing {@code Ways} test.
88 */
89 public Ways() {
90 super(tr("Crossing ways"), CROSSING_WAYS);
91 }
92
93 @Override
94 public boolean isPrimitiveUsable(OsmPrimitive w) {
95 return super.isPrimitiveUsable(w)
96 && !isProposedOrAbandoned(w)
97 && (isHighway(w)
98 || w.hasKey(WATERWAY)
99 || isRailway(w)
100 || isCoastline(w)
101 || isBuilding(w)
102 || isResidentialArea(w));
103 }
104
105 @Override
106 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
107 if (w1 == w2)
108 return false;
109 if (!Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))) {
110 return true;
111 }
112 if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) {
113 return true;
114 }
115 if ((w1.hasKey(HIGHWAY, RAILWAY, WATERWAY) && isResidentialArea(w2))
116 || (w2.hasKey(HIGHWAY, RAILWAY, WATERWAY) && isResidentialArea(w1)))
117 return true;
118 if (isSubwayOrTramOrRazed(w2)) {
119 return true;
120 }
121 if (isCoastline(w1) != isCoastline(w2)) {
122 return true;
123 }
124 if ((w1.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w2.hasTag(WATERWAY, "riverbank"))
125 || (w2.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w1.hasTag(WATERWAY, "riverbank"))) {
126 return true;
127 }
128 return isProposedOrAbandoned(w2);
129 }
130
131 @Override
132 MessageHelper createMessage(Way w1, Way w2) {
133 WayType[] types = {WayType.of(w1), WayType.of(w2)};
134 Arrays.sort(types);
135
136 if (types[0] == types[1]) {
137 switch(types[0]) {
138 case BUILDING:
139 return new MessageHelper(tr("Crossing buildings"), 610);
140 case HIGHWAY:
141 return new MessageHelper(tr("Crossing highways"), 620);
142 case RAILWAY:
143 return new MessageHelper(tr("Crossing railways"), 630);
144 case RESIDENTIAL_AREA:
145 return new MessageHelper(tr("Crossing residential areas"), 640);
146 case WATERWAY:
147 return new MessageHelper(tr("Crossing waterways"), 650);
148 case WAY:
149 default:
150 return new MessageHelper(tr("Crossing ways"), CROSSING_WAYS);
151 }
152 } else {
153 switch (types[0]) {
154 case BUILDING:
155 switch (types[1]) {
156 case HIGHWAY:
157 return new MessageHelper(tr("Crossing building/highway"), 612);
158 case RAILWAY:
159 return new MessageHelper(tr("Crossing building/railway"), 613);
160 case RESIDENTIAL_AREA:
161 return new MessageHelper(tr("Crossing building/residential area"), 614);
162 case WATERWAY:
163 return new MessageHelper(tr("Crossing building/waterway"), 615);
164 case WAY:
165 default:
166 return new MessageHelper(tr("Crossing building/way"), 611);
167 }
168 case HIGHWAY:
169 switch (types[1]) {
170 case RAILWAY:
171 return new MessageHelper(tr("Crossing highway/railway"), 622);
172 case WATERWAY:
173 return new MessageHelper(tr("Crossing highway/waterway"), 623);
174 case WAY:
175 default:
176 return new MessageHelper(tr("Crossing highway/way"), 621);
177 }
178 case RAILWAY:
179 switch (types[1]) {
180 case WATERWAY:
181 return new MessageHelper(tr("Crossing railway/waterway"), 632);
182 case WAY:
183 default:
184 return new MessageHelper(tr("Crossing railway/way"), 631);
185 }
186 case RESIDENTIAL_AREA:
187 switch (types[1]) {
188 case WAY:
189 default:
190 return new MessageHelper(tr("Crossing residential area/way"), 641);
191 }
192 case WATERWAY:
193 default:
194 return new MessageHelper(tr("Crossing waterway/way"), 651);
195 }
196 }
197 }
198 }
199
200 /**
201 * Crossing boundaries ways test.
202 */
203 public static class Boundaries extends CrossingWays {
204
205 protected static final int CROSSING_BOUNDARIES = 602;
206
207 /**
208 * Constructs a new crossing {@code Boundaries} test.
209 */
210 public Boundaries() {
211 super(tr("Crossing boundaries"), CROSSING_BOUNDARIES);
212 }
213
214 @Override
215 public boolean isPrimitiveUsable(OsmPrimitive p) {
216 return super.isPrimitiveUsable(p) && p.hasKey("boundary")
217 && (!(p instanceof Relation) || (((Relation) p).isMultipolygon() && !((Relation) p).hasIncompleteMembers()));
218 }
219
220 @Override
221 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
222 return !Objects.equals(w1.get("boundary"), w2.get("boundary"));
223 }
224
225 @Override
226 MessageHelper createMessage(Way w1, Way w2) {
227 return new MessageHelper(tr("Crossing boundaries"), CROSSING_BOUNDARIES);
228 }
229
230 @Override
231 public void visit(Relation r) {
232 for (Way w : r.getMemberPrimitives(Way.class)) {
233 visit(w);
234 }
235 }
236 }
237
238 /**
239 * Crossing barriers ways test.
240 */
241 public static class Barrier extends CrossingWays {
242
243 protected static final int CROSSING_BARRIERS = 603;
244
245 /**
246 * Constructs a new crossing {@code Barrier} test.
247 */
248 public Barrier() {
249 super(tr("Crossing barriers"), CROSSING_BARRIERS);
250 }
251
252 @Override
253 public boolean isPrimitiveUsable(OsmPrimitive p) {
254 return super.isPrimitiveUsable(p) && p.hasKey("barrier");
255 }
256
257 @Override
258 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
259 return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2));
260 }
261
262 @Override
263 MessageHelper createMessage(Way w1, Way w2) {
264 return new MessageHelper(tr("Crossing barriers"), CROSSING_BARRIERS);
265 }
266 }
267
268 /**
269 * Self crossing ways test (for all the rest)
270 */
271 public static class SelfCrossing extends CrossingWays {
272
273 protected static final int CROSSING_SELF = 604;
274
275 CrossingWays.Ways normalTest = new Ways();
276 CrossingWays.Barrier barrierTest = new Barrier();
277 CrossingWays.Boundaries boundariesTest = new Boundaries();
278
279 /**
280 * Constructs a new SelfIntersection test.
281 */
282 public SelfCrossing() {
283 super(tr("Self crossing"), CROSSING_SELF);
284 }
285
286 @Override
287 public boolean isPrimitiveUsable(OsmPrimitive p) {
288 return super.isPrimitiveUsable(p) && !(normalTest.isPrimitiveUsable(p) || barrierTest.isPrimitiveUsable(p)
289 || boundariesTest.isPrimitiveUsable(p));
290 }
291
292 @Override
293 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
294 return w1 != w2; // should not happen
295 }
296
297 @Override
298 MessageHelper createMessage(Way w1, Way w2) {
299 return new MessageHelper(tr("Self-crossing ways"), CROSSING_SELF);
300 }
301 }
302
303 /**
304 * Constructs a new {@code CrossingWays} test.
305 * @param title The test title
306 * @param code The test code
307 * @since 12958
308 */
309 public CrossingWays(String title, int code) {
310 super(title, tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, " +
311 "but are not connected by a node."));
312 this.code = code;
313 }
314
315 @Override
316 public void startTest(ProgressMonitor monitor) {
317 super.startTest(monitor);
318 cellSegments.clear();
319 seenWays.clear();
320 }
321
322 @Override
323 public void endTest() {
324 super.endTest();
325 cellSegments.clear();
326 seenWays.clear();
327 }
328
329 static boolean isCoastline(OsmPrimitive w) {
330 return w.hasTag("natural", "water", "coastline") || w.hasTag(LANDUSE, "reservoir");
331 }
332
333 static boolean isHighway(OsmPrimitive w) {
334 return w.hasTagDifferent(HIGHWAY, "rest_area", "services", "bus_stop", "platform");
335 }
336
337 static boolean isRailway(OsmPrimitive w) {
338 return w.hasKey(RAILWAY) && !isSubwayOrTramOrRazed(w);
339 }
340
341 static boolean isSubwayOrTramOrRazed(OsmPrimitive w) {
342 return w.hasTag(RAILWAY, "subway", "tram", "razed") ||
343 (w.hasTag(RAILWAY, "construction") && w.hasTag("construction", "tram")) ||
344 (w.hasTag(RAILWAY, "disused") && w.hasTag("disused", "tram"));
345 }
346
347 static boolean isProposedOrAbandoned(OsmPrimitive w) {
348 return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned");
349 }
350
351 abstract boolean ignoreWaySegmentCombination(Way w1, Way w2);
352
353 abstract MessageHelper createMessage(Way w1, Way w2);
354
355 @Override
356 public void visit(Way w) {
357 if (this instanceof SelfCrossing) {
358 // free memory, we are not interested in previous ways
359 cellSegments.clear();
360 seenWays.clear();
361 }
362
363 int nodesSize = w.getNodesCount();
364 for (int i = 0; i < nodesSize - 1; i++) {
365 final WaySegment es1 = new WaySegment(w, i);
366 final EastNorth en1 = es1.getFirstNode().getEastNorth();
367 final EastNorth en2 = es1.getSecondNode().getEastNorth();
368 if (en1 == null || en2 == null) {
369 Logging.warn("Crossing ways test skipped " + es1);
370 continue;
371 }
372 for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) {
373 for (WaySegment es2 : segments) {
374 List<Way> prims;
375 List<WaySegment> highlight;
376
377 if (!es1.intersects(es2) || ignoreWaySegmentCombination(es1.way, es2.way)) {
378 continue;
379 }
380
381 prims = new ArrayList<>();
382 prims.add(es1.way);
383 if (es1.way != es2.way)
384 prims.add(es2.way);
385 if ((highlight = seenWays.get(prims)) == null) {
386 highlight = new ArrayList<>();
387 highlight.add(es1);
388 highlight.add(es2);
389
390 final MessageHelper message = createMessage(es1.way, es2.way);
391 errors.add(TestError.builder(this, Severity.WARNING, message.code)
392 .message(message.message)
393 .primitives(prims)
394 .highlightWaySegments(highlight)
395 .build());
396 seenWays.put(prims, highlight);
397 } else {
398 highlight.add(es1);
399 highlight.add(es2);
400 }
401 }
402 segments.add(es1);
403 }
404 }
405 }
406
407 /**
408 * Returns all the cells this segment crosses. Each cell contains the list
409 * of segments already processed
410 * @param cellSegments map with already collected way segments
411 * @param n1 The first EastNorth
412 * @param n2 The second EastNorth
413 * @return A list with all the cells the segment crosses
414 */
415 public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, EastNorth n1, EastNorth n2) {
416 List<List<WaySegment>> cells = new ArrayList<>();
417 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail())) {
418 cells.add(cellSegments.computeIfAbsent(cell, k -> new ArrayList<>()));
419 }
420 return cells;
421 }
422}
Note: See TracBrowser for help on using the repository browser.