From 92a30bcbf7a1a224cde084a09d873ec1183a147c Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 28 Nov 2013 16:16:52 +0100 Subject: [PATCH 1/3] Use ol.structs.RBush in ol.source.Vector --- src/ol/source/vectorsource.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index a2098845a4..7e25a17b55 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -14,7 +14,7 @@ goog.require('ol.FeatureEventType'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.source.Source'); -goog.require('ol.structs.RTree'); +goog.require('ol.structs.RBush'); /** @@ -251,10 +251,10 @@ ol.source.Vector.prototype.handleFeatureChange_ = function(evt) { extents.push(evt.oldExtent); } var geometry = feature.getGeometry(); + var extent = geometry.getBounds(); if (!goog.isNull(geometry)) { - this.featureCache_.remove(feature, evt.oldExtent); - this.featureCache_.add(feature); - extents.push(geometry.getBounds()); + this.featureCache_.updateExtent(feature, extent); + extents.push(extent); } this.dispatchEvent(new ol.source.VectorEvent(ol.source.VectorEventType.CHANGE, [feature], extents)); @@ -353,10 +353,10 @@ ol.source.FeatureCache = function() { this.idLookup_; /** - * @type {ol.structs.RTree} + * @type {ol.structs.RBush} * @private */ - this.rTree_; + this.rBush_; this.clear(); @@ -368,7 +368,7 @@ ol.source.FeatureCache = function() { */ ol.source.FeatureCache.prototype.clear = function() { this.idLookup_ = {}; - this.rTree_ = new ol.structs.RTree(); + this.rBush_ = new ol.structs.RBush(); }; @@ -384,7 +384,7 @@ ol.source.FeatureCache.prototype.add = function(feature) { // index by bounding box if (!goog.isNull(geometry)) { - this.rTree_.insert(geometry.getBounds(), feature); + this.rBush_.insert(geometry.getBounds(), feature); } }; @@ -409,7 +409,7 @@ ol.source.FeatureCache.prototype.getFeaturesObject = function() { */ ol.source.FeatureCache.prototype.forEach = function(extent, callback, opt_thisArg) { - this.rTree_.forEach( + this.rBush_.forEachInExtent( extent, /** @type {function(Object)} */ (callback), opt_thisArg); }; @@ -417,17 +417,24 @@ ol.source.FeatureCache.prototype.forEach = /** * Remove a feature from the cache. * @param {ol.Feature} feature Feature. - * @param {ol.Extent=} opt_extent Optional extent (used when the current feature - * extent is different than the one in the index). */ -ol.source.FeatureCache.prototype.remove = function(feature, opt_extent) { +ol.source.FeatureCache.prototype.remove = function(feature) { var id = goog.getUid(feature).toString(), geometry = feature.getGeometry(); delete this.idLookup_[id]; // index by bounding box if (!goog.isNull(geometry)) { - var extent = goog.isDef(opt_extent) ? opt_extent : geometry.getBounds(); - this.rTree_.remove(extent, feature); + this.rBush_.remove(feature); } }; + + +/** + * Updates a feature's extent in the spatial index. + * @param {ol.Feature} feature Feature. + * @param {ol.Extent} extent Extent. + */ +ol.source.FeatureCache.prototype.updateExtent = function(feature, extent) { + this.rBush_.update(extent, feature); +}; From d4bdac715b55f6e1d03e82453519798c5dfb0615 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 28 Nov 2013 16:17:17 +0100 Subject: [PATCH 2/3] Get rid of ol.structs.RTree --- src/ol/structs/rtree.js | 638 ----------------------------- test/spec/ol/structs/rtree.test.js | 151 ------- 2 files changed, 789 deletions(-) delete mode 100644 src/ol/structs/rtree.js delete mode 100644 test/spec/ol/structs/rtree.test.js diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js deleted file mode 100644 index fc314e79c3..0000000000 --- a/src/ol/structs/rtree.js +++ /dev/null @@ -1,638 +0,0 @@ -// 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.array'); -goog.require('ol.extent'); - - -/** - * @typedef {{extent: ol.Extent, - * leaf: (Object|undefined), - * nodes: (Array.|undefined), - * target: (Object|undefined), - * type: (string|number|undefined)}} - */ -ol.structs.RTreeNode; - - - -/** - * @constructor - * @param {number=} opt_maxWidth Width before a node is split. Default is `6`. - */ -ol.structs.RTree = function(opt_maxWidth) { - - /** - * Maximum width of any node before a split. - * @private - * @type {number} - */ - this.maxWidth_ = goog.isDef(opt_maxWidth) ? opt_maxWidth : 6; - - /** - * Minimum width of any node before a merge. - * @private - * @type {number} - */ - this.minWidth_ = Math.floor(this.maxWidth_ / 2); - - /** - * Start with an empty root-tree. - * @private - * @type {ol.structs.RTreeNode} - */ - this.rootTree_ = /** @type {ol.structs.RTreeNode} */ - ({extent: ol.extent.createEmpty(), nodes: []}); - -}; - - -/** - * @param {ol.structs.RTreeNode} node Node. - * @private - */ -ol.structs.RTree.recalculateExtent_ = function(node) { - var n = node.nodes.length; - var extent = node.extent; - if (n === 0) { - ol.extent.empty(extent); - } else { - var firstNodeExtent = node.nodes[0].extent; - extent[0] = firstNodeExtent[0]; - extent[2] = firstNodeExtent[2]; - extent[1] = firstNodeExtent[1]; - extent[3] = firstNodeExtent[3]; - var i; - for (i = 1; i < n; ++i) { - ol.extent.extend(extent, node.nodes[i].extent); - } - } -}; - - -/** - * This is Jon-Carlos Rivera's special addition to the world of r-trees. - * Every other (simple) method he found produced poor trees. - * This skews insertions to prefering squarer and emptier nodes. - * - * @param {number} l L. - * @param {number} w W. - * @param {number} fill Fill. - * @private - * @return {number} Squarified ratio. - */ -ol.structs.RTree.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; -}; - - -/** - * Choose the best for rectangle to be inserted into. - * - * @param {ol.structs.RTreeNode} rect Rectangle. - * @param {ol.structs.RTreeNode} root Root to start search. - * @private - * @return {Array} Leaf node parent. - */ -ol.structs.RTree.prototype.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 = ol.structs.RTree.squarifiedRatio_( - lTree.extent[2] - lTree.extent[0], - lTree.extent[3] - lTree.extent[1], - lTree.nodes.length + 1); - - // Enlarge rectangle to fit new rectangle - var nw = (lTree.extent[2] > rect.extent[2] ? - lTree.extent[2] : rect.extent[2]) - - (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[1] < rect.extent[1] ? - lTree.extent[1] : rect.extent[1]); - - // Area of new enlarged rectangle - var lRatio = ol.structs.RTree.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; -}; - - -/** - * Non-recursive insert function. - * - * @param {ol.Extent} extent Extent. - * @param {Object} obj Object to insert. - * @param {string|number=} opt_type Optional type to store along with the - * object. - */ -ol.structs.RTree.prototype.insert = function(extent, obj, opt_type) { - var node = /** @type {ol.structs.RTreeNode} */ - ({extent: extent, leaf: obj}); - if (goog.isDef(opt_type)) { - node.type = opt_type; - } - this.insertSubtree_(node, this.rootTree_); -}; - - -/** - * Non-recursive internal insert function. - * - * @param {ol.structs.RTreeNode} node Node to insert. - * @param {ol.structs.RTreeNode} root Root to begin insertion at. - * @private - */ -ol.structs.RTree.prototype.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 = ol.extent.clone(node.extent); - 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 = this.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 <= this.maxWidth_) { // Start Resizeing Up the Tree - workingObject = {extent: ol.extent.clone(bc.extent)}; - } 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 = this.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: ol.extent.clone(bc.extent)}); - } - } while (treeStack.length > 0); -}; - - -/** - * Split a set of nodes into two roughly equally-filled nodes. - * - * @param {Array.} nodes Array of nodes. - * @private - * @return {Array.} An array of two nodes. - */ -ol.structs.RTree.prototype.linearSplit_ = function(nodes) { - var n = this.pickLinear_(nodes); - while (nodes.length > 0) { - this.pickNext_(nodes, n[0], n[1]); - } - return n; -}; - - -/** - * Pick the "best" two starter nodes to use as seeds using the "linear" - * criteria. - * - * @param {Array.} nodes Array of source nodes. - * @private - * @return {Array.} An array of two nodes. - */ -ol.structs.RTree.prototype.pickLinear_ = function(nodes) { - var lowestHighX = nodes.length - 1; - var highestLowX = 0; - var lowestHighY = nodes.length - 1; - var highestLowY = 0; - var t1, t2; - - 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[2] < nodes[lowestHighX].extent[1]) { - lowestHighX = i; - } - if (l.extent[1] > nodes[highestLowY].extent[1]) { - highestLowY = i; - } else if (l.extent[3] < nodes[lowestHighY].extent[3]) { - lowestHighY = i; - } - } - var dx = Math.abs(nodes[lowestHighX].extent[2] - - nodes[highestLowX].extent[0]); - var dy = Math.abs(nodes[lowestHighY].extent[3] - - nodes[highestLowY].extent[1]); - 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 { - 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: ol.extent.clone(t1.extent), nodes: [t1]}), - /** @type {ol.structs.RTreeNode} */ - ({extent: ol.extent.clone(t2.extent), nodes: [t2]}) - ]; -}; - - -/** - * 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. - * @private - */ -ol.structs.RTree.prototype.pickNext_ = function(nodes, a, b) { - // Area of new enlarged rectangle - var areaA = ol.structs.RTree.squarifiedRatio_(a.extent[2] - a.extent[0], - a.extent[3] - a.extent[1], a.nodes.length + 1); - var areaB = ol.structs.RTree.squarifiedRatio_(b.extent[2] - b.extent[0], - b.extent[3] - b.extent[1], 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[2] > l.extent[2] ? a.extent[2] : l.extent[2], - a.extent[1] < l.extent[1] ? a.extent[1] : l.extent[1], - a.extent[3] > l.extent[3] ? a.extent[3] : l.extent[3] - ]; - var changeNewAreaA = Math.abs(ol.structs.RTree.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[2] > l.extent[2] ? b.extent[2] : l.extent[2], - b.extent[1] < l.extent[1] ? b.extent[1] : l.extent[1], - b.extent[3] > l.extent[3] ? b.extent[3] : l.extent[3] - ]; - var changeNewAreaB = Math.abs(ol.structs.RTree.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 <= this.minWidth_) { - a.nodes.push(tempNode); - ol.extent.extend(a.extent, tempNode.extent); - } else if (b.nodes.length + nodes.length + 1 <= this.minWidth_) { - b.nodes.push(tempNode); - ol.extent.extend(b.extent, tempNode.extent); - } - else { - lowestGrowthGroup.nodes.push(tempNode); - ol.extent.extend(lowestGrowthGroup.extent, tempNode.extent); - } -}; - - -/** - * 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} - */ -ol.structs.RTree.prototype.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] = this.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(this.removeSubtree_.apply(this, arguments)); - } while (numberDeleted != result.length); - return result; - } else { // Delete a specific item - return this.removeSubtree_.apply(this, arguments); - } -}; - - -/** - * 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. - * @private - * @return {Array} Leaf node parent. - */ -ol.structs.RTree.prototype.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: ol.extent.clone(rect.extent), 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 = this.searchSubtree_(lTree, true, [], lTree); - tree.nodes.splice(i, 1); - } else { - returnArray = tree.nodes.splice(i, 1); - } - // Resize MBR down... - ol.structs.RTree.recalculateExtent_(tree); - workingObject.target = undefined; - if (tree.nodes.length < this.minWidth_) { // Underflow - workingObject.nodes = this.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) { - ol.structs.RTree.recalculateExtent_(tree); - } - for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t) { - this.insertSubtree_(workingObject.nodes[t], tree); - } - workingObject.nodes.length = 0; - if (hitStack.length === 0 && tree.nodes.length <= 1) { - // Underflow..on root! - this.searchSubtree_(tree, true, workingObject.nodes, tree); - tree.nodes.length = 0; - hitStack.push(tree); - countStack.push(1); - } else if (hitStack.length > 0 && tree.nodes.length < this.minWidth_) { - // Underflow..AGAIN! - this.searchSubtree_(tree, true, workingObject.nodes, tree); - tree.nodes.length = 0; - } else { - workingObject.nodes = undefined; // Just start resizing - } - } else { // we are just resizing - ol.structs.RTree.recalculateExtent_(tree); - } - currentDepth -= 1; - } while (hitStack.length > 0); - - return returnArray; -}; - - -/** - * Non-recursive search function - * - * @param {ol.Extent} extent Extent. - * @param {string|number=} opt_type Optional type of the objects we want to - * find. - * @return {Array} Result. - * @this {ol.structs.RTree} - */ -ol.structs.RTree.prototype.search = function(extent, opt_type) { - var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return this.searchSubtree_(rect, false, [], this.rootTree_, opt_type); -}; - - -/** - * Search in the given extent and call the callback with each result. - * - * @param {ol.Extent} extent Extent to search. - * @param {function(this: T, Object)} callback Callback called with each result. - * @param {T=} opt_thisArg The object to be used as the value of 'this' for - * the callback. - * @this {ol.structs.RTree} - * @template T - */ -ol.structs.RTree.prototype.forEach = function(extent, callback, opt_thisArg) { - var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - this.searchSubtree_( - rect, false, [], this.rootTree_, undefined, callback, opt_thisArg); -}; - - -/** - * Non-recursive internal search function - * - * @param {ol.structs.RTreeNode} rect Rectangle. - * @param {boolean} returnNode Do we return nodes? - * @param {Array} result Result. - * @param {ol.structs.RTreeNode} root Root. - * @param {string|number=} opt_type Optional type to search for. - * @param {function(this: T, Object)=} opt_callback Callback called with each - * result. - * @param {T=} opt_thisArg The object to be used as the value of 'this' for - * the callback. - * @private - * @template T - * @return {Array} Result. - */ -ol.structs.RTree.prototype.searchSubtree_ = function( - rect, returnNode, result, root, opt_type, opt_callback, opt_thisArg) { - var hitStack = []; // Contains the elements that overlap - - if (!ol.extent.intersects(rect.extent, root.extent)) { - return result; - } - - hitStack.push(root.nodes); - - 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; - if (goog.isDef(opt_callback)) { - opt_callback.call(opt_thisArg, obj); - } else { - result.push(obj); - } - } - } else { - result.push(lTree); - } - } - } - } - } while (hitStack.length > 0); - - return result; -}; diff --git a/test/spec/ol/structs/rtree.test.js b/test/spec/ol/structs/rtree.test.js deleted file mode 100644 index 08a15373d0..0000000000 --- a/test/spec/ol/structs/rtree.test.js +++ /dev/null @@ -1,151 +0,0 @@ -goog.provide('ol.test.structs.RTree'); - - -describe('ol.structs.RTree', function() { - - var rTree = new ol.structs.RTree(); - - describe('creation', function() { - it('can insert 1k objects', function() { - var i = 1000; - while (i > 0) { - var min = [Math.random() * 10000, Math.random() * 10000]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - rTree.insert(bounds, 'JUST A TEST OBJECT!_' + i); - i--; - } - expect(goog.object.getCount(rTree.search([0, 0, 10600, 10600]))) - .to.be(1000); - }); - it('can insert 1k more objects', function() { - var i = 1000; - while (i > 0) { - var min = [Math.random() * 10000, Math.random() * 10000]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - rTree.insert(bounds, 'JUST A TEST OBJECT!_' + i); - i--; - } - expect(goog.object.getCount(rTree.search([0, 0, 10600, 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 min = [-(Math.random() * 10000 + 501), - -(Math.random() * 10000 + 501)]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - len += rTree.search(bounds).length; - i--; - } - expect(len).to.be(0); - }); - it('can perform 1k in-bounds searches', function() { - var i = 1000; - var len = 0; - while (i > 0) { - var min = [Math.random() * 10000, Math.random() * 10000]; - var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; - var bounds = [min[0], min[1], max[0], max[1]]; - len += rTree.search(bounds).length; - i--; - } - expect(len).not.to.be(0); - }); - }); - - describe('deletion', function() { - var len = 0; - it('can delete half the RTree', function() { - var bounds = [5000, 0, 10500, 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, 0, 5000, 10500]; - len += rTree.remove(bounds).length; - expect(len).to.be(2000); - }); - }); - - describe('result plausibility and structure', function() { - - it('filters by rectangle', function() { - rTree.insert([0, 0, 1, 1], 1); - rTree.insert([1, 1, 4, 4], 2); - rTree.insert([2, 2, 3, 3], 3); - rTree.insert([-5, -5, -4, -4], 4); - rTree.insert([-4, -4, -1, -1], 5); - rTree.insert([-3, -3, -2, -2], 6); - - var result; - result = goog.object.getValues(rTree.search([2, 2, 3, 3])); - expect(result).to.contain(2); - expect(result).to.contain(3); - expect(result.length).to.be(2); - result = goog.object.getValues(rTree.search([-1, -1, 2, 2])); - expect(result).to.contain(1); - expect(result).to.contain(2); - expect(result).to.contain(3); - expect(result).to.contain(5); - expect(result.length).to.be(4); - expect(goog.object.getCount(rTree.search([5, 5, 6, 6]))).to.be(0); - }); - - it('filters by type', function() { - rTree.insert([2, 2, 3, 3], 7, 'type1'); - - var result; - result = rTree.search([1, 2, 4, 4], 'type1'); - expect(result).to.contain(7); - expect(result.length).to.be(1); - result = rTree.search([1, 2, 4, 4]); - expect(result.length).to.be(3); - }); - - }); - - describe('#forEach()', function() { - var tree; - beforeEach(function() { - tree = new ol.structs.RTree(); - }); - - it('calls a callback for each result in the search extent', function() { - var one = {}; - tree.insert([4.5, 4.5, 5, 5], one); - - var two = {}; - tree.insert([5, 5, 5.5, 5.5], two); - - var callback = sinon.spy(); - tree.forEach([4, 4, 6, 6], callback); - expect(callback.callCount).to.be(2); - expect(callback.calledWith(one)).to.be(true); - expect(callback.calledWith(two)).to.be(true); - }); - - it('accepts a this argument', function() { - var obj = {}; - tree.insert([5, 5, 5, 5], obj); - - var callback = sinon.spy(); - var thisArg = {}; - tree.forEach([4, 4, 6, 6], callback, thisArg); - expect(callback.callCount).to.be(1); - expect(callback.calledWith(obj)).to.be(true); - expect(callback.calledOn(thisArg)).to.be(true); - }); - - }); - -}); - -goog.require('goog.object'); -goog.require('ol.structs.RTree'); From 45d9f6ce0b4103a894bdb63fa8566e1b9c685ee8 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 28 Nov 2013 16:17:29 +0100 Subject: [PATCH 3/3] Rename rbush test to match the naming scheme --- test/spec/ol/structs/{rbush.js => rbush.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/spec/ol/structs/{rbush.js => rbush.test.js} (100%) diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.test.js similarity index 100% rename from test/spec/ol/structs/rbush.js rename to test/spec/ol/structs/rbush.test.js