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.Line2D;
007import java.awt.geom.Point2D;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015
016import org.openstreetmap.josm.data.osm.Node;
017import org.openstreetmap.josm.data.osm.Way;
018import org.openstreetmap.josm.data.osm.WaySegment;
019import org.openstreetmap.josm.data.validation.OsmValidator;
020import org.openstreetmap.josm.data.validation.Severity;
021import org.openstreetmap.josm.data.validation.Test;
022import org.openstreetmap.josm.data.validation.TestError;
023import org.openstreetmap.josm.data.validation.util.ValUtil;
024import org.openstreetmap.josm.gui.progress.ProgressMonitor;
025
026/**
027 * Tests if there are segments that crosses in the same layer
028 *
029 * @author frsantos
030 */
031public class CrossingWays extends Test {
032    protected static final int CROSSING_WAYS = 601;
033
034    /** All way segments, grouped by cells */
035    private Map<Point2D,List<ExtendedSegment>> cellSegments;
036    /** The already detected errors */
037    private Set<WaySegment> errorSegments;
038    /** The already detected ways in error */
039    private Map<List<Way>, List<WaySegment>> seenWays;
040
041    /**
042     * Constructor
043     */
044    public CrossingWays() {
045        super(tr("Crossing ways."),
046                tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, but are not connected by a node."));
047    }
048
049    @Override
050    public void startTest(ProgressMonitor monitor) {
051        super.startTest(monitor);
052        cellSegments = new HashMap<Point2D,List<ExtendedSegment>>(1000);
053        errorSegments = new HashSet<WaySegment>();
054        seenWays = new HashMap<List<Way>, List<WaySegment>>(50);
055    }
056
057    @Override
058    public void endTest() {
059        super.endTest();
060        cellSegments = null;
061        errorSegments = null;
062        seenWays = null;
063    }
064
065    @Override
066    public void visit(Way w) {
067        if(!w.isUsable())
068            return;
069
070        String natural1 = w.get("natural");
071        String landuse1 = w.get("landuse");
072        boolean isCoastline1 = "water".equals(natural1) || "coastline".equals(natural1) || "reservoir".equals(landuse1);
073        String highway1 = w.get("highway");
074        String railway1 = w.get("railway");
075        boolean isSubway1 = "subway".equals(railway1);
076        boolean isTram1 = "tram".equals(railway1);
077        boolean isBuilding = isBuilding(w);
078        String waterway1 = w.get("waterway");
079
080        if (w.get("highway") == null && w.get("waterway") == null
081                && (railway1 == null || isSubway1 || isTram1)
082                && !isCoastline1 && !isBuilding)
083            return;
084
085        String level1 = w.get("level");
086        String layer1 = w.get("layer");
087        if ("0".equals(layer1)) {
088            layer1 = null; // 0 is default value for layer. Don't assume the same for levels
089        }
090
091        int nodesSize = w.getNodesCount();
092        for (int i = 0; i < nodesSize - 1; i++) {
093            WaySegment ws = new WaySegment(w, i);
094            ExtendedSegment es1 = new ExtendedSegment(ws, layer1, highway1, railway1, isCoastline1, waterway1, level1);
095            for (List<ExtendedSegment> segments : getSegments(es1.n1, es1.n2)) {
096                for (ExtendedSegment es2 : segments) {
097                    List<Way> prims;
098                    List<WaySegment> highlight;
099
100                    if (errorSegments.contains(ws) && errorSegments.contains(es2.ws)) {
101                        continue;
102                    }
103
104                    String level2 = es2.level;
105                    String layer2 = es2.layer;
106                    String highway2 = es2.highway;
107                    String railway2 = es2.railway;
108                    boolean isCoastline2 = es2.coastline;
109                    if (layer1 == null ? layer2 != null : !layer1.equals(layer2)) {
110                        continue;
111                    }
112                    // Ignore indoor highways on different levels
113                    if (highway1 != null && highway2 != null && level1 != null && level2 != null && !level1.equals(level2)) {
114                        continue;
115                    }
116
117                    if (!es1.intersects(es2) ) {
118                        continue;
119                    }
120                    if (isSubway1 && "subway".equals(railway2)) {
121                        continue;
122                    }
123                    if (isTram1 && "tram".equals(railway2)) {
124                        continue;
125                    }
126
127                    if (isCoastline1 != isCoastline2) {
128                        continue;
129                    }
130                    if (("river".equals(waterway1) && "riverbank".equals(es2.waterway))
131                            || ("riverbank".equals(waterway1) && "river".equals(es2.waterway))) {
132                        continue;
133                    }
134
135                    if ("proposed".equals(es1.highway) || "proposed".equals(highway2)
136                     || "proposed".equals(es1.railway) || "proposed".equals(railway2)
137                     || "abandoned".equals(es1.railway) || "abandoned".equals(railway2)) {
138                        continue;
139                    }
140
141                    prims = Arrays.asList(es1.ws.way, es2.ws.way);
142                    if ((highlight = seenWays.get(prims)) == null) {
143                        highlight = new ArrayList<WaySegment>();
144                        highlight.add(es1.ws);
145                        highlight.add(es2.ws);
146
147                        String message;
148                        if (isBuilding) {
149                            message = tr("Crossing buildings");
150                        } else if ((es1.waterway != null && es2.waterway != null)) {
151                            message = tr("Crossing waterways");
152                        } else if ((es1.waterway != null && es2.highway != null)
153                                || (es2.waterway != null && es1.highway != null)) {
154                            message = tr("Crossing waterway/highway");
155                        } else {
156                            message = tr("Crossing ways");
157                        }
158
159                        errors.add(new TestError(this, Severity.WARNING,
160                                message,
161                                CROSSING_WAYS,
162                                prims,
163                                highlight));
164                        seenWays.put(prims, highlight);
165                    } else {
166                        highlight.add(es1.ws);
167                        highlight.add(es2.ws);
168                    }
169                }
170                segments.add(es1);
171            }
172        }
173    }
174
175    /**
176     * Returns all the cells this segment crosses.  Each cell contains the list
177     * of segments already processed
178     *
179     * @param n1 The first node
180     * @param n2 The second node
181     * @return A list with all the cells the segment crosses
182     */
183    public List<List<ExtendedSegment>> getSegments(Node n1, Node n2) {
184
185        List<List<ExtendedSegment>> cells = new ArrayList<List<ExtendedSegment>>();
186        for(Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) {
187            List<ExtendedSegment> segments = cellSegments.get(cell);
188            if (segments == null) {
189                segments = new ArrayList<ExtendedSegment>();
190                cellSegments.put(cell, segments);
191            }
192            cells.add(segments);
193        }
194        return cells;
195    }
196
197    /**
198     * A way segment with some additional information
199     */
200    public static class ExtendedSegment {
201        private final Node n1, n2;
202
203        private final WaySegment ws;
204
205        /** The layer */
206        private final String layer;
207
208        /** The highway type */
209        private final String highway;
210
211        /** The railway type */
212        private final String railway;
213
214        /** The waterway type */
215        private final String waterway;
216
217        /** The coastline type */
218        private final boolean coastline;
219
220        /** The level, only considered for indoor highways */
221        private final String level;
222
223        /**
224         * Constructor
225         * @param ws The way segment
226         * @param layer The layer of the way this segment is in
227         * @param highway The highway type of the way this segment is in
228         * @param railway The railway type of the way this segment is in
229         * @param coastline The coastline flag of the way the segment is in
230         * @param waterway The waterway type of the way this segment is in
231         * @param level The level of the way this segment is in
232         */
233        public ExtendedSegment(WaySegment ws, String layer, String highway, String railway, boolean coastline, String waterway, String level) {
234            this.ws = ws;
235            this.n1 = ws.way.getNodes().get(ws.lowerIndex);
236            this.n2 = ws.way.getNodes().get(ws.lowerIndex + 1);
237            this.layer = layer;
238            this.highway = highway;
239            this.railway = railway;
240            this.coastline = coastline;
241            this.waterway = waterway;
242            this.level = level;
243        }
244
245        /**
246         * Checks whether this segment crosses other segment
247         * @param s2 The other segment
248         * @return true if both segments crosses
249         */
250        public boolean intersects(ExtendedSegment s2) {
251            if (n1.equals(s2.n1) || n2.equals(s2.n2) ||
252                    n1.equals(s2.n2) || n2.equals(s2.n1))
253                return false;
254
255            return Line2D.linesIntersect(
256                    n1.getEastNorth().east(), n1.getEastNorth().north(),
257                    n2.getEastNorth().east(), n2.getEastNorth().north(),
258                    s2.n1.getEastNorth().east(), s2.n1.getEastNorth().north(),
259                    s2.n2.getEastNorth().east(), s2.n2.getEastNorth().north());
260        }
261    }
262}