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, 10 months 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.