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 }