/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany 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 3 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, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.indexStructures; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Stack; import xxl.core.collections.MapEntry; import xxl.core.collections.ReversedList; import xxl.core.collections.containers.Container; import xxl.core.cursors.AbstractCursor; import xxl.core.cursors.Cursor; import xxl.core.cursors.Cursors; import xxl.core.cursors.filters.Filter; import xxl.core.cursors.sources.EmptyCursor; import xxl.core.cursors.sources.SingleObjectCursor; import xxl.core.cursors.unions.Sequentializer; import xxl.core.functions.AbstractFunction; import xxl.core.functions.Constant; import xxl.core.functions.Function; import xxl.core.io.converters.ConvertableConverter; import xxl.core.io.converters.Converter; import xxl.core.io.converters.MeasuredConverter; import xxl.core.predicates.AbstractPredicate; import xxl.core.spatial.points.DoublePoint; import xxl.core.spatial.points.Point; import xxl.core.spatial.rectangles.DoublePointRectangle; import xxl.core.spatial.rectangles.Rectangle; /** * * HilbertRTree with R*tree split(1-2 split policy) and BPlusTree merge * strategies. This Implementation is based on BPlusTree. The indexEntries of * BPlusTree were extended to manage MBBs. To use spatial semantic of the tree * queries run {@link HilbertRTree#queryOR(Rectangle, int)} For a detailed * discussion see Ibrahim Kamel, Christos Faloutsos : * "Hilbert R-tree: An Improved R-tree Using Fractals" Proceedings of the 20th * VLDB Conference Sanitago, Chile, 1994 * * @see Tree * @see BPlusTree * @see ORTree * @see RTree */ public class HilbertRTree extends BPlusTree { /** Dimension of the data */ protected int dimension; /** * minMaxFactor the quotient between minimum and maximum number of entries * in an node, e.g. 0.5 (BPlusTRee) standard Split or e.g. 1d/3d R*Split */ protected double minMaxFactor; /** Function to compute MBB from data */ protected Function getRectangle; /** Converter to serialize MBBs */ protected Converter descriptorConverter; /** Function to compute middle point of the MBB */ protected Function computeMiddlePoint; /** Normalize function to hypercube [0..1) */ protected Function normalize; /** Universe-hypercube of the data */ protected Rectangle universe; /** * Creates a new <tt>HilbertRTree</tt>. With a default setting of duplicates * = true, and minMaxFactor 0.5 (standard BPlusTree split) * * @param blockSize * @param universe * Universe hypercube of the data */ public HilbertRTree(int blockSize, Rectangle universe) { this(blockSize, universe, 0.5d, true); } /** * Creates a new <tt>HilbertRTree</tt>. With a default setting minMaxFactor * 0.5 (standard BPlusTree split) * * @param blockSize * @param universe * Universe hypercube of the data * @param duplicate */ public HilbertRTree(int blockSize, Rectangle universe, boolean duplicate) { this(blockSize, universe, 0.5d, duplicate); } /** * Creates a new <tt>HilbertRTree</tt>. With a default setting of duplicates * = true. * * @param blockSize * @param universe * Universe hypercube of the data * @param minMaxFactor * minMaxFactor the quotient between minimum and maximum number * of entries in an node, e.g. 0.5 (BPlusTRee) standard Split or * e.g. 1d/3d R*Split */ public HilbertRTree(int blockSize, Rectangle universe, Double minMaxFactor) { this(blockSize, universe, minMaxFactor, true); } /** * Creates a new <tt>HilbertRTree</tt>. * * @param blockSize * @param universe * Universe hypercube of the data * @param minMaxFactor * minMaxFactor the quotient between minimum and maximum number * of entries in an node, e.g. 0.5 (BPlusTRee) standard Split or * e.g. 1d/3d R*Split * @param duplicate */ public HilbertRTree(int blockSize, Rectangle universe, double minMaxFactor, boolean duplicate) { super(blockSize, minMaxFactor); this.duplicate = duplicate; this.minMaxFactor = minMaxFactor; this.universe = universe; this.dimension = universe.dimensions(); } /** * Initializes the <tt>HilbertRTree</tt>. * * @param computeSFCurveValue * Function which computes SpaceFillingCurve value of the middle * point of the normalized dataMBR * @param getMBR * function which computes the MBR of the dataObject * @param container * container of the tree * @param keyConverter * converter for keys of the dataObjects * @param dataConverter * @param createORSeparator * a factory Function to create (Overlaping)Separators * @param createKeyRange * a factory Function to create (Overlaping)KeyRanges * @return the initialized <tt>HilbertRTree</tt> itself */ public HilbertRTree initialize(final Function computeSFCurveValue, Function getMBR, Container container, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createORSeparator, Function createKeyRange) { return initialize(null, null, computeSFCurveValue, getMBR, container, keyConverter, dataConverter, createORSeparator, createKeyRange); } /** * Initializes the <tt>HilbertRTree</tt>. * * @param computeSFCurveValue * computeSFCurveValue Function which computes SpaceFillingCurve * value of the middle Point of the normalized dataMBR * @param getMBR * function which computes the MBR of the dataObject * @return the initialized <tt>HilbertRTree</tt> itself */ protected HilbertRTree initialize(final Function computeSFCurveValue, Function getMBR) { getRectangle = getMBR; normalize = new AbstractFunction() { public Object invoke(Object entry) { if (entry instanceof DoublePointRectangle) { DoublePointRectangle normMBR = (DoublePointRectangle) ((DoublePointRectangle) entry) .clone(); return normMBR.normalize(universe); } else { System.out.println("entry " + entry); throw new IllegalArgumentException(); } } }; descriptorConverter = new ConvertableConverter(new AbstractFunction() { public Object invoke() { return new DoublePointRectangle(dimension); } }); // Normalize and compute middle Point computeMiddlePoint = new AbstractFunction() { public Object invoke(Object entry) { Rectangle rectangle = (Rectangle) normalize.invoke(entry); double[] pointArray = new double[rectangle.dimensions()]; for (int i = 0; i < pointArray.length; i++) { pointArray[i] = (rectangle.getCorner(true).getValue(i) + rectangle .getCorner(false).getValue(i)) / 2d; } return new DoublePoint(pointArray); } }; // Edit this.getKey = new AbstractFunction() { public Object invoke(Object entry) { Rectangle rectangle = (Rectangle) getRectangle.invoke(entry); Point middlePoint = (Point) computeMiddlePoint .invoke(rectangle); return computeSFCurveValue.invoke(middlePoint); } }; this.getDescriptor = new AbstractFunction() { public Object invoke(Object entry) { if (entry instanceof ORSeparator) return (ORSeparator) entry; if (entry instanceof IndexEntry) return ((IndexEntry) entry).separator; return createORSeparator(key(entry), (Descriptor) getRectangle.invoke(entry)); } }; return this; } /** * This initialization method mainly is used to restore the persistent tree. * * @param rootEntry * rootEntry of the tree * @param rootDescriptor * the ORKeyRange of the tree {@link HilbertRTree.ORKeyRange} * @param computeSFCurveValue * Function which computes SpaceFillingCurve value of the middle * point of the dataMBR * @param getMBR * function which computes the MBR of the dataObject * @param container * container of the tree * @param keyConverter * converter for keys of the dataObjects * @param dataConverter * @param createORSeparator * a factory Function to create (overlapping)Separators * @param createKeyRange * a factory Function to create (overlapping)KeyRanges * @return the initialized <tt>HilbertRTree</tt> itself */ public HilbertRTree initialize(IndexEntry rootEntry, Descriptor rootDescriptor, final Function computeSFCurveValue, Function getMBR, Container container, MeasuredConverter keyConverter, MeasuredConverter dataConverter, Function createORSeparator, Function createKeyRange) { super.initialize(rootEntry, rootDescriptor, getKey, container, keyConverter, dataConverter, createORSeparator, createKeyRange, new Constant(minMaxFactor), new Constant( ((1.0 - minMaxFactor) <= minMaxFactor) ? minMaxFactor : 1.0 - minMaxFactor)); return initialize(computeSFCurveValue, getMBR); } /** * Creates a new node on a given level. * * @param level * the level of the new Node * * @see xxl.core.indexStructures.Tree#createNode(int) */ public Tree.Node createNode(int level) { Node node = new Node(level); return node; } /** * returns the space filling curve value of data object ( e.g. Hilbert-Curve * value of the MBRs middle point) * * @param data * @return */ public Comparable getSFCValue(Object data) { return key(data); } /** * returns comparator based on the functions of the initialized tree * * @return comparator for universe of the tree */ public Comparator getDefaultComparator() { return new Comparator() { public int compare(Object o1, Object o2) { ORSeparator s1 = orSeparator(o1); ORSeparator s2 = orSeparator(o2); return s1.compareTo(s2); } }; } /** * Returns ORSeparator of the data entry * * @param entry * @return ORSeparator */ protected ORSeparator orSeparator(Object entry) { return (ORSeparator) separator(entry); } /** * * @return descriptor of the entry */ public Descriptor descriptor(Object entry) { return (Descriptor) getDescriptor.invoke(entry); } /** * creates overlapping separator * * @param sepValue * @param mbr * @return */ protected ORSeparator createORSeparator(Comparable sepValue, Descriptor mbr) { return (ORSeparator) this.createSeparator.invoke(sepValue, mbr); } /** * creates overlapping KeyRange * * @param sepValue * @param mbr * @return */ protected ORKeyRange createORKeyRange(Comparable sepValue, Comparable maxBound, Rectangle mbr) { return (ORKeyRange) this.createKeyRange.invoke(Arrays .asList(new Object[] { sepValue, maxBound, mbr })); } /** * */ protected KeyRange createKeyRange(Comparable min, Comparable max) { Rectangle rect; if (rootDescriptor == null) { rect = (Rectangle) ((Descriptor) universe).clone(); } else { rect = (Rectangle) ((ORKeyRange) rootDescriptor).getIndexEntryMBR() .clone(); } return createORKeyRange(min, max, rect); } /** * computes Descriptor(MBB) of the entry * * @param entry * @return */ protected Rectangle rectangle(Object entry) { ORSeparator separator = (ORSeparator) separator(entry); return (Rectangle) ((Descriptor) separator.getIndexEntryMBR()).clone(); } /** * Computes the union of the descriptors(MBRs) of the collection's entries. * * @param collection * a collection of objects * @return the union of the MBRs of the collection's entries * */ public Rectangle computeMBR(Iterator entries) { Rectangle descriptor = null; if (entries.hasNext()) { descriptor = (Rectangle) ((Descriptor) rectangle(entries.next())) .clone(); while (entries.hasNext()) descriptor.union(rectangle(entries.next())); } return descriptor; } /** * */ protected NodeConverter createNodeConverter() { return new NodeConverter(); } /** * */ protected void insert(Object data, Descriptor descriptor, int targetLevel) { if (rootEntry() == null) { Comparable key = ((Separator) descriptor).sepValue(); Rectangle indexEntryMBR = ((ORSeparator) descriptor) .getIndexEntryMBR(); Rectangle mbr = (Rectangle) ((Descriptor) indexEntryMBR).clone(); rootDescriptor = createORKeyRange(key, key, mbr); grow(data); } else super.insert(data, descriptor, targetLevel); } /** * Original Algorithm from ORTree The cursor does not support remove * operations. To remove the object from the tree call * {@link HilbertRTree#remove(Object)} * * @see ORTree#query() * @param queryDescriptor * @param targetLevel * @return */ public Cursor queryOR(final Rectangle queryRectangle, final int targetLevel) { final Iterator[] iterators = new Iterator[height() + 1]; Arrays.fill(iterators, EmptyCursor.DEFAULT_INSTANCE); if (height() > 0 && queryRectangle.overlaps(((ORKeyRange) rootDescriptor()) .getIndexEntryMBR())) iterators[height()] = new SingleObjectCursor(rootEntry()); return new AbstractCursor() { int queryAllLevel = 0; Object toRemove = null; Stack path = new Stack(); public boolean hasNextObject() { for (int parentLevel = targetLevel;;) if (iterators[parentLevel].hasNext()) if (parentLevel == targetLevel) return true; else { IndexEntry indexEntry = (IndexEntry) iterators[parentLevel] .next(); if (indexEntry.level() >= targetLevel) { Tree.Node node = indexEntry.get(true); Iterator queryIterator; if (parentLevel <= queryAllLevel || queryRectangle .contains(((ORSeparator) indexEntry .separator()) .getIndexEntryMBR())) { queryIterator = node.entries(); // Falls // alle // entries if (parentLevel > queryAllLevel && !iterators[node.level].hasNext()) queryAllLevel = node.level; } else // edit queryIterator = ((Node) node) .queryOR(queryRectangle); iterators[parentLevel = node.level] = iterators[parentLevel] .hasNext() ? new Sequentializer( queryIterator, iterators[parentLevel]) : queryIterator; path.push(new MapEntry(indexEntry, node)); } } else if (parentLevel == height()) return false; else { if (parentLevel == queryAllLevel) queryAllLevel = 0; if (level(path) == parentLevel) path.pop(); iterators[parentLevel++] = EmptyCursor.DEFAULT_INSTANCE; } } public Object nextObject() { return toRemove = iterators[targetLevel].next(); } public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } }; } /** * This method is an implementation of an efficient querying algorithm. The * result is a lazy cursor pointing to all leaf entries whose descriptors * overlap with the given <tt>queryDescriptor</tt>. * * @param queryDescriptor * describes the query in terms of a descriptor * @return a lazy <tt>Cursor</tt> pointing to all response objects * @see HilbertRTree#query(Descriptor, int) */ public Cursor queryOR(Rectangle queryDescriptor) { return queryOR(queryDescriptor, 0); } /** * This method executes a query using the rootDescriptor on a given level. * That means, that the response consists of all entries of the given level. * * @param level * the target level of the query * @return a lazy <tt>Cursor</tt> pointing to all response objects */ public Cursor queryOR(int level) { return queryOR(((ORKeyRange) rootDescriptor()).entryMBR, level); } /** * This method executes a query using the rootDescriptor on the leaf level. * That means, that the response consists of all leaf entries (i.e. data * objects) stored in the tree. * * @return a lazy Cursor pointing to all response objects */ public Cursor queryOR() { return queryOR(((ORKeyRange) rootDescriptor()).entryMBR, 0); } /** * @see BPlusTree.Node * */ public class Node extends BPlusTree.Node { /** * * @param level */ public Node(int level) { super(level); } /** * @see BPlusTree.Node#initialize(Object) */ public Tree.Node.SplitInfo initialize(Object entry) { Stack path = new Stack(); Rectangle entryMBR = ((ORKeyRange) rootDescriptor).entryMBR; if (height() > 0) { IndexEntry indexEntry = (IndexEntry) entry; if (indexEntry == rootEntry) { indexEntry.separator = createORSeparator( ((ORKeyRange) rootDescriptor).maxBound, (Rectangle) ((Descriptor) entryMBR).clone()); } } grow(entry, path); // separator of new root entry in tree.grow() method Comparable bound = ((ORKeyRange) rootDescriptor).maxBound; return new SplitInfo(path).initialize(createORSeparator(bound, (Rectangle) ((Descriptor) entryMBR).clone())); } /** * Returns an iterator pointing to entries whose MBRs overlap * <tt>queryMBR</tt>. * * @param queryRectangle * the descriptor describing the query * @return an iterator pointing to entries whose MBRs overlap with * <tt>queryMBR</tt> */ public Iterator queryOR(final Rectangle queryRectangle) { return new Filter(entries(), new AbstractPredicate() { public boolean invoke(Object object) { return rectangle(object).overlaps(queryRectangle); } }); } /** * @see BPlusTree.Node#chooseSubtree(Descriptor, Stack) */ protected Tree.IndexEntry chooseSubtree(Descriptor descriptor, Stack path) { IndexEntry indexEntry = (IndexEntry) super.chooseSubtree( descriptor, path); ORSeparator entryDescriptor = (ORSeparator) descriptor; ORSeparator indexEntryDescriptor = (ORSeparator) indexEntry .separator(); if (!indexEntryDescriptor.containsMBR(entryDescriptor .getIndexEntryMBR())) { indexEntryDescriptor.unionMBR(entryDescriptor); update(path); } return indexEntry; } /** * This method always returns false. * * @see BPlusTree.Node#redistributeLeaf(Stack) * */ protected boolean redistributeLeaf(Stack path) { return false; } /** * * This method is used to split the first <tt>Node</tt> on the path in * two <tt>Nodes</tt>. The current <tt>Node</tt> should be the empty new * <tt>Node</tt>. The method distributes the entries of overflowing * <tt>Node</tt> to the both <tt>Nodes</tt>. The Split-Algorithm (This * is equivalent to the R*Tree SplitAlgorithm after the suitable axis * was choosen) tests all distributions (M-2m+2) according the * SpaceFillingCurve order and chooses distribution with minimal * overlap-, margin- and area Value. * * @param path * the <tt>Node</tt> already visited during this insert * @return a <tt>SplitInfo</tt> containig all needed information about * the split */ protected Tree.Node.SplitInfo split(Stack path) { Node node = (Node) node(path); Distribution distribution; List<Distribution> distributionsList = new ArrayList<Distribution>(); int minEntries = node.splitMinNumber(); int maxEntries = node.splitMaxNumber(); maxEntries = (maxEntries < node.number() && maxEntries > minEntries) ? maxEntries : minEntries; // copied from rtree split algorithmus Rectangle[][] rectangles = new Rectangle[2][]; for (int k = 0; k < 2; k++) { Iterator entries = (k == 0 ? node.entries : new ReversedList( node.entries)).iterator(); Rectangle rectangle = new DoublePointRectangle( rectangle(entries.next())); for (int l = (k == 0 ? minEntries : node.number() - maxEntries); --l > 0;) rectangle.union(rectangle(entries.next())); (rectangles[k] = new Rectangle[maxEntries - minEntries + 1])[0] = rectangle; for (int j = 1; j <= maxEntries - minEntries; rectangles[k][j++] = rectangle) rectangle = Descriptors.union(rectangle, rectangle(entries.next())); } // Creation of the distributions for (int j = minEntries; j <= maxEntries; j++) distributionsList.add(new Distribution(node.entries.toArray(), j, rectangles[0][j - minEntries], rectangles[1][maxEntries - j])); // Choose the distributions of the chosen dimension with minimal // overlap distributionsList = Cursors.minima(distributionsList.iterator(), new AbstractFunction() { public Object invoke(Object object) { return new Double(((Distribution) object) .overlapValue()); } }); // If still more than one distribution has to be considered, choose // one // with minimal perimeter distributionsList = Cursors.minima(distributionsList.iterator(), new AbstractFunction() { public Object invoke(Object object) { // return new // Double(((Distribution)object).areaValue()); return new Double(((Distribution) object) .marginValue()); } }); distributionsList = Cursors.minima(distributionsList.iterator(), new AbstractFunction() { public Object invoke(Object object) { return new Double(((Distribution) object) .areaValue()); // return new // Double(((Distribution)object).marginValue()); } }); distribution = (Distribution) distributionsList.get(0); node.entries.clear(); node.entries.addAll(distribution.entries(false)); entries.addAll(distribution.entries(true)); ORSeparator sepNewNode = (ORSeparator) separator(this.getLast()) .clone(); sepNewNode.entryMBR = (Rectangle) distribution.descriptor(true) .clone(); // update the descriptor of the old index entry ((ORSeparator) (((BPlusTree.IndexEntry) (indexEntry(path))).separator)).entryMBR = (Rectangle) distribution .descriptor(false).clone(); return (new SplitInfo(path)).initialize(sepNewNode); } /** * Updates MBR after the entry was deleted but the node was not overflow */ protected boolean adjustSeparatorValue(BPlusTree.Node node, IndexEntry indexEntry, Stack path) { super.adjustSeparatorValue(node, indexEntry, path); if (!path.isEmpty()) {// indexEntry.separator() != null ){ Rectangle mbr = (Rectangle) ((Descriptor) computeMBR(node .entries())).clone(); ((ORSeparator) indexEntry.separator).updateMBR(mbr); return true; } return false; } /** * @see BPlusTree.Node#redressUnderflow(xxl.core.indexStructures.BPlusTree.IndexEntry, * xxl.core.indexStructures.BPlusTree.Node, Stack, boolean) */ protected boolean redressUnderflow(IndexEntry indexEntry, BPlusTree.Node node, Stack path, boolean up) { Node parentNode = (Node) node(path); // The merge... MergeInfo mergeInfo = node.merge(indexEntry, path); // Post the changes to the parent Node... if (mergeInfo.isMerge()) { if (mergeInfo.isRightSide()) { ORSeparator separatorNode = (ORSeparator) mergeInfo .newSeparator(); Rectangle mbrNode = (Rectangle) ((Descriptor) computeMBR(mergeInfo .node().entries())).clone(); separatorNode.entryMBR = mbrNode; ((IndexEntry) parentNode.getEntry(mergeInfo.index())).separator = separatorNode; mergeInfo.indexEntry().update(mergeInfo.node(), true); ((IndexEntry) parentNode.remove(mergeInfo.index() + 1)) .remove(); } else { ORSeparator separatorSibling = (ORSeparator) mergeInfo .siblingNewSeparator(); Rectangle mbrSiblingNode = (Rectangle) ((Descriptor) computeMBR(mergeInfo .siblingNode().entries())).clone(); separatorSibling.entryMBR = mbrSiblingNode; ((IndexEntry) parentNode.getEntry(mergeInfo.index() - 1)).separator = separatorSibling; mergeInfo.siblingIndexEntry().update( mergeInfo.siblingNode(), true); ((IndexEntry) parentNode.remove(mergeInfo.index())) .remove(); } } else { ORSeparator separatorNode = (ORSeparator) mergeInfo .newSeparator(); Rectangle mbrNode = (Rectangle) ((Descriptor) computeMBR(mergeInfo .node().entries())).clone(); separatorNode.entryMBR = mbrNode; ORSeparator separatorSibling = (ORSeparator) mergeInfo .siblingNewSeparator(); Rectangle mbrSiblingNode = (Rectangle) ((Descriptor) computeMBR(mergeInfo .siblingNode().entries())).clone(); separatorSibling.entryMBR = mbrSiblingNode; ((IndexEntry) parentNode.getEntry(mergeInfo.index())).separator = separatorNode; int sibIndex = mergeInfo.isRightSide() ? mergeInfo.index() + 1 : mergeInfo.index() - 1; ((IndexEntry) parentNode.getEntry(sibIndex)).separator = separatorSibling; if (up) { mergeInfo.indexEntry().update(mergeInfo.node(), true); mergeInfo.siblingIndexEntry().update( mergeInfo.siblingNode(), true); } } return true; } /** * <tt>Distribution</tt> is the class used to represent the distribution * of entries of a node of the <tt>HilbertRTree</tt> into two partitions * used for a split. * * @see RTree.Node.Distribution */ protected class Distribution { /** * Entries stored in this distribution. */ protected Object[] entries; /** * Start index of the second part of the distribution. */ protected int secondStart; /** * Bounding Rectangle of the first part of the distribution. */ protected Rectangle firstDescriptor; /** * Bounding Rectangle of the first part of the distribution. */ protected Rectangle secondDescriptor; /** * @param entries * an array containing all entries to be distributed * @param secondStart * the start index of the second partition * @param firstDescriptor * the descriptor for the first partition * @param secondDescriptor * the descriptor for the second partition * @param dim * the number of dimensions */ protected Distribution(Object[] entries, int secondStart, Rectangle firstDescriptor, Rectangle secondDescriptor) { this.entries = entries; this.secondStart = secondStart; this.firstDescriptor = firstDescriptor; this.secondDescriptor = secondDescriptor; } /** * Returns one of the partitions of this distribution. * * @param second * a <tt>boolean</tt> value determining if the second * partition should be returned * @return the entries of the first (if <tt>second == false</tt>) or * of the second partition (if <tt>second == true</tt>) */ protected List entries(boolean second) { return Arrays.asList(entries).subList(second ? secondStart : 0, second ? entries.length : secondStart); } /** * Returns a descriptor of one of the partitions of this * distribution. * * @param second * a <tt>boolean</tt> value determining if the descriptor * of second partition should be returned * @return the descriptor of the first (if <tt>second == false</tt>) * or of the second partition (if <tt>second == true</tt>) */ protected Descriptor descriptor(boolean second) { return second ? secondDescriptor : firstDescriptor; } /** * Returns the sum of the margins of the two partitions. * * @return the sum of the margins of the two partitions */ protected double marginValue() { return firstDescriptor.margin() + secondDescriptor.margin(); } /** * Returns the overlap of the two partitions. * * @return the overlap of the two partitions */ protected double overlapValue() { return firstDescriptor.overlap(secondDescriptor); } /** * Returns the sum of the areas of the two partitions. * * @return the sum of the areas of the two partitions */ protected double areaValue() { return firstDescriptor.area() + secondDescriptor.area(); } } } /** * Extends Separator with Rectangle field which represents the MBR. * * @see Separator */ public static abstract class ORSeparator extends Separator { /** */ protected Rectangle entryMBR; /** * * @param separatorValue * @param entryMBR */ public ORSeparator(Comparable separatorValue, Rectangle entryMBR) { super(separatorValue); this.entryMBR = entryMBR; } /** * */ public Rectangle getIndexEntryMBR() { return entryMBR; } /** * * @param descriptor * @return */ public boolean overlapsMBR(Descriptor descriptor) { if (descriptor instanceof ORSeparator) { return this.entryMBR.overlaps(((ORSeparator) descriptor) .getIndexEntryMBR()); } else if (descriptor instanceof Rectangle) { return this.entryMBR.overlaps(descriptor); } else { throw new IllegalArgumentException(); } } /** * * @param descriptor * @return */ public boolean containsMBR(Descriptor descriptor) { if (descriptor instanceof ORSeparator) { return this.entryMBR.contains(((ORSeparator) descriptor) .getIndexEntryMBR()); } else if (descriptor instanceof Rectangle) { return this.entryMBR.contains(descriptor); } else { throw new IllegalArgumentException(); } } /** * * @param descriptor * @return */ public void unionMBR(Descriptor descriptor) { if (descriptor instanceof ORSeparator) { this.entryMBR.union(((ORSeparator) descriptor) .getIndexEntryMBR()); } else if (descriptor instanceof Rectangle) { this.entryMBR.union(descriptor); } else { throw new IllegalArgumentException(); } } public void updateMBR(Rectangle mbr) { this.entryMBR = mbr; } } /** * @see BPlusTree.KeyRange */ public static abstract class ORKeyRange extends KeyRange { protected Rectangle entryMBR; public ORKeyRange(Comparable min, Comparable max, Rectangle entryMBR) { super(min, max); this.entryMBR = entryMBR; } /** * */ public Descriptor getIndexEntryMBR() { return this.entryMBR; } /** * */ public void union(Descriptor descriptor) { if (!(descriptor instanceof ORSeparator)) return; super.union(descriptor); if (descriptor instanceof ORKeyRange) { this.entryMBR.union(((ORKeyRange) descriptor) .getIndexEntryMBR()); } else if (descriptor instanceof ORSeparator) { this.entryMBR.union(((ORSeparator) descriptor) .getIndexEntryMBR()); } } /** * */ public void updateMBR(Rectangle mbr) { this.entryMBR = mbr; } } /** * @see BPlusTree.NodeConverter */ public class NodeConverter extends BPlusTree.NodeConverter { @Override public Object read(DataInput dataInput, Object object) throws IOException { int level = dataInput.readInt(); Node node = (Node) createNode(level); int number = dataInput.readInt(); boolean readNext = dataInput.readBoolean(); if (readNext) { node.nextNeighbor = (IndexEntry) createIndexEntry(level + 1); Container container = node.nextNeighbor.container(); node.nextNeighbor.id = container.objectIdConverter().read( dataInput, null); } else node.nextNeighbor = null; for (int i = 0; i < number; i++) { Object entry; if (node.level == 0) entry = dataConverter.read(dataInput, null); else entry = readIndexEntry(dataInput, createIndexEntry(level)); node.entries.add(i, entry); } return node; } public void write(DataOutput dataOutput, Object object) throws IOException { Node node = (Node) object; // 2x Integer dataOutput.writeInt(node.level); dataOutput.writeInt(node.number()); // Boolean dataOutput.writeBoolean(node.nextNeighbor != null); // ID if (node.nextNeighbor != null) { Container container = node.nextNeighbor.container(); container.objectIdConverter().write(dataOutput, node.nextNeighbor.id()); } // entries for (int i = 0; i < node.number(); i++) { if (node.level == 0) dataConverter.write(dataOutput, node.getEntry(i)); else writeIndexEntry(dataOutput, node.getEntry(i)); } } protected void writeIndexEntry(DataOutput dataOutput, Object entry) throws IOException { IndexEntry indexEntry = (IndexEntry) entry; Container container = indexEntry.container(); container.objectIdConverter().write(dataOutput, indexEntry.id()); keyConverter.write(dataOutput, indexEntry.separator().sepValue); ORSeparator orSeparator = (ORSeparator) indexEntry.separator(); descriptorConverter.write(dataOutput, orSeparator.entryMBR); } protected Object readIndexEntry(DataInput dataInput, Object entry) throws IOException { IndexEntry indexEntry = (IndexEntry) entry; Container container = indexEntry.container(); indexEntry.id = container.objectIdConverter().read(dataInput, null); Comparable sepValue = (Comparable) keyConverter.read(dataInput, null); Rectangle mbr = (Rectangle) descriptorConverter.read(dataInput, null); indexEntry.separator = createORSeparator(sepValue, mbr); return indexEntry; } /** * Computes the maximal size (in bytes) of an <tt>IndexEntry</tt>. It * calls the method getIdSize() of the tree container. If the tree * container has not been initialized a NullPointerException is thrown. * * @return the maximal size of an <tt>IndexEntry</tt> */ protected int indexEntrySize() { return super.indexEntrySize() + dimension * 2 * 8; } protected int leafEntrySize() { return dataConverter.getMaxObjectSize(); } } }