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

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

fix #18293 - handle level tag like layer in CrossingWays test

  • Property svn:eol-style set to native
File size: 14.8 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/level
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 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 (areLayerOrLevelDifferent(w1, 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 public void visit(Relation r) {
227 for (Way w : r.getMemberPrimitives(Way.class)) {
228 visit(w);
229 }
230 }
231 }
232
233 /**
234 * Crossing barriers ways test.
235 */
236 public static class Barrier extends CrossingWays {
237
238 protected static final int CROSSING_BARRIERS = 603;
239
240 /**
241 * Constructs a new crossing {@code Barrier} test.
242 */
243 public Barrier() {
244 super(tr("Crossing barriers"), CROSSING_BARRIERS);
245 }
246
247 @Override
248 public boolean isPrimitiveUsable(OsmPrimitive p) {
249 return super.isPrimitiveUsable(p) && p.hasKey("barrier");
250 }
251
252 @Override
253 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
254 return areLayerOrLevelDifferent(w1, w2);
255 }
256 }
257
258 /**
259 * Self crossing ways test (for all the rest)
260 */
261 public static class SelfCrossing extends CrossingWays {
262
263 protected static final int CROSSING_SELF = 604;
264
265 CrossingWays.Ways normalTest = new Ways();
266 CrossingWays.Barrier barrierTest = new Barrier();
267 CrossingWays.Boundaries boundariesTest = new Boundaries();
268
269 /**
270 * Constructs a new SelfIntersection test.
271 */
272 public SelfCrossing() {
273 super(tr("Self crossing ways"), CROSSING_SELF);
274 }
275
276 @Override
277 public boolean isPrimitiveUsable(OsmPrimitive p) {
278 return super.isPrimitiveUsable(p) && !(normalTest.isPrimitiveUsable(p) || barrierTest.isPrimitiveUsable(p)
279 || boundariesTest.isPrimitiveUsable(p));
280 }
281
282 @Override
283 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
284 return w1 != w2; // should not happen
285 }
286 }
287
288 /**
289 * Constructs a new {@code CrossingWays} test.
290 * @param title The test title
291 * @param code The test code
292 * @since 12958
293 */
294 public CrossingWays(String title, int code) {
295 super(title, tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, " +
296 "but are not connected by a node."));
297 this.code = code;
298 }
299
300 @Override
301 public void startTest(ProgressMonitor monitor) {
302 super.startTest(monitor);
303 cellSegments.clear();
304 seenWays.clear();
305 }
306
307 @Override
308 public void endTest() {
309 super.endTest();
310 cellSegments.clear();
311 seenWays.clear();
312 }
313
314 static boolean isCoastline(OsmPrimitive w) {
315 return w.hasTag("natural", "water", "coastline") || w.hasTag(LANDUSE, "reservoir");
316 }
317
318 static boolean isHighway(OsmPrimitive w) {
319 return w.hasTagDifferent(HIGHWAY, "rest_area", "services", "bus_stop", "platform");
320 }
321
322 static boolean isRailway(OsmPrimitive w) {
323 return w.hasKey(RAILWAY) && !isSubwayOrTramOrRazed(w);
324 }
325
326 static boolean isSubwayOrTramOrRazed(OsmPrimitive w) {
327 return w.hasTag(RAILWAY, "subway", "tram", "razed") ||
328 (w.hasTag(RAILWAY, "construction") && w.hasTag("construction", "tram")) ||
329 (w.hasTag(RAILWAY, "disused") && w.hasTag("disused", "tram"));
330 }
331
332 static boolean isProposedOrAbandoned(OsmPrimitive w) {
333 return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned");
334 }
335
336 abstract boolean ignoreWaySegmentCombination(Way w1, Way w2);
337
338 MessageHelper createMessage(Way w1, Way w2) {
339 return new MessageHelper(this.name, this.code);
340 }
341
342 @Override
343 public void visit(Way w) {
344 if (this instanceof SelfCrossing) {
345 // free memory, we are not interested in previous ways
346 cellSegments.clear();
347 seenWays.clear();
348 }
349
350 int nodesSize = w.getNodesCount();
351 for (int i = 0; i < nodesSize - 1; i++) {
352 final WaySegment es1 = new WaySegment(w, i);
353 final EastNorth en1 = es1.getFirstNode().getEastNorth();
354 final EastNorth en2 = es1.getSecondNode().getEastNorth();
355 if (en1 == null || en2 == null) {
356 Logging.warn("Crossing ways test skipped " + es1);
357 continue;
358 }
359 for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) {
360 for (WaySegment es2 : segments) {
361 List<Way> prims;
362 List<WaySegment> highlight;
363
364 if (!es1.intersects(es2) || ignoreWaySegmentCombination(es1.way, es2.way)) {
365 continue;
366 }
367
368 prims = new ArrayList<>();
369 prims.add(es1.way);
370 if (es1.way != es2.way)
371 prims.add(es2.way);
372 if ((highlight = seenWays.get(prims)) == null) {
373 highlight = new ArrayList<>();
374 highlight.add(es1);
375 highlight.add(es2);
376
377 final MessageHelper message = createMessage(es1.way, es2.way);
378 errors.add(TestError.builder(this, Severity.WARNING, message.code)
379 .message(message.message)
380 .primitives(prims)
381 .highlightWaySegments(highlight)
382 .build());
383 seenWays.put(prims, highlight);
384 } else {
385 highlight.add(es1);
386 highlight.add(es2);
387 }
388 }
389 segments.add(es1);
390 }
391 }
392 }
393
394 private static boolean areLayerOrLevelDifferent(Way w1, Way w2) {
395 return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))
396 || !Objects.equals(w1.get("level"), w2.get("level"));
397 }
398
399 /**
400 * Returns all the cells this segment crosses. Each cell contains the list
401 * of segments already processed
402 * @param cellSegments map with already collected way segments
403 * @param n1 The first EastNorth
404 * @param n2 The second EastNorth
405 * @return A list with all the cells the segment crosses
406 */
407 public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, EastNorth n1, EastNorth n2) {
408 List<List<WaySegment>> cells = new ArrayList<>();
409 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail())) {
410 cells.add(cellSegments.computeIfAbsent(cell, k -> new ArrayList<>()));
411 }
412 return cells;
413 }
414}
Note: See TracBrowser for help on using the repository browser.