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.Point2D; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Objects; 014import java.util.Set; 015 016import org.openstreetmap.josm.Main; 017import org.openstreetmap.josm.data.coor.EastNorth; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.Way; 021import org.openstreetmap.josm.data.osm.WaySegment; 022import org.openstreetmap.josm.data.validation.OsmValidator; 023import org.openstreetmap.josm.data.validation.Severity; 024import org.openstreetmap.josm.data.validation.Test; 025import org.openstreetmap.josm.data.validation.TestError; 026import org.openstreetmap.josm.data.validation.util.ValUtil; 027import org.openstreetmap.josm.gui.progress.ProgressMonitor; 028 029/** 030 * Tests if there are segments that crosses in the same layer 031 * 032 * @author frsantos 033 */ 034public abstract class CrossingWays extends Test { 035 protected static final int CROSSING_WAYS = 601; 036 037 private static final String HIGHWAY = "highway"; 038 private static final String RAILWAY = "railway"; 039 private static final String WATERWAY = "waterway"; 040 041 /** All way segments, grouped by cells */ 042 private Map<Point2D,List<WaySegment>> cellSegments; 043 /** The already detected errors */ 044 private Set<WaySegment> errorSegments; 045 /** The already detected ways in error */ 046 private Map<List<Way>, List<WaySegment>> seenWays; 047 048 /** 049 * General crossing ways test. 050 */ 051 public static class Ways extends CrossingWays { 052 053 /** 054 * Constructs a new crossing {@code Ways} test. 055 */ 056 public Ways() { 057 super(tr("Crossing ways")); 058 } 059 060 @Override 061 public boolean isPrimitiveUsable(OsmPrimitive w) { 062 return super.isPrimitiveUsable(w) 063 && !isProposedOrAbandoned(w) 064 && (w.hasKey(HIGHWAY) 065 || w.hasKey(WATERWAY) 066 || (w.hasKey(RAILWAY) && !isSubwayOrTram(w)) 067 || isCoastline(w) 068 || isBuilding(w)); 069 } 070 071 @Override 072 boolean ignoreWaySegmentCombination(Way w1, Way w2) { 073 if (!Objects.equals(getLayer(w1), getLayer(w2))) { 074 return true; 075 } 076 if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) { 077 return true; 078 } 079 if (isSubwayOrTram(w2)) { 080 return true; 081 } 082 if (isCoastline(w1) != isCoastline(w2)) { 083 return true; 084 } 085 if ((w1.hasTag(WATERWAY, "river") && w2.hasTag(WATERWAY, "riverbank")) 086 || (w2.hasTag(WATERWAY, "river") && w1.hasTag(WATERWAY, "riverbank"))) { 087 return true; 088 } 089 if (isProposedOrAbandoned(w2)) { 090 return true; 091 } 092 return false; 093 } 094 095 @Override 096 String createMessage(Way w1, Way w2) { 097 if (isBuilding(w1)) { 098 return tr("Crossing buildings"); 099 } else if (w1.hasKey(WATERWAY) && w2.hasKey(WATERWAY)) { 100 return tr("Crossing waterways"); 101 } else if ((w1.hasKey(HIGHWAY) && w2.hasKey(WATERWAY)) 102 || (w2.hasKey(HIGHWAY) && w1.hasKey(WATERWAY))) { 103 return tr("Crossing waterway/highway"); 104 } else { 105 return tr("Crossing ways"); 106 } 107 } 108 } 109 110 /** 111 * Crossing boundaries ways test. 112 */ 113 public static class Boundaries extends CrossingWays { 114 115 /** 116 * Constructs a new crossing {@code Boundaries} test. 117 */ 118 public Boundaries() { 119 super(tr("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 /** 152 * Constructs a new crossing {@code Barrier} test. 153 */ 154 public Barrier() { 155 super(tr("Crossing barriers")); 156 } 157 158 @Override 159 public boolean isPrimitiveUsable(OsmPrimitive p) { 160 return super.isPrimitiveUsable(p) && p.hasKey("barrier"); 161 } 162 163 @Override 164 boolean ignoreWaySegmentCombination(Way w1, Way w2) { 165 if (!Objects.equals(getLayer(w1), getLayer(w2))) { 166 return true; 167 } 168 return false; 169 } 170 171 @Override 172 String createMessage(Way w1, Way w2) { 173 return tr("Crossing barriers"); 174 } 175 } 176 177 /** 178 * Constructs a new {@code CrossingWays} test. 179 * @param title The test title 180 * @since 6691 181 */ 182 public CrossingWays(String title) { 183 super(title, tr("This test checks if two roads, railways, waterways or buildings crosses in the same layer, but are not connected by a node.")); 184 } 185 186 @Override 187 public void startTest(ProgressMonitor monitor) { 188 super.startTest(monitor); 189 cellSegments = new HashMap<>(1000); 190 errorSegments = new HashSet<>(); 191 seenWays = new HashMap<>(50); 192 } 193 194 @Override 195 public void endTest() { 196 super.endTest(); 197 cellSegments = null; 198 errorSegments = null; 199 seenWays = null; 200 } 201 202 static String getLayer(OsmPrimitive w) { 203 String layer1 = w.get("layer"); 204 if ("0".equals(layer1)) { 205 layer1 = null; // 0 is default value for layer. 206 } 207 return layer1; 208 } 209 210 static boolean isCoastline(OsmPrimitive w) { 211 return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir"); 212 } 213 214 static boolean isSubwayOrTram(OsmPrimitive w) { 215 return w.hasTag(RAILWAY, "subway", "tram"); 216 } 217 218 static boolean isProposedOrAbandoned(OsmPrimitive w) { 219 return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned"); 220 } 221 222 abstract boolean ignoreWaySegmentCombination(Way w1, Way w2); 223 224 abstract String createMessage(Way w1, Way w2); 225 226 @Override 227 public void visit(Way w) { 228 229 int nodesSize = w.getNodesCount(); 230 for (int i = 0; i < nodesSize - 1; i++) { 231 final WaySegment es1 = new WaySegment(w, i); 232 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 233 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 234 if (en1 == null || en2 == null) { 235 Main.warn("Crossing ways test skipped "+es1); 236 continue; 237 } 238 for (List<WaySegment> segments : getSegments(en1, en2)) { 239 for (WaySegment es2 : segments) { 240 List<Way> prims; 241 List<WaySegment> highlight; 242 243 if (errorSegments.contains(es1) && errorSegments.contains(es2) 244 || !es1.intersects(es2) 245 || ignoreWaySegmentCombination(es1.way, es2.way)) { 246 continue; 247 } 248 249 prims = Arrays.asList(es1.way, es2.way); 250 if ((highlight = seenWays.get(prims)) == null) { 251 highlight = new ArrayList<>(); 252 highlight.add(es1); 253 highlight.add(es2); 254 255 final String message = createMessage(es1.way, es2.way); 256 errors.add(new TestError(this, Severity.WARNING, 257 message, 258 CROSSING_WAYS, 259 prims, 260 highlight)); 261 seenWays.put(prims, highlight); 262 } else { 263 highlight.add(es1); 264 highlight.add(es2); 265 } 266 } 267 segments.add(es1); 268 } 269 } 270 } 271 272 /** 273 * Returns all the cells this segment crosses. Each cell contains the list 274 * of segments already processed 275 * 276 * @param n1 The first EastNorth 277 * @param n2 The second EastNorth 278 * @return A list with all the cells the segment crosses 279 */ 280 public List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) { 281 282 List<List<WaySegment>> cells = new ArrayList<>(); 283 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) { 284 List<WaySegment> segments = cellSegments.get(cell); 285 if (segments == null) { 286 segments = new ArrayList<>(); 287 cellSegments.put(cell, segments); 288 } 289 cells.add(segments); 290 } 291 return cells; 292 } 293}