[8378] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3669] | 2 | package org.openstreetmap.josm.data.validation.tests;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
| 6 | import java.awt.geom.Point2D;
|
---|
| 7 | import java.util.ArrayList;
|
---|
| 8 | import java.util.HashMap;
|
---|
| 9 | import java.util.List;
|
---|
| 10 | import java.util.Map;
|
---|
[7083] | 11 | import java.util.Objects;
|
---|
[3669] | 12 |
|
---|
[6869] | 13 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
[6579] | 14 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[12986] | 15 | import org.openstreetmap.josm.data.osm.OsmUtils;
|
---|
[6581] | 16 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
[3669] | 17 | import org.openstreetmap.josm.data.osm.Way;
|
---|
| 18 | import org.openstreetmap.josm.data.osm.WaySegment;
|
---|
| 19 | import org.openstreetmap.josm.data.validation.OsmValidator;
|
---|
| 20 | import org.openstreetmap.josm.data.validation.Severity;
|
---|
| 21 | import org.openstreetmap.josm.data.validation.Test;
|
---|
| 22 | import org.openstreetmap.josm.data.validation.TestError;
|
---|
| 23 | import org.openstreetmap.josm.data.validation.util.ValUtil;
|
---|
| 24 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
---|
[12620] | 25 | import org.openstreetmap.josm.tools.Logging;
|
---|
[3669] | 26 |
|
---|
| 27 | /**
|
---|
| 28 | * Tests if there are segments that crosses in the same layer
|
---|
| 29 | *
|
---|
| 30 | * @author frsantos
|
---|
| 31 | */
|
---|
[6581] | 32 | public abstract class CrossingWays extends Test {
|
---|
[6869] | 33 |
|
---|
[11913] | 34 | static final String HIGHWAY = "highway";
|
---|
| 35 | static final String RAILWAY = "railway";
|
---|
| 36 | static final String WATERWAY = "waterway";
|
---|
[3669] | 37 |
|
---|
| 38 | /** All way segments, grouped by cells */
|
---|
[10250] | 39 | private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000);
|
---|
[3669] | 40 | /** The already detected ways in error */
|
---|
[10250] | 41 | private final Map<List<Way>, List<WaySegment>> seenWays = new HashMap<>(50);
|
---|
[3669] | 42 |
|
---|
[12958] | 43 | private final int code;
|
---|
| 44 |
|
---|
[6691] | 45 | /**
|
---|
| 46 | * General crossing ways test.
|
---|
| 47 | */
|
---|
[6581] | 48 | public static class Ways extends CrossingWays {
|
---|
| 49 |
|
---|
[12958] | 50 | protected static final int CROSSING_WAYS = 601;
|
---|
| 51 |
|
---|
[6691] | 52 | /**
|
---|
| 53 | * Constructs a new crossing {@code Ways} test.
|
---|
| 54 | */
|
---|
| 55 | public Ways() {
|
---|
[12958] | 56 | super(tr("Crossing ways"), CROSSING_WAYS);
|
---|
[6691] | 57 | }
|
---|
| 58 |
|
---|
[6581] | 59 | @Override
|
---|
| 60 | public boolean isPrimitiveUsable(OsmPrimitive w) {
|
---|
| 61 | return super.isPrimitiveUsable(w)
|
---|
| 62 | && !isProposedOrAbandoned(w)
|
---|
[11587] | 63 | && (isHighway(w)
|
---|
[6623] | 64 | || w.hasKey(WATERWAY)
|
---|
[11587] | 65 | || isRailway(w)
|
---|
[6581] | 66 | || isCoastline(w)
|
---|
| 67 | || isBuilding(w));
|
---|
| 68 | }
|
---|
| 69 |
|
---|
| 70 | @Override
|
---|
| 71 | boolean ignoreWaySegmentCombination(Way w1, Way w2) {
|
---|
[11136] | 72 | if (w1 == w2)
|
---|
| 73 | return false;
|
---|
[12986] | 74 | if (!Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))) {
|
---|
[6581] | 75 | return true;
|
---|
| 76 | }
|
---|
[7083] | 77 | if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) {
|
---|
[6581] | 78 | return true;
|
---|
| 79 | }
|
---|
[11155] | 80 | if (isSubwayOrTramOrRazed(w2)) {
|
---|
[6581] | 81 | return true;
|
---|
| 82 | }
|
---|
| 83 | if (isCoastline(w1) != isCoastline(w2)) {
|
---|
| 84 | return true;
|
---|
| 85 | }
|
---|
[10528] | 86 | if ((w1.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w2.hasTag(WATERWAY, "riverbank"))
|
---|
| 87 | || (w2.hasTag(WATERWAY, "river", "stream", "canal", "drain", "ditch") && w1.hasTag(WATERWAY, "riverbank"))) {
|
---|
[6581] | 88 | return true;
|
---|
| 89 | }
|
---|
[11893] | 90 | return isProposedOrAbandoned(w2);
|
---|
[6581] | 91 | }
|
---|
| 92 |
|
---|
| 93 | @Override
|
---|
| 94 | String createMessage(Way w1, Way w2) {
|
---|
| 95 | if (isBuilding(w1)) {
|
---|
[7245] | 96 | return tr("Crossing buildings");
|
---|
[6623] | 97 | } else if (w1.hasKey(WATERWAY) && w2.hasKey(WATERWAY)) {
|
---|
[6581] | 98 | return tr("Crossing waterways");
|
---|
[6623] | 99 | } else if ((w1.hasKey(HIGHWAY) && w2.hasKey(WATERWAY))
|
---|
| 100 | || (w2.hasKey(HIGHWAY) && w1.hasKey(WATERWAY))) {
|
---|
[6581] | 101 | return tr("Crossing waterway/highway");
|
---|
| 102 | } else {
|
---|
| 103 | return tr("Crossing ways");
|
---|
| 104 | }
|
---|
| 105 | }
|
---|
| 106 | }
|
---|
| 107 |
|
---|
[6691] | 108 | /**
|
---|
| 109 | * Crossing boundaries ways test.
|
---|
| 110 | */
|
---|
[6581] | 111 | public static class Boundaries extends CrossingWays {
|
---|
| 112 |
|
---|
[12958] | 113 | protected static final int CROSSING_BOUNDARIES = 602;
|
---|
| 114 |
|
---|
[6691] | 115 | /**
|
---|
| 116 | * Constructs a new crossing {@code Boundaries} test.
|
---|
| 117 | */
|
---|
| 118 | public Boundaries() {
|
---|
[12958] | 119 | super(tr("Crossing boundaries"), CROSSING_BOUNDARIES);
|
---|
[6691] | 120 | }
|
---|
| 121 |
|
---|
[6581] | 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) {
|
---|
[7083] | 130 | return !Objects.equals(w1.get("boundary"), w2.get("boundary"));
|
---|
[6581] | 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 |
|
---|
[6691] | 146 | /**
|
---|
| 147 | * Crossing barriers ways test.
|
---|
| 148 | */
|
---|
[6581] | 149 | public static class Barrier extends CrossingWays {
|
---|
| 150 |
|
---|
[12958] | 151 | protected static final int CROSSING_BARRIERS = 603;
|
---|
| 152 |
|
---|
[6691] | 153 | /**
|
---|
| 154 | * Constructs a new crossing {@code Barrier} test.
|
---|
| 155 | */
|
---|
| 156 | public Barrier() {
|
---|
[12958] | 157 | super(tr("Crossing barriers"), CROSSING_BARRIERS);
|
---|
[6691] | 158 | }
|
---|
| 159 |
|
---|
[6581] | 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) {
|
---|
[12986] | 167 | return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2));
|
---|
[6581] | 168 | }
|
---|
| 169 |
|
---|
| 170 | @Override
|
---|
| 171 | String createMessage(Way w1, Way w2) {
|
---|
| 172 | return tr("Crossing barriers");
|
---|
| 173 | }
|
---|
| 174 | }
|
---|
| 175 |
|
---|
[6691] | 176 | /**
|
---|
[11136] | 177 | * Self crossing ways test (for all the rest)
|
---|
| 178 | */
|
---|
| 179 | public static class SelfCrossing extends CrossingWays {
|
---|
[12958] | 180 |
|
---|
| 181 | protected static final int CROSSING_SELF = 604;
|
---|
| 182 |
|
---|
[11136] | 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() {
|
---|
[12958] | 191 | super(tr("Self crossing"), CROSSING_SELF);
|
---|
[11136] | 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) {
|
---|
[11191] | 202 | return w1 != w2; // should not happen
|
---|
[11136] | 203 | }
|
---|
| 204 |
|
---|
| 205 | @Override
|
---|
| 206 | String createMessage(Way w1, Way w2) {
|
---|
| 207 | return tr("Self-crossing ways");
|
---|
| 208 | }
|
---|
| 209 | }
|
---|
| 210 |
|
---|
| 211 | /**
|
---|
[6691] | 212 | * Constructs a new {@code CrossingWays} test.
|
---|
| 213 | * @param title The test title
|
---|
[12958] | 214 | * @param code The test code
|
---|
| 215 | * @since 12958
|
---|
[6691] | 216 | */
|
---|
[12958] | 217 | public CrossingWays(String title, int code) {
|
---|
[8510] | 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."));
|
---|
[12958] | 220 | this.code = code;
|
---|
[3669] | 221 | }
|
---|
| 222 |
|
---|
| 223 | @Override
|
---|
[3671] | 224 | public void startTest(ProgressMonitor monitor) {
|
---|
[3669] | 225 | super.startTest(monitor);
|
---|
[10250] | 226 | cellSegments.clear();
|
---|
| 227 | seenWays.clear();
|
---|
[3669] | 228 | }
|
---|
| 229 |
|
---|
| 230 | @Override
|
---|
[3671] | 231 | public void endTest() {
|
---|
[3669] | 232 | super.endTest();
|
---|
[10250] | 233 | cellSegments.clear();
|
---|
| 234 | seenWays.clear();
|
---|
[3669] | 235 | }
|
---|
| 236 |
|
---|
[6580] | 237 | static boolean isCoastline(OsmPrimitive w) {
|
---|
| 238 | return w.hasTag("natural", "water", "coastline") || w.hasTag("landuse", "reservoir");
|
---|
| 239 | }
|
---|
| 240 |
|
---|
[11587] | 241 | static boolean isHighway(OsmPrimitive w) {
|
---|
[11608] | 242 | return w.hasTagDifferent(HIGHWAY, "rest_area", "services");
|
---|
[11587] | 243 | }
|
---|
| 244 |
|
---|
| 245 | static boolean isRailway(OsmPrimitive w) {
|
---|
| 246 | return w.hasKey(RAILWAY) && !isSubwayOrTramOrRazed(w);
|
---|
| 247 | }
|
---|
| 248 |
|
---|
[11155] | 249 | static boolean isSubwayOrTramOrRazed(OsmPrimitive w) {
|
---|
| 250 | return w.hasTag(RAILWAY, "subway", "tram", "razed");
|
---|
[6580] | 251 | }
|
---|
| 252 |
|
---|
| 253 | static boolean isProposedOrAbandoned(OsmPrimitive w) {
|
---|
[6623] | 254 | return w.hasTag(HIGHWAY, "proposed") || w.hasTag(RAILWAY, "proposed", "abandoned");
|
---|
[6580] | 255 | }
|
---|
| 256 |
|
---|
[6581] | 257 | abstract boolean ignoreWaySegmentCombination(Way w1, Way w2);
|
---|
[6580] | 258 |
|
---|
[6581] | 259 | abstract String createMessage(Way w1, Way w2);
|
---|
[6580] | 260 |
|
---|
[3669] | 261 | @Override
|
---|
[6580] | 262 | public void visit(Way w) {
|
---|
[11136] | 263 | if (this instanceof SelfCrossing) {
|
---|
| 264 | // free memory, we are not interested in previous ways
|
---|
| 265 | cellSegments.clear();
|
---|
| 266 | seenWays.clear();
|
---|
| 267 | }
|
---|
[3669] | 268 |
|
---|
| 269 | int nodesSize = w.getNodesCount();
|
---|
| 270 | for (int i = 0; i < nodesSize - 1; i++) {
|
---|
[6580] | 271 | final WaySegment es1 = new WaySegment(w, i);
|
---|
[6869] | 272 | final EastNorth en1 = es1.getFirstNode().getEastNorth();
|
---|
| 273 | final EastNorth en2 = es1.getSecondNode().getEastNorth();
|
---|
| 274 | if (en1 == null || en2 == null) {
|
---|
[12620] | 275 | Logging.warn("Crossing ways test skipped "+es1);
|
---|
[6869] | 276 | continue;
|
---|
| 277 | }
|
---|
[11136] | 278 | for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) {
|
---|
[6580] | 279 | for (WaySegment es2 : segments) {
|
---|
[3669] | 280 | List<Way> prims;
|
---|
| 281 | List<WaySegment> highlight;
|
---|
| 282 |
|
---|
[10803] | 283 | if (!es1.intersects(es2) || ignoreWaySegmentCombination(es1.way, es2.way)) {
|
---|
[3669] | 284 | continue;
|
---|
[4869] | 285 | }
|
---|
[3669] | 286 |
|
---|
[11136] | 287 | prims = new ArrayList<>();
|
---|
| 288 | prims.add(es1.way);
|
---|
| 289 | if (es1.way != es2.way)
|
---|
| 290 | prims.add(es2.way);
|
---|
[6240] | 291 | if ((highlight = seenWays.get(prims)) == null) {
|
---|
[7005] | 292 | highlight = new ArrayList<>();
|
---|
[6580] | 293 | highlight.add(es1);
|
---|
| 294 | highlight.add(es2);
|
---|
[3669] | 295 |
|
---|
[6580] | 296 | final String message = createMessage(es1.way, es2.way);
|
---|
[12958] | 297 | errors.add(TestError.builder(this, Severity.WARNING, code)
|
---|
[11129] | 298 | .message(message)
|
---|
| 299 | .primitives(prims)
|
---|
| 300 | .highlightWaySegments(highlight)
|
---|
| 301 | .build());
|
---|
[6240] | 302 | seenWays.put(prims, highlight);
|
---|
[3671] | 303 | } else {
|
---|
[6580] | 304 | highlight.add(es1);
|
---|
| 305 | highlight.add(es2);
|
---|
[3669] | 306 | }
|
---|
| 307 | }
|
---|
| 308 | segments.add(es1);
|
---|
| 309 | }
|
---|
| 310 | }
|
---|
| 311 | }
|
---|
| 312 |
|
---|
| 313 | /**
|
---|
[4869] | 314 | * Returns all the cells this segment crosses. Each cell contains the list
|
---|
| 315 | * of segments already processed
|
---|
[11136] | 316 | * @param cellSegments map with already collected way segments
|
---|
[6869] | 317 | * @param n1 The first EastNorth
|
---|
| 318 | * @param n2 The second EastNorth
|
---|
[4869] | 319 | * @return A list with all the cells the segment crosses
|
---|
| 320 | */
|
---|
[11136] | 321 | public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, EastNorth n1, EastNorth n2) {
|
---|
[7005] | 322 | List<List<WaySegment>> cells = new ArrayList<>();
|
---|
[11852] | 323 | for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail())) {
|
---|
[12865] | 324 | cells.add(cellSegments.computeIfAbsent(cell, k -> new ArrayList<>()));
|
---|
[3669] | 325 | }
|
---|
| 326 | return cells;
|
---|
| 327 | }
|
---|
| 328 | }
|
---|