// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.osm; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * GWT * * changelog * moved inner class QBLevel to separate file to work around gwt bug * (see http://code.google.com/p/google-web-toolkit/issues/detail?id=5483) * QBLevel now requires type parameter and reference to 'this' in constructor * search_cache: private -> package private */ /** * Note: bbox of primitives added to QuadBuckets has to stay the same. In case of coordinate change, primitive must * be removed and readded. * * This class is (no longer) thread safe. * */ public class QuadBuckets<T extends OsmPrimitive> implements Collection<T> { private static boolean debug = false; 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; private QBLevel<T> root; QBLevel<T> search_cache; private int size; public QuadBuckets() { clear(); } public void clear() { root = new QBLevel<T>(this); 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) { 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; } public boolean remove(Object o) { @SuppressWarnings("unchecked") T t = (T) o; search_cache = null; // Search cache might point to one of removed buckets QBLevel<T> bucket = root.findBucket(t.getBBox()); if (bucket.remove_content(t)) { size--; return true; } else return false; } public boolean contains(Object o) { @SuppressWarnings("unchecked") T t = (T) o; QBLevel<T> bucket = root.findBucket(t.getBBox()); return bucket != null && bucket.content != null && bucket.content.contains(t); } 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<T> current_node; int content_index; int iterated_over; QBLevel<T> next_content_node(QBLevel<T> q) { if (q == null) return null; QBLevel<T> orig = q; QBLevel<T> next; 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.hasChildren() || 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() { 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<T> 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<T> 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); } } for (QBLevel<T> child:level.getChildren()) { 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); } }