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