diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index cb934c612e..1855984f49 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -69,7 +69,7 @@ ol.layer.FeatureCache.prototype.add = function(feature) { if (!goog.isNull(geometry)) { var geometryType = geometry.getType(); this.geometryTypeIndex_[geometryType][id] = feature; - this.rTree_.put(geometry.getBounds(), + this.rTree_.insert(geometry.getBounds(), feature, geometryType); } }; @@ -87,7 +87,7 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) { if (opt_filter instanceof ol.filter.Geometry) { features = this.geometryTypeIndex_[opt_filter.getType()]; } else if (opt_filter instanceof ol.filter.Extent) { - features = this.rTree_.find(opt_filter.getExtent()); + features = this.rTree_.searchReturningObject(opt_filter.getExtent()); } else if (opt_filter instanceof ol.filter.Logical && opt_filter.operator === ol.filter.LogicalOperator.AND) { var filters = opt_filter.getFilters(); @@ -104,7 +104,7 @@ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_filter) { if (extentFilter && geometryFilter) { var type = geometryFilter.getType(); features = goog.object.isEmpty(this.geometryTypeIndex_[type]) ? {} : - this.rTree_.find(extentFilter.getExtent(), type); + this.rTree_.searchReturningObject(extentFilter.getExtent(), type); } } } diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 4500334be2..e1548e8bb4 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -34,7 +34,7 @@ goog.require('ol.extent'); /** - * @typedef {{extent: (ol.Extent), leaf: (Object|undefined), + * @typedef {{extent: ol.Extent, leaf: (Object|undefined), * nodes: (Array.|undefined), * target: (Object|undefined), type: (string|undefined)}} */ @@ -50,17 +50,18 @@ 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)) { + if (goog.isDef(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: []}); /** * This is Jon-Carlos Rivera's special addition to the world of r-trees. - * Every other (simple) method he found produced crap trees. + * Every other (simple) method he found produced poor trees. * This skews insertions to prefering squarer and emptier nodes. * * @param {number} l L. @@ -402,9 +403,13 @@ ol.structs.RTree = function(opt_width) { * @param {Array|Object} result Result. * @param {ol.structs.RTreeNode} root Root. * @param {string=} opt_type Optional type to search for. + * @param {boolean=} opt_resultAsObject If set, result will be an object keyed + * by UID. * @return {Array|Object} Result. */ - var searchSubtree = function(rect, returnNode, result, root, opt_type) { + var searchSubtree = function(rect, returnNode, result, root, opt_type, + opt_resultAsObject) { + var resultObject = {}; var hitStack = []; // Contains the elements that overlap if (!ol.extent.intersects(rect.extent, root.extent)) { @@ -427,7 +432,11 @@ ol.structs.RTree = function(opt_width) { // 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; + if (goog.isDef(opt_resultAsObject)) { + resultObject[goog.getUid(obj).toString()] = obj; + } else { + result.push(obj); + } } } else { result.push(lTree); @@ -437,7 +446,11 @@ ol.structs.RTree = function(opt_width) { } } while (hitStack.length > 0); - return result; + if (goog.isDef(opt_resultAsObject)) { + return resultObject; + } else { + return result; + } }; /** @@ -515,6 +528,20 @@ ol.structs.RTree = function(opt_width) { } 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 {Array} Result. + * @this {ol.structs.RTree} + */ + this.search = function(extent, opt_type) { + var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); + return /** @type {Array} */ (searchSubtree.apply(this, [rect, false, [], + rootTree, opt_type])); + }; + /** * Non-recursive search function * @@ -523,9 +550,10 @@ ol.structs.RTree = function(opt_width) { * @return {Object} Result. Keys are UIDs of the values. * @this {ol.structs.RTree} */ - this.find = function(extent, opt_type) { + this.searchReturningObject = function(extent, opt_type) { var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return searchSubtree.apply(this, [rect, false, {}, rootTree, opt_type]); + return /** @type {Object} */ (searchSubtree.apply(this, [rect, false, [], + rootTree, opt_type, true])); }; /** @@ -566,7 +594,7 @@ ol.structs.RTree = function(opt_width) { * @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) { + this.insert = function(extent, obj, opt_type) { var node = /** @type {ol.structs.RTreeNode} */ ({extent: extent, leaf: obj}); if (goog.isDef(opt_type)) { diff --git a/test/spec/ol/structs/rtree.test.js b/test/spec/ol/structs/rtree.test.js index e5ee9baef6..0f120e33c2 100644 --- a/test/spec/ol/structs/rtree.test.js +++ b/test/spec/ol/structs/rtree.test.js @@ -14,10 +14,10 @@ describe('ol.structs.RTree', function() { 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); + rTree.insert(bounds, 'JUST A TEST OBJECT!_' + i); i--; } - expect(goog.object.getCount(rTree.find([0, 10600, 0, 10600]))) + expect(goog.object.getCount(rTree.search([0, 10600, 0, 10600]))) .to.be(1000); }); it('can insert 1k more objects', function() { @@ -28,10 +28,10 @@ describe('ol.structs.RTree', function() { 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); + rTree.insert(bounds, 'JUST A TEST OBJECT!_' + i); i--; } - expect(goog.object.getCount(rTree.find([0, 10600, 0, 10600]))) + expect(goog.object.getCount(rTree.search([0, 10600, 0, 10600]))) .to.be(2000); }); }); @@ -46,7 +46,7 @@ describe('ol.structs.RTree', function() { 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)); + len += rTree.search(bounds).length; i--; } expect(len).to.be(0); @@ -60,7 +60,7 @@ describe('ol.structs.RTree', function() { 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)); + len += rTree.search(bounds).length; i--; } expect(len).not.to.be(0); @@ -81,28 +81,46 @@ describe('ol.structs.RTree', function() { }); }); - describe('result plausibility', function() { + describe('result plausibility and structure', 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); + rTree.insert([0, 1, 0, 1], 1); + rTree.insert([1, 4, 1, 4], 2); + rTree.insert([2, 3, 2, 3], 3); + rTree.insert([-5, -4, -5, -4], 4); + rTree.insert([-4, -1, -4, -1], 5); + rTree.insert([-3, -2, -3, -2], 6); var result; - result = goog.object.getValues(rTree.find([2, 3, 2, 3])); + result = goog.object.getValues(rTree.search([2, 3, 2, 3])); expect(result).to.contain(2); expect(result).to.contain(3); expect(result.length).to.be(2); - result = goog.object.getValues(rTree.find([-1, 2, -1, 2])); + result = goog.object.getValues(rTree.search([-1, 2, -1, 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.find([5, 6, 5, 6]))).to.be(0); + expect(goog.object.getCount(rTree.search([5, 6, 5, 6]))).to.be(0); + }); + + it('filters by type', function() { + rTree.insert([2, 3, 2, 3], 7, 'type1'); + + var result; + result = rTree.search([1, 4, 2, 4], 'type1'); + expect(result).to.contain(7); + expect(result.length).to.be(1); + result = rTree.search([1, 4, 2, 4]); + expect(result.length).to.be(3); + }); + + it('can return objects instead of arrays', function() { + var obj = {foo: 'bar'}; + rTree.insert([5, 5, 5, 5], obj); + var result = rTree.searchReturningObject([4, 6, 4, 6]); + expect(result[goog.getUid(obj)]).to.equal(obj); }); });