/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.index.strtree; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; import com.revolsys.geometry.index.SpatialIndex; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.BoundingBoxProxy; import com.revolsys.geometry.util.Assert; import com.revolsys.geometry.util.PriorityQueue; import com.revolsys.util.Pair; /** * A query-only R-tree created using the Sort-Tile-Recursive (STR) algorithm. * For two-dimensional spatial data. * <P> * The STR packed R-tree is simple to implement and maximizes space * utilization; that is, as many leaves as possible are filled to capacity. * Overlap between nodes is far less than in a basic R-tree. However, once the * tree has been built (explicitly or on the first call to #query), items may * not be added or removed. * <P> * Described in: P. Rigaux, Michel Scholl and Agnes Voisard. * <i>Spatial Databases With Application To GIS</i>. * Morgan Kaufmann, San Francisco, 2002. * * @version 1.7 */ public class STRtree<I> extends AbstractSTRtree<BoundingBox, I, BoundingBoxNode<I>> implements SpatialIndex<I>, Serializable, Comparator<Boundable<BoundingBox, I>> { private static final int DEFAULT_NODE_CAPACITY = 10; private static final long serialVersionUID = 259274702368956900L; private static double avg(final double a, final double b) { return (a + b) / 2d; } private static double centreX(final BoundingBox e) { return avg(e.getMinX(), e.getMaxX()); }; private static double centreY(final BoundingBox e) { return avg(e.getMinY(), e.getMaxY()); } /** * Constructs an STRtree with the default node capacity. */ public STRtree() { this(DEFAULT_NODE_CAPACITY); } /** * Constructs an STRtree with the given maximum number of child nodes that * a node may have. * <p> * The minimum recommended capacity setting is 4. * */ public STRtree(final int nodeCapacity) { super(nodeCapacity); } @Override public int compare(final Boundable<BoundingBox, I> o1, final Boundable<BoundingBox, I> o2) { return compareDoubles(centreY(o1.getBounds()), centreY(o2.getBounds())); } public int compareX(final Boundable<BoundingBox, I> o1, final Boundable<BoundingBox, I> o2) { return compareDoubles(centreX(o1.getBounds()), centreX(o2.getBounds())); } /** * Returns the number of items in the tree. * * @return the number of items in the tree */ @Override public int depth() { return super.depth(); }; @Override public void forEach(final BoundingBoxProxy boundingBox, final Consumer<? super I> action) { query(boundingBox.getBoundingBox(), action); } @Override public void forEach(final Consumer<? super I> action) { throw new UnsupportedOperationException(); } @Override public void forEach(final double x, final double y, final Consumer<? super I> action) { throw new UnsupportedOperationException(); } @Override public void forEach(final double minX, final double minY, final double maxX, final double maxY, final Consumer<? super I> action) { final BoundingBox boundingBox = getGeometryFactory().newBoundingBox(minX, minY, maxX, maxY); query(boundingBox, action); } @Override protected Comparator<Boundable<BoundingBox, I>> getComparator() { return this; } @Override public int getSize() { return this.root.getItemCount(); } /** * Inserts an item having the given bounds into the tree. */ @Override public void insertItem(final BoundingBox itemEnv, final I item) { if (!itemEnv.isEmpty()) { super.insert(itemEnv, item); } } @Override protected boolean intersects(final BoundingBox aBounds, final BoundingBox bBounds) { return aBounds.intersectsFast(bBounds); } private Pair<I, I> nearestNeighbour(final BoundablePair<I> initBndPair, final ItemDistance<I> itemDistance) { return nearestNeighbour(initBndPair, itemDistance, Double.POSITIVE_INFINITY); } private Pair<I, I> nearestNeighbour(final BoundablePair<I> initBndPair, final ItemDistance<I> itemDistance, final double maxDistance) { double distanceLowerBound = maxDistance; BoundablePair<I> minPair = null; // initialize internal structures final PriorityQueue<BoundablePair<I>> priorityQueue = new PriorityQueue<>(); // initialize queue priorityQueue.add(initBndPair); while (!priorityQueue.isEmpty() && distanceLowerBound > 0.0) { // pop head of queue and expand one side of pair final BoundablePair<I> bndPair = priorityQueue.poll(); final double currentDistance = bndPair.getDistance(); /** * If the distance for the first node in the queue * is >= the current minimum distance, all other nodes * in the queue must also have a greater distance. * So the current minDistance must be the true minimum, * and we are done. */ if (currentDistance >= distanceLowerBound) { break; } /** * If the pair members are leaves * then their distance is the exact lower bound. * Update the distanceLowerBound to reflect this * (which must be smaller, due to the test * immediately prior to this). */ if (bndPair.isLeaves()) { // assert: currentDistance < minimumDistanceFound distanceLowerBound = currentDistance; minPair = bndPair; } else { // testing - does allowing a tolerance improve speed? // Ans: by only about 10% - not enough to matter /* * double maxDist = bndPair.getMaximumDistance(); if (maxDist * .99 < lastComputedDistance) * return; // */ /** * Otherwise, expand one side of the pair, * (the choice of which side to expand is heuristically determined) * and insert the new expanded pairs into the queue */ bndPair.expandToQueue(priorityQueue, itemDistance, distanceLowerBound); } } final Boundable<BoundingBox, I> boundable1 = minPair.getBoundable(0); final Boundable<BoundingBox, I> boundable2 = minPair.getBoundable(1); final I item1 = boundable1.getItem(); final I item2 = boundable2.getItem(); return new Pair<>(item1, item2); } /** * Finds the item in this tree which is nearest to the given {@link Object}, * using {@link ItemDistance} as the distance metric. * A Branch-and-Bound tree traversal algorithm is used * to provide an efficient search. * <p> * The query <tt>object</tt> does <b>not</b> have to be * contained in the tree, but it does * have to be compatible with the <tt>itemDistance</tt> * distance metric. * * @param env the envelope of the query item * @param item the item to find the nearest neighbour of * @param itemDistance a distance metric applicable to the items in this tree and the query item * @return the nearest item in this tree */ public I nearestNeighbour(final BoundingBox env, final I item, final ItemDistance<I> itemDistance) { final Boundable<BoundingBox, I> bnd = new ItemBoundable<>(env, item); final BoundingBoxNode<I> root = getRoot(); final BoundablePair<I> bp = new BoundablePair<>(root, bnd, itemDistance); return nearestNeighbour(bp, itemDistance).getValue1(); } /** * Finds the two nearest items in the tree, * using {@link ItemDistance} as the distance metric. * A Branch-and-Bound tree traversal algorithm is used * to provide an efficient search. * * @param itemDistance a distance metric applicable to the items in this tree * @return the pair of the nearest items */ public Pair<I, I> nearestNeighbour(final ItemDistance<I> itemDistance) { final BoundingBoxNode<I> root = getRoot(); final BoundablePair<I> bp = new BoundablePair<>(root, root, itemDistance); return nearestNeighbour(bp, itemDistance); } /** * Finds the two nearest items from this tree * and another tree, * using {@link ItemDistance} as the distance metric. * A Branch-and-Bound tree traversal algorithm is used * to provide an efficient search. * The result value is a pair of items, * the first from this tree and the second * from the argument tree. * * @param tree another tree * @param itemDistance a distance metric applicable to the items in the trees * @return the pair of the nearest items, one from each tree */ public Pair<I, I> nearestNeighbour(final STRtree<I> tree, final ItemDistance<I> itemDistance) { final BoundablePair<I> bp = new BoundablePair<>(getRoot(), tree.getRoot(), itemDistance); return nearestNeighbour(bp, itemDistance); } @Override protected BoundingBoxNode<I> newNode(final int level) { return new BoundingBoxNode<>(level); } /** * Creates the parent level for the given child level. First, orders the items * by the x-values of the midpoints, and groups them into vertical slices. * For each slice, orders the items by the y-values of the midpoints, and * group them into runs of size M (the node capacity). For each run, creates * a new (parent) node. */ @Override protected List<BoundingBoxNode<I>> newParentBoundables( final List<? extends Boundable<BoundingBox, I>> childBoundables, final int newLevel) { Assert.isTrue(!childBoundables.isEmpty()); final int minLeafCount = (int)Math.ceil(childBoundables.size() / (double)getNodeCapacity()); final List<Boundable<BoundingBox, I>> sortedChildBoundables = new ArrayList<>(childBoundables); Collections.sort(sortedChildBoundables, this::compareX); final List<List<Boundable<BoundingBox, I>>> verticalSlices = verticalSlices( sortedChildBoundables, (int)Math.ceil(Math.sqrt(minLeafCount))); return newParentBoundablesFromVerticalSlices(verticalSlices, newLevel); } protected List<BoundingBoxNode<I>> newParentBoundablesFromVerticalSlice( final List<? extends Boundable<BoundingBox, I>> childBoundables, final int newLevel) { return super.newParentBoundables(childBoundables, newLevel); } private List<BoundingBoxNode<I>> newParentBoundablesFromVerticalSlices( final List<List<Boundable<BoundingBox, I>>> verticalSlices, final int newLevel) { Assert.isTrue(verticalSlices.size() > 0); final List<BoundingBoxNode<I>> parentBoundables = new ArrayList<>(); for (final List<? extends Boundable<BoundingBox, I>> verticalSlice : verticalSlices) { parentBoundables.addAll(newParentBoundablesFromVerticalSlice(verticalSlice, newLevel)); } return parentBoundables; } /** * Removes a single item from the tree. * * @param itemEnv the BoundingBox of the item to remove * @param item the item to remove * @return <code>true</code> if the item was found */ @Override public boolean removeItem(final BoundingBox itemEnv, final I item) { return super.remove(itemEnv, item); } /** * @param childBoundables Must be sorted by the x-value of the envelope midpoints */ protected List<List<Boundable<BoundingBox, I>>> verticalSlices( final List<Boundable<BoundingBox, I>> childBoundables, final int sliceCount) { final int sliceCapacity = (int)Math.ceil(childBoundables.size() / (double)sliceCount); final List<List<Boundable<BoundingBox, I>>> slices = new ArrayList<>(sliceCapacity); final Iterator<Boundable<BoundingBox, I>> i = childBoundables.iterator(); for (int j = 0; j < sliceCount; j++) { final List<Boundable<BoundingBox, I>> slice = new ArrayList<>(); slices.add(slice); int boundablesAddedToSlice = 0; while (i.hasNext() && boundablesAddedToSlice < sliceCapacity) { final Boundable<BoundingBox, I> childBoundable = i.next(); slice.add(childBoundable); boundablesAddedToSlice++; } } return slices; } }