package com.revolsys.geometry.index.quadtree;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.Consumer;
import com.revolsys.collection.map.WeakKeyValueMap;
import com.revolsys.geometry.index.AbstractPointSpatialIndex;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.BoundingBoxProxy;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.coordinates.LineSegmentUtil;
import com.revolsys.geometry.model.impl.BoundingBoxDoubleXY;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.util.ExitLoopException;
import com.revolsys.util.Property;
public class PointQuadTree<T> extends AbstractPointSpatialIndex<T> {
private static final WeakKeyValueMap<Geometry, PointQuadTree<int[]>> CACHE = new WeakKeyValueMap<>();
public static PointQuadTree<int[]> get(final Geometry geometry) {
if (Property.hasValue(geometry)) {
PointQuadTree<int[]> index = CACHE.get(geometry);
if (index == null) {
final GeometryFactory geometryFactory = geometry.getGeometryFactory();
index = new PointQuadTree<>(geometryFactory);
for (final Vertex vertex : geometry.vertices()) {
final double x = vertex.getX();
final double y = vertex.getY();
final int[] vertexId = vertex.getVertexId();
index.put(x, y, vertexId);
}
CACHE.put(geometry, index);
}
return index;
} else {
return null;
}
}
private GeometryFactory geometryFactory;
private PointQuadTreeNode<T> root;
public PointQuadTree() {
}
public PointQuadTree(final GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
}
public boolean contains(final Point point) {
if (this.root == null) {
return false;
} else {
return this.root.contains(point);
}
}
public List<Entry<Point, T>> findEntriesWithinDistance(final Point from, final Point to,
final double maxDistance) {
final BoundingBox boundingBox = this.geometryFactory.newBoundingBox(from.getX(), from.getY(),
to.getX(), to.getY());
final List<Entry<Point, T>> entries = new ArrayList<>();
this.root.findEntriesWithin(entries, boundingBox);
for (final Iterator<Entry<Point, T>> iterator = entries.iterator(); iterator.hasNext();) {
final Entry<Point, T> entry = iterator.next();
final Point coordinates = entry.getKey();
final double distance = LineSegmentUtil.distanceLinePoint(from, to, coordinates);
if (distance >= maxDistance) {
iterator.remove();
}
}
return entries;
}
public List<T> findWithin(BoundingBox boundingBox) {
if (this.geometryFactory != null) {
boundingBox = boundingBox.convert(this.geometryFactory);
}
final List<T> results = new ArrayList<>();
if (this.root != null) {
this.root.findWithin(results, boundingBox);
}
return results;
}
public List<T> findWithinDistance(final Point point, final double maxDistance) {
final double x = point.getX();
final double y = point.getY();
BoundingBox envelope = new BoundingBoxDoubleXY(x, y);
envelope = envelope.expand(maxDistance);
final List<T> results = new ArrayList<>();
if (this.root != null) {
this.root.findWithin(results, x, y, maxDistance, envelope);
}
return results;
}
public List<T> findWithinDistance(final Point from, final Point to, final double maxDistance) {
final List<Entry<Point, T>> entries = findEntriesWithinDistance(from, to, maxDistance);
final List<T> results = new ArrayList<>();
for (final Entry<Point, T> entry : entries) {
final T value = entry.getValue();
results.add(value);
}
return results;
}
@Override
public void forEach(final BoundingBoxProxy boundingBoxProxy, final Consumer<? super T> action) {
if (this.root != null) {
final BoundingBox boundingBox = boundingBoxProxy.getBoundingBox();
this.root.forEach(action, boundingBox);
}
}
@Override
public void forEach(final Consumer<? super T> action) {
if (this.root != null) {
try {
this.root.forEach(action);
} catch (final ExitLoopException e) {
}
}
}
public void put(final double x, final double y, final T value) {
final PointQuadTreeNode<T> node = new PointQuadTreeNode<>(value, x, y);
if (this.root == null) {
this.root = node;
} else {
this.root.put(x, y, node);
}
}
@Override
public void put(final Point point, final T value) {
if (!point.isEmpty()) {
final double x = point.getX();
final double y = point.getY();
put(x, y, value);
}
}
public boolean remove(final double x, final double y, final T value) {
if (this.root == null) {
return false;
} else {
this.root = this.root.remove(x, y, value);
// TODO change so it returns if the item was removed
return true;
}
}
@Override
public boolean remove(final Point point, final T value) {
final double x = point.getX();
final double y = point.getY();
return remove(x, y, value);
}
}