/* * 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.List; import java.util.function.Consumer; import com.revolsys.geometry.util.Assert; import com.revolsys.util.Emptyable; import com.revolsys.util.ExitLoopException; /** * Base class for STRtree and SIRtree. STR-packed R-trees are described in: * P. Rigaux, Michel Scholl and Agnes Voisard. <i>Spatial Databases With * Application To GIS.</i> Morgan Kaufmann, San Francisco, 2002. * <p> * This implementation is based on {@link Boundable}s rather than {@link AbstractNode}s, * because the STR algorithm operates on both nodes and * data, both of which are treated as Boundables. * * @see STRtree * @see SIRtree * * @version 1.7 */ public abstract class AbstractSTRtree<B, I, N extends AbstractNode<B, I>> implements Emptyable, Serializable { private static final int DEFAULT_NODE_CAPACITY = 10; /** * */ private static final long serialVersionUID = -3886435814360241337L; protected static int compareDoubles(final double a, final double b) { return a > b ? 1 : a < b ? -1 : 0; } private boolean built = false; /** * Set to <tt>null</tt> when index is built, to avoid retaining memory. */ private List<ItemBoundable<B, I>> itemBoundables = new ArrayList<>(); private final int nodeCapacity; protected N root; /** * Constructs an AbstractSTRtree with the * default node capacity. */ public AbstractSTRtree() { this(DEFAULT_NODE_CAPACITY); } /** * Constructs an AbstractSTRtree with the specified maximum number of child * nodes that a node may have * * @param nodeCapacity the maximum number of child nodes in a node */ public AbstractSTRtree(final int nodeCapacity) { Assert.isTrue(nodeCapacity > 1, "Node capacity must be greater than 1"); this.nodeCapacity = nodeCapacity; } protected List<Boundable<B, I>> boundablesAtLevel(final int level) { final List<Boundable<B, I>> boundables = new ArrayList<>(); this.root.boundablesAtLevel(level, boundables); return boundables; } /** * Creates parent nodes, grandparent nodes, and so forth up to the root * node, for the data that has been inserted into the tree. Can only be * called once, and thus can be called only after all of the data has been * inserted into the tree. */ public synchronized void build() { if (this.built) { return; } this.root = this.itemBoundables.isEmpty() ? newNode(0) : newNodeHigherLevels(this.itemBoundables, -1); // the item list is no longer needed this.itemBoundables = null; this.built = true; } protected int depth() { if (isEmpty()) { return 0; } else { build(); return this.root.getDepth(); } } protected abstract Comparator<Boundable<B, I>> getComparator(); /** * Returns the maximum number of child nodes that a node may have */ public int getNodeCapacity() { return this.nodeCapacity; } public N getRoot() { build(); return this.root; } protected void insert(final B bounds, final I item) { Assert.isTrue(!this.built, "Cannot insert items into an STR packed R-tree after it has been built."); final ItemBoundable<B, I> itemBoundable = new ItemBoundable<>(bounds, item); this.itemBoundables.add(itemBoundable); } /** * For STRtrees, the bounds will be Envelopes; for SIRtrees, Intervals; * for other subclasses of AbstractSTRtree, some other class. * @param aBounds the bounds of one spatial object * @param bBounds the bounds of another spatial object * @return whether the two bounds intersect */ protected abstract boolean intersects(B bounds1, B bounds2); /** * Tests whether the index contains any items. * This method does not build the index, * so items can still be inserted after it has been called. * * @return true if the index does not contain any items */ @Override public boolean isEmpty() { if (!this.built) { return this.itemBoundables.isEmpty(); } return this.root.isEmpty(); } /** * Gets a tree structure (as a nested list) * corresponding to the structure of the items and nodes in this tree. * <p> * The returned {@link List}s contain either {@link Object} items, * or Lists which correspond to subtrees of the tree * Subtrees which do not contain any items are not included. * <p> * Builds the tree if necessary. * * @return a List of items and/or Lists */ public List<?> itemsTree() { build(); final List<?> valuesTree = itemsTree(this.root); if (valuesTree == null) { return new ArrayList<>(); } return valuesTree; } @SuppressWarnings({ "rawtypes", "unchecked" }) private List itemsTree(final N node) { final List valuesTreeForNode = new ArrayList(); for (final Boundable<B, I> childBoundable : node.getChildren()) { if (childBoundable instanceof AbstractNode) { final List valuesTreeForChild = itemsTree((N)childBoundable); // only add if not null (which indicates an item somewhere in this tree if (valuesTreeForChild != null) { valuesTreeForNode.add(valuesTreeForChild); } } else if (childBoundable instanceof ItemBoundable) { valuesTreeForNode.add(((ItemBoundable)childBoundable).getItem()); } else { Assert.shouldNeverReachHere(); } } if (valuesTreeForNode.size() <= 0) { return null; } return valuesTreeForNode; } protected N lastNode(final List<N> nodes) { return nodes.get(nodes.size() - 1); } protected abstract N newNode(int level); /** * Creates the levels higher than the given level * * @param boundablesOfALevel * the level to build on * @param level * the level of the Boundables, or -1 if the boundables are item * boundables (that is, below level 0) * @return the root, which may be a ParentNode or a LeafNode */ private N newNodeHigherLevels(final List<? extends Boundable<B, I>> boundablesOfALevel, final int level) { Assert.isTrue(!boundablesOfALevel.isEmpty()); final List<N> parentBoundables = newParentBoundables(boundablesOfALevel, level + 1); if (parentBoundables.size() == 1) { return parentBoundables.get(0); } return newNodeHigherLevels(parentBoundables, level + 1); } /** * Sorts the childBoundables then divides them into groups of size M, where * M is the node capacity. */ protected List<N> newParentBoundables(final List<? extends Boundable<B, I>> childBoundables, final int newLevel) { Assert.isTrue(!childBoundables.isEmpty()); final List<N> parentBoundables = new ArrayList<>(); parentBoundables.add(newNode(newLevel)); final List<Boundable<B, I>> sortedChildBoundables = new ArrayList<>(childBoundables); Collections.sort(sortedChildBoundables, getComparator()); for (final Boundable<B, I> childBoundable : sortedChildBoundables) { if (lastNode(parentBoundables).getChildCount() == getNodeCapacity()) { parentBoundables.add(newNode(newLevel)); } lastNode(parentBoundables).addChild(childBoundable); } return parentBoundables; } /** * Also builds the tree, if necessary. */ public List<I> query(final B searchBounds) { build(); final List<I> matches = new ArrayList<>(); if (!isEmpty()) { query(searchBounds, matches::add); } return matches; } /** * Also builds the tree, if necessary. */ public void query(final B searchBounds, final Consumer<? super I> visitor) { build(); if (!isEmpty()) { try { this.root.query(this, searchBounds, visitor); } catch (final ExitLoopException e) { } } } /** * Removes an item from the tree. * (Builds the tree, if necessary.) */ protected boolean remove(final B searchBounds, final I item) { build(); if (this.itemBoundables.isEmpty()) { Assert.isTrue(this.root.getBounds() == null); } if (intersects(this.root.getBounds(), searchBounds)) { return this.root.remove(this, searchBounds, item); } return false; } public int size() { if (isEmpty()) { return 0; } else { build(); return this.root.getItemCount(); } } }