001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.geom.Point2D;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Map;
013import java.util.Objects;
014import java.util.Set;
015
016import org.openstreetmap.josm.Main;
017import org.openstreetmap.josm.data.coor.EastNorth;
018import org.openstreetmap.josm.data.osm.OsmPrimitive;
019import org.openstreetmap.josm.data.osm.Relation;
020import org.openstreetmap.josm.data.osm.Way;
021import org.openstreetmap.josm.data.osm.WaySegment;
022import org.openstreetmap.josm.data.validation.OsmValidator;
023import org.openstreetmap.josm.data.validation.Severity;
024import org.openstreetmap.josm.data.validation.Test;
025import org.openstreetmap.josm.data.validation.TestError;
026import org.openstreetmap.josm.data.validation.util.ValUtil;
027import org.openstreetmap.josm.gui.progress.ProgressMonitor;
028
029/**
030 * Tests if there are segments that crosses in the same layer
031 *
032 * @author frsantos
033 */
034public abstract class CrossingWays extends Test {
035    protected static final int CROSSING_WAYS = 601;
036
037    private static final String HIGHWAY = "highway";
038    private static final String RAILWAY = "railway";
039    private static final String WATERWAY = "waterway";
040
041    /** All way segments, grouped by cells */
042    private Map<Point2D,List<WaySegment>> cellSegments;
043    /** The already detected errors */
044    private Set<WaySegment> errorSegments;
045    /** The already detected ways in error */
046    private Map<List<Way>, List<WaySegment>> seenWays;
047
048    /**
049     * General crossing ways test.
050     */
051    public static class Ways extends CrossingWays {
052
053        /**
054         * Constructs a new crossing {@code Ways} test.
055         */
056        public Ways() {
057            super(tr("Crossing ways"));
058        }
059
060        @Override
061        public boolean isPrimitiveUsable(OsmPrimitive w) {
062            return super.isPrimitiveUsable(w)
063                    && !isProposedOrAbandoned(w)
064                    && (w.hasKey(HIGHWAY)
065                    || w.hasKey(WATERWAY)
066                    || (w.hasKey(RAILWAY) && !isSubwayOrTram(w))
067                    || isCoastline(w)
068                    || isBuilding(w));
069        }
070
071        @Override
072        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
073            if (!Objects.equals(getLayer(w1), getLayer(w2))) {
074                return true;
075            }
076            if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) {
077                return true;
078            }
079            if (isSubwayOrTram(w2)) {
080                return true;
081            }
082            if (isCoastline(w1) != isCoastline(w2)) {
083                return true;
084            }
085            if ((w1.hasTag(WATERWAY, "river") && w2.hasTag(WATERWAY, "riverbank"))
086                    || (w2.hasTag(WATERWAY, "river") && w1.hasTag(WATERWAY, "riverbank"))) {
087                return true;
088            }
089            if (isProposedOrAbandoned(w2)) {
090                return true;
091            }
092            return false;
093        }
094
095        @Override
096        String createMessage(Way w1, Way w2) {
097            if (isBuilding(w1)) {
098                return tr("Crossing buildings");
099            } else if (w1.hasKey(WATERWAY) && w2.hasKey(WATERWAY)) {
100                return tr("Crossing waterways");
101            } else if ((w1.hasKey(HIGHWAY) && w2.hasKey(WATERWAY))
102                    || (w2.hasKey(HIGHWAY) && w1.hasKey(WATERWAY))) {
103                return tr("Crossing waterway/highway");
104            } else {
105                return tr("Crossing ways");
106            }
107        }
108    }
109
110    /**
111     * Crossing boundaries ways test.
112     */
113    public static class Boundaries extends CrossingWays {
114
115        /**
116         * Constructs a new crossing {@code Boundaries} test.
117         */
118        public Boundaries() {
119            super(tr("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        /**
152         * Constructs a new crossing {@code Barrier} test.
153         */
154        public Barrier() {
155            super(tr("Crossing barriers"));
156        }
157
158        @Override
159        public boolean isPrimitiveUsable(OsmPrimitive p) {
160            return super.isPrimitiveUsable(p) && p.hasKey("barrier");
161        }
162
163        @Override
164        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
165            if (!Objects.equals(getLayer(w1), getLayer(w2))) {
166                return true;
167            }
168            return false;
169        }
170
171        @Override
172        String createMessage(Way w1, Way w2) {
173            return tr("Crossing barriers");
174        }
175    }
176
177    /**
178     * Constructs a new {@code CrossingWays} test.
179     * @param title The test title
180     * @since 6691
181     */
182    public CrossingWays(String title) {
183        super(title, tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, but are not connected by a node."));
184    }
185
186    @Override
187    public void startTest(ProgressMonitor monitor) {
188        super.startTest(monitor);
189        cellSegments = new HashMap<>(1000);
190        errorSegments = new HashSet<>();
191        seenWays = new HashMap<>(50);
192    }
193
194    @Override
195    public void endTest() {
196        super.endTest();
197        cellSegments = null;
198        errorSegments = null;
199        seenWays = null;
200    }
201
202    static String getLayer(OsmPrimitive w) {
203        String layer1 = w.get("layer");
204        if ("0".equals(layer1)) {
205            layer1 = null; // 0 is default value for layer.
206        }
207        return layer1;
208    }
209
210    static boolean isCoastline(OsmPrimitive w) {
211        return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir");
212    }
213
214    static boolean isSubwayOrTram(OsmPrimitive w) {
215        return w.hasTag(RAILWAY, "subway", "tram");
216    }
217
218    static boolean isProposedOrAbandoned(OsmPrimitive w) {
219        return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned");
220    }
221
222    abstract boolean ignoreWaySegmentCombination(Way w1, Way w2);
223
224    abstract String createMessage(Way w1, Way w2);
225
226    @Override
227    public void visit(Way w) {
228
229        int nodesSize = w.getNodesCount();
230        for (int i = 0; i < nodesSize - 1; i++) {
231            final WaySegment es1 = new WaySegment(w, i);
232            final EastNorth en1 = es1.getFirstNode().getEastNorth();
233            final EastNorth en2 = es1.getSecondNode().getEastNorth();
234            if (en1 == null || en2 == null) {
235                Main.warn("Crossing ways test skipped "+es1);
236                continue;
237            }
238            for (List<WaySegment> segments : getSegments(en1, en2)) {
239                for (WaySegment es2 : segments) {
240                    List<Way> prims;
241                    List<WaySegment> highlight;
242
243                    if (errorSegments.contains(es1) && errorSegments.contains(es2)
244                            || !es1.intersects(es2)
245                            || ignoreWaySegmentCombination(es1.way, es2.way)) {
246                        continue;
247                    }
248
249                    prims = Arrays.asList(es1.way, es2.way);
250                    if ((highlight = seenWays.get(prims)) == null) {
251                        highlight = new ArrayList<>();
252                        highlight.add(es1);
253                        highlight.add(es2);
254
255                        final String message = createMessage(es1.way, es2.way);
256                        errors.add(new TestError(this, Severity.WARNING,
257                                message,
258                                CROSSING_WAYS,
259                                prims,
260                                highlight));
261                        seenWays.put(prims, highlight);
262                    } else {
263                        highlight.add(es1);
264                        highlight.add(es2);
265                    }
266                }
267                segments.add(es1);
268            }
269        }
270    }
271
272    /**
273     * Returns all the cells this segment crosses.  Each cell contains the list
274     * of segments already processed
275     *
276     * @param n1 The first EastNorth
277     * @param n2 The second EastNorth
278     * @return A list with all the cells the segment crosses
279     */
280    public List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) {
281
282        List<List<WaySegment>> cells = new ArrayList<>();
283        for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) {
284            List<WaySegment> segments = cellSegments.get(cell);
285            if (segments == null) {
286                segments = new ArrayList<>();
287                cellSegments.put(cell, segments);
288            }
289            cells.add(segments);
290        }
291        return cells;
292    }
293}