source: josm/trunk/src/org/openstreetmap/josm/data/osm/QuadBuckets.java@ 6171

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

fix #8986 - Data loading/bbox index counting optimization (patch by shinigami) + code cleanup in Quad* classes

  • Property svn:eol-style set to native
File size: 18.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.lang.reflect.Array;
5import java.util.ArrayList;
6import java.util.Arrays;
7import java.util.Collection;
8import java.util.Iterator;
9import java.util.List;
10
11import org.openstreetmap.josm.Main;
12import org.openstreetmap.josm.data.coor.LatLon;
13import org.openstreetmap.josm.data.coor.QuadTiling;
14
15/**
16 * Note: bbox of primitives added to QuadBuckets has to stay the same. In case of coordinate change, primitive must
17 * be removed and readded.
18 *
19 * This class is (no longer) thread safe.
20 *
21 */
22public class QuadBuckets<T extends OsmPrimitive> implements Collection<T>
23{
24 private static final boolean consistency_testing = false;
25 private static final int NW_INDEX = 1;
26 private static final int NE_INDEX = 3;
27 private static final int SE_INDEX = 2;
28 private static final int SW_INDEX = 0;
29
30 static void abort(String s) {
31 throw new AssertionError(s);
32 }
33
34 public static final int MAX_OBJECTS_PER_LEVEL = 16;
35
36 class QBLevel
37 {
38 final int level;
39 private final BBox bbox;
40 final long quad;
41 final QBLevel parent;
42 private boolean isLeaf = true;
43
44 public List<T> content;
45 public QBLevel nw, ne, sw, se;
46
47 private QBLevel getChild(int index) {
48 switch (index) {
49 case NE_INDEX:
50 if (ne == null) {
51 ne = new QBLevel(this, index);
52 }
53 return ne;
54 case NW_INDEX:
55 if (nw == null) {
56 nw = new QBLevel(this, index);
57 }
58 return nw;
59 case SE_INDEX:
60 if (se == null) {
61 se = new QBLevel(this, index);
62 }
63 return se;
64 case SW_INDEX:
65 if (sw == null) {
66 sw = new QBLevel(this, index);
67 }
68 return sw;
69 default:
70 return null;
71 }
72 }
73
74 private QBLevel[] getChildren() {
75 // This is ugly and hackish. But, it seems to work,
76 // and using an ArrayList here seems to cost us
77 // a significant performance penalty -- 50% in my
78 // testing. Child access is one of the single
79 // hottest code paths in this entire class.
80 @SuppressWarnings("unchecked")
81 QBLevel[] result = (QBLevel[]) Array.newInstance(this.getClass(), QuadTiling.TILES_PER_LEVEL);
82 result[NW_INDEX] = nw;
83 result[NE_INDEX] = ne;
84 result[SW_INDEX] = sw;
85 result[SE_INDEX] = se;
86 return result;
87 }
88
89 @Override
90 public String toString() {
91 return super.toString()+ "["+level+"]: " + bbox();
92 }
93
94 /**
95 * Constructor for root node
96 */
97 public QBLevel() {
98 level = 0;
99 quad = 0;
100 parent = null;
101 bbox = new BBox(-180, 90, 180, -90);
102 }
103
104 public QBLevel(QBLevel parent, int parent_index) {
105 this.parent = parent;
106 this.level = parent.level + 1;
107 int shift = (QuadTiling.NR_LEVELS - level) * 2;
108 long mult = 1;
109 // Java blows the big one. It seems to wrap when you shift by > 31
110 if (shift >= 30) {
111 shift -= 30;
112 mult = 1<<30;
113 }
114 long this_quadpart = mult * (parent_index << shift);
115 this.quad = parent.quad | this_quadpart;
116 this.bbox = calculateBBox(); // calculateBBox reference quad
117 }
118
119 private BBox calculateBBox() {
120 LatLon bottom_left = this.coor();
121 double lat = bottom_left.lat() + parent.height() / 2;
122 double lon = bottom_left.lon() + parent.width() / 2;
123 LatLon top_right = new LatLon(lat, lon);
124 return new BBox(bottom_left, top_right);
125 }
126
127 QBLevel findBucket(BBox bbox) {
128 if (!hasChildren())
129 return this;
130 else {
131 int index = bbox.getIndex(level);
132 if (index == -1)
133 return this;
134 return getChild(index).findBucket(bbox);
135 }
136 }
137
138 boolean remove_content(T o) {
139 // If two threads try to remove item at the same time from different buckets of this QBLevel,
140 // it might happen that one thread removes bucket but don't remove parent because it still sees
141 // another bucket set. Second thread do the same. Due to thread memory caching, it's possible that
142 // changes made by threads will show up in children array too late, leading to QBLevel with all children
143 // set to null
144 if (content == null)
145 return false;
146 boolean ret = this.content.remove(o);
147 if (this.content.isEmpty()) {
148 this.content = null;
149 }
150 if (this.canRemove()) {
151 this.remove_from_parent();
152 }
153 return ret;
154 }
155
156 /*
157 * There is a race between this and qb.nextContentNode().
158 * If nextContentNode() runs into this bucket, it may
159 * attempt to null out 'children' because it thinks this
160 * is a dead end.
161 */
162 void __split() {
163 List<T> tmpcontent = content;
164 content = null;
165
166 for (T o: tmpcontent) {
167 int index = o.getBBox().getIndex(level);
168 if (index == -1) {
169 __add_content(o);
170 } else {
171 getChild(index).doAdd(o);
172 }
173 }
174 isLeaf = false; // It's not enough to check children because all items could end up in this level (index == -1)
175 }
176
177 boolean __add_content(T o) {
178 boolean ret = false;
179 // The split_lock will keep two concurrent calls from overwriting content
180 if (content == null) {
181 content = new ArrayList<T>();
182 }
183 ret = content.add(o);
184 return ret;
185 }
186
187 boolean matches(T o, BBox search_bbox) {
188 // This can be optimized when o is a node
189 return o.getBBox().intersects(search_bbox);
190 }
191
192 private void search_contents(BBox search_bbox, List<T> result) {
193 /*
194 * It is possible that this was created in a split
195 * but never got any content populated.
196 */
197 if (content == null)
198 return;
199
200 for (T o : content) {
201 if (matches(o, search_bbox)) {
202 result.add(o);
203 }
204 }
205 }
206
207 /*
208 * This is stupid. I tried to have a QBLeaf and QBBranch
209 * class decending from a QBLevel. It's more than twice
210 * as slow. So, this throws OO out the window, but it
211 * is fast. Runtime type determination must be slow.
212 */
213 boolean isLeaf() {
214 return isLeaf;
215 }
216
217 boolean hasChildren() {
218 return nw != null || ne != null || sw != null || se != null;
219 }
220
221 QBLevel next_sibling() {
222 boolean found_me = false;
223 if (parent == null)
224 return null;
225 for (QBLevel sibling : parent.getChildren()) {
226 if (sibling == null) {
227 continue;
228 }
229 // We're looking for the *next* child after us.
230 if (sibling == this) {
231 found_me = true;
232 continue;
233 }
234 if (found_me)
235 return sibling;
236 }
237 return null;
238 }
239
240 boolean hasContent() {
241 return content != null;
242 }
243
244 QBLevel nextSibling() {
245 QBLevel next = this;
246 QBLevel sibling = next.next_sibling();
247 // Walk back up the tree to find the
248 // next sibling node. It may be either
249 // a leaf or branch.
250 while (sibling == null) {
251 next = next.parent;
252 if (next == null) {
253 break;
254 }
255 sibling = next.next_sibling();
256 }
257 next = sibling;
258 return next;
259 }
260
261 QBLevel firstChild() {
262 QBLevel ret = null;
263 for (QBLevel child : getChildren()) {
264 if (child == null) {
265 continue;
266 }
267 ret = child;
268 break;
269 }
270 return ret;
271 }
272
273 QBLevel nextNode() {
274 if (!this.hasChildren())
275 return this.nextSibling();
276 return this.firstChild();
277 }
278
279 QBLevel nextContentNode() {
280 QBLevel next = this.nextNode();
281 if (next == null)
282 return next;
283 if (next.hasContent())
284 return next;
285 return next.nextContentNode();
286 }
287
288 void doAdd(T o) {
289 if (consistency_testing) {
290 if (!matches(o, this.bbox())) {
291 o.getBBox().getIndex(level);
292 o.getBBox().getIndex(level-1);
293 int nr = 0;
294 abort("\nobject " + o + " does not belong in node at level: " + level + " bbox: " + this.bbox());
295 }
296 }
297 __add_content(o);
298 if (isLeaf() && content.size() > MAX_OBJECTS_PER_LEVEL && level < QuadTiling.NR_LEVELS) {
299 __split();
300 }
301 }
302
303 void add(T o) {
304 findBucket(o.getBBox()).doAdd(o);
305 }
306
307 private void search(BBox search_bbox, List<T> result) {
308 if (!this.bbox().intersects(search_bbox))
309 return;
310 else if (bbox().bounds(search_bbox)) {
311 search_cache = this;
312 }
313
314 if (this.hasContent()) {
315 search_contents(search_bbox, result);
316 }
317
318 //TODO Coincidence vector should be calculated here and only buckets that match search_bbox should be checked
319
320 if (nw != null) {
321 nw.search(search_bbox, result);
322 }
323 if (ne != null) {
324 ne.search(search_bbox, result);
325 }
326 if (se != null) {
327 se.search(search_bbox, result);
328 }
329 if (sw != null) {
330 sw.search(search_bbox, result);
331 }
332 }
333
334 public String quads() {
335 return Long.toHexString(quad);
336 }
337
338 int index_of(QBLevel find_this) {
339 QBLevel[] children = getChildren();
340 for (int i = 0; i < QuadTiling.TILES_PER_LEVEL; i++) {
341 if (children[i] == find_this)
342 return i;
343 }
344 return -1;
345 }
346
347 double width() {
348 return bbox.width();
349 }
350
351 double height() {
352 return bbox.height();
353 }
354
355 public BBox bbox() {
356 return bbox;
357 }
358
359 /*
360 * This gives the coordinate of the bottom-left
361 * corner of the box
362 */
363 LatLon coor() {
364 return QuadTiling.tile2LatLon(this.quad);
365 }
366
367 void remove_from_parent() {
368 if (parent == null)
369 return;
370
371 if (!canRemove()) {
372 abort("attempt to remove non-empty child: " + this.content + " " + Arrays.toString(this.getChildren()));
373 }
374
375 if (parent.nw == this) {
376 parent.nw = null;
377 } else if (parent.ne == this) {
378 parent.ne = null;
379 } else if (parent.sw == this) {
380 parent.sw = null;
381 } else if (parent.se == this) {
382 parent.se = null;
383 }
384
385 if (parent.canRemove()) {
386 parent.remove_from_parent();
387 }
388 }
389
390 boolean canRemove() {
391 if (content != null && !content.isEmpty())
392 return false;
393 if (this.hasChildren())
394 return false;
395 return true;
396 }
397 }
398
399 private QBLevel root;
400 private QBLevel search_cache;
401 private int size;
402
403 /**
404 * Constructs a new {@code QuadBuckets}.
405 */
406 public QuadBuckets() {
407 clear();
408 }
409
410 @Override
411 public void clear() {
412 root = new QBLevel();
413 search_cache = null;
414 size = 0;
415 }
416
417 @Override
418 public boolean add(T n) {
419 root.add(n);
420 size++;
421 return true;
422 }
423
424 @Override
425 public boolean retainAll(Collection<?> objects) {
426 for (T o : this) {
427 if (objects.contains(o)) {
428 continue;
429 }
430 if (!this.remove(o))
431 return false;
432 }
433 return true;
434 }
435
436 @Override
437 public boolean removeAll(Collection<?> objects) {
438 boolean changed = false;
439 for (Object o : objects) {
440 changed = changed | remove(o);
441 }
442 return changed;
443 }
444
445 @Override
446 public boolean addAll(Collection<? extends T> objects) {
447 boolean changed = false;
448 for (T o : objects) {
449 changed = changed | this.add(o);
450 }
451 return changed;
452 }
453
454 @Override
455 public boolean containsAll(Collection<?> objects) {
456 for (Object o : objects) {
457 if (!this.contains(o))
458 return false;
459 }
460 return true;
461 }
462
463 @Override
464 public boolean remove(Object o) {
465 @SuppressWarnings("unchecked") T t = (T) o;
466 search_cache = null; // Search cache might point to one of removed buckets
467 QBLevel bucket = root.findBucket(t.getBBox());
468 if (bucket.remove_content(t)) {
469 size--;
470 return true;
471 } else
472 return false;
473 }
474
475 @Override
476 public boolean contains(Object o) {
477 @SuppressWarnings("unchecked") T t = (T) o;
478 QBLevel bucket = root.findBucket(t.getBBox());
479 return bucket != null && bucket.content != null && bucket.content.contains(t);
480 }
481
482 public ArrayList<T> toArrayList() {
483 ArrayList<T> a = new ArrayList<T>();
484 for (T n : this) {
485 a.add(n);
486 }
487 return a;
488 }
489
490 @Override
491 public Object[] toArray() {
492 return this.toArrayList().toArray();
493 }
494
495 @Override
496 public <A> A[] toArray(A[] template) {
497 return this.toArrayList().toArray(template);
498 }
499
500 class QuadBucketIterator implements Iterator<T>
501 {
502 QBLevel current_node;
503 int content_index;
504 int iterated_over;
505 QBLevel next_content_node(QBLevel q) {
506 if (q == null)
507 return null;
508 QBLevel orig = q;
509 QBLevel next;
510 next = q.nextContentNode();
511 //if (consistency_testing && (orig == next))
512 if (orig == next) {
513 abort("got same leaf back leaf: " + q.isLeaf());
514 }
515 return next;
516 }
517
518 public QuadBucketIterator(QuadBuckets<T> qb) {
519 if (!qb.root.hasChildren() || qb.root.hasContent()) {
520 current_node = qb.root;
521 } else {
522 current_node = next_content_node(qb.root);
523 }
524 iterated_over = 0;
525 }
526
527 @Override
528 public boolean hasNext() {
529 if (this.peek() == null)
530 return false;
531 return true;
532 }
533
534 T peek() {
535 if (current_node == null)
536 return null;
537 while((current_node.content == null) ||
538 (content_index >= current_node.content.size())) {
539 content_index = 0;
540 current_node = next_content_node(current_node);
541 if (current_node == null) {
542 break;
543 }
544 }
545 if (current_node == null || current_node.content == null)
546 return null;
547 return current_node.content.get(content_index);
548 }
549
550 @Override
551 public T next() {
552 T ret = peek();
553 content_index++;
554 iterated_over++;
555 return ret;
556 }
557
558 @Override
559 public void remove() {
560 // two uses
561 // 1. Back up to the thing we just returned
562 // 2. move the index back since we removed
563 // an element
564 content_index--;
565 T object = peek();
566 current_node.remove_content(object);
567 }
568 }
569
570 @Override
571 public Iterator<T> iterator() {
572 return new QuadBucketIterator(this);
573 }
574
575 @Override
576 public int size() {
577 return size;
578 }
579
580 @Override
581 public boolean isEmpty() {
582 if (this.size() == 0)
583 return true;
584 return false;
585 }
586
587 public List<T> search(BBox search_bbox) {
588 List<T> ret = new ArrayList<T>();
589 // Doing this cuts down search cost on a real-life data set by about 25%
590 boolean cache_searches = true;
591 if (cache_searches) {
592 if (search_cache == null) {
593 search_cache = root;
594 }
595 // Walk back up the tree when the last search spot can not cover the current search
596 while (search_cache != null && !search_cache.bbox().bounds(search_bbox)) {
597 search_cache = search_cache.parent;
598 }
599
600 if (search_cache == null) {
601 search_cache = root;
602 Main.info("bbox: " + search_bbox + " is out of the world");
603 }
604 } else {
605 search_cache = root;
606 }
607
608 // Save parent because search_cache might change during search call
609 QBLevel tmp = search_cache.parent;
610
611 search_cache.search(search_bbox, ret);
612
613 // A way that spans this bucket may be stored in one
614 // of the nodes which is a parent of the search cache
615 while (tmp != null) {
616 tmp.search_contents(search_bbox, ret);
617 tmp = tmp.parent;
618 }
619 return ret;
620 }
621
622 public void printTree() {
623 printTreeRecursive(root, 0);
624 }
625
626 private void printTreeRecursive(QBLevel level, int indent) {
627 if (level == null) {
628 printIndented(indent, "<empty child>");
629 return;
630 }
631 printIndented(indent, level);
632 if (level.hasContent()) {
633 for (T o:level.content) {
634 printIndented(indent, o);
635 }
636 }
637 for (QBLevel child:level.getChildren()) {
638 printTreeRecursive(child, indent + 2);
639 }
640 }
641
642 private void printIndented(int indent, Object msg) {
643 for (int i=0; i<indent; i++) {
644 System.out.print(' ');
645 }
646 System.out.println(msg);
647 }
648}
Note: See TracBrowser for help on using the repository browser.