package org.h2.mvstore.rtree;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.type.DataType;
public class SpatialDataType implements DataType {
private final int dimensions;
public SpatialDataType(int dimensions) {
DataUtils.checkArgument(
dimensions >= 1 && dimensions < 32,
"Dimensions must be between 1 and 31, is {0}", dimensions);
this.dimensions = dimensions;
}
@Override
public int compare(Object a, Object b) {
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
}
long la = ((SpatialKey) a).getId();
long lb = ((SpatialKey) b).getId();
return Long.compare(la, lb);
}
public boolean equals(Object a, Object b) {
if (a == b) {
return true;
} else if (a == null || b == null) {
return false;
}
long la = ((SpatialKey) a).getId();
long lb = ((SpatialKey) b).getId();
return la == lb;
}
@Override
public int getMemory(Object obj) {
return 40 + dimensions * 4;
}
@Override
public void read(ByteBuffer buff, Object[] obj, int len, boolean key) {
for (int i = 0; i < len; i++) {
obj[i] = read(buff);
}
}
@Override
public void write(WriteBuffer buff, Object[] obj, int len, boolean key) {
for (int i = 0; i < len; i++) {
write(buff, obj[i]);
}
}
@Override
public void write(WriteBuffer buff, Object obj) {
SpatialKey k = (SpatialKey) obj;
if (k.isNull()) {
buff.putVarInt(-1);
buff.putVarLong(k.getId());
return;
}
int flags = 0;
for (int i = 0; i < dimensions; i++) {
if (k.min(i) == k.max(i)) {
flags |= 1 << i;
}
}
buff.putVarInt(flags);
for (int i = 0; i < dimensions; i++) {
buff.putFloat(k.min(i));
if ((flags & (1 << i)) == 0) {
buff.putFloat(k.max(i));
}
}
buff.putVarLong(k.getId());
}
@Override
public Object read(ByteBuffer buff) {
int flags = DataUtils.readVarInt(buff);
if (flags == -1) {
long id = DataUtils.readVarLong(buff);
return new SpatialKey(id);
}
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);
}
public boolean isOverlap(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
if (a.isNull() || b.isNull()) {
return false;
}
for (int i = 0; i < dimensions; i++) {
if (a.max(i) < b.min(i) || a.min(i) > b.max(i)) {
return false;
}
}
return true;
}
public void increaseBounds(Object bounds, Object add) {
SpatialKey a = (SpatialKey) add;
SpatialKey b = (SpatialKey) bounds;
if (a.isNull() || b.isNull()) {
return;
}
for (int i = 0; i < dimensions; i++) {
float v = a.min(i);
if (v < b.min(i)) {
b.setMin(i, v);
}
v = a.max(i);
if (v > b.max(i)) {
b.setMax(i, v);
}
}
}
public float getAreaIncrease(Object objA, Object objB) {
SpatialKey b = (SpatialKey) objB;
SpatialKey a = (SpatialKey) objA;
if (a.isNull() || b.isNull()) {
return 0;
}
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;
}
float getCombinedArea(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
if (a.isNull()) {
return getArea(b);
} else if (b.isNull()) {
return getArea(a);
}
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;
}
private float getArea(SpatialKey a) {
if (a.isNull()) {
return 0;
}
float area = 1;
for (int i = 0; i < dimensions; i++) {
area *= a.max(i) - a.min(i);
}
return area;
}
public boolean contains(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
if (a.isNull() || b.isNull()) {
return false;
}
for (int i = 0; i < dimensions; i++) {
if (a.min(i) > b.min(i) || a.max(i) < b.max(i)) {
return false;
}
}
return true;
}
public boolean isInside(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
if (a.isNull() || b.isNull()) {
return false;
}
for (int i = 0; i < dimensions; i++) {
if (a.min(i) <= b.min(i) || a.max(i) >= b.max(i)) {
return false;
}
}
return true;
}
Object createBoundingBox(Object objA) {
SpatialKey a = (SpatialKey) objA;
if (a.isNull()) {
return a;
}
return new SpatialKey(0, a);
}
public int[] getExtremes(ArrayList<Object> list) {
list = getNotNull(list);
if (list.isEmpty()) {
return null;
}
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 (Object o : list) {
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 static ArrayList<Object> getNotNull(ArrayList<Object> list) {
boolean foundNull = false;
for (Object o : list) {
SpatialKey a = (SpatialKey) o;
if (a.isNull()) {
foundNull = true;
break;
}
}
if (!foundNull) {
return list;
}
ArrayList<Object> result = new ArrayList<>();
for (Object o : list) {
SpatialKey a = (SpatialKey) o;
if (!a.isNull()) {
result.add(a);
}
}
return result;
}
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)));
}
}
}