diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 4f1688582b..536b3338a9 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -19,7 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// FIXME removal // FIXME bulk inserts goog.provide('ol.structs.RBush'); @@ -357,6 +356,27 @@ ol.structs.RBush.prototype.clear = function() { }; +/** + * @param {Array.>} path Path. + * @private + */ +ol.structs.RBush.prototype.condense_ = function(path) { + var i; + for (i = path.length - 1; i >= 0; --i) { + var node = path[i]; + if (node.children.length === 0) { + if (i > 0) { + goog.array.remove(path[i - 1].children, node); + } else { + this.clear(); + } + } else { + node.updateExtent(); + } + } +}; + + /** * @param {function(this: S, T): *} callback Callback. * @param {S=} opt_obj Scope. @@ -524,8 +544,41 @@ ol.structs.RBush.prototype.remove = function(value) { * @private */ ol.structs.RBush.prototype.remove_ = function(extent, value) { - // FIXME - goog.asserts.fail(); + var node = this.root_; + var index = 0; + /** @type {Array.>} */ + var path = [node]; + /** @type {Array.} */ + var indexes = [0]; + var child, children, i, ii; + while (path.length > 0) { + goog.asserts.assert(node.height > 0); + if (node.height == 1) { + children = node.children; + for (i = 0, ii = children.length; i < ii; ++i) { + child = children[i]; + if (child.value === value) { + goog.array.removeAt(children, i); + this.condense_(path); + return; + } + } + ++index; + } else if (index < node.children.length) { + child = node.children[index]; + if (ol.extent.containsExtent(child.extent, extent)) { + path.push(child); + indexes.push(index + 1); + node = child; + index = 0; + } else { + ++index; + } + } else { + node = path.pop(); + index = indexes.pop(); + } + } }; diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js index b1f7eb1bbf..7549662134 100644 --- a/test/spec/ol/structs/rbush.js +++ b/test/spec/ol/structs/rbush.js @@ -55,6 +55,78 @@ describe('ol.structs.RBush', function() { }); + describe('#remove', function() { + + it('can remove each object', function() { + var i, ii; + for (i = 0, ii = objs.length; i < ii; ++i) { + expect(rBush.all()).to.contain(objs[i]); + rBush.remove(objs[i]); + expect(rBush.all()).not.to.contain(objs[i]); + } + }); + + }); + + }); + + describe('with 100 objects', function() { + + var extents, objs; + beforeEach(function() { + extents = []; + objs = []; + var i; + for (i = 0; i < 100; ++i) { + extents[i] = [i - 0.1, i - 0.1, i + 0.1, i + 0.1]; + objs[i] = {id: i}; + rBush.insert(extents[i], objs[i]); + } + }); + + describe('#allInExtent', function() { + + it('returns the expected objects', function() { + var i, ii; + for (i = 0, ii = objs.length; i < ii; ++i) { + expect(rBush.allInExtent(extents[i])).to.eql([objs[i]]); + } + }); + + }); + + describe('#remove', function() { + + it('can remove each object in turn', function() { + var i, ii; + for (i = 0, ii = objs.length; i < ii; ++i) { + expect(rBush.allInExtent(extents[i])).to.eql([objs[i]]); + rBush.remove(objs[i]); + expect(rBush.allInExtent(extents[i])).to.be.empty(); + } + expect(rBush.all()).to.be.empty(); + }); + + it('can remove objects in random order', function() { + var i, ii, j; + // http://en.wikipedia.org/wiki/Random_permutation + var indexes = []; + for (i = 0, ii = objs.length; i < ii; ++i) { + j = Math.floor(Math.random() * (i + 1)); + indexes[i] = indexes[j]; + indexes[j] = i; + } + for (i = 0, ii = objs.length; i < ii; ++i) { + var index = indexes[i]; + expect(rBush.allInExtent(extents[index])).to.eql([objs[index]]); + rBush.remove(objs[index]); + expect(rBush.allInExtent(extents[index])).to.be.empty(); + } + expect(rBush.all()).to.be.empty(); + }); + + }); + }); describe('with 1000 objects', function() {