001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.geom.Point2D; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011import java.util.Objects; 012 013import org.openstreetmap.josm.data.coor.EastNorth; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.OsmUtils; 016import org.openstreetmap.josm.data.osm.Relation; 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; 025import org.openstreetmap.josm.tools.Logging; 026 027/** 028 * Tests if there are segments that crosses in the same layer 029 * 030 * @author frsantos 031 */ 032public abstract class CrossingWays extends Test { 033 034 static final String HIGHWAY = "highway"; 035 static final String RAILWAY = "railway"; 036 static final String WATERWAY = "waterway"; 037 038 /** All way segments, grouped by cells */ 039 private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 040 /** The already detected ways in error */ 041 private final Map<List<Way>, List<WaySegment>> seenWays = new HashMap<>(50); 042 043 private final int code; 044 045 /** 046 * General crossing ways test. 047 */ 048 public static class Ways extends CrossingWays { 049 050 protected static final int CROSSING_WAYS = 601; 051 052 /** 053 * Constructs a new crossing {@code Ways} test. 054 */ 055 public Ways() { 056 super(tr("Crossing ways"), CROSSING_WAYS); 057 } 058 059 @Override 060 public boolean isPrimitiveUsable(OsmPrimitive w) { 061 return super.isPrimitiveUsable(w) 062 && !isProposedOrAbandoned(w) 063 && (isHighway(w) 064 || w.hasKey(WATERWAY) 065 || isRailway(w) 066 || isCoastline(w) 067 || isBuilding(w)); 068 } 069 070 @Override 071 boolean ignoreWaySegmentCombination(Way w1, Way w2) { 072 if (w1 == w2) 073 return false; 074 if (!Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))) { 075 return true; 076 } 077 if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) { 078 return true; 079 } 080 if (isSubwayOrTramOrRazed(w2)) { 081 return true; 082 } 083 if (isCoastline(w1) != isCoastline(w2)) { 084 return true; 085 } 086 if ((w1.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w2.hasTag(WATERWAY, "riverbank")) 087 || (w2.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w1.hasTag(WATERWAY, "riverbank"))) { 088 return true; 089 } 090 return isProposedOrAbandoned(w2); 091 } 092 093 @Override 094 String createMessage(Way w1, Way w2) { 095 if (isBuilding(w1)) { 096 return tr("Crossing buildings"); 097 } else if (w1.hasKey(WATERWAY) && w2.hasKey(WATERWAY)) { 098 return tr("Crossing waterways"); 099 } else if ((w1.hasKey(HIGHWAY) && w2.hasKey(WATERWAY)) 100 || (w2.hasKey(HIGHWAY) && w1.hasKey(WATERWAY))) { 101 return tr("Crossing waterway/highway"); 102 } else { 103 return tr("Crossing ways"); 104 } 105 } 106 } 107 108 /** 109 * Crossing boundaries ways test. 110 */ 111 public static class Boundaries extends CrossingWays { 112 113 protected static final int CROSSING_BOUNDARIES = 602; 114 115 /** 116 * Constructs a new crossing {@code Boundaries} test. 117 */ 118 public Boundaries() { 119 super(tr("Crossing boundaries"), CROSSING_BOUNDARIES); 120 } 121 122 @Override 123 public boolean isPrimitiveUsable(OsmPrimitive p) { 124 return super.isPrimitiveUsable(p) && p.hasKey("boundary") 125 && (!(p instanceof Relation) || (((Relation) p).isMultipolygon() && !((Relation) p).hasIncompleteMembers())); 126 } 127 128 @Override 129 boolean ignoreWaySegmentCombination(Way w1, Way w2) { 130 return !Objects.equals(w1.get("boundary"), w2.get("boundary")); 131 } 132 133 @Override 134 String createMessage(Way w1, Way w2) { 135 return tr("Crossing boundaries"); 136 } 137 138 @Override 139 public void visit(Relation r) { 140 for (Way w : r.getMemberPrimitives(Way.class)) { 141 visit(w); 142 } 143 } 144 } 145 146 /** 147 * Crossing barriers ways test. 148 */ 149 public static class Barrier extends CrossingWays { 150 151 protected static final int CROSSING_BARRIERS = 603; 152 153 /** 154 * Constructs a new crossing {@code Barrier} test. 155 */ 156 public Barrier() { 157 super(tr("Crossing barriers"), CROSSING_BARRIERS); 158 } 159 160 @Override 161 public boolean isPrimitiveUsable(OsmPrimitive p) { 162 return super.isPrimitiveUsable(p) && p.hasKey("barrier"); 163 } 164 165 @Override 166 boolean ignoreWaySegmentCombination(Way w1, Way w2) { 167 return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2)); 168 } 169 170 @Override 171 String createMessage(Way w1, Way w2) { 172 return tr("Crossing barriers"); 173 } 174 } 175 176 /** 177 * Self crossing ways test (for all the rest) 178 */ 179 public static class SelfCrossing extends CrossingWays { 180 181 protected static final int CROSSING_SELF = 604; 182 183 CrossingWays.Ways normalTest = new Ways(); 184 CrossingWays.Barrier barrierTest = new Barrier(); 185 CrossingWays.Boundaries boundariesTest = new Boundaries(); 186 187 /** 188 * Constructs a new SelfIntersection test. 189 */ 190 public SelfCrossing() { 191 super(tr("Self crossing"), CROSSING_SELF); 192 } 193 194 @Override 195 public boolean isPrimitiveUsable(OsmPrimitive p) { 196 return super.isPrimitiveUsable(p) && !(normalTest.isPrimitiveUsable(p) || barrierTest.isPrimitiveUsable(p) 197 || boundariesTest.isPrimitiveUsable(p)); 198 } 199 200 @Override 201 boolean ignoreWaySegmentCombination(Way w1, Way w2) { 202 return w1 != w2; // should not happen 203 } 204 205 @Override 206 String createMessage(Way w1, Way w2) { 207 return tr("Self-crossing ways"); 208 } 209 } 210 211 /** 212 * Constructs a new {@code CrossingWays} test. 213 * @param title The test title 214 * @param code The test code 215 * @since 12958 216 */ 217 public CrossingWays(String title, int code) { 218 super(title, tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, " + 219 "but are not connected by a node.")); 220 this.code = code; 221 } 222 223 @Override 224 public void startTest(ProgressMonitor monitor) { 225 super.startTest(monitor); 226 cellSegments.clear(); 227 seenWays.clear(); 228 } 229 230 @Override 231 public void endTest() { 232 super.endTest(); 233 cellSegments.clear(); 234 seenWays.clear(); 235 } 236 237 static boolean isCoastline(OsmPrimitive w) { 238 return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir"); 239 } 240 241 static boolean isHighway(OsmPrimitive w) { 242 return w.hasTagDifferent(HIGHWAY, "rest_area", "services"); 243 } 244 245 static boolean isRailway(OsmPrimitive w) { 246 return w.hasKey(RAILWAY) && !isSubwayOrTramOrRazed(w); 247 } 248 249 static boolean isSubwayOrTramOrRazed(OsmPrimitive w) { 250 return w.hasTag(RAILWAY, "subway", "tram", "razed"); 251 } 252 253 static boolean isProposedOrAbandoned(OsmPrimitive w) { 254 return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned"); 255 } 256 257 abstract boolean ignoreWaySegmentCombination(Way w1, Way w2); 258 259 abstract String createMessage(Way w1, Way w2); 260 261 @Override 262 public void visit(Way w) { 263 if (this instanceof SelfCrossing) { 264 // free memory, we are not interested in previous ways 265 cellSegments.clear(); 266 seenWays.clear(); 267 } 268 269 int nodesSize = w.getNodesCount(); 270 for (int i = 0; i < nodesSize - 1; i++) { 271 final WaySegment es1 = new WaySegment(w, i); 272 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 273 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 274 if (en1 == null || en2 == null) { 275 Logging.warn("Crossing ways test skipped "+es1); 276 continue; 277 } 278 for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) { 279 for (WaySegment es2 : segments) { 280 List<Way> prims; 281 List<WaySegment> highlight; 282 283 if (!es1.intersects(es2) || ignoreWaySegmentCombination(es1.way, es2.way)) { 284 continue; 285 } 286 287 prims = new ArrayList<>(); 288 prims.add(es1.way); 289 if (es1.way != es2.way) 290 prims.add(es2.way); 291 if ((highlight = seenWays.get(prims)) == null) { 292 highlight = new ArrayList<>(); 293 highlight.add(es1); 294 highlight.add(es2); 295 296 final String message = createMessage(es1.way, es2.way); 297 errors.add(TestError.builder(this, Severity.WARNING, code) 298 .message(message) 299 .primitives(prims) 300 .highlightWaySegments(highlight) 301 .build()); 302 seenWays.put(prims, highlight); 303 } else { 304 highlight.add(es1); 305 highlight.add(es2); 306 } 307 } 308 segments.add(es1); 309 } 310 } 311 } 312 313 /** 314 * Returns all the cells this segment crosses. Each cell contains the list 315 * of segments already processed 316 * @param cellSegments map with already collected way segments 317 * @param n1 The first EastNorth 318 * @param n2 The second EastNorth 319 * @return A list with all the cells the segment crosses 320 */ 321 public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, EastNorth n1, EastNorth n2) { 322 List<List<WaySegment>> cells = new ArrayList<>(); 323 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail())) { 324 cells.add(cellSegments.computeIfAbsent(cell, k -> new ArrayList<>())); 325 } 326 return cells; 327 } 328}