diff --git a/src/ol/extent.js b/src/ol/extent.js
index 5a28036f51..b396d8c33d 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);
};
@@ -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();
+ }
};
@@ -114,18 +123,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 {
@@ -134,6 +143,63 @@ ol.extent.createOrUpdate = function(minX, maxX, minY, 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.} 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.
+ * @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.
@@ -162,6 +228,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) {
@@ -177,6 +244,7 @@ ol.extent.extend = function(extent1, extent2) {
if (extent2[3] > extent1[3]) {
extent1[3] = extent2[3];
}
+ return extent1;
};
@@ -201,6 +269,71 @@ 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.} 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.
+ * @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.
+ * @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 {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.
@@ -231,6 +364,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.
@@ -271,6 +418,29 @@ 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 {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.
@@ -349,6 +519,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.
diff --git a/src/ol/structs/rbush.js b/src/ol/structs/rbush.js
new file mode 100644
index 0000000000..88c21b6c73
--- /dev/null
+++ b/src/ol/structs/rbush.js
@@ -0,0 +1,636 @@
+// 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 bulk inserts
+// FIXME is level argument needed to insert_?
+
+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} maxEntries Max entries.
+ */
+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(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(maxEntries);
+ }
+ }
+};
+
+
+/**
+ * @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;
+};
+
+
+/**
+ * FIXME empty description for jsdoc
+ */
+ol.structs.RBush.prototype.assertValid = function() {
+ this.root_.assertValid(this.maxEntries_);
+};
+
+
+/**
+ * @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 {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.
+ * @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] = ol.extent.clone(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) {
+ 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();
+ }
+ }
+};
+
+
+/**
+ * @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);
+};
+
+
+/**
+ * @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);
+ ol.extent.clone(extent, currentExtent);
+ }
+};
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);
};
diff --git a/test/spec/ol/structs/rbush.js b/test/spec/ol/structs/rbush.js
new file mode 100644
index 0000000000..7549662134
--- /dev/null
+++ b/test/spec/ol/structs/rbush.js
@@ -0,0 +1,208 @@
+goog.provide('ol.test.structs.RBush');
+
+
+describe('ol.structs.RBush', function() {
+
+ var rBush;
+ beforeEach(function() {
+ rBush = new ol.structs.RBush();
+ });
+
+ describe('when empty', function() {
+
+ describe('#all', function() {
+
+ it('returns the expected number of objects', function() {
+ expect(rBush.all()).to.be.empty();
+ });
+
+ });
+
+ });
+
+ describe('with a few objects', function() {
+
+ 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('#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() {
+
+ 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);
+ });
+
+ });
+
+ });
+
+});
+
+goog.require('goog.object');
+goog.require('ol.structs.RBush');