package org.openstreetmap.josm.data.osm; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.coor.QuadTiling; /** * Note: bbox of primitives added to QuadBuckets has to stay the same. In case of coordinate change, primitive must * be removed and readded. * */ public class QuadBuckets<T extends OsmPrimitive> implements Collection<T> { private static boolean debug = false; private static final boolean consistency_testing = false; /* * Functions prefixed with __ need locking before * being called. */ private final Object split_lock = new Object(); static void abort(String s) { throw new AssertionError(s); } static void out(String s) { System.out.println(s); } // periodic output long last_out = -1; void pout(String s) { long now = System.currentTimeMillis(); if (now - last_out < 300) return; last_out = now; System.out.print(s + "\r"); } void pout(String s, int i, int total) { long now = System.currentTimeMillis(); if ((now - last_out < 300) && ((i+1) < total)) return; last_out = now; // cast to float to keep the output size down System.out.print(s + " " + (float)((i+1)*100.0/total) + "% done \r"); } public static int MAX_OBJECTS_PER_LEVEL = 16; class QBLevel { final int level; private final BBox bbox; final long quad; final QBLevel parent; public List<T> content; public QBLevel children[]; @Override public String toString() { return super.toString()+ "["+level+"]: " + bbox(); } /** * Constructor for root node */ public QBLevel() { level = 0; quad = 0; parent = null; bbox = new BBox(-180, 90, 180, -90); } public QBLevel(QBLevel parent, int parent_index) { this.parent = parent; this.level = parent.level + 1; int shift = (QuadTiling.NR_LEVELS - level) * 2; long mult = 1; // Java blows the big one. It seems to wrap when // you shift by > 31 if (shift >= 30) { shift -= 30; mult = 1<<30; } long this_quadpart = mult * (parent_index << shift); this.quad = parent.quad | this_quadpart; this.bbox = calculateBBox(); // calculateBBox reference quad if (debug) { out("new level["+this.level+"] bbox["+parent_index+"]: " + this.bbox() + " coor: " + this.coor() + " quadpart: " + Long.toHexString(this_quadpart) + " quad: " + Long.toHexString(this.quad)); } } private BBox calculateBBox() { LatLon bottom_left = this.coor(); double lat = bottom_left.lat() + parent.height() / 2; double lon = bottom_left.lon() + parent.width() / 2; LatLon top_right = new LatLon(lat, lon); return new BBox(bottom_left, top_right); } QBLevel findBucket(BBox bbox) { if (isLeaf()) return this; else { int index = get_index(bbox, level); if (index == -1) return this; if (children[index] == null) { children[index] = new QBLevel(this, index); } return children[index].findBucket(bbox); } } boolean remove_content(T o) { synchronized (split_lock) { // If two threads try to remove item at the same time from different buckets of this QBLevel, // it might happen that one thread removes bucket but don't remove parent because it still sees // another bucket set. Second thread do the same. Due to thread memory caching, it's possible that // changes made by threads will show up in children array too late, leading to QBLevel with all children // set to null if (content == null) return false; boolean ret = this.content.remove(o); if (this.content.size() == 0) { this.content = null; } if (this.canRemove()) { this.remove_from_parent(); } return ret; } } QBLevel[] newChildren() { // This is ugly and hackish. But, it seems to work, // and using an ArrayList here seems to cost us // a significant performance penalty -- 50% in my // testing. Child access is one of the single // hottest code paths in this entire class. return (QBLevel[])Array.newInstance(this.getClass(), QuadTiling.TILES_PER_LEVEL); } // Get the correct index for the given primitive // at the given level. If the primitive can not // fit into a single quad at this level, return -1 int get_index(BBox bbox, int level) { int index = -1; for (LatLon c : bbox.points()) { if (debug) { out("getting index for point: " + c); } if (index == -1) { index = QuadTiling.index(c, level); if (debug) { out("set initial index to: " + index); } continue; } int another_index = QuadTiling.index(c, level); if (debug) { out("other point index: " + another_index); } if (another_index != index) return -1; } return index; } /* * There is a race between this and qb.nextContentNode(). * If nextContentNode() runs into this bucket, it may * attempt to null out 'children' because it thinks this * is a dead end. */ void __split() { if (debug) { out("splitting "+this.bbox()+" level "+level+" with " + content.size() + " entries (my dimensions: " + this.bbox().width()+", "+this.bbox().height()+")"); } // deferring allocation of children until use // seems a bit faster //for (int i = 0; i < TILES_PER_LEVEL; i++) // children[i] = new QBLevel(this, i); List<T> tmpcontent = content; content = null; children = newChildren(); for (T o: tmpcontent) { add(o); } if (!hasChildren()) { // All items stay at this level (get_index == -1). Create at least first child to keep the contract // that at least one child always exists children[0] = new QBLevel(this, 0); } } boolean __add_content(T o) { boolean ret = false; // The split_lock will keep two concurrent // calls from overwriting content if (content == null) { content = new ArrayList<T>(); } ret = content.add(o); if (debug && !this.isLeaf()) { pout("added "+o.getClass().getName()+" to non-leaf with size: " + content.size()); } return ret; } boolean matches(T o, BBox search_bbox) { // This can be optimized when o is a node //return search_bbox.bounds(coor)); return o.getBBox().intersects(search_bbox); } private void search_contents(BBox search_bbox, List<T> result) { if (debug) { out("searching contents (size: " + content == null?"<null>":content.size() + ") for " + search_bbox); } /* * It is possible that this was created in a split * but never got any content populated. */ if (content == null) return; for (T o : content) { if (matches(o, search_bbox)) { result.add(o); } } if (debug) { out("done searching quad " + Long.toHexString(this.quad)); } } /* * This is stupid. I tried to have a QBLeaf and QBBranch * class decending from a QBLevel. It's more than twice * as slow. So, this throws OO out the window, but it * is fast. Runtime type determination must be slow. */ boolean isLeaf() { if (children == null) return true; return false; } QBLevel next_sibling() { boolean found_me = false; if (parent == null) return null; if (parent.children == null) return null; int __nr = 0; for (QBLevel sibling : parent.children) { __nr++; int nr = __nr-1; if (sibling == null) { if (debug) { out("[" + this.level + "] null child nr: " + nr); } continue; } // We're looking for the *next* child // after us. if (sibling == this) { if (debug) { out("[" + this.level + "] I was child nr: " + nr); } found_me = true; continue; } if (found_me) { if (debug) { out("[" + this.level + "] next sibling was child nr: " + nr); } return sibling; } if (debug) { out("[" + this.level + "] nr: " + nr + " is before me, ignoring..."); } } return null; } boolean hasContent() { return content != null; } QBLevel nextSibling() { QBLevel next = this; QBLevel sibling = next.next_sibling(); // Walk back up the tree to find the // next sibling node. It may be either // a leaf or branch. while (sibling == null) { if (debug) { out("no siblings at level["+next.level+"], moving to parent"); } next = next.parent; if (next == null) { break; } sibling = next.next_sibling(); } next = sibling; return next; } QBLevel firstChild() { QBLevel ret = null; for (QBLevel child : this.children) { if (child == null) { continue; } ret = child; break; } return ret; } QBLevel nextNode() { if (this.isLeaf()) return this.nextSibling(); return this.firstChild(); } QBLevel nextContentNode() { QBLevel next = this.nextNode(); if (next == null) return next; if (next.hasContent()) return next; return next.nextContentNode(); } void doAdd(T o) { if (consistency_testing) { if (!matches(o, this.bbox())) { out("-----------------------------"); debug = true; get_index(o.getBBox(), level); get_index(o.getBBox(), level-1); int nr = 0; for (QBLevel sibling : parent.children) { out("sibling["+ (nr++) +"]: " + sibling.bbox() + " this: " + (this==sibling)); } abort("\nobject " + o + " does not belong in node at level: " + level + " bbox: " + this.bbox()); } } synchronized (split_lock) { __add_content(o); if (isLeaf() && content.size() > MAX_OBJECTS_PER_LEVEL && level < QuadTiling.NR_LEVELS) { __split(); } } } void add(T o) { synchronized (split_lock) { findBucket(o.getBBox()).doAdd(o); } } private void search(BBox search_bbox, List<T> result) { if (debug) { System.out.print("[" + level + "] qb bbox: " + this.bbox() + " "); } if (!this.bbox().intersects(search_bbox)) { if (debug) { out("miss " + Long.toHexString(this.quad)); //QuadTiling.tile2xy(this.quad); } return; } if (this.hasContent()) { search_contents(search_bbox, result); } if (this.isLeaf()) return; if (debug) { out("hit " + this.quads()); } if (debug) { out("[" + level + "] not leaf, moving down"); } for (int i = 0; i < children.length; i++) { QBLevel q = children[i]; if (debug) { out("child["+i+"]: " + q); } if (q == null) { continue; } if (debug) { System.out.print(i+": "); } q.search(search_bbox, result); if (q.bbox().bounds(search_bbox)) { search_cache = q; // optimization: do not continue searching // other tiles if this one wholly contained // what we were looking for. if (debug) { out("break early"); } break; } } } public String quads() { return Long.toHexString(quad); } int index_of(QBLevel find_this) { if (this.isLeaf()) return -1; for (int i = 0; i < QuadTiling.TILES_PER_LEVEL; i++) { if (children[i] == find_this) return i; } return -1; } double width() { return bbox.width(); } double height() { return bbox.height(); } public BBox bbox() { return bbox; } /* * This gives the coordinate of the bottom-left * corner of the box */ LatLon coor() { return QuadTiling.tile2LatLon(this.quad); } boolean hasChildren() { if (children == null) return false; for (QBLevel child : children) { if (child != null) return true; } return false; } void remove_from_parent() { if (parent == null) return; int nr_siblings = 0; for (int i = 0; i < parent.children.length; i++) { QBLevel sibling = parent.children[i]; if (sibling != null) { nr_siblings++; } if (sibling != this) { continue; } if (!canRemove()) { abort("attempt to remove non-empty child: " + this.content + " " + Arrays.toString(this.children)); } parent.children[i] = null; nr_siblings--; } if (nr_siblings == 0) { parent.children = null; } if (parent.canRemove()) { parent.remove_from_parent(); } } boolean canRemove() { if (content != null && content.size() > 0) return false; if (this.hasChildren()) return false; return true; } } private QBLevel root; private QBLevel search_cache; private int size; public QuadBuckets() { clear(); } public void clear() { synchronized (split_lock) { root = new QBLevel(); search_cache = null; size = 0; if (debug) { out("QuadBuckets() cleared: " + this); out("root: " + root + " level: " + root.level + " bbox: " + root.bbox()); } } } public boolean add(T n) { synchronized (split_lock) { root.add(n); size++; return true; } } public void unsupported() { out("unsupported operation"); throw new UnsupportedOperationException(); } public boolean retainAll(Collection<?> objects) { for (T o : this) { if (objects.contains(o)) { continue; } if (!this.remove(o)) return false; } return true; } public boolean removeAll(Collection<?> objects) { boolean changed = false; for (Object o : objects) { changed = changed | remove(o); } return changed; } public boolean addAll(Collection<? extends T> objects) { boolean changed = false; for (T o : objects) { changed = changed | this.add(o); } return changed; } public boolean containsAll(Collection<?> objects) { for (Object o : objects) { if (!this.contains(o)) return false; } return true; } // If anyone has suggestions for how to fix // this properly, I'm listening :) @SuppressWarnings("unchecked") private T convert(Object raw) { return (T)raw; } public boolean remove(Object o) { return this.remove(convert(o)); } public boolean remove(T o) { synchronized (split_lock) { search_cache = null; // Search cache might point to one of removed buckets QBLevel bucket = root.findBucket(o.getBBox()); if (bucket.remove_content(o)) { size--; return true; } else return false; } } public boolean contains(Object o) { QBLevel bucket = root.findBucket(convert(o).getBBox()); return bucket != null && bucket.content != null && bucket.content.contains(o); } public ArrayList<T> toArrayList() { ArrayList<T> a = new ArrayList<T>(); for (T n : this) { a.add(n); } if (debug) { out("returning array list with size: " + a.size()); } return a; } public Object[] toArray() { return this.toArrayList().toArray(); } public <A> A[] toArray(A[] template) { return this.toArrayList().toArray(template); } class QuadBucketIterator implements Iterator<T> { QBLevel current_node; int content_index; int iterated_over; QBLevel next_content_node(QBLevel q) { if (q == null) return null; QBLevel orig = q; QBLevel next; synchronized (split_lock) { next = q.nextContentNode(); } //if (consistency_testing && (orig == next)) if (orig == next) { abort("got same leaf back leaf: " + q.isLeaf()); } return next; } public QuadBucketIterator(QuadBuckets<T> qb) { if (debug) { out(this + " is a new iterator qb: " + qb + " size: " + qb.size()); } if (qb.root.isLeaf() || qb.root.hasContent()) { current_node = qb.root; } else { current_node = next_content_node(qb.root); } if (debug) { out("\titerator first leaf: " + current_node); } iterated_over = 0; } public boolean hasNext() { if (this.peek() == null) { if (debug) { out(this + " no hasNext(), but iterated over so far: " + iterated_over); } return false; } return true; } T peek() { if (current_node == null) { if (debug) { out("null current leaf, nowhere to go"); } return null; } while((current_node.content == null) || (content_index >= current_node.content.size())) { if (debug) { out("moving to next leaf"); } content_index = 0; current_node = next_content_node(current_node); if (current_node == null) { break; } } if (current_node == null || current_node.content == null) { if (debug) { out("late nowhere to go " + current_node); } return null; } return current_node.content.get(content_index); } public T next() { T ret = peek(); content_index++; if (debug) { out("iteration["+iterated_over+"] " + content_index + " leaf: " + current_node); } iterated_over++; if (ret == null) { if (debug) { out(this + " no next node, but iterated over so far: " + iterated_over); } } return ret; } public void remove() { // two uses // 1. Back up to the thing we just returned // 2. move the index back since we removed // an element content_index--; T object = peek(); current_node.remove_content(object); } } public Iterator<T> iterator() { return new QuadBucketIterator(this); } public int size() { synchronized (split_lock) { return size; } } public boolean isEmpty() { if (this.size() == 0) return true; return false; } public List<T> search(BBox search_bbox) { if (debug) { out("qb root search at " + search_bbox); out("root bbox: " + root.bbox()); } List<T> ret = new ArrayList<T>(); // Doing this cuts down search cost on a real-life data // set by about 25% boolean cache_searches = true; if (cache_searches) { if (search_cache == null) { search_cache = root; } // Walk back up the tree when the last // search spot can not cover the current // search while (!search_cache.bbox().bounds(search_bbox)) { if (debug) { out("bbox: " + search_bbox); } if (debug) { out("search_cache: " + search_cache + " level: " + search_cache.level); out("search_cache.bbox(): " + search_cache.bbox()); } search_cache = search_cache.parent; if (debug) { out("new search_cache: " + search_cache); } } } else { search_cache = root; } // Save parent because search_cache might change during search call QBLevel tmp = search_cache.parent; search_cache.search(search_bbox, ret); // A way that spans this bucket may be stored in one // of the nodes which is a parent of the search cache while (tmp != null) { tmp.search_contents(search_bbox, ret); tmp = tmp.parent; } if (debug) { out("search of QuadBuckets for " + search_bbox + " ret len: " + ret.size()); } return ret; } public void printTree() { printTreeRecursive(root, 0); } private void printTreeRecursive(QBLevel level, int indent) { if (level == null) { printIndented(indent, "<empty child>"); return; } printIndented(indent, level); if (level.hasContent()) { for (T o:level.content) { printIndented(indent, o); } } if (level.children != null) { for (QBLevel child:level.children) { printTreeRecursive(child, indent + 2); } } } private void printIndented(int indent, Object msg) { for (int i=0; i<indent; i++) { System.out.print(' '); } System.out.println(msg); } }