// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import org.openstreetmap.josm.Main;
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 re-added.
*
* This class is (no longer) thread safe.
* @param <T> type of primitives
* @since 2165
*/
public class QuadBuckets<T extends OsmPrimitive> implements Collection<T> {
private static final boolean consistency_testing = false;
private static final byte NW_INDEX = 1;
private static final byte NE_INDEX = 3;
private static final byte SE_INDEX = 2;
private static final byte SW_INDEX = 0;
static void abort(String s) {
throw new AssertionError(s);
}
private static final int MAX_OBJECTS_PER_NODE = 48;
static class QBLevel<T extends OsmPrimitive> extends BBox {
private final byte level;
private final byte index;
private final long quad;
private final QBLevel<T> parent;
private boolean isLeaf = true;
private List<T> content;
// child order by index is sw, nw, se, ne
private QBLevel<T> nw, ne, sw, se;
private QBLevel<T> getChild(byte index) {
switch (index) {
case NE_INDEX:
if (ne == null) {
ne = new QBLevel<>(this, index);
}
return ne;
case NW_INDEX:
if (nw == null) {
nw = new QBLevel<>(this, index);
}
return nw;
case SE_INDEX:
if (se == null) {
se = new QBLevel<>(this, index);
}
return se;
case SW_INDEX:
if (sw == null) {
sw = new QBLevel<>(this, index);
}
return sw;
default:
return null;
}
}
@SuppressWarnings("unchecked")
private QBLevel<T>[] getChildren() {
return new QBLevel[] {sw, nw, se, ne};
}
@Override
public String toString() {
return super.toString() + '[' + level + "]: ";
}
/**
* Constructor for root node
*/
QBLevel() {
super(-180, 90, 180, -90);
level = 0;
index = 0;
quad = 0;
parent = null;
}
QBLevel(QBLevel<T> parent, byte index) {
this.parent = parent;
this.level = (byte) (parent.level + 1);
this.index = index;
int shift = (QuadTiling.NR_LEVELS - level) * 2;
long quadpart = (long) index << shift;
this.quad = parent.quad | quadpart;
LatLon bottomLeft = QuadTiling.tile2LatLon(this.quad);
xmin = bottomLeft.lon();
ymin = bottomLeft.lat();
xmax = xmin + parent.width() / 2;
ymax = ymin + parent.height() / 2;
}
QBLevel<T> findBucket(BBox bbox) {
if (!hasChildren())
return this;
else {
byte idx = bbox.getIndex(level);
if (idx == -1)
return this;
QBLevel<T> child = getChild(idx);
if (child == null)
return this;
return child.findBucket(bbox);
}
}
boolean removeContent(T o) {
// 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.isEmpty()) {
this.content = null;
}
if (this.canRemove()) {
this.removeFromParent();
}
return ret;
}
/*
* 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 doSplit() {
List<T> tmpcontent = content;
content = null;
for (T o : tmpcontent) {
byte idx = o.getBBox().getIndex(level);
if (idx == -1) {
doAddContent(o);
} else {
QBLevel<T> child = getChild(idx);
if (child != null)
child.doAdd(o);
}
}
isLeaf = false; // It's not enough to check children because all items could end up in this level (index == -1)
}
boolean doAddContent(T o) {
// The split_lock will keep two concurrent calls from overwriting content
if (content == null) {
content = new ArrayList<>();
}
return content.add(o);
}
boolean matches(final T o, final BBox searchBbox) {
return o.getBBox().intersects(searchBbox);
}
private void searchContents(BBox searchBbox, List<T> result) {
/*
* 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, searchBbox)) {
result.add(o);
}
}
}
/*
* This is stupid. I tried to have a QBLeaf and QBBranch
* class descending 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() {
return isLeaf;
}
boolean hasChildren() {
return nw != null || ne != null || sw != null || se != null;
}
QBLevel<T> findNextSibling() {
return (parent == null) ? null : parent.firstSiblingOf(this);
}
boolean hasContent() {
return content != null;
}
QBLevel<T> nextSibling() {
QBLevel<T> next = this;
QBLevel<T> sibling = next.findNextSibling();
// Walk back up the tree to find the next sibling node.
// It may be either a leaf or branch.
while (sibling == null) {
next = next.parent;
if (next == null) {
break;
}
sibling = next.findNextSibling();
}
return sibling;
}
QBLevel<T> firstChild() {
if (sw != null)
return sw;
if (nw != null)
return nw;
if (se != null)
return se;
return ne;
}
QBLevel<T> firstSiblingOf(final QBLevel<T> child) {
switch (child.index) {
case SW_INDEX:
if (nw != null)
return nw;
if (se != null)
return se;
return ne;
case NW_INDEX:
if (se != null)
return se;
return ne;
case SE_INDEX:
return ne;
default:
return null;
}
}
QBLevel<T> nextNode() {
if (!this.hasChildren())
return this.nextSibling();
return this.firstChild();
}
QBLevel<T> nextContentNode() {
QBLevel<T> next = this.nextNode();
if (next == null)
return next;
if (next.hasContent())
return next;
return next.nextContentNode();
}
void doAdd(T o) {
if (consistency_testing) {
if (o instanceof Node && !matches(o, this)) {
o.getBBox().getIndex(level);
o.getBBox().getIndex(level - 1);
abort("\nobject " + o + " does not belong in node at level: " + level + " bbox: " + super.toString());
}
}
doAddContent(o);
if (level < QuadTiling.NR_LEVELS && isLeaf() && content.size() > MAX_OBJECTS_PER_NODE) {
doSplit();
}
}
void add(T o) {
findBucket(o.getBBox()).doAdd(o);
}
private void search(QuadBuckets<T> buckets, BBox searchBbox, List<T> result) {
if (!this.intersects(searchBbox))
return;
else if (this.bounds(searchBbox)) {
buckets.searchCache = this;
}
if (this.hasContent()) {
searchContents(searchBbox, result);
}
//TODO Coincidence vector should be calculated here and only buckets that match search_bbox should be checked
if (nw != null) {
nw.search(buckets, searchBbox, result);
}
if (ne != null) {
ne.search(buckets, searchBbox, result);
}
if (se != null) {
se.search(buckets, searchBbox, result);
}
if (sw != null) {
sw.search(buckets, searchBbox, result);
}
}
public String quads() {
return Long.toHexString(quad);
}
int indexOf(QBLevel<T> findThis) {
QBLevel<T>[] children = getChildren();
for (int i = 0; i < QuadTiling.TILES_PER_LEVEL; i++) {
if (children[i] == findThis) {
return i;
}
}
return -1;
}
void removeFromParent() {
if (parent == null)
return;
if (!canRemove()) {
abort("attempt to remove non-empty child: " + this.content + ' ' + Arrays.toString(this.getChildren()));
}
if (parent.nw == this) {
parent.nw = null;
} else if (parent.ne == this) {
parent.ne = null;
} else if (parent.sw == this) {
parent.sw = null;
} else if (parent.se == this) {
parent.se = null;
}
if (parent.canRemove()) {
parent.removeFromParent();
}
}
boolean canRemove() {
return (content == null || content.isEmpty()) && !this.hasChildren();
}
}
private QBLevel<T> root;
private QBLevel<T> searchCache;
private int size;
private Collection<T> invalidBBoxPrimitives;
/**
* Constructs a new {@code QuadBuckets}.
*/
public QuadBuckets() {
clear();
}
@Override
public final void clear() {
root = new QBLevel<>();
invalidBBoxPrimitives = new LinkedHashSet<>();
searchCache = null;
size = 0;
}
@Override
public boolean add(T n) {
if (n.getBBox().isValid()) {
root.add(n);
} else {
invalidBBoxPrimitives.add(n);
}
size++;
return true;
}
@Override
public boolean retainAll(Collection<?> objects) {
for (T o : this) {
if (objects.contains(o)) {
continue;
}
if (!this.remove(o)) {
return false;
}
}
return true;
}
@Override
public boolean removeAll(Collection<?> objects) {
boolean changed = false;
for (Object o : objects) {
changed = changed | remove(o);
}
return changed;
}
@Override
public boolean addAll(Collection<? extends T> objects) {
boolean changed = false;
for (T o : objects) {
changed = changed | this.add(o);
}
return changed;
}
@Override
public boolean containsAll(Collection<?> objects) {
for (Object o : objects) {
if (!this.contains(o)) {
return false;
}
}
return true;
}
@Override
public boolean remove(Object o) {
@SuppressWarnings("unchecked")
T t = (T) o;
searchCache = null; // Search cache might point to one of removed buckets
QBLevel<T> bucket = root.findBucket(t.getBBox());
boolean removed = bucket.removeContent(t);
if (!removed) {
removed = invalidBBoxPrimitives.remove(o);
}
if (removed) {
size--;
}
return removed;
}
@Override
public boolean contains(Object o) {
@SuppressWarnings("unchecked")
T t = (T) o;
if (!t.getBBox().isValid()) {
return invalidBBoxPrimitives.contains(o);
}
QBLevel<T> bucket = root.findBucket(t.getBBox());
return bucket != null && bucket.content != null && bucket.content.contains(t);
}
/**
* Converts to list.
* @return elements as list
*/
public List<T> toList() {
List<T> a = new ArrayList<>();
for (T n : this) {
a.add(n);
}
return a;
}
@Override
public Object[] toArray() {
return this.toList().toArray();
}
@Override
public <A> A[] toArray(A[] template) {
return this.toList().toArray(template);
}
class QuadBucketIterator implements Iterator<T> {
private QBLevel<T> currentNode;
private int contentIndex;
private final Iterator<T> invalidBBoxIterator = invalidBBoxPrimitives.iterator();
boolean fromInvalidBBoxPrimitives;
QuadBuckets<T> qb;
final QBLevel<T> nextContentNode(QBLevel<T> q) {
if (q == null)
return null;
QBLevel<T> orig = q;
QBLevel<T> next;
next = q.nextContentNode();
if (orig == next) {
abort("got same leaf back leaf: " + q.isLeaf());
}
return next;
}
QuadBucketIterator(QuadBuckets<T> qb) {
if (!qb.root.hasChildren() || qb.root.hasContent()) {
currentNode = qb.root;
} else {
currentNode = nextContentNode(qb.root);
}
this.qb = qb;
}
@Override
public boolean hasNext() {
if (this.peek() == null) {
fromInvalidBBoxPrimitives = true;
return invalidBBoxIterator.hasNext();
}
return true;
}
T peek() {
if (currentNode == null)
return null;
while ((currentNode.content == null) || (contentIndex >= currentNode.content.size())) {
contentIndex = 0;
currentNode = nextContentNode(currentNode);
if (currentNode == null) {
break;
}
}
if (currentNode == null || currentNode.content == null)
return null;
return currentNode.content.get(contentIndex);
}
@Override
public T next() {
if (fromInvalidBBoxPrimitives) {
return invalidBBoxIterator.next();
}
T ret = peek();
if (ret == null)
throw new NoSuchElementException();
contentIndex++;
return ret;
}
@Override
public void remove() {
if (fromInvalidBBoxPrimitives) {
invalidBBoxIterator.remove();
qb.size--;
} else {
// two uses
// 1. Back up to the thing we just returned
// 2. move the index back since we removed
// an element
contentIndex--;
T object = peek();
if (currentNode.removeContent(object))
qb.size--;
}
}
}
@Override
public Iterator<T> iterator() {
return new QuadBucketIterator(this);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* Search the tree for objects in the bbox (or crossing the bbox if they are ways)
* @param searchBbox the bbox
* @return List of primitives within the bbox (or crossing the bbox if they are ways). Can be empty, but not null.
*/
public List<T> search(BBox searchBbox) {
List<T> ret = new ArrayList<>();
if (!searchBbox.isValid()) {
return ret;
}
// Doing this cuts down search cost on a real-life data set by about 25%
if (searchCache == null) {
searchCache = root;
}
// Walk back up the tree when the last search spot can not cover the current search
while (searchCache != null && !searchCache.bounds(searchBbox)) {
searchCache = searchCache.parent;
}
if (searchCache == null) {
searchCache = root;
Main.info("bbox: " + searchBbox + " is out of the world");
}
// Save parent because searchCache might change during search call
QBLevel<T> tmp = searchCache.parent;
searchCache.search(this, searchBbox, 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.searchContents(searchBbox, ret);
tmp = tmp.parent;
}
return ret;
}
}