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() {