/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.store;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataUtils;
/**
* A spatial data type. This class supports up to 255 dimensions. Each dimension
* can have a minimum and a maximum value of type float. For each dimension, the
* maximum value is only stored when it is not the same as the minimum.
*/
public class SpatialType implements DataType {
private final int dimensions;
private SpatialType(int dimensions) {
if (dimensions <= 0 || dimensions > 255) {
throw new IllegalArgumentException("Dimensions: " + dimensions);
}
this.dimensions = dimensions;
}
/**
* Read a value from a string.
*
* @param s the string
* @return the value
*/
public static SpatialType fromString(String s) {
return new SpatialType(Integer.parseInt(s.substring(1)));
}
@Override
public int compare(Object a, Object b) {
long la = ((SpatialKey) a).getId();
long lb = ((SpatialKey) b).getId();
return la < lb ? -1 : la > lb ? 1 : 0;
}
/**
* Check whether two spatial values are equal.
*
* @param a the first value
* @param b the second value
* @return true if they are equal
*/
public boolean equals(Object a, Object b) {
long la = ((SpatialKey) a).getId();
long lb = ((SpatialKey) b).getId();
return la == lb;
}
@Override
public int getMaxLength(Object obj) {
return 1 + dimensions * 8 + DataUtils.MAX_VAR_LONG_LEN;
}
@Override
public int getMemory(Object obj) {
return 40 + dimensions * 4;
}
@Override
public void write(ByteBuffer buff, Object obj) {
SpatialKey k = (SpatialKey) obj;
int flags = 0;
for (int i = 0; i < dimensions; i++) {
if (k.min(i) == k.max(i)) {
flags |= 1 << i;
}
}
DataUtils.writeVarInt(buff, flags);
for (int i = 0; i < dimensions; i++) {
buff.putFloat(k.min(i));
if ((flags & (1 << i)) == 0) {
buff.putFloat(k.max(i));
}
}
DataUtils.writeVarLong(buff, k.getId());
}
@Override
public Object read(ByteBuffer buff) {
int flags = DataUtils.readVarInt(buff);
float[] minMax = new float[dimensions * 2];
for (int i = 0; i < dimensions; i++) {
float min = buff.getFloat();
float max;
if ((flags & (1 << i)) != 0) {
max = min;
} else {
max = buff.getFloat();
}
minMax[i + i] = min;
minMax[i + i + 1] = max;
}
long id = DataUtils.readVarLong(buff);
return new SpatialKey(id, minMax);
}
@Override
public String asString() {
return "s" + dimensions;
}
/**
* Check whether the two objects overlap.
*
* @param objA the first object
* @param objB the second object
* @return true if they overlap
*/
public boolean isOverlap(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
for (int i = 0; i < dimensions; i++) {
if (a.max(i) < b.min(i) || a.min(i) > b.max(i)) {
return false;
}
}
return true;
}
/**
* Increase the bounds in the given spatial object.
*
* @param bounds the bounds (may be modified)
* @param add the value
*/
public void increaseBounds(Object bounds, Object add) {
SpatialKey b = (SpatialKey) bounds;
SpatialKey a = (SpatialKey) add;
for (int i = 0; i < dimensions; i++) {
b.setMin(i, Math.min(b.min(i), a.min(i)));
b.setMax(i, Math.max(b.max(i), a.max(i)));
}
}
/**
* Get the area increase by extending a to contain b.
*
* @param objA the bounding box
* @param objB the object
* @return the area
*/
public float getAreaIncrease(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
float min = a.min(0);
float max = a.max(0);
float areaOld = max - min;
min = Math.min(min, b.min(0));
max = Math.max(max, b.max(0));
float areaNew = max - min;
for (int i = 1; i < dimensions; i++) {
min = a.min(i);
max = a.max(i);
areaOld *= max - min;
min = Math.min(min, b.min(i));
max = Math.max(max, b.max(i));
areaNew *= max - min;
}
return areaNew - areaOld;
}
/**
* Get the combined area of both objects.
*
* @param objA the first object
* @param objB the second object
* @return the area
*/
float getCombinedArea(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
float area = 1;
for (int i = 0; i < dimensions; i++) {
float min = Math.min(a.min(i), b.min(i));
float max = Math.max(a.max(i), b.max(i));
area *= max - min;
}
return area;
}
/**
* Check whether a contains b.
*
* @param objA the bounding box
* @param objB the object
* @return the area
*/
public boolean contains(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
for (int i = 0; i < dimensions; i++) {
if (a.min(i) > b.min(i) || a.max(i) < b.max(i)) {
return false;
}
}
return true;
}
/**
* Check whether a given object is completely inside and does not touch the
* given bound.
*
* @param objA the object to check
* @param objB the bounds
* @return true if a is completely inside b
*/
public boolean isInside(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
for (int i = 0; i < dimensions; i++) {
if (a.min(i) <= b.min(i) || a.max(i) >= b.max(i)) {
return false;
}
}
return true;
}
/**
* Create a bounding box starting with the given object.
*
* @param objA the object
* @return the bounding box
*/
Object createBoundingBox(Object objA) {
float[] minMax = new float[dimensions * 2];
SpatialKey a = (SpatialKey) objA;
for (int i = 0; i < dimensions; i++) {
minMax[i + i] = a.min(i);
minMax[i + i + 1] = a.max(i);
}
return new SpatialKey(0, minMax);
}
/**
* Get the most extreme pair (elements that are as far apart as possible).
* This method is used to split a page (linear split). If no extreme objects
* could be found, this method returns null.
*
* @param list the objects
* @return the indexes of the extremes
*/
public int[] getExtremes(ArrayList<Object> list) {
SpatialKey bounds = (SpatialKey) createBoundingBox(list.get(0));
SpatialKey boundsInner = (SpatialKey) createBoundingBox(bounds);
for (int i = 0; i < dimensions; i++) {
float t = boundsInner.min(i);
boundsInner.setMin(i, boundsInner.max(i));
boundsInner.setMax(i, t);
}
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
increaseBounds(bounds, o);
increaseMaxInnerBounds(boundsInner, o);
}
double best = 0;
int bestDim = 0;
for (int i = 0; i < dimensions; i++) {
float inner = boundsInner.max(i) - boundsInner.min(i);
if (inner < 0) {
continue;
}
float outer = bounds.max(i) - bounds.min(i);
float d = inner / outer;
if (d > best) {
best = d;
bestDim = i;
}
}
if (best <= 0) {
return null;
}
float min = boundsInner.min(bestDim);
float max = boundsInner.max(bestDim);
int firstIndex = -1, lastIndex = -1;
for (int i = 0; i < list.size() && (firstIndex < 0 || lastIndex < 0); i++) {
SpatialKey o = (SpatialKey) list.get(i);
if (firstIndex < 0 && o.max(bestDim) == min) {
firstIndex = i;
} else if (lastIndex < 0 && o.min(bestDim) == max) {
lastIndex = i;
}
}
return new int[] { firstIndex, lastIndex };
}
private void increaseMaxInnerBounds(Object bounds, Object add) {
SpatialKey b = (SpatialKey) bounds;
SpatialKey a = (SpatialKey) add;
for (int i = 0; i < dimensions; i++) {
b.setMin(i, Math.min(b.min(i), a.max(i)));
b.setMax(i, Math.max(b.max(i), a.min(i)));
}
}
}