package com.revolsys.geometry.index.quadtree;
import java.io.Serializable;
import java.util.Arrays;
import java.util.function.Consumer;
import com.revolsys.geometry.index.DoubleBits;
import com.revolsys.geometry.index.IntervalSize;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.util.BoundingBoxUtil;
import com.revolsys.util.Emptyable;
public abstract class AbstractQuadTreeNode<T> implements Emptyable, Serializable {
private static final long serialVersionUID = 1L;
private final int level;
private double maxX;
private double maxY;
private double minX;
private double minY;
@SuppressWarnings("unchecked")
protected final AbstractQuadTreeNode<T>[] nodes = new AbstractQuadTreeNode[4];
protected AbstractQuadTreeNode() {
this.level = Integer.MIN_VALUE;
}
public AbstractQuadTreeNode(final int level, final double minX, final double minY,
final double maxX, final double maxY) {
this.level = level;
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
}
protected abstract boolean add(final QuadTree<T> tree, final double minX, final double minY,
final double maxX, final double maxY, final T item);
public void clear() {
Arrays.fill(this.nodes, null);
}
private boolean coversBoundingBox(final double minX, final double minY, final double maxX,
final double maxY) {
return BoundingBoxUtil.covers(this.minX, this.minY, this.maxX, this.maxY, minX, minY, maxX,
maxY);
}
public int depth() {
int depth = 0;
for (final AbstractQuadTreeNode<T> node : this.nodes) {
if (node != null) {
final int nodeDepth = node.depth();
if (nodeDepth > depth) {
depth = nodeDepth;
}
}
}
return depth + 1;
}
protected AbstractQuadTreeNode<T> find(final double minX, final double minY, final double maxX,
final double maxY) {
final int subnodeIndex = getSubnodeIndex(minX, minY, maxX, maxY);
if (subnodeIndex == -1) {
return this;
}
final AbstractQuadTreeNode<T> node = this.nodes[subnodeIndex];
if (node != null) {
return node.find(minX, minY, maxX, maxY);
}
return this;
}
protected void forEach(final QuadTree<T> tree, final Consumer<? super T> action) {
forEachItem(tree, action);
for (final AbstractQuadTreeNode<T> node : this.nodes) {
if (node != null) {
node.forEach(tree, action);
}
}
}
protected void forEach(final QuadTree<T> tree, final double x, final double y,
final Consumer<? super T> action) {
if (isSearchMatch(x, y)) {
forEachItem(tree, x, y, action);
for (final AbstractQuadTreeNode<T> node : this.nodes) {
if (node != null) {
node.forEach(tree, x, y, action);
}
}
}
}
protected void forEach(final QuadTree<T> tree, final double minX, final double minY,
final double maxX, final double maxY, final Consumer<? super T> action) {
if (isSearchMatch(minX, minY, maxX, maxY)) {
forEachItem(tree, minX, minY, maxX, maxY, action);
for (final AbstractQuadTreeNode<T> node : this.nodes) {
if (node != null) {
node.forEach(tree, minX, minY, maxX, maxY, action);
}
}
}
}
protected abstract void forEachItem(final QuadTree<T> tree, final Consumer<? super T> action);
protected void forEachItem(final QuadTree<T> tree, final double x, final double y,
final Consumer<? super T> action) {
forEachItem(tree, x, y, x, y, action);
}
protected abstract void forEachItem(final QuadTree<T> tree, final double minX, double minY,
double maxX, double maxY, final Consumer<? super T> action);
private double getCentreX() {
if (isRoot()) {
return 0;
} else {
return (this.minX + this.maxX) / 2;
}
}
private double getCentreY() {
if (isRoot()) {
return 0;
} else {
return (this.minY + this.maxY) / 2;
}
}
public abstract int getItemCount();
private AbstractQuadTreeNode<T> getNode(final QuadTree<T> tree, final double minX,
final double minY, final double maxX, final double maxY) {
final int subnodeIndex = getSubnodeIndex(minX, minY, maxX, maxY);
if (subnodeIndex == -1) {
return this;
} else {
final GeometryFactory geometryFactory = tree.getGeometryFactory();
if (geometryFactory.makeXPreciseFloor((this.maxX - this.minX) / 2) <= geometryFactory
.getResolutionX()) {
return this;
} else {
AbstractQuadTreeNode<T> node = this.nodes[subnodeIndex];
if (node == null) {
node = newSubnode(subnodeIndex);
this.nodes[subnodeIndex] = node;
}
return node.getNode(tree, minX, minY, maxX, maxY);
}
}
}
private int getSubnodeIndex(final double minX, final double minY, final double maxX,
final double maxY) {
final double centreX;
final double centreY;
if (isRoot()) {
centreX = 0;
centreY = 0;
} else {
centreX = (this.minX + this.maxX) / 2;
centreY = (this.minY + this.maxY) / 2;
}
if (minX >= centreX) {
if (minY >= centreY) {
return 3;
} else if (maxY <= centreY) {
return 1;
}
} else if (maxX <= centreX) {
if (minY >= centreY) {
return 2;
} else if (maxY <= centreY) {
return 0;
}
}
return -1;
}
private boolean hasChildren() {
for (final AbstractQuadTreeNode<T> node : this.nodes) {
if (node != null) {
return true;
}
}
return false;
}
private boolean hasItems() {
return getItemCount() > 0;
}
private boolean insertContained(final QuadTree<T> tree, final double minX, final double minY,
final double maxX, final double maxY, final T item) {
final boolean isZeroX = IntervalSize.isZeroWidth(maxX, minX);
final boolean isZeroY = IntervalSize.isZeroWidth(maxY, minY);
AbstractQuadTreeNode<T> node;
if (isZeroX || isZeroY) {
node = find(minX, minY, maxX, maxY);
} else {
node = getNode(tree, minX, minY, maxX, maxY);
}
return node.add(tree, minX, minY, maxX, maxY, item);
}
private void insertNode(final AbstractQuadTreeNode<T> node) {
final int index = getSubnodeIndex(node.minX, node.minY, node.maxX, node.maxY);
if (node.level == this.level - 1) {
this.nodes[index] = node;
} else {
final AbstractQuadTreeNode<T> childNode = newSubnode(index);
childNode.insertNode(node);
this.nodes[index] = childNode;
}
}
protected boolean insertRoot(final QuadTree<T> tree, final double minX, final double minY,
final double maxX, final double maxY, final T item) {
final int index = getSubnodeIndex(minX, minY, maxX, maxY);
if (index == -1) {
return add(tree, minX, minY, maxX, maxY, item);
} else {
AbstractQuadTreeNode<T> node = this.nodes[index];
if (node == null) {
final AbstractQuadTreeNode<T> newNode = newNode(tree, minX, minY, maxX, maxY);
this.nodes[index] = newNode;
node = newNode;
} else if (!node.coversBoundingBox(minX, minY, maxX, maxY)) {
final AbstractQuadTreeNode<T> newNode = node.newNodeExpanded(tree, minX, minY, maxX, maxY);
this.nodes[index] = newNode;
node = newNode;
}
return node.insertContained(tree, minX, minY, maxX, maxY, item);
}
}
@Override
public boolean isEmpty() {
boolean isEmpty = !hasItems();
for (final AbstractQuadTreeNode<T> node : this.nodes) {
if (node != null) {
if (!node.isEmpty()) {
isEmpty = false;
}
}
}
return isEmpty;
}
private boolean isPrunable() {
return !(hasChildren() || hasItems());
}
private boolean isRoot() {
return this.level == Integer.MIN_VALUE;
}
private boolean isSearchMatch(final double x, final double y) {
if (isRoot()) {
return true;
} else {
return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY;
}
}
private boolean isSearchMatch(final double minX, final double minY, final double maxX,
final double maxY) {
if (isRoot()) {
return true;
} else {
return !(minX > this.maxX || maxX < this.minX || minY > this.maxY || maxY < this.minY);
}
}
protected abstract AbstractQuadTreeNode<T> newNode(int level, double minX, final double minY,
double maxX, final double maxY);
private AbstractQuadTreeNode<T> newNode(final QuadTree<T> tree, double minX, double minY,
final double maxX, final double maxY) {
double deltaX = maxX - minX;
if (deltaX == 0) {
deltaX = tree.getMinExtentTimes2();
minX -= tree.getMinExtent();
}
double deltaY = maxY - minY;
if (deltaY == 0) {
deltaY = tree.getMinExtentTimes2();
minY -= tree.getMinExtent();
}
int level;
if (deltaX > deltaY) {
level = DoubleBits.exponent(deltaX);
} else {
level = DoubleBits.exponent(deltaY);
}
double quadSize = DoubleBits.powerOf2(level);
double newMinX;
double newMinY;
double newMaxX;
double newMaxY;
do {
level++;
quadSize *= 2;
newMinX = Math.floor(minX / quadSize) * quadSize;
newMinY = Math.floor(minY / quadSize) * quadSize;
newMaxX = newMinX + quadSize;
newMaxY = newMinY + quadSize;
} while (!BoundingBoxUtil.covers(newMinX, newMinY, newMaxX, newMaxY, minX, minY, maxX, maxY));
return newNode(level, newMinX, newMinY, newMaxX, newMaxY);
}
private AbstractQuadTreeNode<T> newNodeExpanded(final QuadTree<T> tree, double minX, double minY,
double maxX, double maxY) {
if (this.minX < minX) {
minX = this.minX;
}
if (this.maxX > maxX) {
maxX = this.maxX;
}
if (this.minY < minY) {
minY = this.minY;
}
if (this.maxY > maxY) {
maxY = this.maxY;
}
final AbstractQuadTreeNode<T> newNode = newNode(tree, minX, minY, maxX, maxY);
newNode.insertNode(this);
return newNode;
}
private AbstractQuadTreeNode<T> newSubnode(final int index) {
// Construct a new new subquad in the appropriate quadrant
double minX = 0.0;
double maxX = 0.0;
double minY = 0.0;
double maxY = 0.0;
final double centreX = getCentreX();
final double centreY = getCentreY();
switch (index) {
case 0:
minX = this.minX;
maxX = centreX;
minY = this.minY;
maxY = centreY;
break;
case 1:
minX = centreX;
maxX = this.maxX;
minY = this.minY;
maxY = centreY;
break;
case 2:
minX = this.minX;
maxX = centreX;
minY = centreY;
maxY = this.maxY;
break;
case 3:
minX = centreX;
maxX = this.maxX;
minY = centreY;
maxY = this.maxY;
break;
}
final AbstractQuadTreeNode<T> node = newNode(this.level - 1, minX, minY, maxX, maxY);
return node;
}
protected boolean removeItem(final QuadTree<T> tree, final double minX, final double minY,
final double maxX, final double maxY, final T item) {
final boolean removed = false;
if (isSearchMatch(minX, minY, maxX, maxY)) {
final AbstractQuadTreeNode<T>[] nodes = this.nodes;
final int nodeCount = nodes.length;
for (int i = 0; i < nodeCount; i++) {
final AbstractQuadTreeNode<T> node = nodes[i];
if (node != null) {
if (node.removeItem(tree, minX, minY, maxX, maxY, item)) {
if (node.isPrunable()) {
nodes[i] = null;
}
return true;
}
}
}
if (removeItem(tree, item)) {
return true;
}
}
return removed;
}
protected abstract boolean removeItem(final QuadTree<T> tree, final T item);
@Override
public String toString() {
return this.level + " BBOX(" + this.minX + " " + this.minY + "," + this.maxX + " " + this.maxY
+ ") " + getItemCount();
}
}