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 }