diff --git a/src/ol/extent.js b/src/ol/extent.js
index dee6c57968..dc937c2df2 100644
--- a/src/ol/extent.js
+++ b/src/ol/extent.js
@@ -70,8 +70,7 @@ ol.extent.containsCoordinate = function(extent, coordinate) {
/**
- * Checks if the passed extent is contained or on the edge of the
- * extent.
+ * Checks if `extent2` is contained by or on the edge of `extent1`.
*
* @param {ol.Extent} extent1 Extent 1.
* @param {ol.Extent} extent2 Extent 2.
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index a3f5ca98cc..cb934c612e 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -102,8 +102,9 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) {
}
}
if (extentFilter && geometryFilter) {
- features = this.rTree_.find(
- extentFilter.getExtent(), geometryFilter.getType());
+ var type = geometryFilter.getType();
+ features = goog.object.isEmpty(this.geometryTypeIndex_[type]) ? {} :
+ this.rTree_.find(extentFilter.getExtent(), type);
}
}
}
diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js
index b56164f527..4500334be2 100644
--- a/src/ol/structs/rtree.js
+++ b/src/ol/structs/rtree.js
@@ -1,208 +1,579 @@
+/******************************************************************************
+ rtree.js - General-Purpose Non-Recursive Javascript R-Tree Library
+ Version 0.6.2, December 5st 2009
+
+ Copyright (c) 2009 Jon-Carlos Rivera
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Jon-Carlos Rivera - imbcmdth@hotmail.com
+******************************************************************************/
+
+
goog.provide('ol.structs.RTree');
-goog.require('goog.object');
+goog.require('goog.array');
goog.require('ol.extent');
+/**
+ * @typedef {{extent: (ol.Extent), leaf: (Object|undefined),
+ * nodes: (Array.
|undefined),
+ * target: (Object|undefined), type: (string|undefined)}}
+ */
+ol.structs.RTreeNode;
+
+
/**
- * @private
+ * @param {number=} opt_width Width before a node is split. Default is 6.
* @constructor
- * @param {ol.Extent} bounds Extent.
- * @param {ol.structs.RTreeNode_} parent Parent node.
- * @param {number} level Level in the tree hierarchy.
*/
-ol.structs.RTreeNode_ = function(bounds, parent, level) {
+ol.structs.RTree = function(opt_width) {
+ // Variables to control tree-dimensions
+ var minWidth = 3; // Minimum width of any node before a merge
+ var maxWidth = 6; // Maximum width of any node before a split
+ if (!isNaN(opt_width)) {
+ minWidth = Math.floor(opt_width / 2);
+ maxWidth = opt_width;
+ }
+ // Start with an empty root-tree
+ var rootTree = /** @type {ol.structs.RTreeNode} */
+ ({extent: [0, 0, 0, 0], nodes: []});
/**
- * @type {ol.Extent}
+ * This is Jon-Carlos Rivera's special addition to the world of r-trees.
+ * Every other (simple) method he found produced crap trees.
+ * This skews insertions to prefering squarer and emptier nodes.
+ *
+ * @param {number} l L.
+ * @param {number} w W.
+ * @param {number} fill Fill.
+ * @return {number} Squarified ratio.
*/
- this.bounds = bounds;
+ var squarifiedRatio = function(l, w, fill) {
+ // Area of new enlarged rectangle
+ var peri = (l + w) / 2; // Average size of a side of the new rectangle
+ var area = l * w; // Area of new rectangle
+ // return the ratio of the perimeter to the area - the closer to 1 we are,
+ // the more "square" a rectangle is. conversly, when approaching zero the
+ // more elongated a rectangle is
+ var geo = area / (peri * peri);
+ return area * fill / geo;
+ };
/**
- * @type {Object}
+ * Generates a minimally bounding rectangle for all rectangles in
+ * array "nodes". `rect` is modified into the MBR.
+ *
+ * @param {Array} nodes Nodes.
+ * @param {ol.structs.RTreeNode} rect Rectangle.
+ * @return {ol.structs.RTreeNode} Rectangle.
*/
- this.object;
+ var makeMBR = function(nodes, rect) {
+ if (nodes.length < 1) {
+ return {extent: [0, 0, 0, 0]};
+ }
+ rect.extent = nodes[0].extent.concat();
+
+ for (var i = nodes.length - 1; i > 0; --i) {
+ ol.extent.extend(rect.extent, nodes[i].extent);
+ }
+
+ return rect;
+ };
/**
- * @type {string}
+ * Find the best specific node(s) for object to be deleted from.
+ *
+ * @param {ol.structs.RTreeNode} rect Rectangle.
+ * @param {Object} obj Object.
+ * @param {ol.structs.RTreeNode} root Root to start search.
+ * @return {Array} Leaf node parent.
*/
- this.objectId;
+ var removeSubtree = function(rect, obj, root) {
+ var hitStack = []; // Contains the elements that overlap
+ var countStack = []; // Contains the elements that overlap
+ var returnArray = [];
+ var currentDepth = 1;
+
+ if (!rect || !ol.extent.intersects(rect.extent, root.extent)) {
+ return returnArray;
+ }
+
+ /** @type {ol.structs.RTreeNode} */
+ var workingObject = /** @type {ol.structs.RTreeNode} */
+ ({extent: rect.extent.concat(), target: obj});
+
+ countStack.push(root.nodes.length);
+ hitStack.push(root);
+
+ do {
+ var tree = hitStack.pop();
+ var i = countStack.pop() - 1;
+
+ if (goog.isDef(workingObject.target)) {
+ // We are searching for a target
+ while (i >= 0) {
+ var lTree = tree.nodes[i];
+ if (ol.extent.intersects(workingObject.extent, lTree.extent)) {
+ if ((workingObject.target && goog.isDef(lTree.leaf) &&
+ lTree.leaf === workingObject.target) ||
+ (!workingObject.target && (goog.isDef(lTree.leaf) ||
+ ol.extent.containsExtent(workingObject.extent, lTree.extent))))
+ { // A Match !!
+ // Yup we found a match...
+ // we can cancel search and start walking up the list
+ if (goog.isDef(lTree.nodes)) {
+ // If we are deleting a node not a leaf...
+ returnArray = searchSubtree(lTree, true, [], lTree);
+ tree.nodes.splice(i, 1);
+ } else {
+ returnArray = tree.nodes.splice(i, 1);
+ }
+ // Resize MBR down...
+ makeMBR(tree.nodes, tree);
+ workingObject.target = undefined;
+ if (tree.nodes.length < minWidth) { // Underflow
+ workingObject.nodes = /** @type {Array} */
+ (searchSubtree(tree, true, [], tree));
+ }
+ break;
+ } else if (goog.isDef(lTree.nodes)) {
+ // Not a Leaf
+ currentDepth += 1;
+ countStack.push(i);
+ hitStack.push(tree);
+ tree = lTree;
+ i = lTree.nodes.length;
+ }
+ }
+ i -= 1;
+ }
+ } else if (goog.isDef(workingObject.nodes)) {
+ // We are unsplitting
+ tree.nodes.splice(i + 1, 1); // Remove unsplit node
+ // workingObject.nodes contains a list of elements removed from the
+ // tree so far
+ if (tree.nodes.length > 0) {
+ makeMBR(tree.nodes, tree);
+ }
+ for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t) {
+ insertSubtree(workingObject.nodes[t], tree);
+ }
+ workingObject.nodes.length = 0;
+ if (hitStack.length === 0 && tree.nodes.length <= 1) {
+ // Underflow..on root!
+ workingObject.nodes = /** @type {Array} */
+ (searchSubtree(tree, true, workingObject.nodes, tree));
+ tree.nodes.length = 0;
+ hitStack.push(tree);
+ countStack.push(1);
+ } else if (hitStack.length > 0 && tree.nodes.length < minWidth) {
+ // Underflow..AGAIN!
+ workingObject.nodes = /** @type {Array} */
+ (searchSubtree(tree, true, workingObject.nodes, tree));
+ tree.nodes.length = 0;
+ } else {
+ workingObject.nodes = undefined; // Just start resizing
+ }
+ } else { // we are just resizing
+ makeMBR(tree.nodes, tree);
+ }
+ currentDepth -= 1;
+ } while (hitStack.length > 0);
+
+ return returnArray;
+ };
/**
- * @type {ol.structs.RTreeNode_}
+ * Choose the best damn node for rectangle to be inserted into.
+ *
+ * @param {ol.structs.RTreeNode} rect Rectangle.
+ * @param {ol.structs.RTreeNode} root Root to start search.
+ * @return {Array} Leaf node parent.
*/
- this.parent = parent;
+ var chooseLeafSubtree = function(rect, root) {
+ var bestChoiceIndex = -1;
+ var bestChoiceStack = [];
+ var bestChoiceArea;
+
+ bestChoiceStack.push(root);
+ var nodes = root.nodes;
+
+ do {
+ if (bestChoiceIndex != -1) {
+ bestChoiceStack.push(nodes[bestChoiceIndex]);
+ nodes = nodes[bestChoiceIndex].nodes;
+ bestChoiceIndex = -1;
+ }
+
+ for (var i = nodes.length - 1; i >= 0; --i) {
+ var lTree = nodes[i];
+ if (goog.isDef(lTree.leaf)) {
+ // Bail out of everything and start inserting
+ bestChoiceIndex = -1;
+ break;
+ }
+ // Area of new enlarged rectangle
+ var oldLRatio = squarifiedRatio(lTree.extent[1] - lTree.extent[0],
+ lTree.extent[3] - lTree.extent[2], lTree.nodes.length + 1);
+
+ // Enlarge rectangle to fit new rectangle
+ var nw = (lTree.extent[1] > rect.extent[1] ?
+ lTree.extent[1] : rect.extent[1]) -
+ (lTree.extent[0] < rect.extent[0] ?
+ lTree.extent[0] : rect.extent[0]);
+ var nh = (lTree.extent[3] > rect.extent[3] ?
+ lTree.extent[3] : rect.extent[3]) -
+ (lTree.extent[2] < rect.extent[2] ?
+ lTree.extent[2] : rect.extent[2]);
+
+ // Area of new enlarged rectangle
+ var lRatio = squarifiedRatio(nw, nh, lTree.nodes.length + 2);
+
+ if (bestChoiceIndex < 0 ||
+ Math.abs(lRatio - oldLRatio) < bestChoiceArea) {
+ bestChoiceArea = Math.abs(lRatio - oldLRatio);
+ bestChoiceIndex = i;
+ }
+ }
+ } while (bestChoiceIndex != -1);
+
+ return bestChoiceStack;
+ };
/**
- * @type {number}
+ * Split a set of nodes into two roughly equally-filled nodes.
+ *
+ * @param {Array.} nodes Array of nodes.
+ * @return {Array.>} An array of two new arrays
+ * of nodes.
*/
- this.level = level;
+ var linearSplit = function(nodes) {
+ var n = pickLinear(nodes);
+ while (nodes.length > 0) {
+ pickNext(nodes, n[0], n[1]);
+ }
+ return n;
+ };
/**
- * @type {Object.}
+ * Insert the best source rectangle into the best fitting parent node: a or b.
+ *
+ * @param {Array.} nodes Source node array.
+ * @param {ol.structs.RTreeNode} a Target node array a.
+ * @param {ol.structs.RTreeNode} b Target node array b.
*/
- this.types = {};
+ var pickNext = function(nodes, a, b) {
+ // Area of new enlarged rectangle
+ var areaA = squarifiedRatio(a.extent[1] - a.extent[0],
+ a.extent[3] - a.extent[2], a.nodes.length + 1);
+ var areaB = squarifiedRatio(b.extent[1] - b.extent[0],
+ b.extent[3] - b.extent[2], b.nodes.length + 1);
+ var highAreaDelta;
+ var highAreaNode;
+ var lowestGrowthGroup;
+
+ for (var i = nodes.length - 1; i >= 0; --i) {
+ var l = nodes[i];
+
+ var newAreaA = [
+ a.extent[0] < l.extent[0] ? a.extent[0] : l.extent[0],
+ a.extent[1] > l.extent[1] ? a.extent[1] : l.extent[1],
+ a.extent[2] < l.extent[2] ? a.extent[2] : l.extent[2],
+ a.extent[3] > l.extent[3] ? a.extent[3] : l.extent[3]
+ ];
+ var changeNewAreaA = Math.abs(squarifiedRatio(newAreaA[1] - newAreaA[0],
+ newAreaA[3] - newAreaA[2], a.nodes.length + 2) - areaA);
+
+ var newAreaB = [
+ b.extent[0] < l.extent[0] ? b.extent[0] : l.extent[0],
+ b.extent[1] > l.extent[1] ? b.extent[1] : l.extent[1],
+ b.extent[2] < l.extent[2] ? b.extent[2] : l.extent[2],
+ b.extent[3] > l.extent[3] ? b.extent[3] : l.extent[3]
+ ];
+ var changeNewAreaB = Math.abs(squarifiedRatio(
+ newAreaB[1] - newAreaB[0], newAreaB[3] - newAreaB[2],
+ b.nodes.length + 2) - areaB);
+
+ var changeNewAreaDelta = Math.abs(changeNewAreaB - changeNewAreaA);
+ if (!highAreaNode || !highAreaDelta ||
+ changeNewAreaDelta < highAreaDelta) {
+ highAreaNode = i;
+ highAreaDelta = changeNewAreaDelta;
+ lowestGrowthGroup = changeNewAreaB < changeNewAreaA ? b : a;
+ }
+ }
+ var tempNode = nodes.splice(highAreaNode, 1)[0];
+ if (a.nodes.length + nodes.length + 1 <= minWidth) {
+ a.nodes.push(tempNode);
+ ol.extent.extend(a.extent, tempNode.extent);
+ } else if (b.nodes.length + nodes.length + 1 <= minWidth) {
+ b.nodes.push(tempNode);
+ ol.extent.extend(b.extent, tempNode.extent);
+ }
+ else {
+ lowestGrowthGroup.nodes.push(tempNode);
+ ol.extent.extend(lowestGrowthGroup.extent, tempNode.extent);
+ }
+ };
/**
- * @type {Array.}
+ * Pick the "best" two starter nodes to use as seeds using the "linear"
+ * criteria.
+ *
+ * @param {Array.} nodes Array of source nodes.
+ * @return {Array.} An array of two new arrays
+ * of nodes.
*/
- this.children = [];
+ var pickLinear = function(nodes) {
+ var lowestHighX = nodes.length - 1;
+ var highestLowX = 0;
+ var lowestHighY = nodes.length - 1;
+ var highestLowY = 0;
+ var t1, t2;
-};
-
-
-/**
- * Find all objects intersected by a rectangle.
- * @param {ol.Extent} bounds Bounding box.
- * @param {Object.} results Target object for results.
- * @param {string=} opt_type Type for another indexing dimension.
- */
-ol.structs.RTreeNode_.prototype.find = function(bounds, results, opt_type) {
- if ((!goog.isDef(opt_type) || this.types[opt_type] === true) &&
- ol.extent.intersects(this.bounds, bounds)) {
- var numChildren = this.children.length;
- if (numChildren === 0) {
- if (goog.isDef(this.object)) {
- results[this.objectId] = this.object;
+ for (var i = nodes.length - 2; i >= 0; --i) {
+ var l = nodes[i];
+ if (l.extent[0] > nodes[highestLowX].extent[0]) {
+ highestLowX = i;
+ } else if (l.extent[1] < nodes[lowestHighX].extent[2]) {
+ lowestHighX = i;
+ }
+ if (l.extent[2] > nodes[highestLowY].extent[2]) {
+ highestLowY = i;
+ } else if (l.extent[3] < nodes[lowestHighY].extent[3]) {
+ lowestHighY = i;
+ }
+ }
+ var dx = Math.abs(nodes[lowestHighX].extent[1] -
+ nodes[highestLowX].extent[0]);
+ var dy = Math.abs(nodes[lowestHighY].extent[3] -
+ nodes[highestLowY].extent[2]);
+ if (dx > dy) {
+ if (lowestHighX > highestLowX) {
+ t1 = nodes.splice(lowestHighX, 1)[0];
+ t2 = nodes.splice(highestLowX, 1)[0];
+ } else {
+ t2 = nodes.splice(highestLowX, 1)[0];
+ t1 = nodes.splice(lowestHighX, 1)[0];
}
} else {
- for (var i = 0; i < numChildren; ++i) {
- this.children[i].find(bounds, results, opt_type);
+ if (lowestHighY > highestLowY) {
+ t1 = nodes.splice(lowestHighY, 1)[0];
+ t2 = nodes.splice(highestLowY, 1)[0];
+ } else {
+ t2 = nodes.splice(highestLowY, 1)[0];
+ t1 = nodes.splice(lowestHighY, 1)[0];
}
}
- }
-};
+ return [
+ /** @type {ol.structs.RTreeNode} */
+ ({extent: t1.extent.concat(), nodes: [t1]}),
+ /** @type {ol.structs.RTreeNode} */
+ ({extent: t2.extent.concat(), nodes: [t2]})
+ ];
+ };
-/**
- * Find the appropriate node for insertion.
- * @param {ol.Extent} bounds Bounding box.
- * @return {ol.structs.RTreeNode_|undefined} Matching node.
- */
-ol.structs.RTreeNode_.prototype.get = function(bounds) {
- if (ol.extent.intersects(this.bounds, bounds)) {
- var numChildren = this.children.length;
- if (numChildren === 0) {
- return goog.isNull(this.parent) ? this : this.parent;
- }
- var node;
- for (var i = 0; i < numChildren; ++i) {
- node = this.children[i].get(bounds);
- if (goog.isDef(node)) {
- return node;
- }
- }
- return this;
- }
-};
-
-
-/**
- * Update boxes up to the root to ensure correct bounding
- * @param {ol.Extent} bounds Bounding box.
- */
-ol.structs.RTreeNode_.prototype.update = function(bounds) {
- ol.extent.extend(this.bounds, bounds);
- if (!goog.isNull(this.parent)) {
- this.parent.update(bounds);
- }
-};
-
-
-/**
- * Divide @this node's children in half and create two new boxes containing
- * the split items. The top left will be the topmost leftmost child and the
- * bottom right will be the rightmost bottommost child.
- */
-ol.structs.RTreeNode_.prototype.divide = function() {
- var numChildren = this.children.length;
- if (numChildren === 0) {
- return;
- }
-
- var half = Math.ceil(numChildren / 2),
- child, node;
-
- for (var i = 0; i < numChildren; ++i) {
- child = this.children[i];
- if (i % half === 0) {
- node = new ol.structs.RTreeNode_(
- child.bounds.slice(), this, this.level + 1);
- goog.object.extend(this.types, node.types);
- this.children.push(node);
- }
- child.parent = /** @type {ol.structs.RTreeNode_} */ (node);
- goog.object.extend(node.types, child.types);
- node.children.push(child);
- ol.extent.extend(node.bounds, child.bounds);
- }
-};
-
-
-
-/**
- * @constructor
- */
-ol.structs.RTree = function() {
-
/**
- * @private
- * @type {ol.structs.RTreeNode_}
+ * Non-recursive internal search function
+ *
+ * @param {ol.structs.RTreeNode} rect Rectangle.
+ * @param {boolean} returnNode Do we return nodes?
+ * @param {Array|Object} result Result.
+ * @param {ol.structs.RTreeNode} root Root.
+ * @param {string=} opt_type Optional type to search for.
+ * @return {Array|Object} Result.
*/
- this.root_ = new ol.structs.RTreeNode_(
- [-Infinity, Infinity, -Infinity, Infinity], null, 0);
+ var searchSubtree = function(rect, returnNode, result, root, opt_type) {
+ var hitStack = []; // Contains the elements that overlap
-};
+ if (!ol.extent.intersects(rect.extent, root.extent)) {
+ return result;
+ }
+ hitStack.push(root.nodes);
-/**
- * @param {ol.Extent} bounds Bounding box.
- * @param {string=} opt_type Type for another indexing dimension.
- * @return {Object.} Results for the passed bounding box.
- */
-ol.structs.RTree.prototype.find = function(bounds, opt_type) {
- var results = /** @type {Object.} */ ({});
- this.root_.find(bounds, results, opt_type);
- return results;
-};
+ do {
+ var nodes = hitStack.pop();
+ for (var i = nodes.length - 1; i >= 0; --i) {
+ var lTree = nodes[i];
+ if (ol.extent.intersects(rect.extent, lTree.extent)) {
+ if (goog.isDef(lTree.nodes)) { // Not a Leaf
+ hitStack.push(lTree.nodes);
+ } else if (goog.isDef(lTree.leaf)) { // A Leaf !!
+ if (!returnNode) {
+ // TODO keep track of type on all nodes so we don't have to
+ // walk all the way in to the leaf to know that we don't need it
+ if (!goog.isDef(opt_type) || lTree.type == opt_type) {
+ var obj = lTree.leaf;
+ result[goog.getUid(obj).toString()] = obj;
+ }
+ } else {
+ result.push(lTree);
+ }
+ }
+ }
+ }
+ } while (hitStack.length > 0);
-/**
- * @param {ol.Extent} bounds Bounding box.
- * @param {Object} object Object to store with the passed bounds.
- * @param {string=} opt_type Type for another indexing dimension.
- */
-ol.structs.RTree.prototype.put = function(bounds, object, opt_type) {
- var found = this.root_.get(bounds);
- if (found) {
- var node = new ol.structs.RTreeNode_(bounds, found, found.level + 1);
- node.object = object;
- node.objectId = goog.getUid(object).toString();
+ return result;
+ };
- found.children.push(node);
- found.update(bounds);
+ /**
+ * Non-recursive internal insert function.
+ *
+ * @param {ol.structs.RTreeNode} node Node to insert.
+ * @param {ol.structs.RTreeNode} root Root to begin insertion at.
+ */
+ var insertSubtree = function(node, root) {
+ var bc; // Best Current node
+ // Initial insertion is special because we resize the Tree and we don't
+ // care about any overflow (seriously, how can the first object overflow?)
+ if (root.nodes.length === 0) {
+ root.extent = node.extent.concat();
+ root.nodes.push(node);
+ return;
+ }
+ // Find the best fitting leaf node
+ // chooseLeaf returns an array of all tree levels (including root)
+ // that were traversed while trying to find the leaf
+ var treeStack = chooseLeafSubtree(node, root);
+ var workingObject = node;
+
+ // Walk back up the tree resizing and inserting as needed
+ do {
+ //handle the case of an empty node (from a split)
+ if (bc && goog.isDef(bc.nodes) && bc.nodes.length === 0) {
+ var pbc = bc; // Past bc
+ bc = treeStack.pop();
+ for (var t = 0, tt = bc.nodes.length; t < tt; ++t) {
+ if (bc.nodes[t] === pbc || bc.nodes[t].nodes.length === 0) {
+ bc.nodes.splice(t, 1);
+ break;
+ }
+ }
+ } else {
+ bc = treeStack.pop();
+ }
+
+ // If there is data attached to this workingObject
+ var isArray = goog.isArray(workingObject);
+ if (goog.isDef(workingObject.leaf) ||
+ goog.isDef(workingObject.nodes) || isArray) {
+ // Do Insert
+ if (isArray) {
+ for (var ai = 0, aii = workingObject.length; ai < aii; ++ai) {
+ ol.extent.extend(bc.extent, workingObject[ai].extent);
+ }
+ bc.nodes = bc.nodes.concat(workingObject);
+ } else {
+ ol.extent.extend(bc.extent, workingObject.extent);
+ bc.nodes.push(workingObject); // Do Insert
+ }
+
+ if (bc.nodes.length <= maxWidth) { // Start Resizeing Up the Tree
+ workingObject = {extent: bc.extent.concat()};
+ } else { // Otherwise Split this Node
+ // linearSplit() returns an array containing two new nodes
+ // formed from the split of the previous node's overflow
+ var a = linearSplit(bc.nodes);
+ workingObject = a;//[1];
+
+ if (treeStack.length < 1) { // If are splitting the root..
+ bc.nodes.push(a[0]);
+ treeStack.push(bc); // Reconsider the root element
+ workingObject = a[1];
+ }
+ }
+ } else { // Otherwise Do Resize
+ //Just keep applying the new bounding rectangle to the parents..
+ ol.extent.extend(bc.extent, workingObject.extent);
+ workingObject = ({extent: bc.extent.concat()});
+ }
+ } while (treeStack.length > 0);
+ };
+
+ /**
+ * Non-recursive search function
+ *
+ * @param {ol.Extent} extent Extent.
+ * @param {string=} opt_type Optional type of the objects we want to find.
+ * @return {Object} Result. Keys are UIDs of the values.
+ * @this {ol.structs.RTree}
+ */
+ this.find = function(extent, opt_type) {
+ var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent});
+ return searchSubtree.apply(this, [rect, false, {}, rootTree, opt_type]);
+ };
+
+ /**
+ * Non-recursive function that deletes a specific region.
+ *
+ * @param {ol.Extent} extent Extent.
+ * @param {Object=} opt_obj Object.
+ * @return {Array} Result.
+ * @this {ol.structs.RTree}
+ */
+ this.remove = function(extent, opt_obj) {
+ arguments[0] = /** @type {ol.structs.RTreeNode} */ ({extent: extent});
+ switch (arguments.length) {
+ case 1:
+ arguments[1] = false; // opt_obj == false for conditionals
+ case 2:
+ arguments[2] = rootTree; // Add root node to end of argument list
+ default:
+ arguments.length = 3;
+ }
+ if (arguments[1] === false) { // Do area-wide †
+ var numberDeleted = 0;
+ var result = [];
+ do {
+ numberDeleted = result.length;
+ result = result.concat(removeSubtree.apply(this, arguments));
+ } while (numberDeleted != result.length);
+ return result;
+ } else { // Delete a specific item
+ return removeSubtree.apply(this, arguments);
+ }
+ };
+
+ /**
+ * Non-recursive insert function.
+ *
+ * @param {ol.Extent} extent Extent.
+ * @param {Object} obj Object to insert.
+ * @param {string=} opt_type Optional type to store along with the object.
+ */
+ this.put = function(extent, obj, opt_type) {
+ var node = /** @type {ol.structs.RTreeNode} */
+ ({extent: extent, leaf: obj});
if (goog.isDef(opt_type)) {
- node.types[opt_type] = true;
- found.types[opt_type] = true;
+ node.type = opt_type;
}
+ insertSubtree(node, rootTree);
+ };
- if (found.children.length >= ol.structs.RTree.MAX_OBJECTS &&
- found.level < ol.structs.RTree.MAX_SUB_DIVISIONS) {
- found.divide();
- }
- }
+ //End of RTree
};
-
-
-/**
- * @type {number}
- */
-ol.structs.RTree.MAX_SUB_DIVISIONS = 6;
-
-
-/**
- * @type {number}
- */
-ol.structs.RTree.MAX_OBJECTS = 6;
diff --git a/test/spec/ol/structs/rtree.test.js b/test/spec/ol/structs/rtree.test.js
index b2e774fcd5..e5ee9baef6 100644
--- a/test/spec/ol/structs/rtree.test.js
+++ b/test/spec/ol/structs/rtree.test.js
@@ -3,23 +3,94 @@ goog.provide('ol.test.structs.RTree');
describe('ol.structs.RTree', function() {
- describe('put and find', function() {
- var rTree = new ol.structs.RTree();
- rTree.put([0, 1, 0, 1], 1);
- rTree.put([1, 4, 1, 4], 2);
- rTree.put([2, 3, 2, 3], 3);
- rTree.put([-5, -4, -5, -4], 4);
- rTree.put([-4, -1, -4, -1], 5);
- rTree.put([-3, -2, -3, -2], 6);
+ var rTree = new ol.structs.RTree();
- it('stores items', function() {
- expect(goog.object.getCount(rTree.find([
- Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY,
- Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY
- ]))).to.be(6);
+ describe('creation', function() {
+ it('can insert 1k objects', function() {
+ var i = 1000;
+ while (i > 0) {
+ var bounds = new Array(4);
+ bounds[0] = Math.random() * 10000;
+ bounds[1] = bounds[0] + Math.random() * 500;
+ bounds[2] = Math.random() * 10000;
+ bounds[3] = bounds[2] + Math.random() * 500;
+ rTree.put(bounds, 'JUST A TEST OBJECT!_' + i);
+ i--;
+ }
+ expect(goog.object.getCount(rTree.find([0, 10600, 0, 10600])))
+ .to.be(1000);
});
+ it('can insert 1k more objects', function() {
+ var i = 1000;
+ while (i > 0) {
+ var bounds = new Array(4);
+ bounds[0] = Math.random() * 10000;
+ bounds[1] = bounds[0] + Math.random() * 500;
+ bounds[2] = Math.random() * 10000;
+ bounds[3] = bounds[2] + Math.random() * 500;
+ rTree.put(bounds, 'JUST A TEST OBJECT!_' + i);
+ i--;
+ }
+ expect(goog.object.getCount(rTree.find([0, 10600, 0, 10600])))
+ .to.be(2000);
+ });
+ });
+
+ describe('search', function() {
+ it('can perform 1k out-of-bounds searches', function() {
+ var i = 1000;
+ var len = 0;
+ while (i > 0) {
+ var bounds = new Array(4);
+ bounds[0] = -(Math.random() * 10000 + 501);
+ bounds[1] = bounds[0] + Math.random() * 500;
+ bounds[2] = -(Math.random() * 10000 + 501);
+ bounds[3] = bounds[2] + Math.random() * 500;
+ len += goog.object.getCount(rTree.find(bounds));
+ i--;
+ }
+ expect(len).to.be(0);
+ });
+ it('can perform 1k in-bounds searches', function() {
+ var i = 1000;
+ var len = 0;
+ while (i > 0) {
+ var bounds = new Array(4);
+ bounds[0] = -Math.random() * 10000 + 501;
+ bounds[1] = bounds[0] + Math.random() * 500;
+ bounds[2] = -Math.random() * 10000 + 501;
+ bounds[3] = bounds[2] + Math.random() * 500;
+ len += goog.object.getCount(rTree.find(bounds));
+ i--;
+ }
+ expect(len).not.to.be(0);
+ });
+ });
+
+ describe('deletion', function() {
+ var len = 0;
+ it('can delete half the RTree', function() {
+ var bounds = [5000, 10500, 0, 10500];
+ len += rTree.remove(bounds).length;
+ expect(len).to.not.be(0);
+ });
+ it('can delete the other half of the RTree', function() {
+ var bounds = [0, 5000, 0, 10500];
+ len += rTree.remove(bounds).length;
+ expect(len).to.be(2000);
+ });
+ });
+
+ describe('result plausibility', function() {
it('filters by rectangle', function() {
+ rTree.put([0, 1, 0, 1], 1);
+ rTree.put([1, 4, 1, 4], 2);
+ rTree.put([2, 3, 2, 3], 3);
+ rTree.put([-5, -4, -5, -4], 4);
+ rTree.put([-4, -1, -4, -1], 5);
+ rTree.put([-3, -2, -3, -2], 6);
+
var result;
result = goog.object.getValues(rTree.find([2, 3, 2, 3]));
expect(result).to.contain(2);
@@ -34,26 +105,6 @@ describe('ol.structs.RTree', function() {
expect(goog.object.getCount(rTree.find([5, 6, 5, 6]))).to.be(0);
});
- it('can store thousands of items and find fast', function() {
- for (var i = 7; i <= 10000; ++i) {
- rTree.put([
- Math.random() * -10, Math.random() * 10,
- Math.random() * -10, Math.random() * 10
- ], i);
- }
- expect(goog.object.getCount(rTree.find([-10, 10, -10, 10]))).to.be(10000);
- var result = rTree.find([0, 0, 0, 0]);
- expect(goog.object.getCount(result)).to.be(9995);
- var values = goog.object.getValues(result);
- expect(values).to.contain(1);
- expect(values).not.to.contain(2);
- expect(values).not.to.contain(3);
- expect(values).not.to.contain(4);
- expect(values).not.to.contain(5);
- expect(values).not.to.contain(6);
- expect(values).to.contain(7);
- });
-
});
});