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}