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

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

see #15182 - introduce Main.getEditDataSet to avoid unneeded GUI dependence in validator tests and tagging presets

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