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

Last change on this file since 4234 was 4073, checked in by jttt, 13 years ago

Fix #6161 Validating a file

  • Property svn:eol-style set to native
File size: 14.1 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.awt.geom.Line2D;
8import java.awt.geom.Point2D;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import org.openstreetmap.josm.Main;
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.OsmUtils;
26import org.openstreetmap.josm.data.osm.QuadBuckets;
27import org.openstreetmap.josm.data.osm.Way;
28import org.openstreetmap.josm.data.validation.Severity;
29import org.openstreetmap.josm.data.validation.Test;
30import org.openstreetmap.josm.data.validation.TestError;
31import org.openstreetmap.josm.gui.preferences.ValidatorPreference;
32import org.openstreetmap.josm.gui.progress.ProgressMonitor;
33
34/**
35 * Tests if there are segments that crosses in the same layer
36 *
37 * @author frsantos
38 */
39public class UnconnectedWays extends Test {
40
41 protected static int UNCONNECTED_WAYS = 1301;
42 protected static final String PREFIX = ValidatorPreference.PREFIX + "." + UnconnectedWays.class.getSimpleName();
43
44 Set<MyWaySegment> ways;
45 QuadBuckets<Node> endnodes; // nodes at end of way
46 QuadBuckets<Node> endnodes_highway; // nodes at end of way
47 QuadBuckets<Node> middlenodes; // nodes in middle of way
48 Set<Node> othernodes; // nodes appearing at least twice
49 //NodeSearchCache nodecache;
50 QuadBuckets<Node> nodecache;
51 Area ds_area;
52 DataSet ds;
53
54 double mindist;
55 double minmiddledist;
56
57 /**
58 * Constructor
59 */
60 public UnconnectedWays() {
61 super(tr("Unconnected ways."),
62 tr("This test checks if a way has an endpoint very near to another way."));
63 }
64
65 @Override
66 public void startTest(ProgressMonitor monitor) {
67 super.startTest(monitor);
68 ways = new HashSet<MyWaySegment>();
69 endnodes = new QuadBuckets<Node>();
70 endnodes_highway = new QuadBuckets<Node>();
71 middlenodes = new QuadBuckets<Node>();
72 othernodes = new HashSet<Node>();
73 mindist = Main.pref.getDouble(PREFIX + ".node_way_distance", 10.0);
74 minmiddledist = Main.pref.getDouble(PREFIX + ".way_way_distance", 0.0);
75 this.ds = Main.main.getCurrentDataSet();
76 this.ds_area = ds.getDataSourceArea();
77 }
78
79 @Override
80 public void endTest() {
81 Map<Node, Way> map = new HashMap<Node, Way>();
82 for (int iter = 0; iter < 1; iter++) {
83 Collection<MyWaySegment> tmp_ways = ways;
84 for (MyWaySegment s : tmp_ways) {
85 Collection<Node> nearbyNodes = s.nearbyNodes(mindist);
86 for (Node en : nearbyNodes) {
87 if (en == null || !s.highway || !endnodes_highway.contains(en)) {
88 continue;
89 }
90 if ("turning_circle".equals(en.get("highway"))
91 || "bus_stop".equals(en.get("highway"))
92 || "buffer_stop".equals(en.get("railway"))
93 || OsmUtils.isTrue(en.get("noexit"))
94 || en.hasKey("barrier")) {
95 continue;
96 }
97 // There's a small false-positive here. Imagine an intersection
98 // like a 't'. If the top part of the 't' is short enough, it
99 // will trigger the node at the very top of the 't' to be unconnected
100 // to the way that "crosses" the 't'. We should probably check that
101 // the ways to which 'en' belongs are not connected to 's.w'.
102 map.put(en, s.w);
103 }
104 if(isCancelled())
105 return;
106 }
107 }
108 for (Map.Entry<Node, Way> error : map.entrySet()) {
109 errors.add(new TestError(this, Severity.WARNING,
110 tr("Way end node near other highway"),
111 UNCONNECTED_WAYS,
112 Arrays.asList(error.getKey(), error.getValue())));
113 }
114 map.clear();
115 for (MyWaySegment s : ways) {
116 if(isCancelled())
117 return;
118 for (Node en : s.nearbyNodes(mindist)) {
119 if (endnodes_highway.contains(en) && !s.highway && !s.isArea()) {
120 map.put(en, s.w);
121 } else if (endnodes.contains(en) && !s.isArea()) {
122 map.put(en, s.w);
123 }
124 }
125 }
126 for (Map.Entry<Node, Way> error : map.entrySet()) {
127 errors.add(new TestError(this, Severity.WARNING,
128 tr("Way end node near other way"),
129 UNCONNECTED_WAYS,
130 Arrays.asList(error.getKey(), error.getValue())));
131 }
132 /* the following two use a shorter distance */
133 if (minmiddledist > 0.0) {
134 map.clear();
135 for (MyWaySegment s : ways) {
136 if(isCancelled())
137 return;
138 for (Node en : s.nearbyNodes(minmiddledist)) {
139 if (!middlenodes.contains(en)) {
140 continue;
141 }
142 map.put(en, s.w);
143 }
144 }
145 //System.out.println("p3 elapsed: " + (System.currentTimeMillis()-last));
146 //last = System.currentTimeMillis();
147 for (Map.Entry<Node, Way> error : map.entrySet()) {
148 errors.add(new TestError(this, Severity.OTHER,
149 tr("Way node near other way"),
150 UNCONNECTED_WAYS,
151 Arrays.asList(error.getKey(), error.getValue())));
152 }
153 map.clear();
154 for (MyWaySegment s : ways) {
155 for (Node en : s.nearbyNodes(minmiddledist)) {
156 if(isCancelled())
157 return;
158 if (!othernodes.contains(en)) {
159 continue;
160 }
161 map.put(en, s.w);
162 }
163 }
164 //System.out.println("p4 elapsed: " + (System.currentTimeMillis()-last));
165 //last = System.currentTimeMillis();
166 for (Map.Entry<Node, Way> error : map.entrySet()) {
167 errors.add(new TestError(this, Severity.OTHER,
168 tr("Connected way end node near other way"),
169 UNCONNECTED_WAYS,
170 Arrays.asList(error.getKey(), error.getValue())));
171 }
172 }
173 ways = null;
174 endnodes = null;
175 super.endTest();
176 //System.out.println("p99 elapsed: " + (System.currentTimeMillis()-last));
177 //last = System.currentTimeMillis();
178 }
179
180 private class MyWaySegment {
181 private final Line2D line;
182 public final Way w;
183 public final boolean isAbandoned;
184 public final boolean isBoundary;
185 public final boolean highway;
186 private final double len;
187 private Set<Node> nearbyNodeCache;
188 double nearbyNodeCacheDist = -1.0;
189 final Node n1;
190 final Node n2;
191
192 public MyWaySegment(Way w, Node n1, Node n2) {
193 this.w = w;
194 String railway = w.get("railway");
195 String highway = w.get("highway");
196 this.isAbandoned = "abandoned".equals(railway) || OsmUtils.isTrue(w.get("disused"));
197 this.highway = (highway != null || railway != null) && !isAbandoned;
198 this.isBoundary = !this.highway && "administrative".equals(w.get("boundary"));
199 line = new Line2D.Double(n1.getEastNorth().east(), n1.getEastNorth().north(),
200 n2.getEastNorth().east(), n2.getEastNorth().north());
201 len = line.getP1().distance(line.getP2());
202 this.n1 = n1;
203 this.n2 = n2;
204 }
205
206 public boolean nearby(Node n, double dist) {
207 if (w == null) {
208 Main.debug("way null");
209 return false;
210 }
211 if (w.containsNode(n))
212 return false;
213 if (OsmUtils.isTrue(n.get("noexit")))
214 return false;
215 EastNorth coord = n.getEastNorth();
216 if (coord == null)
217 return false;
218 Point2D p = new Point2D.Double(coord.east(), coord.north());
219 if (line.getP1().distance(p) > len+dist)
220 return false;
221 if (line.getP2().distance(p) > len+dist)
222 return false;
223 return line.ptSegDist(p) < dist;
224 }
225
226 public List<LatLon> getBounds(double fudge) {
227 double x1 = n1.getCoor().lon();
228 double x2 = n2.getCoor().lon();
229 if (x1 > x2) {
230 double tmpx = x1;
231 x1 = x2;
232 x2 = tmpx;
233 }
234 double y1 = n1.getCoor().lat();
235 double y2 = n2.getCoor().lat();
236 if (y1 > y2) {
237 double tmpy = y1;
238 y1 = y2;
239 y2 = tmpy;
240 }
241 LatLon topLeft = new LatLon(y2+fudge, x1-fudge);
242 LatLon botRight = new LatLon(y1-fudge, x2+fudge);
243 List<LatLon> ret = new ArrayList<LatLon>();
244 ret.add(topLeft);
245 ret.add(botRight);
246 return ret;
247 }
248
249 public Collection<Node> nearbyNodes(double dist) {
250 // If you're looking for nodes that are farther
251 // away that we looked for last time, the cached
252 // result is no good
253 if (dist > nearbyNodeCacheDist) {
254 //if (nearbyNodeCacheDist != -1)
255 // System.out.println("destroyed MyWaySegment nearby node cache:" + dist + " > " + nearbyNodeCacheDist);
256 nearbyNodeCache = null;
257 }
258 if (nearbyNodeCache != null) {
259 // If we've cached an aread greater than the
260 // one now being asked for...
261 if (nearbyNodeCacheDist > dist) {
262 //System.out.println("had to trim MyWaySegment nearby node cache.");
263 // Used the cached result and trim out
264 // the nodes that are not in the smaller
265 // area, but keep the old larger cache.
266 Set<Node> trimmed = new HashSet<Node>(nearbyNodeCache);
267 Set<Node> initial = new HashSet<Node>(nearbyNodeCache);
268 for (Node n : initial) {
269 if (!nearby(n, dist)) {
270 trimmed.remove(n);
271 }
272 }
273 return trimmed;
274 }
275 return nearbyNodeCache;
276 }
277 /*
278 * We know that any point near the line must be at
279 * least as close as the other end of the line, plus
280 * a little fudge for the distance away ('dist').
281 */
282
283 // This needs to be a hash set because the searches
284 // overlap a bit and can return duplicate nodes.
285 nearbyNodeCache = null;
286 List<LatLon> bounds = this.getBounds(dist);
287 List<Node> found_nodes = endnodes_highway.search(new BBox(bounds.get(0), bounds.get(1)));
288 found_nodes.addAll(endnodes.search(new BBox(bounds.get(0), bounds.get(1))));
289
290 for (Node n : found_nodes) {
291 if (!nearby(n, dist) ||
292 (ds_area != null && !ds_area.contains(n.getCoor()))) {
293 continue;
294 }
295 // It is actually very rare for us to find a node
296 // so defer as much of the work as possible, like
297 // allocating the hash set
298 if (nearbyNodeCache == null) {
299 nearbyNodeCache = new HashSet<Node>();
300 }
301 nearbyNodeCache.add(n);
302 }
303 nearbyNodeCacheDist = dist;
304 if (nearbyNodeCache == null) {
305 nearbyNodeCache = Collections.emptySet();
306 }
307 return nearbyNodeCache;
308 }
309
310 public boolean isArea() {
311 return w.hasKey("landuse")
312 || w.hasKey("leisure")
313 || w.hasKey("amenity")
314 || w.hasKey("building");
315 }
316 }
317
318 List<MyWaySegment> getWaySegments(Way w) {
319 List<MyWaySegment> ret = new ArrayList<MyWaySegment>();
320 if (!w.isUsable()
321 || w.hasKey("barrier")
322 || "cliff".equals(w.get("natural")))
323 return ret;
324
325 int size = w.getNodesCount();
326 if (size < 2)
327 return ret;
328 for (int i = 1; i < size; ++i) {
329 if(i < size-1) {
330 addNode(w.getNode(i), middlenodes);
331 }
332 MyWaySegment ws = new MyWaySegment(w, w.getNode(i-1), w.getNode(i));
333 if (ws.isBoundary || ws.isAbandoned) {
334 continue;
335 }
336 ret.add(ws);
337 }
338 return ret;
339 }
340
341 @Override
342 public void visit(Way w) {
343 if (w.getNodesCount() > 0) {
344 ways.addAll(getWaySegments(w));
345 QuadBuckets<Node> set = endnodes;
346 if (w.hasKey("highway") || w.hasKey("railway")) {
347 set = endnodes_highway;
348 }
349 addNode(w.firstNode(), set);
350 addNode(w.lastNode(), set);
351 }
352 }
353
354 @Override
355 public void visit(Node n) {
356 }
357
358 private void addNode(Node n, QuadBuckets<Node> s) {
359 boolean m = middlenodes.contains(n);
360 boolean e = endnodes.contains(n);
361 boolean eh = endnodes_highway.contains(n);
362 boolean o = othernodes.contains(n);
363 if (!m && !e && !o && !eh) {
364 s.add(n);
365 } else if (!o) {
366 othernodes.add(n);
367 if (e) {
368 endnodes.remove(n);
369 } else if (eh) {
370 endnodes_highway.remove(n);
371 } else {
372 middlenodes.remove(n);
373 }
374 }
375 }
376}
Note: See TracBrowser for help on using the repository browser.