package com.revolsys.geometry.index.quadtree; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.function.Consumer; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.util.MathUtil; public class PointQuadTreeNode<T> { private PointQuadTreeNode<T> northEast; private PointQuadTreeNode<T> northWest; private PointQuadTreeNode<T> southEast; private PointQuadTreeNode<T> southWest; private final T value; private final double x; private final double y; public PointQuadTreeNode(final T value, final double x, final double y) { this.value = value; this.x = x; this.y = y; } public boolean contains(final Point point) { if (point.equalsVertex(this.x, this.y)) { return true; } final boolean xLess = isLessThanX(point.getX()); final boolean yLess = isLessThanY(point.getY()); if (this.southWest != null && xLess && yLess) { if (this.southWest.contains(point)) { return true; } } if (this.northWest != null && xLess && !yLess) { if (this.northWest.contains(point)) { return true; } } if (this.southEast != null && !xLess && yLess) { if (this.southEast.contains(point)) { return true; } } if (this.northEast != null && !xLess && !yLess) { if (this.northEast.contains(point)) { return true; } } return false; } public void findEntriesWithin(final List<Entry<Point, T>> results, final BoundingBox envelope) { final double minX = envelope.getMinX(); final double maxX = envelope.getMaxX(); final double minY = envelope.getMinY(); final double maxY = envelope.getMaxY(); if (envelope.covers(this.x, this.y)) { final Point point = new PointDoubleXY(this.x, this.y); results.add(new SimpleImmutableEntry<>(point, this.value)); } final boolean minXLess = isLessThanX(minX); final boolean maxXLess = isLessThanX(maxX); final boolean minYLess = isLessThanY(minY); final boolean maxYLess = isLessThanY(maxY); if (this.southWest != null && minXLess && minYLess) { this.southWest.findEntriesWithin(results, envelope); } if (this.northWest != null && minXLess && !maxYLess) { this.northWest.findEntriesWithin(results, envelope); } if (this.southEast != null && !maxXLess && minYLess) { this.southEast.findEntriesWithin(results, envelope); } if (this.northEast != null && !maxXLess && !maxYLess) { this.northEast.findEntriesWithin(results, envelope); } } public void findWithin(final List<T> results, final BoundingBox envelope) { final double minX = envelope.getMinX(); final double maxX = envelope.getMaxX(); final double minY = envelope.getMinY(); final double maxY = envelope.getMaxY(); if (envelope.covers(this.x, this.y)) { results.add(this.value); } final boolean minXLess = isLessThanX(minX); final boolean maxXLess = isLessThanX(maxX); final boolean minYLess = isLessThanY(minY); final boolean maxYLess = isLessThanY(maxY); if (this.southWest != null && minXLess && minYLess) { this.southWest.findWithin(results, envelope); } if (this.northWest != null && minXLess && !maxYLess) { this.northWest.findWithin(results, envelope); } if (this.southEast != null && !maxXLess && minYLess) { this.southEast.findWithin(results, envelope); } if (this.northEast != null && !maxXLess && !maxYLess) { this.northEast.findWithin(results, envelope); } } public void findWithin(final List<T> results, final double x, final double y, final double maxDistance, final BoundingBox envelope) { final double minX = envelope.getMinX(); final double maxX = envelope.getMaxX(); final double minY = envelope.getMinY(); final double maxY = envelope.getMaxY(); final double distance = MathUtil.distance(x, y, this.x, this.y); if (distance < maxDistance) { results.add(this.value); } final boolean minXLess = isLessThanX(minX); final boolean maxXLess = isLessThanX(maxX); final boolean minYLess = isLessThanY(minY); final boolean maxYLess = isLessThanY(maxY); if (this.southWest != null && minXLess && minYLess) { this.southWest.findWithin(results, x, y, maxDistance, envelope); } if (this.northWest != null && minXLess && !maxYLess) { this.northWest.findWithin(results, x, y, maxDistance, envelope); } if (this.southEast != null && !maxXLess && minYLess) { this.southEast.findWithin(results, x, y, maxDistance, envelope); } if (this.northEast != null && !maxXLess && !maxYLess) { this.northEast.findWithin(results, x, y, maxDistance, envelope); } } public boolean forEach(final Consumer<? super T> action) { action.accept(this.value); if (this.southWest != null) { if (!this.southWest.forEach(action)) { return false; } } if (this.northWest != null) { if (!this.northWest.forEach(action)) { return false; } } if (this.southEast != null) { if (!this.southEast.forEach(action)) { return false; } } if (this.northEast != null) { if (!this.northEast.forEach(action)) { return false; } } return true; } public boolean forEach(final Consumer<? super T> action, final BoundingBox envelope) { final double minX = envelope.getMinX(); final double maxX = envelope.getMaxX(); final double minY = envelope.getMinY(); final double maxY = envelope.getMaxY(); if (envelope.covers(this.x, this.y)) { action.accept(this.value); } final boolean minXLess = isLessThanX(minX); final boolean maxXLess = isLessThanX(maxX); final boolean minYLess = isLessThanY(minY); final boolean maxYLess = isLessThanY(maxY); if (this.southWest != null && minXLess && minYLess) { if (!this.southWest.forEach(action, envelope)) { return false; } } if (this.northWest != null && minXLess && !maxYLess) { if (!this.northWest.forEach(action, envelope)) { return false; } } if (this.southEast != null && !maxXLess && minYLess) { if (!this.southEast.forEach(action, envelope)) { return false; } } if (this.northEast != null && !maxXLess && !maxYLess) { if (!this.northEast.forEach(action, envelope)) { return false; } } return true; } public boolean isLessThanX(final double x) { return x < this.x; } public boolean isLessThanY(final double y) { return y < this.y; } public void put(final double x, final double y, final PointQuadTreeNode<T> node) { final boolean xLess = isLessThanX(x); final boolean yLess = isLessThanY(y); if (xLess && yLess) { if (this.southWest == null) { this.southWest = node; } else { this.southWest.put(x, y, node); } } else if (xLess && !yLess) { if (this.northWest == null) { this.northWest = node; } else { this.northWest.put(x, y, node); } } else if (!xLess && yLess) { if (this.southEast == null) { this.southEast = node; } else { this.southEast.put(x, y, node); } } else if (!xLess && !yLess) { if (this.northEast == null) { this.northEast = node; } else { this.northEast.put(x, y, node); } } } public PointQuadTreeNode<T> remove(final double x, final double y, final T value) { final boolean xLess = isLessThanX(x); final boolean yLess = isLessThanY(y); if (this.x == x && this.y == y && this.value == value) { final List<PointQuadTreeNode<T>> nodes = new ArrayList<>(); if (this.northWest != null) { nodes.add(this.northWest); } if (this.northEast != null) { nodes.add(this.northEast); } if (this.southWest != null) { nodes.add(this.southWest); } if (this.southEast != null) { nodes.add(this.southEast); } if (nodes.isEmpty()) { return null; } else { final PointQuadTreeNode<T> node = nodes.remove(0); for (final PointQuadTreeNode<T> subNode : nodes) { node.put(subNode.x, subNode.y, subNode); } return node; } } else if (xLess && yLess) { if (this.southWest != null) { this.southWest = this.southWest.remove(x, y, value); } } else if (xLess && !yLess) { if (this.northWest != null) { this.northWest = this.northWest.remove(x, y, value); } } else if (!xLess && yLess) { if (this.southEast != null) { this.southEast = this.southEast.remove(x, y, value); } } else if (!xLess && !yLess) { if (this.northEast != null) { this.northEast = this.northEast.remove(x, y, value); } } return this; } public void setValue(final int index, final double value) { throw new UnsupportedOperationException("Cannot change the coordinates on a quad tree"); } }