using System; using System.Collections; using GisSharpBlog.NetTopologySuite.Utilities; namespace GisSharpBlog.NetTopologySuite.Index.Strtree { /// /// Base class for STRtree and SIRtree. STR-packed R-trees are described in: /// P. Rigaux, Michel Scholl and Agnes Voisard. Spatial Databases With /// Application To GIS. Morgan Kaufmann, San Francisco, 2002. /// /// This implementation is based on Boundables rather than just AbstractNodes, /// because the STR algorithm operates on both nodes and /// data, both of which are treated here as Boundables. /// /// public abstract class AbstractSTRtree { private readonly ArrayList itemBoundables = new ArrayList(); private readonly int nodeCapacity; /// /// /// protected AbstractNode root; private bool built; /// /// Constructs an AbstractSTRtree with the specified maximum number of child /// nodes that a node may have. /// /// protected AbstractSTRtree(int nodeCapacity) { Assert.IsTrue(nodeCapacity > 1, "Node capacity must be greater than 1"); this.nodeCapacity = nodeCapacity; } /// /// Returns the maximum number of child nodes that a node may have. /// public int NodeCapacity { get { return nodeCapacity; } } public int Count { get { if (!built) { Build(); } if (itemBoundables.Count == 0) { return 0; } return GetSize(root); } } public int Depth { get { if (!built) { Build(); } return itemBoundables.Count == 0 ? 0 : GetDepth(root); } } /// /// 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 void Build() { Assert.IsTrue(!built); root = (itemBoundables.Count == 0) ? CreateNode(0) : CreateHigherLevels(itemBoundables, -1); built = true; } /// /// Gets a tree structure (as a nested list) /// corresponding to the structure of the items and nodes in this tree. /// The returned Lists contain either Object items, /// or Lists which correspond to subtrees of the tree /// Subtrees which do not contain any items are not included. /// Builds the tree if necessary. /// public IList ItemsTree() { if (!built) { Build(); } var valuesTree = ItemsTree(root); return valuesTree ?? new ArrayList(); } protected AbstractNode Root { get { return root; } } /// /// A test for intersection between two bounds, necessary because subclasses /// of AbstractSTRtree have different implementations of bounds. /// protected abstract IIntersectsOp IntersectsOp { get; } /// /// /// /// /// protected abstract AbstractNode CreateNode(int level); /// /// Sorts the childBoundables then divides them into groups of size M, where /// M is the node capacity. /// /// /// protected virtual IList CreateParentBoundables(IList childBoundables, int newLevel) { Assert.IsTrue(childBoundables.Count != 0); var parentBoundables = new ArrayList(); parentBoundables.Add(CreateNode(newLevel)); var sortedChildBoundables = new ArrayList(childBoundables); sortedChildBoundables.Sort(GetComparer()); for (var i = sortedChildBoundables.GetEnumerator(); i.MoveNext();) { var childBoundable = (IBoundable) i.Current; if (LastNode(parentBoundables).ChildBoundables.Count == NodeCapacity) { parentBoundables.Add(CreateNode(newLevel)); } LastNode(parentBoundables).AddChildBoundable(childBoundable); } return parentBoundables; } protected AbstractNode LastNode(IList nodes) { return (AbstractNode) nodes[nodes.Count - 1]; } protected int CompareDoubles(double a, double b) { return a > b ? 1 : a < b ? -1 : 0; } protected int GetSize(AbstractNode node) { var size = 0; for (var i = node.ChildBoundables.GetEnumerator(); i.MoveNext();) { var childBoundable = (IBoundable) i.Current; if (childBoundable is AbstractNode) { size += GetSize((AbstractNode) childBoundable); } else if (childBoundable is ItemBoundable) { size += 1; } } return size; } protected int GetDepth(AbstractNode node) { var maxChildDepth = 0; for (var i = node.ChildBoundables.GetEnumerator(); i.MoveNext();) { var childBoundable = (IBoundable) i.Current; if (!(childBoundable is AbstractNode)) { continue; } var childDepth = GetDepth((AbstractNode) childBoundable); if (childDepth > maxChildDepth) { maxChildDepth = childDepth; } } return maxChildDepth + 1; } protected void Insert(object bounds, object item) { Assert.IsTrue(!built, "Cannot insert items into an STR packed R-tree after it has been built."); itemBoundables.Add(new ItemBoundable(bounds, item)); } /// /// Also builds the tree, if necessary. /// /// protected IList Query(object searchBounds) { if (!built) { Build(); } var matches = new ArrayList(); if (itemBoundables.Count == 0) { Assert.IsTrue(root.Bounds == null); return matches; } if (IntersectsOp.Intersects(root.Bounds, searchBounds)) { Query(searchBounds, root, matches); } return matches; } protected void Query(Object searchBounds, IItemVisitor visitor) { if (!built) { Build(); } if (itemBoundables.Count == 0) { Assert.IsTrue(root.Bounds == null); } if (IntersectsOp.Intersects(root.Bounds, searchBounds)) { Query(searchBounds, root, visitor); } } protected bool Remove(object searchBounds, object item) { if (!built) { Build(); } if (itemBoundables.Count == 0) { Assert.IsTrue(root.Bounds == null); } return IntersectsOp.Intersects(root.Bounds, searchBounds) && Remove(searchBounds, root, item); } protected IList BoundablesAtLevel(int level) { IList boundables = new ArrayList(); BoundablesAtLevel(level, root, ref boundables); return boundables; } protected abstract IComparer GetComparer(); /// /// Creates the levels higher than the given level. /// /// The level to build on. /// the level of the Boundables, or -1 if the boundables are item /// boundables (that is, below level 0). /// The root, which may be a ParentNode or a LeafNode. private AbstractNode CreateHigherLevels(IList boundablesOfALevel, int level) { Assert.IsTrue(boundablesOfALevel.Count != 0); var parentBoundables = CreateParentBoundables(boundablesOfALevel, level + 1); if (parentBoundables.Count == 1) { return (AbstractNode) parentBoundables[0]; } return CreateHigherLevels(parentBoundables, level + 1); } private void Query(object searchBounds, AbstractNode node, IList matches) { foreach (var obj in node.ChildBoundables) { var childBoundable = (IBoundable) obj; if (!IntersectsOp.Intersects(childBoundable.Bounds, searchBounds)) { continue; } if (childBoundable is AbstractNode) { Query(searchBounds, (AbstractNode) childBoundable, matches); } else if (childBoundable is ItemBoundable) { matches.Add(((ItemBoundable) childBoundable).Item); } else { Assert.ShouldNeverReachHere(); } } } private void Query(object searchBounds, AbstractNode node, IItemVisitor visitor) { foreach (var obj in node.ChildBoundables) { var childBoundable = (IBoundable) obj; if (!IntersectsOp.Intersects(childBoundable.Bounds, searchBounds)) { continue; } if (childBoundable is AbstractNode) { Query(searchBounds, (AbstractNode) childBoundable, visitor); } else if (childBoundable is ItemBoundable) { visitor.VisitItem(((ItemBoundable) childBoundable).Item); } else { Assert.ShouldNeverReachHere(); } } } private IList ItemsTree(AbstractNode node) { var valuesTreeForNode = new ArrayList(); foreach (IBoundable childBoundable in node.ChildBoundables) { if (childBoundable is AbstractNode) { var valuesTreeForChild = ItemsTree((AbstractNode) childBoundable); // only add if not null (which indicates an item somewhere in this tree if (valuesTreeForChild != null) { valuesTreeForNode.Add(valuesTreeForChild); } } else if (childBoundable is ItemBoundable) { valuesTreeForNode.Add(((ItemBoundable) childBoundable).Item); } else { Assert.ShouldNeverReachHere(); } } return valuesTreeForNode.Count <= 0 ? null : valuesTreeForNode; } private bool RemoveItem(AbstractNode node, object item) { IBoundable childToRemove = null; for (var i = node.ChildBoundables.GetEnumerator(); i.MoveNext();) { var childBoundable = (IBoundable) i.Current; if (childBoundable is ItemBoundable) { if (((ItemBoundable) childBoundable).Item == item) { childToRemove = childBoundable; } } } if (childToRemove != null) { node.ChildBoundables.Remove(childToRemove); return true; } return false; } private bool Remove(object searchBounds, AbstractNode node, object item) { // first try removing item from this node var found = RemoveItem(node, item); if (found) { return true; } AbstractNode childToPrune = null; // next try removing item from lower nodes for (var i = node.ChildBoundables.GetEnumerator(); i.MoveNext();) { var childBoundable = (IBoundable) i.Current; if (!IntersectsOp.Intersects(childBoundable.Bounds, searchBounds)) { continue; } if (!(childBoundable is AbstractNode)) { continue; } found = Remove(searchBounds, (AbstractNode) childBoundable, item); // if found, record child for pruning and exit if (!found) { continue; } childToPrune = (AbstractNode) childBoundable; break; } // prune child if possible if (childToPrune != null) { if (childToPrune.ChildBoundables.Count == 0) { node.ChildBoundables.Remove(childToPrune); } } return found; } private void BoundablesAtLevel(int level, AbstractNode top, ref IList boundables) { Assert.IsTrue(level > -2); if (top.Level == level) { boundables.Add(top); return; } for (var i = top.ChildBoundables.GetEnumerator(); i.MoveNext();) { var boundable = (IBoundable) i.Current; if (boundable is AbstractNode) { BoundablesAtLevel(level, (AbstractNode) boundable, ref boundables); } else { Assert.IsTrue(boundable is ItemBoundable); if (level == -1) { boundables.Add(boundable); } } } } /// /// A test for intersection between two bounds, necessary because subclasses /// of AbstractSTRtree have different implementations of bounds. /// protected interface IIntersectsOp { /// /// For STRtrees, the bounds will be Envelopes; /// for SIRtrees, Intervals; /// for other subclasses of AbstractSTRtree, some other class. /// /// The bounds of one spatial object. /// The bounds of another spatial object. /// Whether the two bounds intersect. bool Intersects(object aBounds, object bBounds); } } }