org.danilopianini.util.FlexibleQuadTree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-quadtree Show documentation
Show all versions of java-quadtree Show documentation
Java spatial indexing tools
/*******************************************************************************
* Copyright (C) 2009-2017, Danilo Pianini and contributors
* listed in the project's build.gradle or pom.xml file.
*
* This file is distributed under the terms of the Apache License, version 2.0
*******************************************************************************/
package org.danilopianini.util;
import static java.lang.Math.ceil;
import static java.lang.Math.floor;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.nextDown;
import static java.lang.Math.nextUp;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nonnull;
import com.google.common.base.Optional;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
/**
*
* @param
*/
public final class FlexibleQuadTree implements SpatialIndex {
/**
* Default maximum number of entries per node.
*/
public static final int DEFAULT_CAPACITY = 10;
private static final long serialVersionUID = 0L;
private Rectangle2D bounds;
private final List>> children = new ArrayList<>(Collections.nCopies(4, Optional.absent()));
private boolean childrenCreated;
private final Deque> elements;
private final int maxElements;
private FlexibleQuadTree parent;
/**
* root is NOT consistent everywhere. It is only guaranteed to be consistent
* in the entry point node and in the current root.
*/
private FlexibleQuadTree root;
/**
* Builds a {@link FlexibleQuadTree} with the default node capacity.
*/
public FlexibleQuadTree() {
this(DEFAULT_CAPACITY);
}
private FlexibleQuadTree(
final double minx, final double maxx, final double miny, final double maxy,
final int elemPerQuad, final FlexibleQuadTree rootNode, final FlexibleQuadTree parentNode) {
this(elemPerQuad, rootNode, parentNode);
bounds = new Rectangle2D(minx, miny, maxx, maxy);
}
private FlexibleQuadTree(final int elemPerQuad, final FlexibleQuadTree rootNode, final FlexibleQuadTree parentNode) {
if (elemPerQuad < 2) {
throw new IllegalArgumentException("At least two elements per quadtree are required for this index to work properly");
}
elements = new LinkedList<>();
maxElements = elemPerQuad;
parent = parentNode;
root = rootNode == null ? this : rootNode;
}
/**
* @param elemPerQuad
* maximum number of elements per quad
*/
public FlexibleQuadTree(final int elemPerQuad) {
this(elemPerQuad, null, null);
}
private double centerX() {
return bounds.getCenterX();
}
private double centerY() {
return bounds.getCenterY();
}
private boolean contains(final double x, final double y) {
return bounds == null || bounds.contains(x, y);
}
private FlexibleQuadTree create(
final double minx, final double maxx, final double miny, final double maxy,
final FlexibleQuadTree father) {
return new FlexibleQuadTree(minx, maxx, miny, maxy, getMaxElementsNumber(), root, father);
}
private void createChildIfAbsent(final Child c) {
if (!children.get(c.ordinal()).isPresent()) {
setChild(c, create(minX(c), maxX(c), minY(c), maxY(c), this));
}
}
private void createParent(final double x, final double y) {
/*
* Determine where the parent should be
*/
if (x < centerX()) {
final double minx = 2 * minX() - maxX();
if (y < centerY()) {
/*
* This will be TR child of the new parent
*/
root = create(minx, maxX(), 2 * minY() - maxY(), maxY(), null);
root.setChild(Child.TR, this);
} else {
/*
* This will be BR child of the new parent
*/
root = create(minx, maxX(), minY(), 2 * maxY() - minY(), null);
root.setChild(Child.BR, this);
}
} else {
final double maxx = 2 * maxX() - minX();
if (y < centerY()) {
/*
* This will be TL child of the new parent
*/
root = create(minX(), maxx, 2 * minY() - maxY(), maxY(), null);
root.setChild(Child.TL, this);
} else {
/*
* This will be BL child of the new parent
*/
root = create(minX(), maxx, minY(), 2 * maxY() - minY(), null);
root.setChild(Child.BL, this);
}
}
/*
* A bit cryptic, but the root of the new root is the root itself.
* Otherwise, the root would point to the previous root.
*/
root.root = root;
root.subdivide();
}
private FlexibleQuadTree getChild(final Child c) {
return children.get(c.ordinal()).get();
}
@Override
public int getDimensions() {
return 2;
}
/**
* @return the maximum number of elements per node
*/
public int getMaxElementsNumber() {
return maxElements;
}
private boolean hasChildren() {
if (!childrenCreated) {
childrenCreated = children.stream().allMatch(Optional::isPresent);
}
return childrenCreated;
}
private boolean hasSpace() {
return elements.size() < maxElements;
}
@Override
public void insert(final E e, final double... pos) {
assert pos.length == 2;
insert(e, pos[0], pos[1]);
}
/**
* Same of {@link #insert(Object, double...)}, but with explicit parameters.
*
* @param e
* element
* @param x
* X
* @param y
* Y
*/
public void insert(final E e, final double x, final double y) {
if (bounds == null) {
if (hasSpace()) {
insertNode(e, x, y);
return;
}
double minx, miny, maxx, maxy;
minx = miny = Double.POSITIVE_INFINITY;
maxx = maxy = Double.NEGATIVE_INFINITY;
for (final QuadTreeEntry element: elements) {
minx = min(minx, element.x);
miny = min(miny, element.y);
maxx = max(maxx, element.x);
maxy = max(maxy, element.y);
}
assert Double.isFinite(minx);
assert Double.isFinite(maxx);
assert Double.isFinite(miny);
assert Double.isFinite(maxy);
bounds = new Rectangle2D(floor(nextDown(minx)), floor(nextDown(miny)), ceil(nextUp(maxx)), ceil(nextUp(maxy)));
}
/*
* I must insert starting from the root. If the root does not contain
* the coordinates, then the tree should be expanded upwards
*/
for (; !root.contains(x, y); root = root.root) {
root.createParent(x, y);
}
root.insertHere(e, x, y);
}
private void insertHere(final E e, final double x, final double y) {
if (hasSpace()) {
insertNode(e, x, y);
} else {
if (!hasChildren()) {
subdivide();
}
selectChild(x, y).insertHere(e, x, y);
}
}
private void insertNode(@Nonnull final E e, final double x, final double y) {
assert elements.size() < maxElements : "Bug in " + getClass() + ". Forced insertion over the container size.";
elements.push(new QuadTreeEntry<>(e, x, y));
}
private double maxX() {
return bounds.getMaxX();
}
private double maxX(final Child c) {
switch (c) {
case TR:
case BR:
return maxX();
case BL:
case TL:
return centerX();
default:
throw new IllegalStateException();
}
}
private double maxY() {
return bounds.getMaxY();
}
private double maxY(final Child c) {
switch (c) {
case BL:
case BR:
return centerY();
case TR:
case TL:
return maxY();
default:
throw new IllegalStateException();
}
}
private double minX() {
return bounds.getMinX();
}
private double minX(final Child c) {
switch (c) {
case TR:
case BR:
return centerX();
case BL:
case TL:
return minX();
default:
throw new IllegalStateException();
}
}
private double minY() {
return bounds.getMinY();
}
private double minY(final Child c) {
switch (c) {
case BL:
case BR:
return minY();
case TR:
case TL:
return centerY();
default:
throw new IllegalStateException();
}
}
/**
* Same of {@link #move(Object, double[], double[])}, but with explicit
* parameters.
*
* @param e
* the element
* @param sx
* the start x
* @param sy
* the start y
* @param fx
* the final x
* @param fy
* the final y
* @return true if the element is found and no error occurred
*/
public boolean move(final E e, final double sx, final double sy, final double fx, final double fy) {
return moveFromNode(root, e, sx, sy, fx, fy);
}
@Override
public boolean move(final E e, final double[] start, final double[] end) {
assert start.length == 2;
assert end.length == 2;
return move(e, start[0], start[1], end[0], end[1]);
}
private boolean moveFromNode(
final FlexibleQuadTree root,
final E e,
final double sx, final double sy,
final double fx, final double fy) {
final QuadTreeEntry toRemove = new QuadTreeEntry<>(e, sx, sy);
for (FlexibleQuadTree cur = root; cur.contains(sx, sy); cur = cur.selectChild(sx, sy)) {
if (cur.elements.remove(toRemove)) {
/*
* Node found.
*/
if (cur.contains(fx, fy)) {
/*
* Moved within the same quadrant.
*/
cur.insertNode(e, fx, fy);
} else if (cur.parent == null
|| !cur.parent.contains(fx, fy)
|| !cur.swapMostStatic(e, fx, fy)) {
/*
* In case:
* - we are the root
* - we moved outside the parent's area
* - the swapping operation failed
*/
insert(e, fx, fy);
}
return true;
}
if (!cur.hasChildren()) {
return false;
}
}
return false;
}
/**
* Same of {@link #query(double[]...)}, but with explicit parameters.
*
* @param x1
* Rectangle X coordinate of the first point
* @param y1
* Rectangle Y coordinate of the first point
* @param x2
* Rectangle X coordinate of the second point
* @param y2
* Rectangle Y coordinate of the second point
* @return {@link List} of Objects in range.
*/
public List query(final double x1, final double y1, final double x2, final double y2) {
final List result = new ArrayList<>();
final double sx = min(x1, x2);
final double sy = min(y1, y2);
final double fx = max(x1, x2);
final double fy = max(y1, y2);
root.query(sx, sy, fx, fy, result);
return result;
}
private void query(// NOPMD: False positive
final double sx, final double sy, final double fx, final double fy, final List results) {
assert !(bounds == null && hasChildren());
if (bounds == null || bounds.intersects(sx, sy, fx, fy)) {
for (final QuadTreeEntry entry : elements) {
if (entry.isIn(sx, sy, fx, fy)) {
results.add(entry.element);
}
}
if (hasChildren()) {
for (final Optional> childOpt: children) {
childOpt.get().query(sx, sy, fx, fy, results);
}
}
}
}
@Override
public List query(final double[]... space) {
if (space.length != 2 || space[0].length != 2 || space[1].length != 2) {
throw new IllegalArgumentException();
}
return query(space[0][0], space[0][1], space[1][0], space[1][1]);
}
@Override
public boolean remove(final E e, final double... pos) {
assert pos.length == 2;
return remove(e, pos[0], pos[1]);
}
/**
* Same of {@link #remove(Object, double...)} with explicit parameters.
*
* @param e
* Element to remove
* @param x
* X position of the element
* @param y
* Y position of the element
* @return true if the element has been found and removed
*/
public boolean remove(final E e, final double x, final double y) {
return root.removeHere(e, x, y);
}
private boolean removeHere(final E e, final double x, final double y) {
if (contains(x, y)) {
return elements.remove(new QuadTreeEntry<>(e, x, y)) || removeInChildren(e, x, y);
}
return false;
}
private boolean removeInChildren(final E e, final double x, final double y) {
return children.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.anyMatch(c -> c.removeHere(e, x, y));
}
private FlexibleQuadTree selectChild(final double x, final double y) {
assert hasChildren();
if (x < centerX()) {
if (y < centerY()) {
return getChild(Child.BL);
}
return getChild(Child.TL);
} else {
if (y < centerY()) {
return getChild(Child.BR);
}
return getChild(Child.TR);
}
}
private void setChild(final Child c, final FlexibleQuadTree child) {
if (children.set(c.ordinal(), Optional.of(child)).isPresent()) {
throw new IllegalStateException();
}
child.parent = this;
}
private void subdivide() {
for (final Child c : Child.values()) {
createChildIfAbsent(c);
}
}
private boolean swapMostStatic(final E e, final double fx, final double fy) {
assert parent != null : "Tried to swap on a null parent.";
final Iterator> iterator = parent.elements.descendingIterator();
while (iterator.hasNext()) {
final QuadTreeEntry target = iterator.next();
if (contains(target.x, target.y)) {
/*
* There is a swappable node
*/
iterator.remove();
elements.push(target);
parent.insertNode(e, fx, fy);
return true;
}
}
return false;
}
@Override
public String toString() {
return (bounds == null ? "Unbounded" : bounds.toString()) + ":" + elements.toString();
}
private enum Child {
TR, BR, BL, TL;
}
private static class QuadTreeEntry implements Serializable {
private static final long serialVersionUID = 9021533648086596986L;
private final E element;
private final double x, y;
QuadTreeEntry(final E el, final double xp, final double yp) {
element = el;
x = xp;
y = yp;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof QuadTreeEntry>) {
final QuadTreeEntry> e = (QuadTreeEntry>) obj;
if (samePosition(e)) {
return element == e.element || element != null && element.equals(e.element);
}
return false;
}
return false;
}
@Override
public int hashCode() {
final Hasher hasher = Hashing.murmur3_32().newHasher();
hasher.putDouble(x);
hasher.putDouble(y);
hasher.putInt(element.hashCode());
return hasher.hash().asInt();
}
public boolean isIn(final double sx, final double sy, final double fx, final double fy) {
return x >= sx && x < fx && y >= sy && y < fy;
}
public boolean samePosition(final QuadTreeEntry> target) {
return x == target.x && y == target.y;
}
@Override
public String toString() {
return element.toString() + "@[" + x + ", " + y + "]";
}
}
private static class Rectangle2D implements Serializable {
private static final long serialVersionUID = -7890062202005580979L;
private final double minx, miny, maxx, maxy;
Rectangle2D(final double sx, final double sy, final double fx, final double fy) {
minx = min(sx, fx);
miny = min(sy, fy);
maxx = max(sx, fx);
maxy = max(sy, fy);
}
public boolean contains(final double x, final double y) {
return x >= minx && y >= miny && x < maxx && y < maxy;
}
public double getCenterX() {
return minx + (maxx - minx) / 2;
}
public double getCenterY() {
return miny + (maxy - miny) / 2;
}
public double getMaxX() {
return maxx;
}
public double getMaxY() {
return maxy;
}
public double getMinX() {
return minx;
}
public double getMinY() {
return miny;
}
public boolean intersects(final double sx, final double sy, final double fx, final double fy) {
return fx >= minx && fy >= miny && sx < maxx && sy < maxy;
}
@Override
public String toString() {
return "[" + minx + "," + miny + " - " + maxx + "," + maxy + "]";
}
}
}