/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mvstore.rtree;
import java.util.ArrayList;
import java.util.Iterator;
import org.h2.mvstore.CursorPos;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.Page;
import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType;
import org.h2.util.New;
/**
* An r-tree implementation. It uses the quadratic split algorithm.
*
* @param <V> the value class
*/
public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
/**
* The spatial key type.
*/
final SpatialDataType keyType;
private boolean quadraticSplit;
public MVRTreeMap(int dimensions, DataType valueType) {
super(new SpatialDataType(dimensions), valueType);
this.keyType = (SpatialDataType) getKeyType();
}
/**
* Create a new map with the given dimensions and value type.
*
* @param <V> the value type
* @param dimensions the number of dimensions
* @param valueType the value type
* @return the map
*/
public static <V> MVRTreeMap<V> create(int dimensions, DataType valueType) {
return new MVRTreeMap<V>(dimensions, valueType);
}
@Override
@SuppressWarnings("unchecked")
public V get(Object key) {
return (V) get(root, key);
}
/**
* Iterate over all keys that have an intersection with the given rectangle.
*
* @param x the rectangle
* @return the iterator
*/
public RTreeCursor findIntersectingKeys(SpatialKey x) {
return new RTreeCursor(root, x) {
@Override
protected boolean check(boolean leaf, SpatialKey key,
SpatialKey test) {
return keyType.isOverlap(key, test);
}
};
}
/**
* Iterate over all keys that are fully contained within the given
* rectangle.
*
* @param x the rectangle
* @return the iterator
*/
public RTreeCursor findContainedKeys(SpatialKey x) {
return new RTreeCursor(root, x) {
@Override
protected boolean check(boolean leaf, SpatialKey key,
SpatialKey test) {
if (leaf) {
return keyType.isInside(key, test);
}
return keyType.isOverlap(key, test);
}
};
}
private boolean contains(Page p, int index, Object key) {
return keyType.contains(p.getKey(index), key);
}
/**
* Get the object for the given key. An exact match is required.
*
* @param p the page
* @param key the key
* @return the value, or null if not found
*/
protected Object get(Page p, Object key) {
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Object o = get(p.getChildPage(i), key);
if (o != null) {
return o;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
return p.getValue(i);
}
}
}
return null;
}
@Override
protected synchronized Object remove(Page p, long writeVersion, Object key) {
Object result = null;
if (p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
result = p.getValue(i);
p.remove(i);
break;
}
}
return result;
}
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page cOld = p.getChildPage(i);
// this will mark the old page as deleted
// so we need to update the parent in any case
// (otherwise the old page might be deleted again)
Page c = cOld.copy(writeVersion);
long oldSize = c.getTotalCount();
result = remove(c, writeVersion, key);
p.setChild(i, c);
if (oldSize == c.getTotalCount()) {
continue;
}
if (c.getTotalCount() == 0) {
// this child was deleted
p.remove(i);
if (p.getKeyCount() == 0) {
c.removePage();
}
break;
}
Object oldBounds = p.getKey(i);
if (!keyType.isInside(key, oldBounds)) {
p.setKey(i, getBounds(c));
}
break;
}
}
return result;
}
private Object getBounds(Page x) {
Object bounds = keyType.createBoundingBox(x.getKey(0));
for (int i = 1; i < x.getKeyCount(); i++) {
keyType.increaseBounds(bounds, x.getKey(i));
}
return bounds;
}
@Override
@SuppressWarnings("unchecked")
public V put(SpatialKey key, V value) {
return (V) putOrAdd(key, value, false);
}
/**
* Add a given key-value pair. The key should not exist (if it exists, the
* result is undefined).
*
* @param key the key
* @param value the value
*/
public void add(SpatialKey key, V value) {
putOrAdd(key, value, true);
}
private synchronized Object putOrAdd(SpatialKey key, V value, boolean alwaysAdd) {
beforeWrite();
long v = writeVersion;
Page p = root.copy(v);
Object result;
if (alwaysAdd || get(key) == null) {
if (p.getMemory() > store.getPageSplitSize() &&
p.getKeyCount() > 3) {
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p, v);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = { k1, k2 };
Page.PageReference[] children = {
new Page.PageReference(p, p.getPos(), p.getTotalCount()),
new Page.PageReference(split, split.getPos(), split.getTotalCount()),
new Page.PageReference(null, 0, 0)
};
p = Page.create(this, v,
keys, null,
children,
totalCount, 0);
// now p is a node; continues
}
add(p, v, key, value);
result = null;
} else {
result = set(p, v, key, value);
}
newRoot(p);
return result;
}
/**
* Update the value for the given key. The key must exist.
*
* @param p the page
* @param writeVersion the write version
* @param key the key
* @param value the new value
* @return the old value (never null)
*/
private Object set(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
return p.setValue(i, value);
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
if (get(c, key) != null) {
c = c.copy(writeVersion);
Object result = set(c, writeVersion, key, value);
p.setChild(i, c);
return result;
}
}
}
}
throw DataUtils.newIllegalStateException(DataUtils.ERROR_INTERNAL,
"Not found: {0}", key);
}
private void add(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) {
p.insertLeaf(p.getKeyCount(), key, value);
return;
}
// p is a node
int index = -1;
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
index = i;
break;
}
}
if (index < 0) {
// a new entry, we don't know where to add yet
float min = Float.MAX_VALUE;
for (int i = 0; i < p.getKeyCount(); i++) {
Object k = p.getKey(i);
float areaIncrease = keyType.getAreaIncrease(k, key);
if (areaIncrease < min) {
index = i;
min = areaIncrease;
}
}
}
Page c = p.getChildPage(index).copy(writeVersion);
if (c.getMemory() > store.getPageSplitSize() && c.getKeyCount() > 4) {
// split on the way down
Page split = split(c, writeVersion);
p.setKey(index, getBounds(c));
p.setChild(index, c);
p.insertNode(index, getBounds(split), split);
// now we are not sure where to add
add(p, writeVersion, key, value);
return;
}
add(c, writeVersion, key, value);
Object bounds = p.getKey(index);
keyType.increaseBounds(bounds, key);
p.setKey(index, bounds);
p.setChild(index, c);
}
private Page split(Page p, long writeVersion) {
return quadraticSplit ?
splitQuadratic(p, writeVersion) :
splitLinear(p, writeVersion);
}
private Page splitLinear(Page p, long writeVersion) {
ArrayList<Object> keys = New.arrayList();
for (int i = 0; i < p.getKeyCount(); i++) {
keys.add(p.getKey(i));
}
int[] extremes = keyType.getExtremes(keys);
if (extremes == null) {
return splitQuadratic(p, writeVersion);
}
Page splitA = newPage(p.isLeaf(), writeVersion);
Page splitB = newPage(p.isLeaf(), writeVersion);
move(p, splitA, extremes[0]);
if (extremes[1] > extremes[0]) {
extremes[1]--;
}
move(p, splitB, extremes[1]);
Object boundsA = keyType.createBoundingBox(splitA.getKey(0));
Object boundsB = keyType.createBoundingBox(splitB.getKey(0));
while (p.getKeyCount() > 0) {
Object o = p.getKey(0);
float a = keyType.getAreaIncrease(boundsA, o);
float b = keyType.getAreaIncrease(boundsB, o);
if (a < b) {
keyType.increaseBounds(boundsA, o);
move(p, splitA, 0);
} else {
keyType.increaseBounds(boundsB, o);
move(p, splitB, 0);
}
}
while (splitB.getKeyCount() > 0) {
move(splitB, p, 0);
}
return splitA;
}
private Page splitQuadratic(Page p, long writeVersion) {
Page splitA = newPage(p.isLeaf(), writeVersion);
Page splitB = newPage(p.isLeaf(), writeVersion);
float largest = Float.MIN_VALUE;
int ia = 0, ib = 0;
for (int a = 0; a < p.getKeyCount(); a++) {
Object objA = p.getKey(a);
for (int b = 0; b < p.getKeyCount(); b++) {
if (a == b) {
continue;
}
Object objB = p.getKey(b);
float area = keyType.getCombinedArea(objA, objB);
if (area > largest) {
largest = area;
ia = a;
ib = b;
}
}
}
move(p, splitA, ia);
if (ia < ib) {
ib--;
}
move(p, splitB, ib);
Object boundsA = keyType.createBoundingBox(splitA.getKey(0));
Object boundsB = keyType.createBoundingBox(splitB.getKey(0));
while (p.getKeyCount() > 0) {
float diff = 0, bestA = 0, bestB = 0;
int best = 0;
for (int i = 0; i < p.getKeyCount(); i++) {
Object o = p.getKey(i);
float incA = keyType.getAreaIncrease(boundsA, o);
float incB = keyType.getAreaIncrease(boundsB, o);
float d = Math.abs(incA - incB);
if (d > diff) {
diff = d;
bestA = incA;
bestB = incB;
best = i;
}
}
if (bestA < bestB) {
keyType.increaseBounds(boundsA, p.getKey(best));
move(p, splitA, best);
} else {
keyType.increaseBounds(boundsB, p.getKey(best));
move(p, splitB, best);
}
}
while (splitB.getKeyCount() > 0) {
move(splitB, p, 0);
}
return splitA;
}
private Page newPage(boolean leaf, long writeVersion) {
Object[] values;
Page.PageReference[] refs;
if (leaf) {
values = Page.EMPTY_OBJECT_ARRAY;
refs = null;
} else {
values = null;
refs = new Page.PageReference[] {
new Page.PageReference(null, 0, 0)};
}
return Page.create(this, writeVersion,
Page.EMPTY_OBJECT_ARRAY, values,
refs, 0, 0);
}
private static void move(Page source, Page target, int sourceIndex) {
Object k = source.getKey(sourceIndex);
if (source.isLeaf()) {
Object v = source.getValue(sourceIndex);
target.insertLeaf(0, k, v);
} else {
Page c = source.getChildPage(sourceIndex);
target.insertNode(0, k, c);
}
source.remove(sourceIndex);
}
/**
* Add all node keys (including internal bounds) to the given list.
* This is mainly used to visualize the internal splits.
*
* @param list the list
* @param p the root page
*/
public void addNodeKeys(ArrayList<SpatialKey> list, Page p) {
if (p != null && !p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
list.add((SpatialKey) p.getKey(i));
addNodeKeys(list, p.getChildPage(i));
}
}
}
public boolean isQuadraticSplit() {
return quadraticSplit;
}
public void setQuadraticSplit(boolean quadraticSplit) {
this.quadraticSplit = quadraticSplit;
}
@Override
protected int getChildPageCount(Page p) {
return p.getRawChildPageCount() - 1;
}
/**
* A cursor to iterate over a subset of the keys.
*/
public static class RTreeCursor implements Iterator<SpatialKey> {
private final SpatialKey filter;
private CursorPos pos;
private SpatialKey current;
private final Page root;
private boolean initialized;
protected RTreeCursor(Page root, SpatialKey filter) {
this.root = root;
this.filter = filter;
}
@Override
public boolean hasNext() {
if (!initialized) {
// init
pos = new CursorPos(root, 0, null);
fetchNext();
initialized = true;
}
return current != null;
}
/**
* Skip over that many entries. This method is relatively fast (for this
* map implementation) even if many entries need to be skipped.
*
* @param n the number of entries to skip
*/
public void skip(long n) {
while (hasNext() && n-- > 0) {
fetchNext();
}
}
@Override
public SpatialKey next() {
if (!hasNext()) {
return null;
}
SpatialKey c = current;
fetchNext();
return c;
}
@Override
public void remove() {
throw DataUtils.newUnsupportedOperationException(
"Removing is not supported");
}
/**
* Fetch the next entry if there is one.
*/
protected void fetchNext() {
while (pos != null) {
Page p = pos.page;
if (p.isLeaf()) {
while (pos.index < p.getKeyCount()) {
SpatialKey c = (SpatialKey) p.getKey(pos.index++);
if (filter == null || check(true, c, filter)) {
current = c;
return;
}
}
} else {
boolean found = false;
while (pos.index < p.getKeyCount()) {
int index = pos.index++;
SpatialKey c = (SpatialKey) p.getKey(index);
if (filter == null || check(false, c, filter)) {
Page child = pos.page.getChildPage(index);
pos = new CursorPos(child, 0, pos);
found = true;
break;
}
}
if (found) {
continue;
}
}
// parent
pos = pos.parent;
}
current = null;
}
/**
* Check a given key.
*
* @param leaf if the key is from a leaf page
* @param key the stored key
* @param test the user-supplied test key
* @return true if there is a match
*/
protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) {
return true;
}
}
@Override
public String getType() {
return "rtree";
}
/**
* A builder for this class.
*
* @param <V> the value type
*/
public static class Builder<V> implements
MVMap.MapBuilder<MVRTreeMap<V>, SpatialKey, V> {
private int dimensions = 2;
private DataType valueType;
/**
* Create a new builder for maps with 2 dimensions.
*/
public Builder() {
// default
}
/**
* Set the dimensions.
*
* @param dimensions the dimensions to use
* @return this
*/
public Builder<V> dimensions(int dimensions) {
this.dimensions = dimensions;
return this;
}
/**
* Set the key data type.
*
* @param valueType the key type
* @return this
*/
public Builder<V> valueType(DataType valueType) {
this.valueType = valueType;
return this;
}
@Override
public MVRTreeMap<V> create() {
if (valueType == null) {
valueType = new ObjectDataType();
}
return new MVRTreeMap<V>(dimensions, valueType);
}
}
}