diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js
index 7359870c49..17d27f8fc6 100644
--- a/src/ol/structs/rtree.js
+++ b/src/ol/structs/rtree.js
@@ -28,14 +28,13 @@
goog.provide('ol.structs.RTree');
-goog.provide('ol.structs.RTreeRectangle');
-goog.require('goog.object');
+goog.require('goog.array');
+goog.require('ol.extent');
/**
- * @typedef {{x: (number), y: (number), w: (number), h: (number),
- * leaf: (Object|undefined),
+ * @typedef {{extent: (ol.Extent), leaf: (Object|undefined),
* nodes: (Array.
|undefined),
* target: (Object|undefined), type: (string|undefined)}}
*/
@@ -56,12 +55,19 @@ ol.structs.RTree = function(opt_width) {
maxWidth = opt_width;
}
// Start with an empty root-tree
- var tree = /** @type {ol.structs.RTreeNode} */
- ({x: 0, y: 0, w: 0, h: 0, nodes: []});
+ var rootTree = /** @type {ol.structs.RTreeNode} */
+ ({extent: [0, 0, 0, 0], nodes: []});
- // This is my special addition to the world of r-trees
- // every other (simple) method I found produced crap trees
- // this skews insertions to prefering squarer and emptier nodes
+ /**
+ * This is Jon-Carlos Rivera's special addition to the world of r-trees.
+ * Every other (simple) method he found produced crap trees.
+ * This skews insertions to prefering squarer and emptier nodes.
+ *
+ * @param {number} l L.
+ * @param {number} w W.
+ * @param {number} fill Fill.
+ * @return {number} Squarified ratio.
+ */
var squarifiedRatio = function(l, w, fill) {
// Area of new enlarged rectangle
var peri = (l + w) / 2; // Average size of a side of the new rectangle
@@ -73,6 +79,27 @@ ol.structs.RTree = function(opt_width) {
return area * fill / geo;
};
+ /**
+ * Generates a minimally bounding rectangle for all rectangles in
+ * array "nodes". `rect` is modified into the MBR.
+ *
+ * @param {Array} nodes Nodes.
+ * @param {ol.structs.RTreeNode} rect Rectangle.
+ * @return {ol.structs.RTreeNode} Rectangle.
+ */
+ var makeMBR = function(nodes, rect) {
+ if (nodes.length < 1) {
+ return {extent: [0, 0, 0, 0]};
+ }
+ rect.extent = goog.array.concat(nodes[0].extent);
+
+ for (var i = nodes.length - 1; i > 0; --i) {
+ ol.extent.extend(rect.extent, nodes[i].extent);
+ }
+
+ return rect;
+ };
+
/**
* Find the best specific node(s) for object to be deleted from.
*
@@ -87,13 +114,13 @@ ol.structs.RTree = function(opt_width) {
var returnArray = [];
var currentDepth = 1;
- if (!rect || !ol.structs.RTreeRectangle.overlapRectangle(rect, root)) {
+ if (!rect || !ol.extent.intersects(rect.extent, root.extent)) {
return returnArray;
}
- var workingObject = /** @type {ol.structs.RTreeNode} */ ({
- x: rect.x, y: rect.y, w: rect.w, h: rect.h, target: obj
- });
+ /** @type {ol.structs.RTreeNode} */
+ var workingObject = /** @type {ol.structs.RTreeNode} */
+ ({extent: goog.array.concat(rect), target: obj});
countStack.push(root.nodes.length);
hitStack.push(root);
@@ -106,13 +133,12 @@ ol.structs.RTree = function(opt_width) {
// We are searching for a target
while (i >= 0) {
var lTree = tree.nodes[i];
- if (ol.structs.RTreeRectangle.overlapRectangle(
- workingObject, lTree)) {
+ if (ol.extent.intersects(workingObject.extent, lTree.extent)) {
if ((workingObject.target && goog.isDef(lTree.leaf) &&
lTree.leaf === workingObject.target) ||
(!workingObject.target && (goog.isDef(lTree.leaf) ||
- ol.structs.RTreeRectangle.containsRectangle(
- lTree, workingObject)))) { // A Match !!
+ ol.extent.containsExtent(workingObject.extent, lTree.extent))))
+ { // A Match !!
// Yup we found a match...
// we can cancel search and start walking up the list
if (goog.isDef(lTree.nodes)) {
@@ -123,7 +149,7 @@ ol.structs.RTree = function(opt_width) {
returnArray = tree.nodes.splice(i, 1);
}
// Resize MBR down...
- ol.structs.RTreeRectangle.makeMBR(tree.nodes, tree);
+ makeMBR(tree.nodes, tree);
workingObject.target = undefined;
if (tree.nodes.length < minWidth) { // Underflow
workingObject.nodes = /** @type {Array} */
@@ -147,11 +173,11 @@ ol.structs.RTree = function(opt_width) {
// workingObject.nodes contains a list of elements removed from the
// tree so far
if (tree.nodes.length > 0)
- ol.structs.RTreeRectangle.makeMBR(tree.nodes, tree);
+ makeMBR(tree.nodes, tree);
for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t)
insertSubtree(workingObject.nodes[t], tree);
workingObject.nodes.length = 0;
- if (hitStack.length == 0 && tree.nodes.length <= 1) {
+ if (hitStack.length === 0 && tree.nodes.length <= 1) {
// Underflow..on root!
workingObject.nodes = /** @type {Array} */
(searchSubtree(tree, true, workingObject.nodes, tree));
@@ -167,7 +193,7 @@ ol.structs.RTree = function(opt_width) {
workingObject.nodes = undefined; // Just start resizing
}
} else { // we are just resizing
- ol.structs.RTreeRectangle.makeMBR(tree.nodes, tree);
+ makeMBR(tree.nodes, tree);
}
currentDepth -= 1;
} while (hitStack.length > 0);
@@ -205,14 +231,18 @@ ol.structs.RTree = function(opt_width) {
break;
}
// Area of new enlarged rectangle
- var oldLRatio = squarifiedRatio(lTree.w, lTree.h,
- lTree.nodes.length + 1);
+ var oldLRatio = squarifiedRatio(lTree.extent[1] - lTree.extent[0],
+ lTree.extent[3] - lTree.extent[2], lTree.nodes.length + 1);
// Enlarge rectangle to fit new rectangle
- var nw = Math.max(lTree.x + lTree.w, rect.x + rect.w) -
- Math.min(lTree.x, rect.x);
- var nh = Math.max(lTree.y + lTree.h, rect.y + rect.h) -
- Math.min(lTree.y, rect.y);
+ var nw = (lTree.extent[1] > rect.extent[1] ?
+ lTree.extent[1] : rect.extent[1]) -
+ (lTree.extent[0] < rect.extent[0] ?
+ lTree.extent[0] : rect.extent[0]);
+ var nh = (lTree.extent[3] > rect.extent[3] ?
+ lTree.extent[3] : rect.extent[3]) -
+ (lTree.extent[2] < rect.extent[2] ?
+ lTree.extent[2] : rect.extent[2]);
// Area of new enlarged rectangle
var lRatio = squarifiedRatio(nw, nh, lTree.nodes.length + 2);
@@ -228,9 +258,12 @@ ol.structs.RTree = function(opt_width) {
return bestChoiceStack;
};
- /* split a set of nodes into two roughly equally-filled nodes
- * [ an array of two new arrays of nodes ] = linearSplit(array of nodes)
- * @private
+ /**
+ * Split a set of nodes into two roughly equally-filled nodes.
+ *
+ * @param {Array.} nodes Array of nodes.
+ * @return {Array.>} An array of two new arrays
+ * of nodes.
*/
var linearSplit = function(nodes) {
var n = pickLinear(nodes);
@@ -240,36 +273,44 @@ ol.structs.RTree = function(opt_width) {
return n;
};
- /* insert the best source rectangle into the best fitting parent node: a or b
- * [] = pick_next(array of source nodes, target node array a, target node
- * array b)
- * @private
+ /**
+ * Insert the best source rectangle into the best fitting parent node: a or b.
+ *
+ * @param {Array.} nodes Source node array.
+ * @param {ol.structs.RTreeNode} a Target node array a.
+ * @param {ol.structs.RTreeNode} b Target node array b.
*/
var pickNext = function(nodes, a, b) {
// Area of new enlarged rectangle
- var areaA = squarifiedRatio(a.w, a.h, a.nodes.length + 1);
- var areaB = squarifiedRatio(b.w, b.h, b.nodes.length + 1);
+ var areaA = squarifiedRatio(a.extent[1] - a.extent[0],
+ a.extent[3] - a.extent[2], a.nodes.length + 1);
+ var areaB = squarifiedRatio(b.extent[1] - b.extent[0],
+ b.extent[3] - b.extent[2], b.nodes.length + 1);
var highAreaDelta;
var highAreaNode;
var lowestGrowthGroup;
for (var i = nodes.length - 1; i >= 0; --i) {
var l = nodes[i];
- var newAreaA = {};
- newAreaA.x = Math.min(a.x, l.x);
- newAreaA.y = Math.min(a.y, l.y);
- newAreaA.w = Math.max(a.x + a.w, l.x + l.w) - newAreaA.x;
- newAreaA.h = Math.max(a.y + a.h, l.y + l.h) - newAreaA.y;
- var changeNewAreaA = Math.abs(squarifiedRatio(newAreaA.w, newAreaA.h,
- a.nodes.length + 2) - areaA);
- var newAreaB = {};
- newAreaB.x = Math.min(b.x, l.x);
- newAreaB.y = Math.min(b.y, l.y);
- newAreaB.w = Math.max(b.x + b.w, l.x + l.w) - newAreaB.x;
- newAreaB.h = Math.max(b.y + b.h, l.y + l.h) - newAreaB.y;
+ var newAreaA = [
+ a.extent[0] < l.extent[0] ? a.extent[0] : l.extent[0],
+ a.extent[1] > l.extent[1] ? a.extent[1] : l.extent[1],
+ a.extent[2] < l.extent[2] ? a.extent[2] : l.extent[2],
+ a.extent[3] > l.extent[3] ? a.extent[3] : l.extent[3]
+ ];
+ var changeNewAreaA = Math.abs(squarifiedRatio(newAreaA[1] - newAreaA[0],
+ newAreaA[3] - newAreaA[2], a.nodes.length + 2) - areaA);
+
+ var newAreaB = [
+ b.extent[0] < l.extent[0] ? b.extent[0] : l.extent[0],
+ b.extent[1] > l.extent[1] ? b.extent[1] : l.extent[1],
+ b.extent[2] < l.extent[2] ? b.extent[2] : l.extent[2],
+ b.extent[3] > l.extent[3] ? b.extent[3] : l.extent[3]
+ ];
var changeNewAreaB = Math.abs(squarifiedRatio(
- newAreaB.w, newAreaB.h, b.nodes.length + 2) - areaB);
+ newAreaB[1] - newAreaB[0], newAreaB[3] - newAreaB[2],
+ b.nodes.length + 2) - areaB);
if (!highAreaNode || !highAreaDelta ||
Math.abs(changeNewAreaB - changeNewAreaA) < highAreaDelta) {
@@ -281,21 +322,24 @@ ol.structs.RTree = function(opt_width) {
var tempNode = nodes.splice(highAreaNode, 1)[0];
if (a.nodes.length + nodes.length + 1 <= minWidth) {
a.nodes.push(tempNode);
- ol.structs.RTreeRectangle.expandRectangle(a, tempNode);
+ ol.extent.extend(a.extent, tempNode.extent);
} else if (b.nodes.length + nodes.length + 1 <= minWidth) {
b.nodes.push(tempNode);
- ol.structs.RTreeRectangle.expandRectangle(b, tempNode);
+ ol.extent.extend(b.extent, tempNode.extent);
}
else {
lowestGrowthGroup.nodes.push(tempNode);
- ol.structs.RTreeRectangle.expandRectangle(lowestGrowthGroup, tempNode);
+ ol.extent.extend(lowestGrowthGroup.extent, tempNode.extent);
}
};
- /* pick the "best" two starter nodes to use as seeds using the "linear"
- * criteria [ an array of two new arrays of nodes ] = pickLinear(array of
- * source nodes)
- * @private
+ /**
+ * Pick the "best" two starter nodes to use as seeds using the "linear"
+ * criteria.
+ *
+ * @param {Array.} nodes Array of source nodes.
+ * @return {Array.} An array of two new arrays
+ * of nodes.
*/
var pickLinear = function(nodes) {
var lowestHighX = nodes.length - 1;
@@ -306,21 +350,21 @@ ol.structs.RTree = function(opt_width) {
for (var i = nodes.length - 2; i >= 0; --i) {
var l = nodes[i];
- if (l.x > nodes[highestLowX].x) {
+ if (l.extent[0] > nodes[highestLowX].extent[0]) {
highestLowX = i;
- } else if (l.x + l.w < nodes[lowestHighX].x + nodes[lowestHighX].w) {
+ } else if (l.extent[1] < nodes[lowestHighX].extent[2]) {
lowestHighX = i;
}
- if (l.y > nodes[highestLowY].y) {
+ if (l.extent[2] > nodes[highestLowY].extent[2]) {
highestLowY = i;
- } else if (l.y + l.h < nodes[lowestHighY].y + nodes[lowestHighY].h) {
+ } else if (l.extent[3] < nodes[lowestHighY].extent[3]) {
lowestHighY = i;
}
}
- var dx = Math.abs((nodes[lowestHighX].x + nodes[lowestHighX].w) -
- nodes[highestLowX].x);
- var dy = Math.abs((nodes[lowestHighY].y + nodes[lowestHighY].h) -
- nodes[highestLowY].y);
+ var dx = Math.abs(nodes[lowestHighX].extent[1] -
+ nodes[highestLowX].extent[0]);
+ var dy = Math.abs(nodes[lowestHighY].extent[3] -
+ nodes[highestLowY].extent[2]);
if (dx > dy) {
if (lowestHighX > highestLowX) {
t1 = nodes.splice(lowestHighX, 1)[0];
@@ -340,18 +384,12 @@ ol.structs.RTree = function(opt_width) {
}
return [
/** @type {ol.structs.RTreeNode} */
- ({x: t1.x, y: t1.y, w: t1.w, h: t1.h, nodes: [t1]}),
+ ({extent: goog.array.concat(t1.extent), nodes: [t1]}),
/** @type {ol.structs.RTreeNode} */
- ({x: t2.x, y: t2.y, w: t2.w, h: t2.h, nodes: [t2]})
+ ({extent: goog.array.concat(t2.extent), nodes: [t2]})
];
};
- var attachData = function(node, moreTree) {
- node.nodes = moreTree.nodes;
- node.x = moreTree.x; node.y = moreTree.y;
- node.w = moreTree.w; node.h = moreTree.h;
- return node;
- };
/**
* Non-recursive internal search function
@@ -366,7 +404,7 @@ ol.structs.RTree = function(opt_width) {
var searchSubtree = function(rect, returnNode, result, root, opt_type) {
var hitStack = []; // Contains the elements that overlap
- if (!ol.structs.RTreeRectangle.overlapRectangle(rect, root)) {
+ if (!ol.extent.intersects(rect.extent, root.extent)) {
return result;
}
@@ -377,7 +415,7 @@ ol.structs.RTree = function(opt_width) {
for (var i = nodes.length - 1; i >= 0; --i) {
var lTree = nodes[i];
- if (ol.structs.RTreeRectangle.overlapRectangle(rect, lTree)) {
+ if (ol.extent.intersects(rect.extent, lTree.extent)) {
if (goog.isDef(lTree.nodes)) { // Not a Leaf
hitStack.push(lTree.nodes);
} else if (goog.isDef(lTree.leaf)) { // A Leaf !!
@@ -399,19 +437,18 @@ ol.structs.RTree = function(opt_width) {
return result;
};
- /* non-recursive internal insert function
- * [] = insertSubtree(rectangle, object to insert, root to begin insertion at)
- * @private
+ /**
+ * Non-recursive internal insert function.
+ *
+ * @param {ol.structs.RTreeNode} node Node to insert.
+ * @param {ol.structs.RTreeNode} root Root to begin insertion at.
*/
var insertSubtree = function(node, root) {
var bc; // Best Current node
// Initial insertion is special because we resize the Tree and we don't
// care about any overflow (seriously, how can the first object overflow?)
- if (root.nodes.length == 0) {
- root.x = node.x;
- root.y = node.y;
- root.w = node.w;
- root.h = node.h;
+ if (root.nodes.length === 0) {
+ root.extent = goog.array.concat(node.extent);
root.nodes.push(node);
return;
}
@@ -420,16 +457,16 @@ ol.structs.RTree = function(opt_width) {
// chooseLeaf returns an array of all tree levels (including root)
// that were traversed while trying to find the leaf
var treeStack = chooseLeafSubtree(node, root);
- var workingObject = node;//{x:rect.x,y:rect.y,w:rect.w,h:rect.h, leaf: obj};
+ var workingObject = node;
// Walk back up the tree resizing and inserting as needed
do {
//handle the case of an empty node (from a split)
- if (bc && goog.isDef(bc.nodes) && bc.nodes.length == 0) {
+ if (bc && goog.isDef(bc.nodes) && bc.nodes.length === 0) {
var pbc = bc; // Past bc
bc = treeStack.pop();
for (var t = 0, tt = bc.nodes.length; t < tt; ++t) {
- if (bc.nodes[t] === pbc || bc.nodes[t].nodes.length == 0) {
+ if (bc.nodes[t] === pbc || bc.nodes[t].nodes.length === 0) {
bc.nodes.splice(t, 1);
break;
}
@@ -445,17 +482,16 @@ ol.structs.RTree = function(opt_width) {
// Do Insert
if (isArray) {
for (var ai = 0, aii = workingObject.length; ai < aii; ++ai) {
- ol.structs.RTreeRectangle.expandRectangle(bc, workingObject[ai]);
+ ol.extent.extend(bc.extent, workingObject[ai].extent);
}
bc.nodes = bc.nodes.concat(workingObject);
} else {
- ol.structs.RTreeRectangle.expandRectangle(bc, workingObject);
+ ol.extent.extend(bc.extent, workingObject.extent);
bc.nodes.push(workingObject); // Do Insert
}
if (bc.nodes.length <= maxWidth) { // Start Resizeing Up the Tree
- workingObject = /** @type {ol.structs.RTreeNode} */
- ({x: bc.x, y: bc.y, w: bc.w, h: bc.h});
+ workingObject = {extent: goog.array.concat(bc.extent)};
} else { // Otherwise Split this Node
// linearSplit() returns an array containing two new nodes
// formed from the split of the previous node's overflow
@@ -470,9 +506,8 @@ ol.structs.RTree = function(opt_width) {
}
} else { // Otherwise Do Resize
//Just keep applying the new bounding rectangle to the parents..
- ol.structs.RTreeRectangle.expandRectangle(bc, workingObject);
- workingObject = /** @type {ol.structs.RTreeNode} */
- ({x: bc.x, y: bc.y, w: bc.w, h: bc.h});
+ ol.extent.extend(bc.extent, workingObject.extent);
+ workingObject = ({extent: goog.array.concat(bc.extent)});
}
} while (treeStack.length > 0);
};
@@ -486,11 +521,8 @@ ol.structs.RTree = function(opt_width) {
* @this {ol.structs.RTree}
*/
this.find = function(extent, opt_type) {
- var rect = /** @type {ol.structs.RTreeNode} */ ({
- x: extent[0], y: extent[2],
- w: extent[1] - extent[0], h: extent[3] - extent[2]
- });
- return searchSubtree.apply(this, [rect, false, {}, tree, opt_type]);
+ var rect = ({extent: extent});
+ return searchSubtree.apply(this, [rect, false, {}, rootTree, opt_type]);
};
/**
@@ -506,7 +538,7 @@ ol.structs.RTree = function(opt_width) {
case 1:
arguments[1] = false; // opt_obj == false for conditionals
case 2:
- arguments[2] = tree; // Add root node to end of argument list
+ arguments[2] = rootTree; // Add root node to end of argument list
default:
arguments.length = 3;
}
@@ -531,94 +563,12 @@ ol.structs.RTree = function(opt_width) {
* @param {string=} opt_type Optional type to store along with the object.
*/
this.put = function(extent, obj, opt_type) {
- var node = /** @type {ol.structs.RTreeNode} */ ({
- x: extent[0], y: extent[2],
- w: extent[1] - extent[0], h: extent[3] - extent[2],
- leaf: obj
- });
+ var node = ({extent: extent, leaf: obj});
if (goog.isDef(opt_type)) {
node.type = opt_type;
}
- insertSubtree(node, tree);
+ insertSubtree(node, rootTree);
};
//End of RTree
};
-
-
-/**
- * Returns true if rectangle 1 overlaps rectangle 2.
- *
- * @param {ol.structs.RTreeNode} a Rectangle A.
- * @param {ol.structs.RTreeNode} b Rectangle B.
- * @return {boolean} Does a overlap b?
- */
-ol.structs.RTreeRectangle.overlapRectangle = function(a, b) {
- // TODO The original implementation has < and > instead of <= and >= or equal.
- // Make sure that this change (which made our tests pass) does not have any
- // unwanted side effects.
- return a.x <= (b.x + b.w) && (a.x + a.w) >= b.x && a.y <= (b.y + b.h) &&
- (a.y + a.h) >= b.y;
-};
-
-
-/**
- * Returns true if rectangle a is contained in rectangle b.
- *
- * @param {ol.structs.RTreeNode} a Rectangle A.
- * @param {ol.structs.RTreeNode} b Rectangle B.
- * @return {boolean} Is a contained in b?
- */
-ol.structs.RTreeRectangle.containsRectangle = function(a, b) {
- return (a.x + a.w) <= (b.x + b.w) && a.x >= b.x && (a.y + a.h) <=
- (b.y + b.h) && a.y >= b.y;
-};
-
-
-/**
- * Expands rectangle A to include rectangle B, rectangle B is untouched.
- *
- * @param {ol.structs.RTreeNode} a Rectangle A.
- * @param {ol.structs.RTreeNode} b Rectangle B.
- * @return {ol.structs.RTreeNode} Rectangle A.
- */
-ol.structs.RTreeRectangle.expandRectangle = function(a, b) {
- var nx = Math.min(a.x, b.x);
- var ny = Math.min(a.y, b.y);
- a.w = Math.max(a.x + a.w, b.x + b.w) - nx;
- a.h = Math.max(a.y + a.h, b.y + b.h) - ny;
- a.x = nx;
- a.y = ny;
- return a;
-};
-
-
-/**
- * Generates a minimally bounding rectangle for all rectangles in
- * array "nodes". If rect is set, it is modified into the MBR. Otherwise,
- * a new rectangle is generated and returned.
- *
- * @param {Array} nodes Nodes.
- * @param {ol.structs.RTreeNode} rect Rectangle.
- * @return {ol.structs.RTreeNode} Rectangle.
- */
-ol.structs.RTreeRectangle.makeMBR = function(nodes, rect) {
- if (nodes.length < 1) {
- return {x: 0, y: 0, w: 0, h: 0};
- }
- if (!rect) {
- rect = {x: nodes[0].x, y: nodes[0].y, w: nodes[0].w, h: nodes[0].h};
- }
- else {
- rect.x = nodes[0].x;
- rect.y = nodes[0].y;
- rect.w = nodes[0].w;
- rect.h = nodes[0].h;
- }
-
- for (var i = nodes.length - 1; i > 0; --i) {
- ol.structs.RTreeRectangle.expandRectangle(rect, nodes[i]);
- }
-
- return rect;
-};