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            return false;
166        }
167
168        @Override
169        String createMessage(Way w1, Way w2) {
170            return tr("Crossing barriers");
171        }
172    }
173
174    /**
175     * Constructs a new {@code CrossingWays} test.
176     * @param title The test title
177     * @since 6691
178     */
179    public CrossingWays(String title) {
180        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."));
181    }
182
183    @Override
184    public void startTest(ProgressMonitor monitor) {
185        super.startTest(monitor);
186        cellSegments = new HashMap<>(1000);
187        errorSegments = new HashSet<>();
188        seenWays = new HashMap<>(50);
189    }
190
191    @Override
192    public void endTest() {
193        super.endTest();
194        cellSegments = null;
195        errorSegments = null;
196        seenWays = null;
197    }
198
199    static String getLayer(OsmPrimitive w) {
200        String layer1 = w.get("layer");
201        if ("0".equals(layer1)) {
202            layer1 = null; // 0 is default value for layer.
203        }
204        return layer1;
205    }
206
207    static boolean isCoastline(OsmPrimitive w) {
208        return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir");
209    }
210
211    static boolean isSubwayOrTram(OsmPrimitive w) {
212        return w.hasTag(RAILWAY, "subway", "tram");
213    }
214
215    static boolean isProposedOrAbandoned(OsmPrimitive w) {
216        return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned");
217    }
218
219    abstract boolean ignoreWaySegmentCombination(Way w1, Way w2);
220
221    abstract String createMessage(Way w1, Way w2);
222
223    @Override
224    public void visit(Way w) {
225
226        int nodesSize = w.getNodesCount();
227        for (int i = 0; i < nodesSize - 1; i++) {
228            final WaySegment es1 = new WaySegment(w, i);
229            final EastNorth en1 = es1.getFirstNode().getEastNorth();
230            final EastNorth en2 = es1.getSecondNode().getEastNorth();
231            if (en1 == null || en2 == null) {
232                Main.warn("Crossing ways test skipped "+es1);
233                continue;
234            }
235            for (List<WaySegment> segments : getSegments(en1, en2)) {
236                for (WaySegment es2 : segments) {
237                    List<Way> prims;
238                    List<WaySegment> highlight;
239
240                    if (errorSegments.contains(es1) && errorSegments.contains(es2)
241                            || !es1.intersects(es2)
242                            || ignoreWaySegmentCombination(es1.way, es2.way)) {
243                        continue;
244                    }
245
246                    prims = Arrays.asList(es1.way, es2.way);
247                    if ((highlight = seenWays.get(prims)) == null) {
248                        highlight = new ArrayList<>();
249                        highlight.add(es1);
250                        highlight.add(es2);
251
252                        final String message = createMessage(es1.way, es2.way);
253                        errors.add(new TestError(this, Severity.WARNING,
254                                message,
255                                CROSSING_WAYS,
256                                prims,
257                                highlight));
258                        seenWays.put(prims, highlight);
259                    } else {
260                        highlight.add(es1);
261                        highlight.add(es2);
262                    }
263                }
264                segments.add(es1);
265            }
266        }
267    }
268
269    /**
270     * Returns all the cells this segment crosses.  Each cell contains the list
271     * of segments already processed
272     *
273     * @param n1 The first EastNorth
274     * @param n2 The second EastNorth
275     * @return A list with all the cells the segment crosses
276     */
277    public List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) {
278
279        List<List<WaySegment>> cells = new ArrayList<>();
280        for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) {
281            List<WaySegment> segments = cellSegments.get(cell);
282            if (segments == null) {
283                segments = new ArrayList<>();
284                cellSegments.put(cell, segments);
285            }
286            cells.add(segments);
287        }
288        return cells;
289    }
290}