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

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

fix #16188 - Detect crossing of residential areas (patch by marxin, modified)

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