source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java@ 14305

Last change on this file since 14305 was 14305, checked in by Don-vip, 6 years ago

fix #16810 - Disable 'Way end node near other highway' warnings for railway=platform_edge

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.data.validation.tests.CrossingWays.HIGHWAY;
5import static org.openstreetmap.josm.data.validation.tests.CrossingWays.RAILWAY;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.geom.Area;
9import java.awt.geom.Line2D;
10import java.awt.geom.Point2D;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20import org.openstreetmap.josm.data.coor.EastNorth;
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.osm.BBox;
23import org.openstreetmap.josm.data.osm.DataSet;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmDataManager;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.QuadBuckets;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
30import org.openstreetmap.josm.data.projection.Ellipsoid;
31import org.openstreetmap.josm.data.validation.Severity;
32import org.openstreetmap.josm.data.validation.Test;
33import org.openstreetmap.josm.data.validation.TestError;
34import org.openstreetmap.josm.gui.progress.ProgressMonitor;
35import org.openstreetmap.josm.spi.preferences.Config;
36import org.openstreetmap.josm.tools.Logging;
37
38/**
39 * Checks if a way has an endpoint very near to another way.
40 * <br>
41 * This class is abstract since highway/railway/waterway/… ways must be handled separately.
42 * An actual implementation must override {@link #isPrimitiveUsable(OsmPrimitive)}
43 * to denote which kind of primitives can be handled.
44 *
45 * @author frsantos
46 */
47public abstract class UnconnectedWays extends Test {
48
49 /**
50 * Unconnected highways test.
51 */
52 public static class UnconnectedHighways extends UnconnectedWays {
53
54 /**
55 * Constructs a new {@code UnconnectedHighways} test.
56 */
57 public UnconnectedHighways() {
58 super(tr("Unconnected highways"));
59 }
60
61 @Override
62 public boolean isPrimitiveUsable(OsmPrimitive p) {
63 return super.isPrimitiveUsable(p) && p.hasKey(HIGHWAY);
64 }
65 }
66
67 /**
68 * Unconnected railways test.
69 */
70 public static class UnconnectedRailways extends UnconnectedWays {
71
72 /**
73 * Constructs a new {@code UnconnectedRailways} test.
74 */
75 public UnconnectedRailways() {
76 super(tr("Unconnected railways"));
77 }
78
79 @Override
80 public boolean isPrimitiveUsable(OsmPrimitive p) {
81 return super.isPrimitiveUsable(p) && p.hasKey("railway");
82 }
83 }
84
85 /**
86 * Unconnected waterways test.
87 */
88 public static class UnconnectedWaterways extends UnconnectedWays {
89
90 /**
91 * Constructs a new {@code UnconnectedWaterways} test.
92 */
93 public UnconnectedWaterways() {
94 super(tr("Unconnected waterways"));
95 }
96
97 @Override
98 public boolean isPrimitiveUsable(OsmPrimitive p) {
99 return super.isPrimitiveUsable(p) && p.hasKey("waterway");
100 }
101 }
102
103 /**
104 * Unconnected natural/landuse test.
105 */
106 public static class UnconnectedNaturalOrLanduse extends UnconnectedWays {
107
108 /**
109 * Constructs a new {@code UnconnectedNaturalOrLanduse} test.
110 */
111 public UnconnectedNaturalOrLanduse() {
112 super(tr("Unconnected natural lands and landuses"));
113 }
114
115 @Override
116 public boolean isPrimitiveUsable(OsmPrimitive p) {
117 return super.isPrimitiveUsable(p) && p.hasKey("natural", "landuse");
118 }
119 }
120
121 /**
122 * Unconnected power ways test.
123 */
124 public static class UnconnectedPower extends UnconnectedWays {
125
126 /**
127 * Constructs a new {@code UnconnectedPower} test.
128 */
129 public UnconnectedPower() {
130 super(tr("Unconnected power ways"));
131 }
132
133 @Override
134 public boolean isPrimitiveUsable(OsmPrimitive p) {
135 return super.isPrimitiveUsable(p) && p.hasTag("power", "line", "minor_line", "cable");
136 }
137 }
138
139 protected static final int UNCONNECTED_WAYS = 1301;
140 protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + UnconnectedWays.class.getSimpleName();
141
142 private Set<MyWaySegment> ways;
143 private QuadBuckets<Node> endnodes; // nodes at end of way
144 private QuadBuckets<Node> endnodesHighway; // nodes at end of way
145 private QuadBuckets<Node> middlenodes; // nodes in middle of way
146 private Set<Node> othernodes; // nodes appearing at least twice
147 private Area dsArea;
148
149 private double mindist;
150 private double minmiddledist;
151
152 /**
153 * Constructs a new {@code UnconnectedWays} test.
154 * @param title The test title
155 * @since 6691
156 */
157 public UnconnectedWays(String title) {
158 super(title, tr("This test checks if a way has an endpoint very near to another way."));
159 }
160
161 @Override
162 public void startTest(ProgressMonitor monitor) {
163 super.startTest(monitor);
164 ways = new HashSet<>();
165 endnodes = new QuadBuckets<>();
166 endnodesHighway = new QuadBuckets<>();
167 middlenodes = new QuadBuckets<>();
168 othernodes = new HashSet<>();
169 mindist = Config.getPref().getDouble(PREFIX + ".node_way_distance", 10.0);
170 minmiddledist = Config.getPref().getDouble(PREFIX + ".way_way_distance", 0.0);
171 DataSet dataSet = OsmDataManager.getInstance().getEditDataSet();
172 dsArea = dataSet == null ? null : dataSet.getDataSourceArea();
173 }
174
175 protected Map<Node, Way> getWayEndNodesNearOtherHighway() {
176 Map<Node, Way> map = new HashMap<>();
177 for (int iter = 0; iter < 1; iter++) {
178 for (MyWaySegment s : ways) {
179 if (isCanceled()) {
180 map.clear();
181 return map;
182 }
183 if (s.highway) {
184 for (Node en : s.nearbyNodes(mindist)) {
185 if (en == null || !endnodesHighway.contains(en)) {
186 continue;
187 }
188 if (en.hasTag(HIGHWAY, "turning_circle", "bus_stop")
189 || en.hasTag("amenity", "parking_entrance")
190 || en.hasTag(RAILWAY, "buffer_stop")
191 || en.isKeyTrue("noexit")
192 || en.hasKey("entrance", "barrier")) {
193 continue;
194 }
195 // to handle intersections of 't' shapes and similar
196 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
197 continue;
198 }
199 map.put(en, s.w);
200 }
201 }
202 }
203 }
204 return map;
205 }
206
207 protected Map<Node, Way> getWayEndNodesNearOtherWay() {
208 Map<Node, Way> map = new HashMap<>();
209 for (MyWaySegment s : ways) {
210 if (isCanceled()) {
211 map.clear();
212 return map;
213 }
214 for (Node en : s.nearbyNodes(mindist)) {
215 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
216 continue;
217 }
218 if (!s.highway && endnodesHighway.contains(en) && !s.w.concernsArea()) {
219 map.put(en, s.w);
220 } else if (endnodes.contains(en) && !s.w.concernsArea()) {
221 map.put(en, s.w);
222 }
223 }
224 }
225 return map;
226 }
227
228 protected Map<Node, Way> getWayNodesNearOtherWay() {
229 Map<Node, Way> map = new HashMap<>();
230 for (MyWaySegment s : ways) {
231 if (isCanceled()) {
232 map.clear();
233 return map;
234 }
235 for (Node en : s.nearbyNodes(minmiddledist)) {
236 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
237 continue;
238 }
239 if (!middlenodes.contains(en)) {
240 continue;
241 }
242 map.put(en, s.w);
243 }
244 }
245 return map;
246 }
247
248 protected Map<Node, Way> getConnectedWayEndNodesNearOtherWay() {
249 Map<Node, Way> map = new HashMap<>();
250 for (MyWaySegment s : ways) {
251 if (isCanceled()) {
252 map.clear();
253 return map;
254 }
255 for (Node en : s.nearbyNodes(minmiddledist)) {
256 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
257 continue;
258 }
259 if (!othernodes.contains(en)) {
260 continue;
261 }
262 map.put(en, s.w);
263 }
264 }
265 return map;
266 }
267
268 protected final void addErrors(Severity severity, Map<Node, Way> errorMap, String message) {
269 for (Map.Entry<Node, Way> error : errorMap.entrySet()) {
270 errors.add(TestError.builder(this, severity, UNCONNECTED_WAYS)
271 .message(message)
272 .primitives(error.getKey(), error.getValue())
273 .highlight(error.getKey())
274 .build());
275 }
276 }
277
278 @Override
279 public void endTest() {
280 addErrors(Severity.WARNING, getWayEndNodesNearOtherHighway(), tr("Way end node near other highway"));
281 addErrors(Severity.WARNING, getWayEndNodesNearOtherWay(), tr("Way end node near other way"));
282 /* the following two use a shorter distance */
283 if (minmiddledist > 0.0) {
284 addErrors(Severity.OTHER, getWayNodesNearOtherWay(), tr("Way node near other way"));
285 addErrors(Severity.OTHER, getConnectedWayEndNodesNearOtherWay(), tr("Connected way end node near other way"));
286 }
287 ways = null;
288 endnodes = null;
289 endnodesHighway = null;
290 middlenodes = null;
291 othernodes = null;
292 dsArea = null;
293 super.endTest();
294 }
295
296 private class MyWaySegment {
297 private final Line2D line;
298 public final Way w;
299 public final boolean isAbandoned;
300 public final boolean isBoundary;
301 public final boolean highway;
302 private final double len;
303 private Set<Node> nearbyNodeCache;
304 private double nearbyNodeCacheDist = -1.0;
305 private final Node n1;
306 private final Node n2;
307
308 MyWaySegment(Way w, Node n1, Node n2) {
309 this.w = w;
310 String railway = w.get(RAILWAY);
311 String highway = w.get(HIGHWAY);
312 this.isAbandoned = "abandoned".equals(railway) || w.isKeyTrue("disused");
313 this.highway = (highway != null || railway != null) && !isAbandoned;
314 this.isBoundary = !this.highway && w.hasTag("boundary", "administrative");
315 line = new Line2D.Double(n1.getEastNorth().east(), n1.getEastNorth().north(),
316 n2.getEastNorth().east(), n2.getEastNorth().north());
317 len = line.getP1().distance(line.getP2());
318 this.n1 = n1;
319 this.n2 = n2;
320 }
321
322 public boolean nearby(Node n, double dist) {
323 if (w == null) {
324 Logging.debug("way null");
325 return false;
326 }
327 if (w.containsNode(n))
328 return false;
329 if (n.isKeyTrue("noexit"))
330 return false;
331 EastNorth coord = n.getEastNorth();
332 if (coord == null)
333 return false;
334 Point2D p = new Point2D.Double(coord.east(), coord.north());
335 if (line.getP1().distance(p) > len+dist)
336 return false;
337 if (line.getP2().distance(p) > len+dist)
338 return false;
339 return line.ptSegDist(p) < dist;
340 }
341
342 public List<LatLon> getBounds(double fudge) {
343 double x1 = n1.getCoor().lon();
344 double x2 = n2.getCoor().lon();
345 if (x1 > x2) {
346 double tmpx = x1;
347 x1 = x2;
348 x2 = tmpx;
349 }
350 double y1 = n1.getCoor().lat();
351 double y2 = n2.getCoor().lat();
352 if (y1 > y2) {
353 double tmpy = y1;
354 y1 = y2;
355 y2 = tmpy;
356 }
357 LatLon topLeft = new LatLon(y2+fudge, x1-fudge);
358 LatLon botRight = new LatLon(y1-fudge, x2+fudge);
359 List<LatLon> ret = new ArrayList<>(2);
360 ret.add(topLeft);
361 ret.add(botRight);
362 return ret;
363 }
364
365 public Collection<Node> nearbyNodes(double dist) {
366 // If you're looking for nodes that are farther away that we looked for last time,
367 // the cached result is no good
368 if (dist > nearbyNodeCacheDist) {
369 nearbyNodeCache = null;
370 }
371 if (nearbyNodeCache != null) {
372 // If we've cached an area greater than the
373 // one now being asked for...
374 if (nearbyNodeCacheDist > dist) {
375 // Used the cached result and trim out
376 // the nodes that are not in the smaller
377 // area, but keep the old larger cache.
378 Set<Node> trimmed = new HashSet<>(nearbyNodeCache);
379 Set<Node> initial = new HashSet<>(nearbyNodeCache);
380 for (Node n : initial) {
381 if (!nearby(n, dist)) {
382 trimmed.remove(n);
383 }
384 }
385 return trimmed;
386 }
387 return nearbyNodeCache;
388 }
389 /*
390 * We know that any point near the line must be at
391 * least as close as the other end of the line, plus
392 * a little fudge for the distance away ('dist').
393 */
394
395 // This needs to be a hash set because the searches
396 // overlap a bit and can return duplicate nodes.
397 nearbyNodeCache = null;
398 List<LatLon> bounds = this.getBounds(dist * (360.0d / (Ellipsoid.WGS84.a * 2 * Math.PI)));
399 List<Node> foundNodes = endnodesHighway.search(new BBox(bounds.get(0), bounds.get(1)));
400 foundNodes.addAll(endnodes.search(new BBox(bounds.get(0), bounds.get(1))));
401
402 for (Node n : foundNodes) {
403 if (!nearby(n, dist) || !n.getCoor().isIn(dsArea)) {
404 continue;
405 }
406 // It is actually very rare for us to find a node
407 // so defer as much of the work as possible, like
408 // allocating the hash set
409 if (nearbyNodeCache == null) {
410 nearbyNodeCache = new HashSet<>();
411 }
412 nearbyNodeCache.add(n);
413 }
414 nearbyNodeCacheDist = dist;
415 if (nearbyNodeCache == null) {
416 nearbyNodeCache = Collections.emptySet();
417 }
418 return nearbyNodeCache;
419 }
420 }
421
422 List<MyWaySegment> getWaySegments(Way w) {
423 List<MyWaySegment> ret = new ArrayList<>();
424 if (!w.isUsable()
425 || w.hasKey("barrier")
426 || w.hasTag("natural", "cliff"))
427 return ret;
428
429 int size = w.getNodesCount();
430 if (size < 2)
431 return ret;
432 for (int i = 1; i < size; ++i) {
433 if (i < size-1) {
434 addNode(w.getNode(i), middlenodes);
435 }
436 Node a = w.getNode(i-1);
437 Node b = w.getNode(i);
438 if (a.isDrawable() && b.isDrawable()) {
439 MyWaySegment ws = new MyWaySegment(w, a, b);
440 if (ws.isBoundary || ws.isAbandoned) {
441 continue;
442 }
443 ret.add(ws);
444 }
445 }
446 return ret;
447 }
448
449 @Override
450 public void visit(Way w) {
451 // do not consider empty ways
452 if (w.getNodesCount() > 0
453 // ignore addr:interpolation ways as they are not physical features and most of
454 // the time very near the associated highway, which is perfectly normal, see #9332
455 && !w.hasKey("addr:interpolation")
456 // similarly for public transport platforms, tree rows
457 && !w.hasTag(HIGHWAY, "platform") && !w.hasTag(RAILWAY, "platform", "platform_edge") && !w.hasTag("natural", "tree_row")
458 ) {
459 ways.addAll(getWaySegments(w));
460 QuadBuckets<Node> set = endnodes;
461 if (w.hasKey(HIGHWAY, RAILWAY)) {
462 set = endnodesHighway;
463 }
464 addNode(w.firstNode(), set);
465 addNode(w.lastNode(), set);
466 }
467 }
468
469 private void addNode(Node n, QuadBuckets<Node> s) {
470 boolean m = middlenodes.contains(n);
471 boolean e = endnodes.contains(n);
472 boolean eh = endnodesHighway.contains(n);
473 boolean o = othernodes.contains(n);
474 if (!m && !e && !o && !eh) {
475 s.add(n);
476 } else if (!o) {
477 othernodes.add(n);
478 if (e) {
479 endnodes.remove(n);
480 } else if (eh) {
481 endnodesHighway.remove(n);
482 } else {
483 middlenodes.remove(n);
484 }
485 }
486 }
487}
Note: See TracBrowser for help on using the repository browser.