package de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.Stack; import de.lmu.ifi.dbs.elki.data.HyperBoundingBox; import de.lmu.ifi.dbs.elki.data.ModifiableHyperBoundingBox; import de.lmu.ifi.dbs.elki.data.spatial.SpatialComparable; import de.lmu.ifi.dbs.elki.data.spatial.SpatialUtil; import de.lmu.ifi.dbs.elki.database.ids.DBIDRef; import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil; import de.lmu.ifi.dbs.elki.index.tree.BreadthFirstEnumeration; import de.lmu.ifi.dbs.elki.index.tree.IndexTreePath; import de.lmu.ifi.dbs.elki.index.tree.LeafEntry; import de.lmu.ifi.dbs.elki.index.tree.TreeIndexHeader; import de.lmu.ifi.dbs.elki.index.tree.TreeIndexPathComponent; import de.lmu.ifi.dbs.elki.index.tree.spatial.SpatialDirectoryEntry; import de.lmu.ifi.dbs.elki.index.tree.spatial.SpatialEntry; import de.lmu.ifi.dbs.elki.index.tree.spatial.SpatialIndexTree; import de.lmu.ifi.dbs.elki.index.tree.spatial.SpatialPointLeafEntry; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.bulk.BulkSplit; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.insert.InsertionStrategy; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.insert.LeastOverlapInsertionStrategy; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.overflow.LimitedReinsertOverflowTreatment; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.overflow.OverflowTreatment; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.split.SplitStrategy; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.strategies.split.TopologicalSplitter; import de.lmu.ifi.dbs.elki.index.tree.spatial.rstarvariants.util.NodeArrayAdapter; import de.lmu.ifi.dbs.elki.persistent.PageFile; import de.lmu.ifi.dbs.elki.persistent.PageFileUtil; import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; /** * Abstract superclass for index structures based on a R*-Tree. * * Implementation Note: The restriction on NumberVector (as opposed to e.g. * FeatureVector) is intentional, because we have spatial requirements. * * @author Elke Achtert * * @apiviz.landmark * @apiviz.has AbstractRStarTreeNode oneway - - contains * @apiviz.composedOf BulkSplit * @apiviz.composedOf SplitStrategy * @apiviz.composedOf InsertionStrategy * @apiviz.composedOf OverflowTreatment * * @param Node type * @param Entry type */ public abstract class AbstractRStarTree, E extends SpatialEntry> extends SpatialIndexTree { /** * Development flag: This will enable some extra integrity checks on the tree. */ protected final static boolean extraIntegrityChecks = false; /** * The height of this R*-Tree. */ protected int height; /** * For counting the number of distance computations. */ public int distanceCalcs = 0; /** * The last inserted entry */ E lastInsertedEntry = null; /** * The strategy for bulk load. */ protected BulkSplit bulkSplitter; /** * The split strategy */ protected SplitStrategy nodeSplitter = TopologicalSplitter.STATIC; /** * The insertion strategy to use */ protected InsertionStrategy insertionStrategy = LeastOverlapInsertionStrategy.STATIC; /** * Overflow treatment */ protected OverflowTreatment overflowTreatment = LimitedReinsertOverflowTreatment.RSTAR_OVERFLOW; /** * Relative minimum fill */ protected double relativeMinFill = 0.4; /** * Constructor * * @param pagefile Page file */ public AbstractRStarTree(PageFile pagefile) { super(pagefile); } /** * Set the bulk loading strategy * * @param bulkSplitter Bulk loading strategy */ public void setBulkStrategy(BulkSplit bulkSplitter) { this.bulkSplitter = bulkSplitter; } /** * Set the node splitting strategy. * * @param nodeSplitter the split strategy to set */ public void setNodeSplitStrategy(SplitStrategy nodeSplitter) { if(nodeSplitter != null) { this.nodeSplitter = nodeSplitter; } else { getLogger().warning("Ignoring setNodeSplitStrategy(null)"); } } /** * Set insertion strategy * * @param insertionStrategy the insertion strategy to set */ public void setInsertionStrategy(InsertionStrategy insertionStrategy) { if(insertionStrategy != null) { this.insertionStrategy = insertionStrategy; } else { getLogger().warning("Ignoring setInsertionStrategy(null)"); } } /** * Set the overflow treatment strategy. * * @param overflowTreatment overflow treatment strategy */ public void setOverflowTreatment(OverflowTreatment overflowTreatment) { this.overflowTreatment = overflowTreatment; } /** * Set the relative minimum fill. (Only supported before the tree was used!) * * @param relative Relative minimum fill */ public void setMinimumFill(double relative) { this.relativeMinFill = relative; } /** * Returns the path to the leaf entry in the specified subtree that represents * the data object with the specified mbr and id. * * @param subtree the subtree to be tested * @param mbr the mbr to look for * @param id the id to look for * @return the path to the leaf entry of the specified subtree that represents * the data object with the specified mbr and id */ protected IndexTreePath findPathToObject(IndexTreePath subtree, SpatialComparable mbr, DBIDRef id) { N node = getNode(subtree.getLastPathComponent().getEntry()); if(node.isLeaf()) { for(int i = 0; i < node.getNumEntries(); i++) { if(((LeafEntry) node.getEntry(i)).getDBID().sameDBID(id)) { return subtree.pathByAddingChild(new TreeIndexPathComponent(node.getEntry(i), i)); } } } // directory node else { for(int i = 0; i < node.getNumEntries(); i++) { if(SpatialUtil.intersects(node.getEntry(i), mbr)) { IndexTreePath childSubtree = subtree.pathByAddingChild(new TreeIndexPathComponent(node.getEntry(i), i)); IndexTreePath path = findPathToObject(childSubtree, mbr, id); if(path != null) { return path; } } } } return null; } @Override public void insertLeaf(E leaf) { if(!initialized) { initialize(leaf); } overflowTreatment.reinitialize(); preInsert(leaf); insertLeafEntry(leaf); doExtraIntegrityChecks(); } /** * Inserts the specified leaf entry into this R*-Tree. * * @param entry the leaf entry to be inserted */ protected void insertLeafEntry(E entry) { lastInsertedEntry = entry; // choose subtree for insertion IndexTreePath subtree = choosePath(getRootPath(), entry, 1); if(getLogger().isDebugging()) { getLogger().debugFine("insertion-subtree " + subtree); } N parent = getNode(subtree.getLastPathComponent().getEntry()); parent.addLeafEntry(entry); writeNode(parent); // adjust the tree from subtree to root adjustTree(subtree); } /** * Inserts the specified directory entry at the specified level into this * R*-Tree. * * @param entry the directory entry to be inserted * @param level the level at which the directory entry is to be inserted */ protected void insertDirectoryEntry(E entry, int level) { lastInsertedEntry = entry; // choose node for insertion of o IndexTreePath subtree = choosePath(getRootPath(), entry, level); if(getLogger().isDebugging()) { getLogger().debugFine("subtree " + subtree); } N parent = getNode(subtree.getLastPathComponent().getEntry()); parent.addDirectoryEntry(entry); writeNode(parent); // adjust the tree from subtree to root adjustTree(subtree); } /** * Delete a leaf at a given path - deletions for non-leaves are not supported! * * @param deletionPath Path to delete */ protected void deletePath(IndexTreePath deletionPath) { N leaf = getNode(deletionPath.getParentPath().getLastPathComponent().getEntry()); int index = deletionPath.getLastPathComponent().getIndex(); // delete o E entry = leaf.getEntry(index); leaf.deleteEntry(index); writeNode(leaf); // condense the tree Stack stack = new Stack(); condenseTree(deletionPath.getParentPath(), stack); // reinsert underflow nodes while(!stack.empty()) { N node = stack.pop(); if(node.isLeaf()) { for(int i = 0; i < node.getNumEntries(); i++) { overflowTreatment.reinitialize(); // Intended? this.insertLeafEntry(node.getEntry(i)); } } else { for(int i = 0; i < node.getNumEntries(); i++) { stack.push(getNode(node.getEntry(i))); } } deleteNode(node); } postDelete(entry); doExtraIntegrityChecks(); } /** * Initializes this R*-Tree from an existing persistent file. */ @Override public void initializeFromFile(TreeIndexHeader header, PageFile file) { super.initializeFromFile(header, file); // compute height this.height = computeHeight(); if(getLogger().isDebugging()) { StringBuffer msg = new StringBuffer(); msg.append(getClass()); msg.append("\n height = ").append(height); getLogger().debugFine(msg.toString()); } } @Override protected void initializeCapacities(E exampleLeaf) { /* Simulate the creation of a leaf page to get the page capacity */ try { int cap = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); SpatialPointLeafEntry sl = new SpatialPointLeafEntry(DBIDUtil.importInteger(0), new double[exampleLeaf.getDimensionality()]); while(baos.size() <= getPageSize()) { sl.writeExternal(oos); oos.flush(); cap++; } // the last one caused the page to overflow. leafCapacity = cap - 1; } catch(IOException e) { throw new AbortException("Error determining page sizes.", e); } /* Simulate the creation of a directory page to get the capacity */ try { int cap = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); ModifiableHyperBoundingBox hb = new ModifiableHyperBoundingBox(new double[exampleLeaf.getDimensionality()], new double[exampleLeaf.getDimensionality()]); SpatialDirectoryEntry sl = new SpatialDirectoryEntry(0, hb); while(baos.size() <= getPageSize()) { sl.writeExternal(oos); oos.flush(); cap++; } dirCapacity = cap - 1; } catch(IOException e) { throw new AbortException("Error determining page sizes.", e); } if(dirCapacity <= 1) { throw new IllegalArgumentException("Node size of " + getPageSize() + " Bytes is chosen too small!"); } if(dirCapacity < 10) { getLogger().warning("Page size is choosen very small! Maximum number of entries " + "in a directory node = " + (dirCapacity - 1)); } // minimum entries per directory node dirMinimum = (int) Math.round((dirCapacity - 1) * relativeMinFill); if(dirMinimum < 2) { dirMinimum = 2; } if(leafCapacity <= 1) { throw new IllegalArgumentException("Node size of " + getPageSize() + " Bytes is chosen too small!"); } if(leafCapacity < 10) { getLogger().warning("Page size is choosen very small! Maximum number of entries " + "in a leaf node = " + (leafCapacity - 1)); } // minimum entries per leaf node leafMinimum = (int) Math.round((leafCapacity - 1) * relativeMinFill); if(leafMinimum < 2) { leafMinimum = 2; } if(getLogger().isVerbose()) { getLogger().verbose("Directory Capacity: " + (dirCapacity - 1) + "\nDirectory minimum: " + dirMinimum + "\nLeaf Capacity: " + (leafCapacity - 1) + "\nLeaf Minimum: " + leafMinimum); } } /** * Test whether a bulk insert is still possible. * * @return Success code */ public boolean canBulkLoad() { return (bulkSplitter != null && !initialized); } /** * Creates and returns the leaf nodes for bulk load. * * @param objects the objects to be inserted * @return the array of leaf nodes containing the objects */ protected List createBulkLeafNodes(List objects) { int minEntries = leafMinimum; int maxEntries = leafCapacity - 1; ArrayList result = new ArrayList(); List> partitions = bulkSplitter.partition(objects, minEntries, maxEntries); for(List partition : partitions) { // create leaf node N leafNode = createNewLeafNode(); // insert data for(E o : partition) { leafNode.addLeafEntry(o); } // write to file writeNode(leafNode); result.add(createNewDirectoryEntry(leafNode)); if(getLogger().isDebugging()) { getLogger().debugFine("Created leaf page "+leafNode.getPageID()); } } if(getLogger().isDebugging()) { getLogger().debugFine("numDataPages = " + result.size()); } return result; } /** * Performs a bulk load on this RTree with the specified data. Is called by * the constructor. */ abstract protected void bulkLoad(List entrys); /** * Returns the height of this R*-Tree. * * @return the height of this R*-Tree */ public final int getHeight() { return height; } /** * Sets the height of this R*-Tree. * * @param height the height to be set */ protected void setHeight(int height) { this.height = height; } /** * Computes the height of this RTree. Is called by the constructor. * * @return the height of this RTree */ abstract protected int computeHeight(); /** * Returns true if in the specified node an overflow occurred, false * otherwise. * * @param node the node to be tested for overflow * @return true if in the specified node an overflow occurred, false otherwise */ abstract protected boolean hasOverflow(N node); /** * Returns true if in the specified node an underflow occurred, false * otherwise. * * @param node the node to be tested for underflow * @return true if in the specified node an underflow occurred, false * otherwise */ abstract protected boolean hasUnderflow(N node); /** * Creates a new directory entry representing the specified node. * * @param node the node to be represented by the new entry * @return the newly created directory entry */ abstract protected E createNewDirectoryEntry(N node); /** * Creates a new root node that points to the two specified child nodes and * return the path to the new root. * * @param oldRoot the old root of this RTree * @param newNode the new split node * @return the path to the new root node that points to the two specified * child nodes */ protected IndexTreePath createNewRoot(final N oldRoot, final N newNode) { N root = createNewDirectoryNode(); writeNode(root); // switch the ids oldRoot.setPageID(root.getPageID()); if(!oldRoot.isLeaf()) { for(int i = 0; i < oldRoot.getNumEntries(); i++) { N node = getNode(oldRoot.getEntry(i)); writeNode(node); } } root.setPageID(getRootID()); E oldRootEntry = createNewDirectoryEntry(oldRoot); E newNodeEntry = createNewDirectoryEntry(newNode); root.addDirectoryEntry(oldRootEntry); root.addDirectoryEntry(newNodeEntry); writeNode(root); writeNode(oldRoot); writeNode(newNode); if(getLogger().isDebugging()) { String msg = "Create new Root: ID=" + root.getPageID(); msg += "\nchild1 " + oldRoot + " " + new HyperBoundingBox(oldRootEntry); msg += "\nchild2 " + newNode + " " + new HyperBoundingBox(newNodeEntry); msg += "\n"; getLogger().debugFine(msg); } return new IndexTreePath(new TreeIndexPathComponent(getRootEntry(), null)); } /** * Test on whether or not any child of node contains * mbr. If there are several containing children, the child with * the minimum volume is chosen in order to get compact pages. * * @param node subtree * @param mbr MBR to test for * @return the child of node containing mbr with the * minimum volume or null if none exists */ protected TreeIndexPathComponent containedTest(N node, SpatialComparable mbr) { E containingEntry = null; int index = -1; double cEVol = Double.NaN; E ei; for(int i = 0; i < node.getNumEntries(); i++) { ei = node.getEntry(i); // skip test on pairwise overlaps if(SpatialUtil.contains(ei, mbr)) { if(containingEntry == null) { containingEntry = ei; index = i; } else { double tempVol = SpatialUtil.volume(ei); if(Double.isNaN(cEVol)) { // calculate volume of currently best cEVol = SpatialUtil.volume(containingEntry); } // take containing node with lowest volume if(tempVol < cEVol) { cEVol = tempVol; containingEntry = ei; index = i; } } } } return (containingEntry == null ? null : new TreeIndexPathComponent(containingEntry, index)); } /** * Chooses the best path of the specified subtree for insertion of the given * mbr at the specified level. * * @param subtree the subtree to be tested for insertion * @param mbr the mbr to be inserted * @param level the level at which the mbr should be inserted (level 1 * indicates leaf-level) * @return the path of the appropriate subtree to insert the given mbr */ protected IndexTreePath choosePath(IndexTreePath subtree, SpatialComparable mbr, int level) { if(getLogger().isDebuggingFiner()) { getLogger().debugFiner("node " + subtree + ", level " + level); } N node = getNode(subtree.getLastPathComponent().getEntry()); if(node == null) { throw new RuntimeException("Page file did not return node for node id: " + getPageID(subtree.getLastPathComponent().getEntry())); } if(node.isLeaf()) { return subtree; } // first test on containment TreeIndexPathComponent containingEntry = containedTest(node, mbr); if(containingEntry != null) { IndexTreePath newSubtree = subtree.pathByAddingChild(containingEntry); if(height - subtree.getPathCount() == level) { return newSubtree; } else { return choosePath(newSubtree, mbr, level); } } N childNode = getNode(node.getEntry(0)); int num = insertionStrategy.choose(node, NodeArrayAdapter.STATIC, mbr, height, subtree.getPathCount()); TreeIndexPathComponent comp = new TreeIndexPathComponent(node.getEntry(num), num); // children are leafs if(childNode.isLeaf()) { if(height - subtree.getPathCount() == level) { return subtree.pathByAddingChild(comp); } else { throw new IllegalArgumentException("childNode is leaf, but currentLevel != level: " + (height - subtree.getPathCount()) + " != " + level); } } // children are directory nodes else { IndexTreePath newSubtree = subtree.pathByAddingChild(comp); // desired level is reached if(height - subtree.getPathCount() == level) { return newSubtree; } else { return choosePath(newSubtree, mbr, level); } } } /** * Treatment of overflow in the specified node: if the node is not the root * node and this is the first call of overflowTreatment in the given level * during insertion the specified node will be reinserted, otherwise the node * will be split. * * @param node the node where an overflow occurred * @param path the path to the specified node * @return the newly created split node in case of split, null in case of * reinsertion */ private N overflowTreatment(N node, IndexTreePath path) { if(overflowTreatment.handleOverflow(this, node, path)) { return null; } return split(node); } /** * Splits the specified node and returns the newly created split node. * * @param node the node to be split * @return the newly created split node */ private N split(N node) { // choose the split dimension and the split point int minimum = node.isLeaf() ? leafMinimum : dirMinimum; BitSet split = nodeSplitter.split(node, NodeArrayAdapter.STATIC, minimum); // New node final N newNode; if(node.isLeaf()) { newNode = createNewLeafNode(); } else { newNode = createNewDirectoryNode(); } // do the split node.splitByMask(newNode, split); // write changes to file writeNode(node); writeNode(newNode); return newNode; } /** * Reinserts the specified node at the specified level. * * @param node the node to be reinserted * @param path the path to the node * @param offs the nodes indexes to reinsert */ public void reInsert(N node, IndexTreePath path, int[] offs) { final int level = height - (path.getPathCount() - 1); BitSet remove = new BitSet(); List reInsertEntries = new ArrayList(offs.length); for(int i = 0; i < offs.length; i++) { reInsertEntries.add(node.getEntry(offs[i])); remove.set(offs[i]); } // Remove the entries we reinsert node.removeMask(remove); writeNode(node); // and adapt the mbrs IndexTreePath childPath = path; N child = node; while(childPath.getParentPath() != null) { N parent = getNode(childPath.getParentPath().getLastPathComponent().getEntry()); int indexOfChild = childPath.getLastPathComponent().getIndex(); if(child.adjustEntry(parent.getEntry(indexOfChild))) { writeNode(parent); childPath = childPath.getParentPath(); child = parent; } else { break; // TODO: stop writing when MBR didn't change! } } // reinsert the first entries for(E entry : reInsertEntries) { if(node.isLeaf()) { if(getLogger().isDebugging()) { getLogger().debug("reinsert " + entry); } insertLeafEntry(entry); } else { if(getLogger().isDebugging()) { getLogger().debug("reinsert " + entry + " at " + level); } insertDirectoryEntry(entry, level); } } } /** * Adjusts the tree after insertion of some nodes. * * @param subtree the subtree to be adjusted */ protected void adjustTree(IndexTreePath subtree) { if(getLogger().isDebugging()) { getLogger().debugFine("Adjust tree " + subtree); } // get the root of the subtree N node = getNode(subtree.getLastPathComponent().getEntry()); // overflow in node if(hasOverflow(node)) { // treatment of overflow: reinsertion or split N split = overflowTreatment(node, subtree); // node was split if(split != null) { // if root was split: create a new root that points the two // split nodes if(isRoot(node)) { IndexTreePath newRootPath = createNewRoot(node, split); height++; adjustTree(newRootPath); } // node is not root else { // get the parent and add the new split node N parent = getNode(subtree.getParentPath().getLastPathComponent().getEntry()); if(getLogger().isDebugging()) { getLogger().debugFine("parent " + parent); } parent.addDirectoryEntry(createNewDirectoryEntry(split)); // adjust the entry representing the (old) node, that has // been split // This does not work in the persistent version // node.adjustEntry(subtree.getLastPathComponent().getEntry()); node.adjustEntry(parent.getEntry(subtree.getLastPathComponent().getIndex())); // write changes in parent to file writeNode(parent); adjustTree(subtree.getParentPath()); } } } // no overflow, only adjust parameters of the entry representing the // node else { if(!isRoot(node)) { N parent = getNode(subtree.getParentPath().getLastPathComponent().getEntry()); E entry = parent.getEntry(subtree.getLastPathComponent().getIndex()); boolean changed = node.adjustEntryIncremental(entry, lastInsertedEntry); if(changed) { // node.adjustEntry(parent.getEntry(index)); // write changes in parent to file writeNode(parent); adjustTree(subtree.getParentPath()); } } // root level is reached else { node.adjustEntry(getRootEntry()); } } } /** * Condenses the tree after deletion of some nodes. * * @param subtree the subtree to be condensed * @param stack the stack holding the nodes to be reinserted after the tree * has been condensed */ private void condenseTree(IndexTreePath subtree, Stack stack) { N node = getNode(subtree.getLastPathComponent().getEntry()); // node is not root if(!isRoot(node)) { N parent = getNode(subtree.getParentPath().getLastPathComponent().getEntry()); int index = subtree.getLastPathComponent().getIndex(); if(hasUnderflow(node)) { if(parent.deleteEntry(index)) { stack.push(node); } else { node.adjustEntry(parent.getEntry(index)); } } else { node.adjustEntry(parent.getEntry(index)); } writeNode(parent); // get subtree to parent condenseTree(subtree.getParentPath(), stack); } // node is root else { if(hasUnderflow(node) & node.getNumEntries() == 1 && !node.isLeaf()) { N child = getNode(node.getEntry(0)); N newRoot; if(child.isLeaf()) { newRoot = createNewLeafNode(); newRoot.setPageID(getRootID()); for(int i = 0; i < child.getNumEntries(); i++) { newRoot.addLeafEntry(child.getEntry(i)); } } else { newRoot = createNewDirectoryNode(); newRoot.setPageID(getRootID()); for(int i = 0; i < child.getNumEntries(); i++) { newRoot.addDirectoryEntry(child.getEntry(i)); } } writeNode(newRoot); height--; } } } @Override public final List getLeaves() { List result = new ArrayList(); if(height == 1) { result.add(getRootEntry()); return result; } getLeafNodes(getRoot(), result, height); return result; } /** * Determines the entries pointing to the leaf nodes of the specified subtree * * @param node the subtree * @param result the result to store the ids in * @param currentLevel the level of the node in the R-Tree */ private void getLeafNodes(N node, List result, int currentLevel) { // Level 1 are the leaf nodes, Level 2 is the one atop! if(currentLevel == 2) { for(int i = 0; i < node.getNumEntries(); i++) { result.add(node.getEntry(i)); } } else { for(int i = 0; i < node.getNumEntries(); i++) { N child = getNode(node.getEntry(i)); getLeafNodes(child, result, (currentLevel - 1)); } } } /** * Perform additional integrity checks. */ public void doExtraIntegrityChecks() { if(extraIntegrityChecks) { getRoot().integrityCheck(this); } } /** * Returns a string representation of this R*-Tree. * * @return a string representation of this R*-Tree */ @Override public String toString() { StringBuffer result = new StringBuffer(); int dirNodes = 0; int leafNodes = 0; int objects = 0; int levels = 0; if(initialized) { N node = getRoot(); int dim = getRootEntry().getDimensionality(); while(!node.isLeaf()) { if(node.getNumEntries() > 0) { E entry = node.getEntry(0); node = getNode(entry); levels++; } } BreadthFirstEnumeration enumeration = new BreadthFirstEnumeration(this, getRootPath()); while(enumeration.hasMoreElements()) { IndexTreePath indexPath = enumeration.nextElement(); E entry = indexPath.getLastPathComponent().getEntry(); if(entry.isLeafEntry()) { objects++; } else { node = getNode(entry); if(node.isLeaf()) { leafNodes++; } else { dirNodes++; } } } result.append(getClass().getName()).append(" has ").append((levels + 1)).append(" levels.\n"); result.append(dirNodes).append(" Directory Knoten (max = ").append(dirCapacity - 1).append(", min = ").append(dirMinimum).append(")\n"); result.append(leafNodes).append(" Daten Knoten (max = ").append(leafCapacity - 1).append(", min = ").append(leafMinimum).append(")\n"); result.append(objects).append(" ").append(dim).append("-dim. Punkte im Baum \n"); PageFileUtil.appendPageFileStatistics(result, getPageFileStatistics()); } else { result.append(getClass().getName()).append(" is empty!\n"); } return result.toString(); } }