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.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 final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 043 /** The already detected errors */ 044 private final Set<WaySegment> errorSegments = new HashSet<>(); 045 /** The already detected ways in error */ 046 private final Map<List<Way>, List<WaySegment>> seenWays = new HashMap<>(50); 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) && !w.hasTag(HIGHWAY, "rest_area", "services")) 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, " + 184 "but are not connected by a node.")); 185 } 186 187 @Override 188 public void startTest(ProgressMonitor monitor) { 189 super.startTest(monitor); 190 cellSegments.clear(); 191 errorSegments.clear(); 192 seenWays.clear(); 193 } 194 195 @Override 196 public void endTest() { 197 super.endTest(); 198 cellSegments.clear(); 199 errorSegments.clear(); 200 seenWays.clear(); 201 } 202 203 static String getLayer(OsmPrimitive w) { 204 String layer1 = w.get("layer"); 205 if ("0".equals(layer1)) { 206 layer1 = null; // 0 is default value for layer. 207 } 208 return layer1; 209 } 210 211 static boolean isCoastline(OsmPrimitive w) { 212 return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir"); 213 } 214 215 static boolean isSubwayOrTram(OsmPrimitive w) { 216 return w.hasTag(RAILWAY, "subway", "tram"); 217 } 218 219 static boolean isProposedOrAbandoned(OsmPrimitive w) { 220 return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned"); 221 } 222 223 abstract boolean ignoreWaySegmentCombination(Way w1, Way w2); 224 225 abstract String createMessage(Way w1, Way w2); 226 227 @Override 228 public void visit(Way w) { 229 230 int nodesSize = w.getNodesCount(); 231 for (int i = 0; i < nodesSize - 1; i++) { 232 final WaySegment es1 = new WaySegment(w, i); 233 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 234 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 235 if (en1 == null || en2 == null) { 236 Main.warn("Crossing ways test skipped "+es1); 237 continue; 238 } 239 for (List<WaySegment> segments : getSegments(en1, en2)) { 240 for (WaySegment es2 : segments) { 241 List<Way> prims; 242 List<WaySegment> highlight; 243 244 if (errorSegments.contains(es1) && errorSegments.contains(es2) 245 || !es1.intersects(es2) 246 || ignoreWaySegmentCombination(es1.way, es2.way)) { 247 continue; 248 } 249 250 prims = Arrays.asList(es1.way, es2.way); 251 if ((highlight = seenWays.get(prims)) == null) { 252 highlight = new ArrayList<>(); 253 highlight.add(es1); 254 highlight.add(es2); 255 256 final String message = createMessage(es1.way, es2.way); 257 errors.add(new TestError(this, Severity.WARNING, 258 message, 259 CROSSING_WAYS, 260 prims, 261 highlight)); 262 seenWays.put(prims, highlight); 263 } else { 264 highlight.add(es1); 265 highlight.add(es2); 266 } 267 } 268 segments.add(es1); 269 } 270 } 271 } 272 273 /** 274 * Returns all the cells this segment crosses. Each cell contains the list 275 * of segments already processed 276 * 277 * @param n1 The first EastNorth 278 * @param n2 The second EastNorth 279 * @return A list with all the cells the segment crosses 280 */ 281 public List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) { 282 283 List<List<WaySegment>> cells = new ArrayList<>(); 284 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) { 285 List<WaySegment> segments = cellSegments.get(cell); 286 if (segments == null) { 287 segments = new ArrayList<>(); 288 cellSegments.put(cell, segments); 289 } 290 cells.add(segments); 291 } 292 return cells; 293 } 294}