From cf0fdff4e25b42725f2f9f6e2f87870451f7f53d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 12:06:32 +0100 Subject: [PATCH 01/19] Add ol.extent.extendXY --- src/ol/extent.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 5a28036f51..cf393fd61b 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -201,6 +201,19 @@ ol.extent.extendCoordinate = function(extent, coordinate) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {number} x X. + * @param {number} y Y. + */ +ol.extent.extendXY = function(extent, x, y) { + extent[0] = Math.min(extent[0], x); + extent[1] = Math.min(extent[1], y); + extent[2] = Math.max(extent[2], x); + extent[3] = Math.max(extent[3], y); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Bottom left coordinate. From 9cbd8de188c37c9fe40cdc3bace046cc3a1e0dfb Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 17:46:14 +0100 Subject: [PATCH 02/19] Fix order of arguments to ol.extent.createOrUpdate --- src/ol/extent.js | 10 +++++----- src/ol/tilegrid/tilegrid.js | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ol/extent.js b/src/ol/extent.js index cf393fd61b..1bb73610d3 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -43,10 +43,10 @@ ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) { goog.asserts.assert(xs.length > 0); goog.asserts.assert(ys.length > 0); var minX = Math.min.apply(null, xs); - var maxX = Math.max.apply(null, xs); var minY = Math.min.apply(null, ys); + var maxX = Math.max.apply(null, xs); var maxY = Math.max.apply(null, ys); - return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; @@ -114,18 +114,18 @@ ol.extent.createEmpty = function() { /** * @param {number} minX Minimum X. - * @param {number} maxX Maximum X. * @param {number} minY Minimum Y. + * @param {number} maxX Maximum X. * @param {number} maxY Maximum Y. * @param {ol.Extent=} opt_extent Destination extent. * @return {ol.Extent} Extent. * @todo stability experimental */ -ol.extent.createOrUpdate = function(minX, maxX, minY, maxY, opt_extent) { +ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { if (goog.isDef(opt_extent)) { opt_extent[0] = minX; - opt_extent[2] = maxX; opt_extent[1] = minY; + opt_extent[2] = maxX; opt_extent[3] = maxY; return opt_extent; } else { diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index 59bf95fb61..e079159cd2 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -223,7 +223,7 @@ ol.tilegrid.TileGrid.prototype.getTileRangeExtent = var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution; var minY = origin[1] + tileRange.minY * tileSize[1] * resolution; var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution; - return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; @@ -287,10 +287,10 @@ ol.tilegrid.TileGrid.prototype.getTileCoordExtent = var resolution = this.getResolution(tileCoord.z); var tileSize = this.getTileSize(tileCoord.z); var minX = origin[0] + tileCoord.x * tileSize[0] * resolution; - var maxX = minX + tileSize[0] * resolution; var minY = origin[1] + tileCoord.y * tileSize[1] * resolution; + var maxX = minX + tileSize[0] * resolution; var maxY = minY + tileSize[1] * resolution; - return ol.extent.createOrUpdate(minX, maxX, minY, maxY, opt_extent); + return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent); }; From ae9f03c8cb7b26ba4e015ffd862544a575101544 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 6 Nov 2013 19:08:34 +0100 Subject: [PATCH 03/19] Add more extent functions --- src/ol/extent.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 1bb73610d3..2346c5fbcf 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -134,6 +134,50 @@ ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) { }; +/** + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateEmpty = function(opt_extent) { + return ol.extent.createOrUpdate( + Infinity, Infinity, -Infinity, -Infinity, opt_extent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) { + var x = coordinate[0]; + var y = coordinate[1]; + return ol.extent.createOrUpdate(x, y, x, y, opt_extent); +}; + + +/** + * @param {Array.} coordinates Coordinates. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendCoordinates(extent, coordinates); +}; + + +/** + * @param {Array.>} rings Rings. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromRings = function(rings, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendRings(extent, rings); +}; + + /** * Empties extent in place. * @param {ol.Extent} extent Extent. @@ -201,6 +245,34 @@ ol.extent.extendCoordinate = function(extent, coordinate) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {Array.} coordinates Coordinates. + * @return {ol.Extent} Extent. + */ +ol.extent.extendCoordinates = function(extent, coordinates) { + var i, ii; + for (i = 0, ii = coordinates.length; i < ii; ++i) { + ol.extent.extendCoordinate(extent, coordinates[i]); + } + return extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {Array.>} rings Rings. + * @return {ol.Extent} Extent. + */ +ol.extent.extendRings = function(extent, rings) { + var i, ii; + for (i = 0, ii = rings.length; i < ii; ++i) { + ol.extent.extendCoordinates(extent, rings[i]); + } + return extent; +}; + + /** * @param {ol.Extent} extent Extent. * @param {number} x X. @@ -362,6 +434,24 @@ ol.extent.normalize = function(extent, coordinate) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.returnOrUpdate = function(extent, opt_extent) { + if (goog.isDef(opt_extent)) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent; + } +}; + + /** * @param {ol.Extent} extent Extent. * @param {number} value Value. From 67bf15b5dc6f4dac3e0dbac2678931eebf50253d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 9 Nov 2013 15:40:15 +0100 Subject: [PATCH 04/19] Add ol.extent.extendFlatCoordinates --- src/ol/extent.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 2346c5fbcf..edd58791a6 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -259,6 +259,21 @@ ol.extent.extendCoordinates = function(extent, coordinates) { }; +/** + * @param {ol.Extent} extent Extent. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @return {ol.Extent} Extent. + */ +ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, stride) { + var i, ii; + for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) { + ol.extent.extendXY(extent, flatCoordinates[i], flatCoordinates[i + 1]); + } + return extent; +}; + + /** * @param {ol.Extent} extent Extent. * @param {Array.>} rings Rings. From d994db46f6545e1d95fa364617a2abeee28a9927 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 9 Nov 2013 15:40:27 +0100 Subject: [PATCH 05/19] Add ol.extent.createOrUpdateFromFlatCoordinates --- src/ol/extent.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index edd58791a6..96f47a11ec 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -167,6 +167,19 @@ ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) { }; +/** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.createOrUpdateFromFlatCoordinates = + function(flatCoordinates, stride, opt_extent) { + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + return ol.extent.extendFlatCoordinates(extent, flatCoordinates, stride); +}; + + /** * @param {Array.>} rings Rings. * @param {ol.Extent=} opt_extent Extent. From df96c08e0ea6ec03b6b4487aecfd14ceb2f094d2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:14:39 +0100 Subject: [PATCH 06/19] Return extent from ol.extent.extend --- src/ol/extent.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 96f47a11ec..08422a84d8 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -219,6 +219,7 @@ ol.extent.equals = function(extent1, extent2) { /** * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent 2. + * @return {ol.Extent} Extent. * @todo stability experimental */ ol.extent.extend = function(extent1, extent2) { @@ -234,6 +235,7 @@ ol.extent.extend = function(extent1, extent2) { if (extent2[3] > extent1[3]) { extent1[3] = extent2[3]; } + return extent1; }; From b74c244088f473f38ee42783a9f9f2a1f528b9f5 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:14:50 +0100 Subject: [PATCH 07/19] Add ol.extent.getArea --- src/ol/extent.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 08422a84d8..ea3e5cb8ce 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -316,6 +316,15 @@ ol.extent.extendXY = function(extent, x, y) { }; +/** + * @param {ol.Extent} extent Extent. + * @return {number} Area. + */ +ol.extent.getArea = function(extent) { + return ol.extent.getWidth(extent) * ol.extent.getHeight(extent); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Coordinate} Bottom left coordinate. From 6b02c7f639e490126663d9e150f4ea1b254ffc7d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:15:07 +0100 Subject: [PATCH 08/19] Add ol.extent.getEnlargedArea --- src/ol/extent.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index ea3e5cb8ce..ee5761541a 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -355,6 +355,20 @@ ol.extent.getCenter = function(extent) { }; +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Enlarged area. + */ +ol.extent.getEnlargedArea = function(extent1, extent2) { + var minX = Math.min(extent1[0], extent2[0]); + var minY = Math.min(extent1[1], extent2[1]); + var maxX = Math.max(extent1[2], extent2[2]); + var maxY = Math.max(extent1[3], extent2[3]); + return (maxX - minX) * (maxY - minY); +}; + + /** * @param {ol.Coordinate} center Center. * @param {number} resolution Resolution. From 2a6f5a63962f0c21a5ba38b21a2dbeb6b39613cd Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:15:41 +0100 Subject: [PATCH 09/19] Add ol.extent.getIntersectionArea --- src/ol/extent.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index ee5761541a..3a00a840c9 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -409,6 +409,20 @@ ol.extent.getHeight = function(extent) { }; +/** + * @param {ol.Extent} extent1 Extent 1. + * @param {ol.Extent} extent2 Extent 2. + * @return {number} Intersection area. + */ +ol.extent.getIntersectionArea = function(extent1, extent2) { + var minX = Math.max(extent1[0], extent2[0]); + var minY = Math.max(extent1[1], extent2[1]); + var maxX = Math.min(extent1[2], extent2[2]); + var maxY = Math.min(extent1[3], extent2[3]); + return Math.max(0, maxX - minX) * Math.max(0, maxY - minY); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Size} Size. From 54d22735f209fd0905112c86f4f1a5c6f32f9601 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:15:56 +0100 Subject: [PATCH 10/19] Add ol.extent.getMargin --- src/ol/extent.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 3a00a840c9..c2e810acb5 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -423,6 +423,15 @@ ol.extent.getIntersectionArea = function(extent1, extent2) { }; +/** + * @param {ol.Extent} extent Extent. + * @return {number} Margin. + */ +ol.extent.getMargin = function(extent) { + return ol.extent.getWidth(extent) + ol.extent.getHeight(extent); +}; + + /** * @param {ol.Extent} extent Extent. * @return {ol.Size} Size. From f12355a17b989366f7f7c7dc7f3f130b6d64d3ac Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Nov 2013 12:15:09 +0100 Subject: [PATCH 11/19] Add optional destination argument to ol.extent.clone --- src/ol/extent.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ol/extent.js b/src/ol/extent.js index c2e810acb5..b396d8c33d 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -67,11 +67,20 @@ ol.extent.buffer = function(extent, value) { * Creates a clone of an extent. * * @param {ol.Extent} extent Extent to clone. + * @param {ol.Extent=} opt_extent Extent. * @return {ol.Extent} The clone. * @todo stability experimental */ -ol.extent.clone = function(extent) { - return extent.slice(); +ol.extent.clone = function(extent, opt_extent) { + if (goog.isDef(opt_extent)) { + opt_extent[0] = extent[0]; + opt_extent[1] = extent[1]; + opt_extent[2] = extent[2]; + opt_extent[3] = extent[3]; + return opt_extent; + } else { + return extent.slice(); + } }; From e823e7fde3849d29aeb2c899e5a7c456eee99310 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 14:17:08 +0100 Subject: [PATCH 12/19] Add ol.structs.RBush --- src/ol/structs/rbush.js | 537 ++++++++++++++++++++++++++++++++++ test/spec/ol/structs/rbush.js | 105 +++++++ 2 files changed, 642 insertions(+) create mode 100644 src/ol/structs/rbush.js create mode 100644 test/spec/ol/structs/rbush.js diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js new file mode 100644 index 0000000000..e2342e512b --- /dev/null +++ b/src/ol/structs/rbush.js @@ -0,0 +1,537 @@ +// Based on rbush https://github.com/mourner/rbush +// Copyright (c) 2013 Vladimir Agafonkin +// +// 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. + +// FIXME removal +// FIXME bulk inserts + +goog.provide('ol.structs.RBush'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.extent'); + + + +/** + * @constructor + * @param {ol.Extent} extent Extent. + * @param {number} height Height. + * @param {Array.>} children Children. + * @param {?T} value Value. + * @template T + */ +ol.structs.RBushNode = function(extent, height, children, value) { + + if (height === 0) { + goog.asserts.assert(goog.isNull(children)); + goog.asserts.assert(!goog.isNull(value)); + } else { + goog.asserts.assert(!goog.isNull(children)); + goog.asserts.assert(goog.isNull(value)); + } + + /** + * @type {ol.Extent} + */ + this.extent = extent; + + /** + * @type {number} + */ + this.height = height; + + /** + * @type {Array.>} + */ + this.children = children; + + /** + * @type {?T} + */ + this.value = value; + +}; + + +/** + * @param {ol.structs.RBushNode.} node1 Node 1. + * @param {ol.structs.RBushNode.} node2 Node 2. + * @return {number} Compare minimum X. + * @template T + */ +ol.structs.RBushNode.compareMinX = function(node1, node2) { + return node1.extent[0] - node2.extent[0]; +}; + + +/** + * @param {ol.structs.RBushNode.} node1 Node 1. + * @param {ol.structs.RBushNode.} node2 Node 2. + * @return {number} Compare minimum Y. + * @template T + */ +ol.structs.RBushNode.compareMinY = function(node1, node2) { + return node1.extent[1] - node2.extent[1]; +}; + + +/** + * @param {number} start Start. + * @param {number} stop Stop. + * @param {ol.Extent=} opt_extent Extent. + * @return {ol.Extent} Extent. + */ +ol.structs.RBushNode.prototype.getChildrenExtent = + function(start, stop, opt_extent) { + goog.asserts.assert(!this.isLeaf()); + var children = this.children; + var extent = ol.extent.createOrUpdateEmpty(opt_extent); + var i; + for (i = start; i < stop; ++i) { + ol.extent.extend(extent, children[i].extent); + } + return extent; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.RBushNode.prototype.updateExtent = function() { + goog.asserts.assert(!this.isLeaf()); + var extent = ol.extent.createOrUpdateEmpty(this.extent); + var children = this.children; + var i, ii; + for (i = 0, ii = children.length; i < ii; ++i) { + ol.extent.extend(extent, children[i].extent); + } +}; + + +/** + * @return {boolean} Is leaf. + */ +ol.structs.RBushNode.prototype.isLeaf = function() { + return goog.isNull(this.children); +}; + + + +/** + * @constructor + * @param {number=} opt_maxEntries Max entries. + * @see https://github.com/mourner/rbush + * @template T + */ +ol.structs.RBush = function(opt_maxEntries) { + + /** + * @private + * @type {number} + */ + this.maxEntries_ = + Math.max(4, goog.isDef(opt_maxEntries) ? opt_maxEntries : 9); + + /** + * @private + * @type {number} + */ + this.minEntries_ = Math.max(2, Math.ceil(0.4 * this.maxEntries_)); + + /** + * @private + * @type {ol.structs.RBushNode.} + */ + this.root_ = new ol.structs.RBushNode(ol.extent.createEmpty(), 1, [], null); + + /** + * @private + * @type {Object.} + */ + this.valueExtent_ = {}; + +}; + + +/** + * @return {Array.} All. + */ +ol.structs.RBush.prototype.all = function() { + var values = []; + this.forEach( + /** + * @param {T} value Value. + */ + function(value) { + values.push(value); + }); + return values; +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @param {function(ol.structs.RBushNode., ol.structs.RBushNode.): number} + * compare Compare. + * @private + * @return {number} All distance margin. + */ +ol.structs.RBush.prototype.allDistMargin_ = function(node, compare) { + var children = node.children; + var m = this.minEntries_; + var M = children.length; + var i; + goog.array.sort(children, compare); + var leftExtent = node.getChildrenExtent(0, m); + var rightExtent = node.getChildrenExtent(M - m, M); + var margin = + ol.extent.getMargin(leftExtent) + ol.extent.getMargin(rightExtent); + for (i = m; i < M - m; ++i) { + ol.extent.extend(leftExtent, children[i].extent); + margin += ol.extent.getMargin(leftExtent); + } + for (i = M - m - 1; i >= m; --i) { + ol.extent.extend(rightExtent, children[i].extent); + margin += ol.extent.getMargin(rightExtent); + } + return margin; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @return {Array.} All in extent. + */ +ol.structs.RBush.prototype.allInExtent = function(extent) { + var values = []; + this.forEachInExtent(extent, + /** + * @param {T} value Value. + */ + function(value) { + values.push(value); + }); + return values; +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @private + */ +ol.structs.RBush.prototype.chooseSplitAxis_ = function(node) { + var xMargin = this.allDistMargin_(node, ol.structs.RBushNode.compareMinX); + var yMargin = this.allDistMargin_(node, ol.structs.RBushNode.compareMinY); + if (xMargin < yMargin) { + goog.array.sort(node.children, ol.structs.RBushNode.compareMinX); + } +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @private + * @return {number} Split index. + */ +ol.structs.RBush.prototype.chooseSplitIndex_ = function(node) { + var children = node.children; + var m = this.minEntries_; + var M = children.length; + var minOverlap = Infinity; + var minArea = Infinity; + var extent1 = ol.extent.createEmpty(); + var extent2 = ol.extent.createEmpty(); + var index = 0; + var i; + for (i = m; i <= M - m; ++i) { + extent1 = node.getChildrenExtent(0, i, extent1); + extent2 = node.getChildrenExtent(i, M, extent2); + var overlap = ol.extent.getIntersectionArea(extent1, extent2); + var area = ol.extent.getArea(extent1) + ol.extent.getArea(extent2); + if (overlap < minOverlap) { + minOverlap = overlap; + minArea = Math.min(area, minArea); + index = i; + } else if (overlap == minOverlap && area < minArea) { + minArea = area; + index = i; + } + } + return index; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.structs.RBushNode.} node Node. + * @param {number} level Level. + * @param {Array.>} path Path. + * @private + * @return {ol.structs.RBushNode.} Node. + */ +ol.structs.RBush.prototype.chooseSubtree_ = + function(extent, node, level, path) { + while (!node.isLeaf() && path.length - 1 != level) { + var minArea = Infinity; + var minEnlargement = Infinity; + var children = node.children; + var bestChild = null; + var i, ii; + for (i = 0, ii = children.length; i < ii; ++i) { + var child = children[i]; + var area = ol.extent.getArea(child.extent); + var enlargement = ol.extent.getEnlargedArea(child.extent, extent) - area; + if (enlargement < minEnlargement) { + minEnlargement = enlargement; + minArea = Math.min(area, minArea); + bestChild = child; + } else if (enlargement == minEnlargement && area < minArea) { + minArea = area; + bestChild = child; + } + } + goog.asserts.assert(!goog.isNull(bestChild)); + node = bestChild; + path.push(node); + } + return node; +}; + + +/** + * FIXME empty description for jsdoc + */ +ol.structs.RBush.prototype.clear = function() { + var node = this.root_; + node.extent = ol.extent.createOrUpdateEmpty(this.root_.extent); + node.height = 1; + node.children.length = 0; + node.value = null; +}; + + +/** + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_obj Scope. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach = function(callback, opt_obj) { + return this.forEach_(this.root_, callback, opt_obj); +}; + + +/** + * @param {ol.structs.RBushNode.} node Node. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_obj Scope. + * @private + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEach_ = function(node, callback, opt_obj) { + goog.asserts.assert(!node.isLeaf()); + /** @type {Array.>} */ + var toVisit = [node]; + var children, i, ii, result; + while (toVisit.length > 0) { + node = toVisit.pop(); + children = node.children; + if (node.height == 1) { + for (i = 0, ii = children.length; i < ii; ++i) { + result = callback.call(opt_obj, children[i].value); + if (result) { + return result; + } + } + } else { + toVisit.push.apply(toVisit, children); + } + } +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {function(this: S, T): *} callback Callback. + * @param {S=} opt_obj Scope. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEachInExtent = + function(extent, callback, opt_obj) { + /** @type {Array.>} */ + var toVisit = [this.root_]; + var result; + while (toVisit.length > 0) { + var node = toVisit.pop(); + if (ol.extent.intersects(extent, node.extent)) { + if (node.isLeaf()) { + result = callback.call(opt_obj, node.value); + if (result) { + return result; + } + } else if (ol.extent.containsExtent(extent, node.extent)) { + result = this.forEach_(node, callback, opt_obj); + if (result) { + return result; + } + } else { + toVisit.push.apply(toVisit, node.children); + } + } + } + return undefined; +}; + + +/** + * @param {function(this: S, ol.structs.RBushNode.): *} callback Callback. + * @param {S=} opt_obj Scope. + * @return {*} Callback return value. + * @template S + */ +ol.structs.RBush.prototype.forEachNode = function(callback, opt_obj) { + /** @type {Array.>} */ + var toVisit = [this.root_]; + while (toVisit.length > 0) { + var node = toVisit.pop(); + var result = callback.call(opt_obj, node); + if (result) { + return result; + } + if (!node.isLeaf()) { + toVisit.push.apply(toVisit, node.children); + } + } + return undefined; +}; + + +/** + * @param {T} value Value. + * @private + * @return {string} Key. + */ +ol.structs.RBush.prototype.getKey_ = function(value) { + goog.asserts.assert(goog.isObject(value)); + return goog.getUid(value) + ''; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.insert = function(extent, value) { + var key = this.getKey_(value); + goog.asserts.assert(!this.valueExtent_.hasOwnProperty(key)); + this.insert_(extent, value, this.root_.height - 1); + this.valueExtent_[key] = extent; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + * @param {number} level Level. + * @private + * @return {ol.structs.RBushNode.} Node. + */ +ol.structs.RBush.prototype.insert_ = function(extent, value, level) { + /** @type {Array.>} */ + var path = [this.root_]; + var node = this.chooseSubtree_(extent, this.root_, level, path); + node.children.push(new ol.structs.RBushNode(extent, 0, null, value)); + ol.extent.extend(node.extent, extent); + var i; + for (i = path.length - 1; i >= 0; --i) { + if (path[i].children.length > this.maxEntries_) { + this.split_(path, i); + } else { + break; + } + } + for (; i >= 0; --i) { + ol.extent.extend(path[i].extent, extent); + } + return node; +}; + + +/** + * @param {T} value Value. + */ +ol.structs.RBush.prototype.remove = function(value) { + var key = this.getKey_(value); + goog.asserts.assert(this.valueExtent_.hasOwnProperty(key)); + var extent = this.valueExtent_[key]; + delete this.valueExtent_[key]; + this.remove_(extent, value); +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + * @private + */ +ol.structs.RBush.prototype.remove_ = function(extent, value) { + // FIXME + goog.asserts.fail(); +}; + + +/** + * @param {Array.>} path Path. + * @param {number} level Level. + * @private + */ +ol.structs.RBush.prototype.split_ = function(path, level) { + var node = path[level]; + this.chooseSplitAxis_(node); + var splitIndex = this.chooseSplitIndex_(node); + // FIXME too few arguments to splice here + var newChildren = node.children.splice(splitIndex); + var newNode = new ol.structs.RBushNode( + ol.extent.createEmpty(), node.height, newChildren, null); + node.updateExtent(); + newNode.updateExtent(); + if (level) { + path[level - 1].children.push(newNode); + } else { + this.splitRoot_(node, newNode); + } +}; + + +/** + * @param {ol.structs.RBushNode.} node1 Node 1. + * @param {ol.structs.RBushNode.} node2 Node 2. + * @private + */ +ol.structs.RBush.prototype.splitRoot_ = function(node1, node2) { + goog.asserts.assert(node1 === this.root_); + var height = node1.height + 1; + var extent = ol.extent.extend(node1.extent.slice(), node2.extent); + var children = [node1, node2]; + this.root_ = new ol.structs.RBushNode(extent, height, children, null); +}; diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js new file mode 100644 index 0000000000..6852e43964 --- /dev/null +++ b/test/spec/ol/structs/rbush.js @@ -0,0 +1,105 @@ +goog.provide('ol.test.structs.RBush'); + + +describe('ol.structs.RBush', function() { + + var rBush = new ol.structs.RBush(); + + 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]]; + rBush.insert(bounds, {id: i}); + i--; + } + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).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]]; + rBush.insert(bounds, {id: i}); + i--; + } + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).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 += rBush.allInExtent(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 += rBush.allInExtent(bounds).length; + i--; + } + expect(len).not.to.be(0); + }); + }); + + describe.skip('deletion', function() { + var len = 0; + it('can delete half the RBush', function() { + var bounds = [5000, 0, 10500, 10500]; + len += rBush.remove(bounds).length; + expect(len).to.not.be(0); + }); + it('can delete the other half of the RBush', function() { + var bounds = [0, 0, 5000, 10500]; + len += rBush.remove(bounds).length; + expect(len).to.be(2000); + }); + }); + + describe('result plausibility and structure', function() { + + it('filters by rectangle', function() { + var objs = [{}, {}, {}, {}, {}, {}]; + rBush.insert([0, 0, 1, 1], objs[0]); + rBush.insert([1, 1, 4, 4], objs[1]); + rBush.insert([2, 2, 3, 3], objs[2]); + rBush.insert([-5, -5, -4, -4], objs[3]); + rBush.insert([-4, -4, -1, -1], objs[4]); + rBush.insert([-3, -3, -2, -2], objs[5]); + + var result; + result = rBush.allInExtent([2, 2, 3, 3]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result.length).to.be(2); + result = rBush.allInExtent([-1, -1, 2, 2]); + expect(result).to.contain(objs[0]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result).to.contain(objs[4]); + expect(result.length).to.be(4); + expect(rBush.allInExtent([5, 5, 6, 6]).length).to.be(0); + }); + + }); + +}); + +goog.require('goog.object'); +goog.require('ol.structs.RBush'); From a76eba34e88c3a2989b8b9648f438983d75308e6 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 24 Nov 2013 18:48:15 +0100 Subject: [PATCH 13/19] Add ol.structs.RBush#assertValid --- src/ol/structs/rbush.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index e2342e512b..2af41213e9 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -93,6 +93,31 @@ ol.structs.RBushNode.compareMinY = function(node1, node2) { }; +/** + * @param {boolean} isRoot Is root. + * @param {number} minEntries Min entries. + * @param {number} maxEntries Max entries. + */ +ol.structs.RBushNode.prototype.assertValid = + function(isRoot, minEntries, maxEntries) { + if (this.height === 0) { + goog.asserts.assert(goog.isNull(this.children)); + goog.asserts.assert(!goog.isNull(this.value)); + } else { + goog.asserts.assert(!goog.isNull(this.children)); + goog.asserts.assert(goog.isNull(this.value)); + goog.asserts.assert(isRoot || minEntries <= this.children.length); + goog.asserts.assert(this.children.length <= maxEntries); + var i, ii; + for (i = 0, ii = this.children.length; i < ii; ++i) { + var child = this.children[i]; + goog.asserts.assert(ol.extent.containsExtent(this.extent, child.extent)); + child.assertValid(false, minEntries, maxEntries); + } + } +}; + + /** * @param {number} start Start. * @param {number} stop Stop. @@ -233,6 +258,14 @@ ol.structs.RBush.prototype.allInExtent = function(extent) { }; +/** + * FIXME empty description for jsdoc + */ +ol.structs.RBush.prototype.assertValid = function() { + this.root_.assertValid(true, this.minEntries_, this.maxEntries_); +}; + + /** * @param {ol.structs.RBushNode.} node Node. * @private From 0ea7c2cd506a0065c8fae79254d699da18775402 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 17:21:24 +0100 Subject: [PATCH 14/19] Refactor ol.structs.RBush tests --- test/spec/ol/structs/rbush.js | 189 ++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 79 deletions(-) diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js index 6852e43964..b1f7eb1bbf 100644 --- a/test/spec/ol/structs/rbush.js +++ b/test/spec/ol/structs/rbush.js @@ -3,98 +3,129 @@ goog.provide('ol.test.structs.RBush'); describe('ol.structs.RBush', function() { - var rBush = new ol.structs.RBush(); - - 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]]; - rBush.insert(bounds, {id: i}); - i--; - } - expect(rBush.allInExtent([0, 0, 10600, 10600]).length).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]]; - rBush.insert(bounds, {id: i}); - i--; - } - expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(2000); - }); + var rBush; + beforeEach(function() { + rBush = new ol.structs.RBush(); }); - 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 += rBush.allInExtent(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 += rBush.allInExtent(bounds).length; - i--; - } - expect(len).not.to.be(0); + describe('when empty', function() { + + describe('#all', function() { + + it('returns the expected number of objects', function() { + expect(rBush.all()).to.be.empty(); + }); + }); + }); - describe.skip('deletion', function() { - var len = 0; - it('can delete half the RBush', function() { - var bounds = [5000, 0, 10500, 10500]; - len += rBush.remove(bounds).length; - expect(len).to.not.be(0); - }); - it('can delete the other half of the RBush', function() { - var bounds = [0, 0, 5000, 10500]; - len += rBush.remove(bounds).length; - expect(len).to.be(2000); - }); - }); + describe('with a few objects', function() { - describe('result plausibility and structure', function() { - - it('filters by rectangle', function() { - var objs = [{}, {}, {}, {}, {}, {}]; + var objs; + beforeEach(function() { + objs = [{}, {}, {}, {}, {}, {}]; rBush.insert([0, 0, 1, 1], objs[0]); rBush.insert([1, 1, 4, 4], objs[1]); rBush.insert([2, 2, 3, 3], objs[2]); rBush.insert([-5, -5, -4, -4], objs[3]); rBush.insert([-4, -4, -1, -1], objs[4]); rBush.insert([-3, -3, -2, -2], objs[5]); + }); + + describe('#allInExtent', function() { + + it('returns the expected objects', function() { + var result; + result = rBush.allInExtent([2, 2, 3, 3]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result.length).to.be(2); + result = rBush.allInExtent([-1, -1, 2, 2]); + expect(result).to.contain(objs[0]); + expect(result).to.contain(objs[1]); + expect(result).to.contain(objs[2]); + expect(result).to.contain(objs[4]); + expect(result.length).to.be(4); + }); + + it('returns an empty array when given a disjoint extent', function() { + expect(rBush.allInExtent([5, 5, 6, 6]).length).to.be(0); + }); + + }); + + }); + + describe('with 1000 objects', function() { + + beforeEach(function() { + var i; + for (i = 0; i < 1000; ++i) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + rBush.insert(extent, {id: i}); + } + }); + + describe('#all', function() { + + it('returns the expected number of objects', function() { + expect(rBush.all().length).to.be(1000); + }); + + }); + + describe('#allInExtent', function() { + + it('returns the expected number of objects', function() { + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(1000); + }); + + it('can perform 1000 in-extent searches', function() { + var n = 0; + var i; + for (i = 0; i < 1000; ++i) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, + min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + n += rBush.allInExtent(extent).length; + } + expect(n).not.to.be(0); + }); + + it('can perform 1000 out-of-extent searches', function() { + var n = 0; + var i; + for (i = 0; i < 1000; ++i) { + var min = [-(Math.random() * 10000 + 501), + -(Math.random() * 10000 + 501)]; + var max = [min[0] + Math.random() * 500, + min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + n += rBush.allInExtent(extent).length; + } + expect(n).to.be(0); + }); + + }); + + describe('#insert', function() { + + it('can insert another 1000 objects', function() { + var i; + for (i = 1000; i < 2000; ++i) { + var min = [Math.random() * 10000, Math.random() * 10000]; + var max = [min[0] + Math.random() * 500, + min[1] + Math.random() * 500]; + var extent = [min[0], min[1], max[0], max[1]]; + rBush.insert(extent, {id: i}); + } + expect(rBush.allInExtent([0, 0, 10600, 10600]).length).to.be(2000); + }); - var result; - result = rBush.allInExtent([2, 2, 3, 3]); - expect(result).to.contain(objs[1]); - expect(result).to.contain(objs[2]); - expect(result.length).to.be(2); - result = rBush.allInExtent([-1, -1, 2, 2]); - expect(result).to.contain(objs[0]); - expect(result).to.contain(objs[1]); - expect(result).to.contain(objs[2]); - expect(result).to.contain(objs[4]); - expect(result.length).to.be(4); - expect(rBush.allInExtent([5, 5, 6, 6]).length).to.be(0); }); }); From b865a7c0e1219f082aaab5ac9e094802f93efbac Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:08:02 +0100 Subject: [PATCH 15/19] Don't check for under-full nodes when validating ol.structs.RBush Removal can lead to under-full nodes. --- src/ol/structs/rbush.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 2af41213e9..4f1688582b 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -94,25 +94,21 @@ ol.structs.RBushNode.compareMinY = function(node1, node2) { /** - * @param {boolean} isRoot Is root. - * @param {number} minEntries Min entries. * @param {number} maxEntries Max entries. */ -ol.structs.RBushNode.prototype.assertValid = - function(isRoot, minEntries, maxEntries) { +ol.structs.RBushNode.prototype.assertValid = function(maxEntries) { if (this.height === 0) { goog.asserts.assert(goog.isNull(this.children)); goog.asserts.assert(!goog.isNull(this.value)); } else { goog.asserts.assert(!goog.isNull(this.children)); goog.asserts.assert(goog.isNull(this.value)); - goog.asserts.assert(isRoot || minEntries <= this.children.length); goog.asserts.assert(this.children.length <= maxEntries); var i, ii; for (i = 0, ii = this.children.length; i < ii; ++i) { var child = this.children[i]; goog.asserts.assert(ol.extent.containsExtent(this.extent, child.extent)); - child.assertValid(false, minEntries, maxEntries); + child.assertValid(maxEntries); } } }; @@ -262,7 +258,7 @@ ol.structs.RBush.prototype.allInExtent = function(extent) { * FIXME empty description for jsdoc */ ol.structs.RBush.prototype.assertValid = function() { - this.root_.assertValid(true, this.minEntries_, this.maxEntries_); + this.root_.assertValid(this.maxEntries_); }; From 1293294d4051a4125d68b2bbd570931f5c27e830 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:08:36 +0100 Subject: [PATCH 16/19] Implement ol.structs.RBush#remove_ --- src/ol/structs/rbush.js | 59 ++++++++++++++++++++++++++-- test/spec/ol/structs/rbush.js | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) 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() { From 7ec456fa709e6caadfa7ab9575d79710bfce1d52 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:27:42 +0100 Subject: [PATCH 17/19] Add FIXME --- src/ol/structs/rbush.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 536b3338a9..59e43722de 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -20,6 +20,7 @@ // THE SOFTWARE. // FIXME bulk inserts +// FIXME is level argument needed to insert_? goog.provide('ol.structs.RBush'); From 880f098f0fc84ad11023b1a8abec40124097e2d9 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Nov 2013 19:27:58 +0100 Subject: [PATCH 18/19] Add ol.structs.RBush#update --- src/ol/structs/rbush.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index 59e43722de..e52bf5ec50 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -618,3 +618,19 @@ ol.structs.RBush.prototype.splitRoot_ = function(node1, node2) { var children = [node1, node2]; this.root_ = new ol.structs.RBushNode(extent, height, children, null); }; + + +/** + * @param {ol.Extent} extent Extent. + * @param {T} value Value. + */ +ol.structs.RBush.prototype.update = function(extent, value) { + var key = this.getKey_(value); + var currentExtent = this.valueExtent_[key]; + goog.asserts.assert(goog.isDef(currentExtent)); + if (!ol.extent.equals(currentExtent, extent)) { + this.remove_(currentExtent, value); + this.insert_(extent, value, this.root_.height - 1); + this.valueExtent_[key] = extent; + } +}; From 92469901f751ecd955be8d4f66decd481f8d5fd2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Nov 2013 12:15:40 +0100 Subject: [PATCH 19/19] Clone extents in ol.structs.RBush to prevent modification --- src/ol/structs/rbush.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js index e52bf5ec50..88c21b6c73 100644 --- a/src/ol/structs/rbush.js +++ b/src/ol/structs/rbush.js @@ -495,7 +495,7 @@ ol.structs.RBush.prototype.insert = function(extent, value) { var key = this.getKey_(value); goog.asserts.assert(!this.valueExtent_.hasOwnProperty(key)); this.insert_(extent, value, this.root_.height - 1); - this.valueExtent_[key] = extent; + this.valueExtent_[key] = ol.extent.clone(extent); }; @@ -631,6 +631,6 @@ ol.structs.RBush.prototype.update = function(extent, value) { if (!ol.extent.equals(currentExtent, extent)) { this.remove_(currentExtent, value); this.insert_(extent, value, this.root_.height - 1); - this.valueExtent_[key] = extent; + ol.extent.clone(extent, currentExtent); } };