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.waterway != null)) {
142 message = tr("Crossing waterways");
143 } else if ((es1.waterway != null && es2.ws.way.get("highway") != null)
144 || (es2.waterway != null && es1.ws.way.get("highway") != null)) {
145 message = tr("Crossing waterway/highway");
146 } else {
147 message = tr("Crossing ways");
148 }
149
150 errors.add(new TestError(this, Severity.WARNING,
151 message,
152 CROSSING_WAYS,
153 prims,
154 highlight));
155 ways_seen.put(prims, highlight);
156 } else {
157 highlight.add(es1.ws);
158 highlight.add(es2.ws);
159 }
160 }
161 segments.add(es1);
162 }
163 }
164 }
165
166 /**
167 * Returns all the cells this segment crosses. Each cell contains the list
168 * of segments already processed
169 *
170 * @param n1 The first node
171 * @param n2 The second node
172 * @return A list with all the cells the segment crosses
173 */
174 public List<List<ExtendedSegment>> getSegments(Node n1, Node n2) {
175
176 List<List<ExtendedSegment>> cells = new ArrayList<List<ExtendedSegment>>();
177 for(Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) {
178 List<ExtendedSegment> segments = cellSegments.get(cell);
179 if (segments == null) {
180 segments = new ArrayList<ExtendedSegment>();
181 cellSegments.put(cell, segments);
182 }
183 cells.add(segments);
184 }
185 return cells;
186 }
187
188 /**
189 * A way segment with some additional information
190 * @author frsantos
191 */
192 public static class ExtendedSegment {
193 public Node n1, n2;
194
195 public WaySegment ws;
196
197 /** The layer */
198 public String layer;
199
200 /** The railway type */
201 public String railway;
202
203 /** The waterway type */
204 public String waterway;
205
206 /** The coastline type */
207 public boolean coastline;
208
209 /**
210 * Constructor
211 * @param ws The way segment
212 * @param layer The layer of the way this segment is in
213 * @param railway The railway type of the way this segment is in
214 * @param coastline The coastline flag of the way the segment is in
215 * @param waterway The waterway type of the way this segment is in
216 */
217 public ExtendedSegment(WaySegment ws, String layer, String railway, boolean coastline, String waterway) {
218 this.ws = ws;
219 this.n1 = ws.way.getNodes().get(ws.lowerIndex);
220 this.n2 = ws.way.getNodes().get(ws.lowerIndex + 1);
221 this.layer = layer;
222 this.railway = railway;
223 this.coastline = coastline;
224 this.waterway = waterway;
225 }
226
227 /**
228 * Checks whether this segment crosses other segment
229 * @param s2 The other segment
230 * @return true if both segments crosses
231 */
232 public boolean intersects(ExtendedSegment s2) {
233 if (n1.equals(s2.n1) || n2.equals(s2.n2) ||
234 n1.equals(s2.n2) || n2.equals(s2.n1))
235 return false;
236
237 return Line2D.linesIntersect(
238 n1.getEastNorth().east(), n1.getEastNorth().north(),
239 n2.getEastNorth().east(), n2.getEastNorth().north(),
240 s2.n1.getEastNorth().east(), s2.n1.getEastNorth().north(),
241 s2.n2.getEastNorth().east(), s2.n2.getEastNorth().north());
242 }
243 }
244 }