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

Last change on this file since 12986 was 12986, checked in by Don-vip, 16 months ago

fix #14132 - MapCSS crossing operator must check layer attribute first

  • Property svn:eol-style set to native
File size: 10.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.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Objects;
12
13import org.openstreetmap.josm.data.coor.EastNorth;
14import org.openstreetmap.josm.data.osm.OsmPrimitive;
15import org.openstreetmap.josm.data.osm.OsmUtils;
16import org.openstreetmap.josm.data.osm.Relation;
17import org.openstreetmap.josm.data.osm.Way;
18import org.openstreetmap.josm.data.osm.WaySegment;
19import org.openstreetmap.josm.data.validation.OsmValidator;
20import org.openstreetmap.josm.data.validation.Severity;
21import org.openstreetmap.josm.data.validation.Test;
22import org.openstreetmap.josm.data.validation.TestError;
23import org.openstreetmap.josm.data.validation.util.ValUtil;
24import org.openstreetmap.josm.gui.progress.ProgressMonitor;
25import org.openstreetmap.josm.tools.Logging;
26
27/**
28 * Tests if there are segments that crosses in the same layer
29 *
30 * @author frsantos
31 */
32public abstract class CrossingWays extends Test {
33
34    static final String HIGHWAY = "highway";
35    static final String RAILWAY = "railway";
36    static final String WATERWAY = "waterway";
37
38    /** All way segments, grouped by cells */
39    private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000);
40    /** The already detected ways in error */
41    private final Map<List<Way>, List<WaySegment>> seenWays = new HashMap<>(50);
42
43    private final int code;
44
45    /**
46     * General crossing ways test.
47     */
48    public static class Ways extends CrossingWays {
49
50        protected static final int CROSSING_WAYS = 601;
51
52        /**
53         * Constructs a new crossing {@code Ways} test.
54         */
55        public Ways() {
56            super(tr("Crossing ways"), CROSSING_WAYS);
57        }
58
59        @Override
60        public boolean isPrimitiveUsable(OsmPrimitive w) {
61            return super.isPrimitiveUsable(w)
62                    && !isProposedOrAbandoned(w)
63                    && (isHighway(w)
64                    || w.hasKey(WATERWAY)
65                    || isRailway(w)
66                    || isCoastline(w)
67                    || isBuilding(w));
68        }
69
70        @Override
71        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
72            if (w1 == w2)
73                return false;
74            if (!Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))) {
75                return true;
76            }
77            if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) {
78                return true;
79            }
80            if (isSubwayOrTramOrRazed(w2)) {
81                return true;
82            }
83            if (isCoastline(w1) != isCoastline(w2)) {
84                return true;
85            }
86            if ((w1.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w2.hasTag(WATERWAY, "riverbank"))
87             || (w2.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w1.hasTag(WATERWAY, "riverbank"))) {
88                return true;
89            }
90            return isProposedOrAbandoned(w2);
91        }
92
93        @Override
94        String createMessage(Way w1, Way w2) {
95            if (isBuilding(w1)) {
96                return tr("Crossing buildings");
97            } else if (w1.hasKey(WATERWAY) && w2.hasKey(WATERWAY)) {
98                return tr("Crossing waterways");
99            } else if ((w1.hasKey(HIGHWAY) && w2.hasKey(WATERWAY))
100                    || (w2.hasKey(HIGHWAY) && w1.hasKey(WATERWAY))) {
101                return tr("Crossing waterway/highway");
102            } else {
103                return tr("Crossing ways");
104            }
105        }
106    }
107
108    /**
109     * Crossing boundaries ways test.
110     */
111    public static class Boundaries extends CrossingWays {
112
113        protected static final int CROSSING_BOUNDARIES = 602;
114
115        /**
116         * Constructs a new crossing {@code Boundaries} test.
117         */
118        public Boundaries() {
119            super(tr("Crossing boundaries"), CROSSING_BOUNDARIES);
120        }
121
122        @Override
123        public boolean isPrimitiveUsable(OsmPrimitive p) {
124            return super.isPrimitiveUsable(p) && p.hasKey("boundary")
125                    && (!(p instanceof Relation) || (((Relation) p).isMultipolygon() && !((Relation) p).hasIncompleteMembers()));
126        }
127
128        @Override
129        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
130            return !Objects.equals(w1.get("boundary"), w2.get("boundary"));
131        }
132
133        @Override
134        String createMessage(Way w1, Way w2) {
135            return tr("Crossing boundaries");
136        }
137
138        @Override
139        public void visit(Relation r) {
140            for (Way w : r.getMemberPrimitives(Way.class)) {
141                visit(w);
142            }
143        }
144    }
145
146    /**
147     * Crossing barriers ways test.
148     */
149    public static class Barrier extends CrossingWays {
150
151        protected static final int CROSSING_BARRIERS = 603;
152
153        /**
154         * Constructs a new crossing {@code Barrier} test.
155         */
156        public Barrier() {
157            super(tr("Crossing barriers"), CROSSING_BARRIERS);
158        }
159
160        @Override
161        public boolean isPrimitiveUsable(OsmPrimitive p) {
162            return super.isPrimitiveUsable(p) && p.hasKey("barrier");
163        }
164
165        @Override
166        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
167            return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2));
168        }
169
170        @Override
171        String createMessage(Way w1, Way w2) {
172            return tr("Crossing barriers");
173        }
174    }
175
176    /**
177     * Self crossing ways test (for all the rest)
178     */
179    public static class SelfCrossing extends CrossingWays {
180
181        protected static final int CROSSING_SELF = 604;
182
183        CrossingWays.Ways normalTest = new Ways();
184        CrossingWays.Barrier barrierTest = new Barrier();
185        CrossingWays.Boundaries boundariesTest = new Boundaries();
186
187        /**
188         * Constructs a new SelfIntersection test.
189         */
190        public SelfCrossing() {
191            super(tr("Self crossing"), CROSSING_SELF);
192        }
193
194        @Override
195        public boolean isPrimitiveUsable(OsmPrimitive p) {
196            return super.isPrimitiveUsable(p) && !(normalTest.isPrimitiveUsable(p) || barrierTest.isPrimitiveUsable(p)
197                    || boundariesTest.isPrimitiveUsable(p));
198        }
199
200        @Override
201        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
202            return w1 != w2; // should not happen
203        }
204
205        @Override
206        String createMessage(Way w1, Way w2) {
207            return tr("Self-crossing ways");
208        }
209    }
210
211    /**
212     * Constructs a new {@code CrossingWays} test.
213     * @param title The test title
214     * @param code The test code
215     * @since 12958
216     */
217    public CrossingWays(String title, int code) {
218        super(title, tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, " +
219                "but are not connected by a node."));
220        this.code = code;
221    }
222
223    @Override
224    public void startTest(ProgressMonitor monitor) {
225        super.startTest(monitor);
226        cellSegments.clear();
227        seenWays.clear();
228    }
229
230    @Override
231    public void endTest() {
232        super.endTest();
233        cellSegments.clear();
234        seenWays.clear();
235    }
236
237    static boolean isCoastline(OsmPrimitive w) {
238        return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir");
239    }
240
241    static boolean isHighway(OsmPrimitive w) {
242        return w.hasTagDifferent(HIGHWAY, "rest_area", "services");
243    }
244
245    static boolean isRailway(OsmPrimitive w) {
246        return w.hasKey(RAILWAY) && !isSubwayOrTramOrRazed(w);
247    }
248
249    static boolean isSubwayOrTramOrRazed(OsmPrimitive w) {
250        return w.hasTag(RAILWAY, "subway", "tram", "razed");
251    }
252
253    static boolean isProposedOrAbandoned(OsmPrimitive w) {
254        return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned");
255    }
256
257    abstract boolean ignoreWaySegmentCombination(Way w1, Way w2);
258
259    abstract String createMessage(Way w1, Way w2);
260
261    @Override
262    public void visit(Way w) {
263        if (this instanceof SelfCrossing) {
264            // free memory, we are not interested in previous ways
265            cellSegments.clear();
266            seenWays.clear();
267        }
268
269        int nodesSize = w.getNodesCount();
270        for (int i = 0; i < nodesSize - 1; i++) {
271            final WaySegment es1 = new WaySegment(w, i);
272            final EastNorth en1 = es1.getFirstNode().getEastNorth();
273            final EastNorth en2 = es1.getSecondNode().getEastNorth();
274            if (en1 == null || en2 == null) {
275                Logging.warn("Crossing ways test skipped "+es1);
276                continue;
277            }
278            for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) {
279                for (WaySegment es2 : segments) {
280                    List<Way> prims;
281                    List<WaySegment> highlight;
282
283                    if (!es1.intersects(es2) || ignoreWaySegmentCombination(es1.way, es2.way)) {
284                        continue;
285                    }
286
287                    prims = new ArrayList<>();
288                    prims.add(es1.way);
289                    if (es1.way != es2.way)
290                        prims.add(es2.way);
291                    if ((highlight = seenWays.get(prims)) == null) {
292                        highlight = new ArrayList<>();
293                        highlight.add(es1);
294                        highlight.add(es2);
295
296                        final String message = createMessage(es1.way, es2.way);
297                        errors.add(TestError.builder(this, Severity.WARNING, code)
298                                .message(message)
299                                .primitives(prims)
300                                .highlightWaySegments(highlight)
301                                .build());
302                        seenWays.put(prims, highlight);
303                    } else {
304                        highlight.add(es1);
305                        highlight.add(es2);
306                    }
307                }
308                segments.add(es1);
309            }
310        }
311    }
312
313    /**
314     * Returns all the cells this segment crosses.  Each cell contains the list
315     * of segments already processed
316     * @param cellSegments map with already collected way segments
317     * @param n1 The first EastNorth
318     * @param n2 The second EastNorth
319     * @return A list with all the cells the segment crosses
320     */
321    public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, EastNorth n1, EastNorth n2) {
322        List<List<WaySegment>> cells = new ArrayList<>();
323        for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail())) {
324            cells.add(cellSegments.computeIfAbsent(cell, k -> new ArrayList<>()));
325        }
326        return cells;
327    }
328}
Note: See TracBrowser for help on using the repository browser.