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

Last change on this file since 15961 was 15961, checked in by GerdP, 4 years ago

fix #16707: Warn about overlapping connected buildings

  • add rule in geometry.mapcss to check overlapping buildings
  • change rule which checks overlapping identical landuses to also check landuse=residential
  • disable the corresponding tests in CrossingWays

effects for the user:

  • Message "Crossing buildings" is replaced by "Overlapping buildings"
  • Message "Crossing residential areas" is replaced by generic "Overlapping Identical Landuses"
  • different highlighting of the overlapping area
  • Property svn:eol-style set to native
File size: 17.6 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.HashSet;
11import java.util.List;
12import java.util.Map;
13import java.util.Objects;
14import java.util.Set;
15
16import org.openstreetmap.josm.data.coor.EastNorth;
17import org.openstreetmap.josm.data.osm.OsmPrimitive;
18import org.openstreetmap.josm.data.osm.OsmUtils;
19import org.openstreetmap.josm.data.osm.Relation;
20import org.openstreetmap.josm.data.osm.Way;
21import org.openstreetmap.josm.data.osm.WaySegment;
22import org.openstreetmap.josm.data.validation.OsmValidator;
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.data.validation.util.ValUtil;
27import org.openstreetmap.josm.gui.progress.ProgressMonitor;
28import org.openstreetmap.josm.tools.Logging;
29
30/**
31 * Tests if there are segments that crosses in the same layer/level
32 *
33 * @author frsantos
34 */
35public abstract class CrossingWays extends Test {
36
37 static final String BARRIER = "barrier";
38 static final String HIGHWAY = "highway";
39 static final String RAILWAY = "railway";
40 static final String WATERWAY = "waterway";
41 static final String LANDUSE = "landuse";
42
43 static final class MessageHelper {
44 final String message;
45 final int code;
46
47 MessageHelper(String message, int code) {
48 this.message = message;
49 this.code = code;
50 }
51 }
52
53 /**
54 * Type of way. Entries have to be declared in alphabetical order, see sort below.
55 */
56 private enum WayType {
57 BARRIER, BUILDING, HIGHWAY, RAILWAY, RESIDENTIAL_AREA, WATERWAY, WAY;
58
59 static WayType of(Way w) {
60 if (w.hasKey(CrossingWays.BARRIER))
61 return BARRIER;
62 if (isBuilding(w))
63 return BUILDING;
64 else if (w.hasKey(CrossingWays.HIGHWAY))
65 return HIGHWAY;
66 else if (isRailway(w))
67 return RAILWAY;
68 else if (isResidentialArea(w))
69 return RESIDENTIAL_AREA;
70 else if (w.hasKey(CrossingWays.WATERWAY))
71 return WATERWAY;
72 else
73 return WAY;
74 }
75 }
76
77 /** All way segments, grouped by cells */
78 private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000);
79 /** The already detected ways in error */
80 private final Map<List<Way>, List<WaySegment>> seenWays = new HashMap<>(50);
81
82 protected final int code;
83
84 /**
85 * General crossing ways test.
86 */
87 public static class Ways extends CrossingWays {
88
89 protected static final int CROSSING_WAYS = 601;
90
91 /**
92 * Constructs a new crossing {@code Ways} test.
93 */
94 public Ways() {
95 super(tr("Crossing ways"), CROSSING_WAYS);
96 }
97
98 @Override
99 public boolean isPrimitiveUsable(OsmPrimitive w) {
100 return super.isPrimitiveUsable(w)
101 && !isProposedOrAbandoned(w)
102 && (isHighway(w)
103 || w.hasKey(WATERWAY)
104 || isRailway(w)
105 || isCoastline(w)
106 || isBuilding(w)
107 || w.hasKey(BARRIER)
108 || isResidentialArea(w));
109 }
110
111 @Override
112 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
113 if (w1 == w2)
114 return false;
115 if (areLayerOrLevelDifferent(w1, w2)) {
116 return true;
117 }
118 if (isBuilding(w1) && isBuilding(w2))
119 return true;
120 if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) {
121 return true;
122 }
123 if (((isResidentialArea(w1) || w1.hasKey(BARRIER, HIGHWAY, RAILWAY, WATERWAY)) && isResidentialArea(w2))
124 || ((isResidentialArea(w2) || w2.hasKey(BARRIER, HIGHWAY, RAILWAY, WATERWAY)) && isResidentialArea(w1)))
125 return true;
126 if (isSubwayOrTramOrRazed(w2)) {
127 return true;
128 }
129 if (isCoastline(w1) != isCoastline(w2)) {
130 return true;
131 }
132 if ((w1.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w2.hasTag(WATERWAY, "riverbank"))
133 || (w2.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w1.hasTag(WATERWAY, "riverbank"))) {
134 return true;
135 }
136 return isProposedOrAbandoned(w2);
137 }
138
139 @Override
140 MessageHelper createMessage(Way w1, Way w2) {
141 WayType[] types = {WayType.of(w1), WayType.of(w2)};
142 Arrays.sort(types);
143
144 if (types[0] == types[1]) {
145 switch (types[0]) {
146 // 610 and 640 where removed for #16707
147 case BARRIER:
148 return new MessageHelper(tr("Crossing barriers"), 603);
149 case HIGHWAY:
150 return new MessageHelper(tr("Crossing highways"), 620);
151 case RAILWAY:
152 return new MessageHelper(tr("Crossing railways"), 630);
153 case WATERWAY:
154 return new MessageHelper(tr("Crossing waterways"), 650);
155 case WAY:
156 default:
157 return new MessageHelper(tr("Crossing ways"), CROSSING_WAYS);
158 }
159 } else {
160 switch (types[0]) {
161 case BARRIER:
162 switch (types[1]) {
163 case BUILDING:
164 return new MessageHelper(tr("Crossing barrier/building"), 661);
165 case HIGHWAY:
166 return new MessageHelper(tr("Crossing barrier/highway"), 662);
167 case RAILWAY:
168 return new MessageHelper(tr("Crossing barrier/railway"), 663);
169 case WATERWAY:
170 return new MessageHelper(tr("Crossing barrier/waterway"), 664);
171 case WAY:
172 default:
173 return new MessageHelper(tr("Crossing barrier/way"), 665);
174 }
175 case BUILDING:
176 switch (types[1]) {
177 case HIGHWAY:
178 return new MessageHelper(tr("Crossing building/highway"), 612);
179 case RAILWAY:
180 return new MessageHelper(tr("Crossing building/railway"), 613);
181 case RESIDENTIAL_AREA:
182 return new MessageHelper(tr("Crossing building/residential area"), 614);
183 case WATERWAY:
184 return new MessageHelper(tr("Crossing building/waterway"), 615);
185 case WAY:
186 default:
187 return new MessageHelper(tr("Crossing building/way"), 611);
188 }
189 case HIGHWAY:
190 switch (types[1]) {
191 case RAILWAY:
192 return new MessageHelper(tr("Crossing highway/railway"), 622);
193 case WATERWAY:
194 return new MessageHelper(tr("Crossing highway/waterway"), 623);
195 case WAY:
196 default:
197 return new MessageHelper(tr("Crossing highway/way"), 621);
198 }
199 case RAILWAY:
200 switch (types[1]) {
201 case WATERWAY:
202 return new MessageHelper(tr("Crossing railway/waterway"), 632);
203 case WAY:
204 default:
205 return new MessageHelper(tr("Crossing railway/way"), 631);
206 }
207 case RESIDENTIAL_AREA:
208 return new MessageHelper(tr("Crossing residential area/way"), 641);
209 case WATERWAY:
210 default:
211 return new MessageHelper(tr("Crossing waterway/way"), 651);
212 }
213 }
214 }
215 }
216
217 /**
218 * Crossing boundaries ways test.
219 */
220 public static class Boundaries extends CrossingWays {
221
222 protected static final int CROSSING_BOUNDARIES = 602;
223
224 /**
225 * Constructs a new crossing {@code Boundaries} test.
226 */
227 public Boundaries() {
228 super(tr("Crossing boundaries"), CROSSING_BOUNDARIES);
229 }
230
231 @Override
232 public boolean isPrimitiveUsable(OsmPrimitive p) {
233 return super.isPrimitiveUsable(p) && p.hasKey("boundary") && !p.hasTag("boundary", "protected_area")
234 && (!(p instanceof Relation) || (((Relation) p).isMultipolygon()));
235 }
236
237 @Override
238 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
239 // ignore ways which have no common boundary tag value
240 Set<String> s1 = getBoundaryTags(w1);
241 Set<String> s2 = getBoundaryTags(w2);
242 for (String type : s1) {
243 if (s2.contains(type))
244 return false;
245 }
246 return true;
247 }
248
249 /**
250 * Collect all boundary tag values of the way and its parent relations
251 * @param w the way to check
252 * @return set with the found boundary tag values
253 */
254 private static Set<String> getBoundaryTags(Way w) {
255 final Set<String> types = new HashSet<>();
256 String type = w.get("boundary");
257 if (type != null)
258 types.add(type);
259 w.referrers(Relation.class).filter(Relation::isMultipolygon).map(r -> r.get("boundary"))
260 .filter(Objects::nonNull).forEach(types::add);
261 types.remove("protected_area");
262 return types;
263 }
264
265 @Override
266 public void visit(Relation r) {
267 for (Way w : r.getMemberPrimitives(Way.class)) {
268 if (!w.isIncomplete())
269 visit(w);
270 }
271 }
272 }
273
274 /**
275 * Self crossing ways test (for all the rest)
276 */
277 public static class SelfCrossing extends CrossingWays {
278
279 protected static final int CROSSING_SELF = 604;
280
281 CrossingWays.Ways normalTest = new Ways();
282 CrossingWays.Boundaries boundariesTest = new Boundaries();
283
284 /**
285 * Constructs a new SelfIntersection test.
286 */
287 public SelfCrossing() {
288 super(tr("Self crossing ways"), CROSSING_SELF);
289 }
290
291 @Override
292 public boolean isPrimitiveUsable(OsmPrimitive p) {
293 return super.isPrimitiveUsable(p) && !(normalTest.isPrimitiveUsable(p)
294 || boundariesTest.isPrimitiveUsable(p));
295 }
296
297 @Override
298 boolean ignoreWaySegmentCombination(Way w1, Way w2) {
299 return w1 != w2; // should not happen
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 MessageHelper createMessage(Way w1, Way w2) {
354 return new MessageHelper(this.name, this.code);
355 }
356
357 @Override
358 public void visit(Way w) {
359 if (this instanceof SelfCrossing) {
360 // free memory, we are not interested in previous ways
361 cellSegments.clear();
362 seenWays.clear();
363 }
364
365 int nodesSize = w.getNodesCount();
366 for (int i = 0; i < nodesSize - 1; i++) {
367 final WaySegment es1 = new WaySegment(w, i);
368 final EastNorth en1 = es1.getFirstNode().getEastNorth();
369 final EastNorth en2 = es1.getSecondNode().getEastNorth();
370 if (en1 == null || en2 == null) {
371 Logging.warn("Crossing ways test skipped " + es1);
372 continue;
373 }
374 for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) {
375 for (WaySegment es2 : segments) {
376 List<Way> prims;
377 List<WaySegment> highlight;
378
379 if (!es1.intersects(es2) || ignoreWaySegmentCombination(es1.way, es2.way)) {
380 continue;
381 }
382
383 prims = new ArrayList<>();
384 prims.add(es1.way);
385 if (es1.way != es2.way)
386 prims.add(es2.way);
387 if ((highlight = seenWays.get(prims)) == null) {
388 highlight = new ArrayList<>();
389 highlight.add(es1);
390 highlight.add(es2);
391
392 final MessageHelper message = createMessage(es1.way, es2.way);
393 errors.add(TestError.builder(this, Severity.WARNING, message.code)
394 .message(message.message)
395 .primitives(prims)
396 .highlightWaySegments(highlight)
397 .build());
398 seenWays.put(prims, highlight);
399 } else {
400 highlight.add(es1);
401 highlight.add(es2);
402 }
403 }
404 segments.add(es1);
405 }
406 }
407 }
408
409 private static boolean areLayerOrLevelDifferent(Way w1, Way w2) {
410 return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))
411 || !Objects.equals(w1.get("level"), w2.get("level"));
412 }
413
414 /**
415 * Returns all the cells this segment crosses. Each cell contains the list
416 * of segments already processed
417 * @param cellSegments map with already collected way segments
418 * @param n1 The first EastNorth
419 * @param n2 The second EastNorth
420 * @return A list with all the cells the segment crosses
421 */
422 public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, EastNorth n1, EastNorth n2) {
423 List<List<WaySegment>> cells = new ArrayList<>();
424 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail())) {
425 cells.add(cellSegments.computeIfAbsent(cell, k -> new ArrayList<>()));
426 }
427 return cells;
428 }
429
430 /**
431 * Find ways which are crossing without sharing a node.
432 * @param w way that is to be checked
433 * @param cellSegments map with already collected way segments
434 * @param crossingWays map to collect crossing ways and related segments
435 * @param findSharedWaySegments true: find shared way segments instead of crossings
436 */
437 public static void findIntersectingWay(Way w, Map<Point2D, List<WaySegment>> cellSegments,
438 Map<List<Way>, List<WaySegment>> crossingWays, boolean findSharedWaySegments) {
439 int nodesSize = w.getNodesCount();
440 for (int i = 0; i < nodesSize - 1; i++) {
441 final WaySegment es1 = new WaySegment(w, i);
442 final EastNorth en1 = es1.getFirstNode().getEastNorth();
443 final EastNorth en2 = es1.getSecondNode().getEastNorth();
444 if (en1 == null || en2 == null) {
445 Logging.warn("Crossing ways test skipped " + es1);
446 continue;
447 }
448 for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) {
449 for (WaySegment es2 : segments) {
450
451 List<WaySegment> highlight;
452 if (es2.way == w // reported by CrossingWays.SelfIntersection
453 || (findSharedWaySegments && !es1.isSimilar(es2))
454 || (!findSharedWaySegments && !es1.intersects(es2)))
455 continue;
456
457 List<Way> prims = Arrays.asList(es1.way, es2.way);
458 if ((highlight = crossingWays.get(prims)) == null) {
459 highlight = new ArrayList<>();
460 highlight.add(es1);
461 highlight.add(es2);
462 crossingWays.put(prims, highlight);
463 } else {
464 highlight.add(es1);
465 highlight.add(es2);
466 }
467 }
468 segments.add(es1);
469 }
470 }
471 }
472
473}
Note: See TracBrowser for help on using the repository browser.