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

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

fix #16264, see #16188, see #16189 - reduce validator false positives:

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