package com.revolsys.geometry.index.quadtree;
import java.io.Serializable;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import com.revolsys.geometry.index.SpatialIndex;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.util.ExitLoopException;
import com.revolsys.visitor.CreateListVisitor;
import com.revolsys.visitor.SingleObjectVisitor;
public class QuadTree<T> implements SpatialIndex<T>, Serializable {
private static final long serialVersionUID = 1L;
public static double[] ensureExtent(final double[] bounds, final double minExtent) {
double minX = bounds[0];
double maxX = bounds[2];
double minY = bounds[1];
double maxY = bounds[3];
if (minX != maxX && minY != maxY) {
return bounds;
} else {
if (minX == maxX) {
minX = minX - minExtent / 2.0;
maxX = minX + minExtent / 2.0;
}
if (minY == maxY) {
minY = minY - minExtent / 2.0;
maxY = minY + minExtent / 2.0;
}
return new double[] {
minX, minY, maxX, maxY
};
}
}
private GeometryFactory geometryFactory = GeometryFactory.DEFAULT_3D;
private double minExtent;
private final double absoluteMinExtent;
private double minExtentTimes2;
private AbstractQuadTreeNode<T> root;
private int size = 0;
private boolean useEquals = false;
public QuadTree(final GeometryFactory geometryFactory) {
this(geometryFactory, new QuadTreeNode<>());
}
protected QuadTree(final GeometryFactory geometryFactory, final AbstractQuadTreeNode<T> root) {
if (geometryFactory == null) {
this.geometryFactory = GeometryFactory.DEFAULT_3D;
} else {
this.geometryFactory = geometryFactory;
}
if (this.geometryFactory.isFloating()) {
this.absoluteMinExtent = 0.00000001;
} else {
this.absoluteMinExtent = this.geometryFactory.getResolutionX();
}
if (this.absoluteMinExtent < 0.5) {
this.minExtent = 0.5;
} else {
this.minExtent = this.absoluteMinExtent;
}
this.minExtentTimes2 = this.minExtent * 2;
this.root = root;
}
public void clear() {
this.root.clear();
this.size = 0;
}
public int depth() {
return this.root.depth();
}
protected boolean equalsItem(final T item1, final T item2) {
if (item1 == item2) {
return true;
} else if (this.useEquals) {
return item1.equals(item2);
} else {
return false;
}
}
// TODO forEach and remove in one call
@Override
public void forEach(final Consumer<? super T> action) {
try {
this.root.forEach(this, action);
} catch (final ExitLoopException e) {
}
}
@Override
public void forEach(final double x, final double y, final Consumer<? super T> action) {
this.root.forEach(this, x, y, action);
}
@Override
public void forEach(final double minX, final double minY, final double maxX, final double maxY,
final Consumer<? super T> action) {
this.root.forEach(this, minX, minY, maxX, maxY, action);
}
public List<T> getAll() {
final CreateListVisitor<T> visitor = new CreateListVisitor<>();
forEach(visitor);
return visitor.getList();
}
public T getFirst(final BoundingBox boundingBox, final Predicate<T> filter) {
final SingleObjectVisitor<T> visitor = new SingleObjectVisitor<>(filter);
forEach(boundingBox, visitor);
return visitor.getObject();
}
public T getFirstBoundingBox(final Geometry geometry, final Predicate<T> filter) {
if (geometry == null) {
return null;
} else {
final BoundingBox boundingBox = geometry.getBoundingBox();
return getFirst(boundingBox, filter);
}
}
@Override
public GeometryFactory getGeometryFactory() {
return this.geometryFactory;
}
public double getMinExtent() {
return this.minExtent;
}
public double getMinExtentTimes2() {
return this.minExtentTimes2;
}
@Override
public int getSize() {
return this.size;
}
@Override
public void insertItem(final BoundingBox boundingBox, final T item) {
final BoundingBox convertedBoundingBox = convertBoundingBox(boundingBox);
if (convertedBoundingBox == null || convertedBoundingBox.isEmpty()) {
throw new IllegalArgumentException("Item bounding box " + boundingBox
+ " must not be null or empty in coordinate system: " + getCoordinateSystemId());
} else {
final double minX = convertedBoundingBox.getMinX();
final double minY = convertedBoundingBox.getMinY();
final double maxX = convertedBoundingBox.getMaxX();
final double maxY = convertedBoundingBox.getMaxY();
insertItem(minX, minY, maxX, maxY, item);
}
}
public void insertItem(final double minX, final double minY, final double maxX, final double maxY,
final T item) {
final double delX = maxX - minX;
if (delX < this.minExtent && delX > 0) {
this.minExtent = this.geometryFactory.makeXyPrecise(delX);
if (this.minExtent < this.absoluteMinExtent) {
this.minExtent = this.absoluteMinExtent;
this.minExtentTimes2 = this.minExtent * 2;
}
}
final double delY = maxY - minY;
if (delY < this.minExtent & delY > 0) {
this.minExtent = this.geometryFactory.makeXyPrecise(delY);
if (this.minExtent < this.absoluteMinExtent) {
this.minExtent = this.absoluteMinExtent;
this.minExtentTimes2 = this.minExtent * 2;
}
}
if (this.root.insertRoot(this, minX, minY, maxX, maxY, item)) {
this.size++;
}
}
public void insertItem(final double x, final double y, final T item) {
insertItem(x, y, x, y, item);
}
public List<T> queryBoundingBox(final Geometry geometry) {
return getItems(geometry);
}
@Override
public boolean removeItem(BoundingBox boundingBox, final T item) {
boundingBox = convertBoundingBox(boundingBox);
if (boundingBox != null && !boundingBox.isEmpty()) {
final double minX = boundingBox.getMinX();
final double minY = boundingBox.getMinY();
final double maxX = boundingBox.getMaxX();
final double maxY = boundingBox.getMaxY();
return removeItem(minX, minY, maxX, maxY, item);
} else {
return false;
}
}
public boolean removeItem(final double minX, final double minY, final double maxX,
final double maxY, final T item) {
final boolean removed = this.root.removeItem(this, minX, minY, maxX, maxY, item);
if (removed) {
this.size--;
}
return removed;
}
public void setGeometryFactory(final GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
}
public void setUseEquals(final boolean useEquals) {
this.useEquals = useEquals;
}
public int size() {
return getSize();
}
}