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

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

sonar - squid:S1192 - String literals should not be duplicated

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