From 7f22455eede72ed3482238625ac44b165647b5e2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 18:12:09 -0500 Subject: [PATCH 01/11] Move squarifiedRatio into private static function --- src/ol/structs/rtree.js | 62 ++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index e1548e8bb4..7f690d637e 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -59,27 +59,6 @@ ol.structs.RTree = function(opt_width) { var rootTree = /** @type {ol.structs.RTreeNode} */ ({extent: [0, 0, 0, 0], nodes: []}); - /** - * This is Jon-Carlos Rivera's special addition to the world of r-trees. - * Every other (simple) method he found produced poor 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 - var area = l * w; // Area of new rectangle - // return the ratio of the perimeter to the area - the closer to 1 we are, - // the more "square" a rectangle is. conversly, when approaching zero the - // more elongated a rectangle is - var geo = area / (peri * peri); - return area * fill / geo; - }; - /** * Generates a minimally bounding rectangle for all rectangles in * array "nodes". `rect` is modified into the MBR. @@ -234,8 +213,10 @@ ol.structs.RTree = function(opt_width) { break; } // Area of new enlarged rectangle - var oldLRatio = squarifiedRatio(lTree.extent[1] - lTree.extent[0], - lTree.extent[3] - lTree.extent[2], lTree.nodes.length + 1); + var oldLRatio = ol.structs.RTree.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 = (lTree.extent[1] > rect.extent[1] ? @@ -248,7 +229,8 @@ ol.structs.RTree = function(opt_width) { lTree.extent[2] : rect.extent[2]); // Area of new enlarged rectangle - var lRatio = squarifiedRatio(nw, nh, lTree.nodes.length + 2); + var lRatio = ol.structs.RTree.squarifiedRatio_( + nw, nh, lTree.nodes.length + 2); if (bestChoiceIndex < 0 || Math.abs(lRatio - oldLRatio) < bestChoiceArea) { @@ -285,9 +267,9 @@ ol.structs.RTree = function(opt_width) { */ var pickNext = function(nodes, a, b) { // Area of new enlarged rectangle - var areaA = squarifiedRatio(a.extent[1] - a.extent[0], + var areaA = ol.structs.RTree.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], + var areaB = ol.structs.RTree.squarifiedRatio_(b.extent[1] - b.extent[0], b.extent[3] - b.extent[2], b.nodes.length + 1); var highAreaDelta; var highAreaNode; @@ -302,7 +284,8 @@ ol.structs.RTree = function(opt_width) { 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], + var changeNewAreaA = Math.abs(ol.structs.RTree.squarifiedRatio_( + newAreaA[1] - newAreaA[0], newAreaA[3] - newAreaA[2], a.nodes.length + 2) - areaA); var newAreaB = [ @@ -311,7 +294,7 @@ ol.structs.RTree = function(opt_width) { 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( + var changeNewAreaB = Math.abs(ol.structs.RTree.squarifiedRatio_( newAreaB[1] - newAreaB[0], newAreaB[3] - newAreaB[2], b.nodes.length + 2) - areaB); @@ -605,3 +588,26 @@ ol.structs.RTree = function(opt_width) { //End of RTree }; + + +/** + * This is Jon-Carlos Rivera's special addition to the world of r-trees. + * Every other (simple) method he found produced poor trees. + * This skews insertions to prefering squarer and emptier nodes. + * + * @param {number} l L. + * @param {number} w W. + * @param {number} fill Fill. + * @private + * @return {number} Squarified ratio. + */ +ol.structs.RTree.squarifiedRatio_ = function(l, w, fill) { + // Area of new enlarged rectangle + var peri = (l + w) / 2; // Average size of a side of the new rectangle + var area = l * w; // Area of new rectangle + // return the ratio of the perimeter to the area - the closer to 1 we are, + // the more "square" a rectangle is. conversly, when approaching zero the + // more elongated a rectangle is + var geo = area / (peri * peri); + return area * fill / geo; +}; From 476e35c27649105b492498da97a25416cd959eca Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 18:16:54 -0500 Subject: [PATCH 02/11] Move makeMBR into private static function --- src/ol/structs/rtree.js | 50 +++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 7f690d637e..66b72efe71 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -59,27 +59,6 @@ ol.structs.RTree = function(opt_width) { var rootTree = /** @type {ol.structs.RTreeNode} */ ({extent: [0, 0, 0, 0], nodes: []}); - /** - * 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 = nodes[0].extent.concat(); - - 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. * @@ -129,7 +108,7 @@ ol.structs.RTree = function(opt_width) { returnArray = tree.nodes.splice(i, 1); } // Resize MBR down... - makeMBR(tree.nodes, tree); + ol.structs.RTree.makeMBR_(tree.nodes, tree); workingObject.target = undefined; if (tree.nodes.length < minWidth) { // Underflow workingObject.nodes = /** @type {Array} */ @@ -153,7 +132,7 @@ ol.structs.RTree = function(opt_width) { // workingObject.nodes contains a list of elements removed from the // tree so far if (tree.nodes.length > 0) { - makeMBR(tree.nodes, tree); + ol.structs.RTree.makeMBR_(tree.nodes, tree); } for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t) { insertSubtree(workingObject.nodes[t], tree); @@ -175,7 +154,7 @@ ol.structs.RTree = function(opt_width) { workingObject.nodes = undefined; // Just start resizing } } else { // we are just resizing - makeMBR(tree.nodes, tree); + ol.structs.RTree.makeMBR_(tree.nodes, tree); } currentDepth -= 1; } while (hitStack.length > 0); @@ -590,6 +569,29 @@ ol.structs.RTree = function(opt_width) { }; +/** + * 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. + * @private + * @return {ol.structs.RTreeNode} Rectangle. + */ +ol.structs.RTree.makeMBR_ = function(nodes, rect) { + if (nodes.length < 1) { + return {extent: [0, 0, 0, 0]}; + } + rect.extent = nodes[0].extent.concat(); + + for (var i = nodes.length - 1; i > 0; --i) { + ol.extent.extend(rect.extent, nodes[i].extent); + } + + return rect; +}; + + /** * This is Jon-Carlos Rivera's special addition to the world of r-trees. * Every other (simple) method he found produced poor trees. From 3cdb9f130bbe8f827f95e69649d262b7ff74f407 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 18:27:38 -0500 Subject: [PATCH 03/11] Make minWidth and maxWidth private member variables --- src/ol/structs/rtree.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 66b72efe71..948a8cd77c 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -43,17 +43,26 @@ ol.structs.RTreeNode; /** - * @param {number=} opt_width Width before a node is split. Default is 6. + * @param {number=} opt_maxWidth Width before a node is split. Default is 6. * @constructor */ -ol.structs.RTree = function(opt_width) { - // Variables to control tree-dimensions - var minWidth = 3; // Minimum width of any node before a merge - var maxWidth = 6; // Maximum width of any node before a split - if (goog.isDef(opt_width)) { - minWidth = Math.floor(opt_width / 2); - maxWidth = opt_width; - } +ol.structs.RTree = function(opt_maxWidth) { + + /** + * Maximum width of any node before a split. + * @private + * @type {number} + */ + this.maxWidth_ = goog.isDef(opt_maxWidth) ? opt_maxWidth : 6; + + /** + * Minimum width of any node before a merge. + * @private + * @type {number} + */ + this.minWidth_ = Math.floor(this.maxWidth_ / 2); + + var that = this; // FIXME remove // Start with an empty root-tree var rootTree = /** @type {ol.structs.RTreeNode} */ @@ -110,7 +119,7 @@ ol.structs.RTree = function(opt_width) { // Resize MBR down... ol.structs.RTree.makeMBR_(tree.nodes, tree); workingObject.target = undefined; - if (tree.nodes.length < minWidth) { // Underflow + if (tree.nodes.length < that.minWidth_) { // Underflow workingObject.nodes = /** @type {Array} */ (searchSubtree(tree, true, [], tree)); } @@ -145,7 +154,7 @@ ol.structs.RTree = function(opt_width) { tree.nodes.length = 0; hitStack.push(tree); countStack.push(1); - } else if (hitStack.length > 0 && tree.nodes.length < minWidth) { + } else if (hitStack.length > 0 && tree.nodes.length < that.minWidth_) { // Underflow..AGAIN! workingObject.nodes = /** @type {Array} */ (searchSubtree(tree, true, workingObject.nodes, tree)); @@ -286,10 +295,10 @@ ol.structs.RTree = function(opt_width) { } } var tempNode = nodes.splice(highAreaNode, 1)[0]; - if (a.nodes.length + nodes.length + 1 <= minWidth) { + if (a.nodes.length + nodes.length + 1 <= that.minWidth_) { a.nodes.push(tempNode); ol.extent.extend(a.extent, tempNode.extent); - } else if (b.nodes.length + nodes.length + 1 <= minWidth) { + } else if (b.nodes.length + nodes.length + 1 <= that.minWidth_) { b.nodes.push(tempNode); ol.extent.extend(b.extent, tempNode.extent); } @@ -468,7 +477,7 @@ ol.structs.RTree = function(opt_width) { bc.nodes.push(workingObject); // Do Insert } - if (bc.nodes.length <= maxWidth) { // Start Resizeing Up the Tree + if (bc.nodes.length <= that.maxWidth_) { // Start Resizeing Up the Tree workingObject = {extent: bc.extent.concat()}; } else { // Otherwise Split this Node // linearSplit() returns an array containing two new nodes From 32c663cf2c3f45a15aeb5806482112485359f855 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 19:24:45 -0500 Subject: [PATCH 04/11] Move more functions into private methods --- src/ol/structs/rtree.js | 889 ++++++++++++++++++++-------------------- 1 file changed, 451 insertions(+), 438 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 948a8cd77c..1e188a2ce0 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -68,437 +68,6 @@ ol.structs.RTree = function(opt_maxWidth) { var rootTree = /** @type {ol.structs.RTreeNode} */ ({extent: [0, 0, 0, 0], nodes: []}); - /** - * Find the best specific node(s) for object to be deleted from. - * - * @param {ol.structs.RTreeNode} rect Rectangle. - * @param {Object} obj Object. - * @param {ol.structs.RTreeNode} root Root to start search. - * @return {Array} Leaf node parent. - */ - var removeSubtree = function(rect, obj, root) { - var hitStack = []; // Contains the elements that overlap - var countStack = []; // Contains the elements that overlap - var returnArray = []; - var currentDepth = 1; - - if (!rect || !ol.extent.intersects(rect.extent, root.extent)) { - return returnArray; - } - - /** @type {ol.structs.RTreeNode} */ - var workingObject = /** @type {ol.structs.RTreeNode} */ - ({extent: rect.extent.concat(), target: obj}); - - countStack.push(root.nodes.length); - hitStack.push(root); - - do { - var tree = hitStack.pop(); - var i = countStack.pop() - 1; - - if (goog.isDef(workingObject.target)) { - // We are searching for a target - while (i >= 0) { - var lTree = tree.nodes[i]; - 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.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)) { - // If we are deleting a node not a leaf... - returnArray = searchSubtree(lTree, true, [], lTree); - tree.nodes.splice(i, 1); - } else { - returnArray = tree.nodes.splice(i, 1); - } - // Resize MBR down... - ol.structs.RTree.makeMBR_(tree.nodes, tree); - workingObject.target = undefined; - if (tree.nodes.length < that.minWidth_) { // Underflow - workingObject.nodes = /** @type {Array} */ - (searchSubtree(tree, true, [], tree)); - } - break; - } else if (goog.isDef(lTree.nodes)) { - // Not a Leaf - currentDepth += 1; - countStack.push(i); - hitStack.push(tree); - tree = lTree; - i = lTree.nodes.length; - } - } - i -= 1; - } - } else if (goog.isDef(workingObject.nodes)) { - // We are unsplitting - tree.nodes.splice(i + 1, 1); // Remove unsplit node - // workingObject.nodes contains a list of elements removed from the - // tree so far - if (tree.nodes.length > 0) { - ol.structs.RTree.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) { - // Underflow..on root! - workingObject.nodes = /** @type {Array} */ - (searchSubtree(tree, true, workingObject.nodes, tree)); - tree.nodes.length = 0; - hitStack.push(tree); - countStack.push(1); - } else if (hitStack.length > 0 && tree.nodes.length < that.minWidth_) { - // Underflow..AGAIN! - workingObject.nodes = /** @type {Array} */ - (searchSubtree(tree, true, workingObject.nodes, tree)); - tree.nodes.length = 0; - } else { - workingObject.nodes = undefined; // Just start resizing - } - } else { // we are just resizing - ol.structs.RTree.makeMBR_(tree.nodes, tree); - } - currentDepth -= 1; - } while (hitStack.length > 0); - - return returnArray; - }; - - /** - * Choose the best damn node for rectangle to be inserted into. - * - * @param {ol.structs.RTreeNode} rect Rectangle. - * @param {ol.structs.RTreeNode} root Root to start search. - * @return {Array} Leaf node parent. - */ - var chooseLeafSubtree = function(rect, root) { - var bestChoiceIndex = -1; - var bestChoiceStack = []; - var bestChoiceArea; - - bestChoiceStack.push(root); - var nodes = root.nodes; - - do { - if (bestChoiceIndex != -1) { - bestChoiceStack.push(nodes[bestChoiceIndex]); - nodes = nodes[bestChoiceIndex].nodes; - bestChoiceIndex = -1; - } - - for (var i = nodes.length - 1; i >= 0; --i) { - var lTree = nodes[i]; - if (goog.isDef(lTree.leaf)) { - // Bail out of everything and start inserting - bestChoiceIndex = -1; - break; - } - // Area of new enlarged rectangle - var oldLRatio = ol.structs.RTree.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 = (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 = ol.structs.RTree.squarifiedRatio_( - nw, nh, lTree.nodes.length + 2); - - if (bestChoiceIndex < 0 || - Math.abs(lRatio - oldLRatio) < bestChoiceArea) { - bestChoiceArea = Math.abs(lRatio - oldLRatio); - bestChoiceIndex = i; - } - } - } while (bestChoiceIndex != -1); - - return bestChoiceStack; - }; - - /** - * 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); - while (nodes.length > 0) { - pickNext(nodes, n[0], n[1]); - } - return n; - }; - - /** - * 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 = ol.structs.RTree.squarifiedRatio_(a.extent[1] - a.extent[0], - a.extent[3] - a.extent[2], a.nodes.length + 1); - var areaB = ol.structs.RTree.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 = [ - 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(ol.structs.RTree.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(ol.structs.RTree.squarifiedRatio_( - newAreaB[1] - newAreaB[0], newAreaB[3] - newAreaB[2], - b.nodes.length + 2) - areaB); - - var changeNewAreaDelta = Math.abs(changeNewAreaB - changeNewAreaA); - if (!highAreaNode || !highAreaDelta || - changeNewAreaDelta < highAreaDelta) { - highAreaNode = i; - highAreaDelta = changeNewAreaDelta; - lowestGrowthGroup = changeNewAreaB < changeNewAreaA ? b : a; - } - } - var tempNode = nodes.splice(highAreaNode, 1)[0]; - if (a.nodes.length + nodes.length + 1 <= that.minWidth_) { - a.nodes.push(tempNode); - ol.extent.extend(a.extent, tempNode.extent); - } else if (b.nodes.length + nodes.length + 1 <= that.minWidth_) { - b.nodes.push(tempNode); - ol.extent.extend(b.extent, tempNode.extent); - } - else { - lowestGrowthGroup.nodes.push(tempNode); - ol.extent.extend(lowestGrowthGroup.extent, tempNode.extent); - } - }; - - /** - * 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; - var highestLowX = 0; - var lowestHighY = nodes.length - 1; - var highestLowY = 0; - var t1, t2; - - for (var i = nodes.length - 2; i >= 0; --i) { - var l = nodes[i]; - if (l.extent[0] > nodes[highestLowX].extent[0]) { - highestLowX = i; - } else if (l.extent[1] < nodes[lowestHighX].extent[2]) { - lowestHighX = i; - } - if (l.extent[2] > nodes[highestLowY].extent[2]) { - highestLowY = i; - } else if (l.extent[3] < nodes[lowestHighY].extent[3]) { - lowestHighY = i; - } - } - 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]; - t2 = nodes.splice(highestLowX, 1)[0]; - } else { - t2 = nodes.splice(highestLowX, 1)[0]; - t1 = nodes.splice(lowestHighX, 1)[0]; - } - } else { - if (lowestHighY > highestLowY) { - t1 = nodes.splice(lowestHighY, 1)[0]; - t2 = nodes.splice(highestLowY, 1)[0]; - } else { - t2 = nodes.splice(highestLowY, 1)[0]; - t1 = nodes.splice(lowestHighY, 1)[0]; - } - } - return [ - /** @type {ol.structs.RTreeNode} */ - ({extent: t1.extent.concat(), nodes: [t1]}), - /** @type {ol.structs.RTreeNode} */ - ({extent: t2.extent.concat(), nodes: [t2]}) - ]; - }; - - - /** - * Non-recursive internal search function - * - * @param {ol.structs.RTreeNode} rect Rectangle. - * @param {boolean} returnNode Do we return nodes? - * @param {Array|Object} result Result. - * @param {ol.structs.RTreeNode} root Root. - * @param {string=} opt_type Optional type to search for. - * @param {boolean=} opt_resultAsObject If set, result will be an object keyed - * by UID. - * @return {Array|Object} Result. - */ - var searchSubtree = function(rect, returnNode, result, root, opt_type, - opt_resultAsObject) { - var resultObject = {}; - var hitStack = []; // Contains the elements that overlap - - if (!ol.extent.intersects(rect.extent, root.extent)) { - return result; - } - - hitStack.push(root.nodes); - - do { - var nodes = hitStack.pop(); - - for (var i = nodes.length - 1; i >= 0; --i) { - var lTree = nodes[i]; - 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 !! - if (!returnNode) { - // TODO keep track of type on all nodes so we don't have to - // walk all the way in to the leaf to know that we don't need it - if (!goog.isDef(opt_type) || lTree.type == opt_type) { - var obj = lTree.leaf; - if (goog.isDef(opt_resultAsObject)) { - resultObject[goog.getUid(obj).toString()] = obj; - } else { - result.push(obj); - } - } - } else { - result.push(lTree); - } - } - } - } - } while (hitStack.length > 0); - - if (goog.isDef(opt_resultAsObject)) { - return resultObject; - } else { - return result; - } - }; - - /** - * 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.extent = node.extent.concat(); - root.nodes.push(node); - return; - } - - // Find the best fitting leaf node - // 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; - - // 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) { - 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) { - bc.nodes.splice(t, 1); - break; - } - } - } else { - bc = treeStack.pop(); - } - - // If there is data attached to this workingObject - var isArray = goog.isArray(workingObject); - if (goog.isDef(workingObject.leaf) || - goog.isDef(workingObject.nodes) || isArray) { - // Do Insert - if (isArray) { - for (var ai = 0, aii = workingObject.length; ai < aii; ++ai) { - ol.extent.extend(bc.extent, workingObject[ai].extent); - } - bc.nodes = bc.nodes.concat(workingObject); - } else { - ol.extent.extend(bc.extent, workingObject.extent); - bc.nodes.push(workingObject); // Do Insert - } - - if (bc.nodes.length <= that.maxWidth_) { // Start Resizeing Up the Tree - workingObject = {extent: bc.extent.concat()}; - } else { // Otherwise Split this Node - // linearSplit() returns an array containing two new nodes - // formed from the split of the previous node's overflow - var a = linearSplit(bc.nodes); - workingObject = a;//[1]; - - if (treeStack.length < 1) { // If are splitting the root.. - bc.nodes.push(a[0]); - treeStack.push(bc); // Reconsider the root element - workingObject = a[1]; - } - } - } else { // Otherwise Do Resize - //Just keep applying the new bounding rectangle to the parents.. - ol.extent.extend(bc.extent, workingObject.extent); - workingObject = ({extent: bc.extent.concat()}); - } - } while (treeStack.length > 0); - }; - /** * Non-recursive search function * @@ -509,8 +78,8 @@ ol.structs.RTree = function(opt_maxWidth) { */ this.search = function(extent, opt_type) { var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return /** @type {Array} */ (searchSubtree.apply(this, [rect, false, [], - rootTree, opt_type])); + return /** @type {Array} */ (that.searchSubtree_.apply(this, + [rect, false, [], rootTree, opt_type])); }; /** @@ -523,8 +92,8 @@ ol.structs.RTree = function(opt_maxWidth) { */ this.searchReturningObject = function(extent, opt_type) { var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return /** @type {Object} */ (searchSubtree.apply(this, [rect, false, [], - rootTree, opt_type, true])); + return /** @type {Object} */ (that.searchSubtree_.apply(this, + [rect, false, [], rootTree, opt_type, true])); }; /** @@ -550,11 +119,11 @@ ol.structs.RTree = function(opt_maxWidth) { var result = []; do { numberDeleted = result.length; - result = result.concat(removeSubtree.apply(this, arguments)); + result = result.concat(this.removeSubtree_.apply(this, arguments)); } while (numberDeleted != result.length); return result; } else { // Delete a specific item - return removeSubtree.apply(this, arguments); + return this.removeSubtree_.apply(this, arguments); } }; @@ -571,7 +140,7 @@ ol.structs.RTree = function(opt_maxWidth) { if (goog.isDef(opt_type)) { node.type = opt_type; } - insertSubtree(node, rootTree); + that.insertSubtree_(node, rootTree); }; //End of RTree @@ -622,3 +191,447 @@ ol.structs.RTree.squarifiedRatio_ = function(l, w, fill) { var geo = area / (peri * peri); return area * fill / geo; }; + + +/** + * Choose the best damn node for rectangle to be inserted into. + * + * @param {ol.structs.RTreeNode} rect Rectangle. + * @param {ol.structs.RTreeNode} root Root to start search. + * @private + * @return {Array} Leaf node parent. + */ +ol.structs.RTree.prototype.chooseLeafSubtree_ = function(rect, root) { + var bestChoiceIndex = -1; + var bestChoiceStack = []; + var bestChoiceArea; + + bestChoiceStack.push(root); + var nodes = root.nodes; + + do { + if (bestChoiceIndex != -1) { + bestChoiceStack.push(nodes[bestChoiceIndex]); + nodes = nodes[bestChoiceIndex].nodes; + bestChoiceIndex = -1; + } + + for (var i = nodes.length - 1; i >= 0; --i) { + var lTree = nodes[i]; + if (goog.isDef(lTree.leaf)) { + // Bail out of everything and start inserting + bestChoiceIndex = -1; + break; + } + // Area of new enlarged rectangle + var oldLRatio = ol.structs.RTree.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 = (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 = ol.structs.RTree.squarifiedRatio_( + nw, nh, lTree.nodes.length + 2); + + if (bestChoiceIndex < 0 || + Math.abs(lRatio - oldLRatio) < bestChoiceArea) { + bestChoiceArea = Math.abs(lRatio - oldLRatio); + bestChoiceIndex = i; + } + } + } while (bestChoiceIndex != -1); + + return bestChoiceStack; +}; + + +/** + * Non-recursive internal insert function. + * + * @param {ol.structs.RTreeNode} node Node to insert. + * @param {ol.structs.RTreeNode} root Root to begin insertion at. + * @private + */ +ol.structs.RTree.prototype.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.extent = node.extent.concat(); + root.nodes.push(node); + return; + } + + // Find the best fitting leaf node + // chooseLeaf returns an array of all tree levels (including root) + // that were traversed while trying to find the leaf + var treeStack = this.chooseLeafSubtree_(node, root); + 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) { + 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) { + bc.nodes.splice(t, 1); + break; + } + } + } else { + bc = treeStack.pop(); + } + + // If there is data attached to this workingObject + var isArray = goog.isArray(workingObject); + if (goog.isDef(workingObject.leaf) || + goog.isDef(workingObject.nodes) || isArray) { + // Do Insert + if (isArray) { + for (var ai = 0, aii = workingObject.length; ai < aii; ++ai) { + ol.extent.extend(bc.extent, workingObject[ai].extent); + } + bc.nodes = bc.nodes.concat(workingObject); + } else { + ol.extent.extend(bc.extent, workingObject.extent); + bc.nodes.push(workingObject); // Do Insert + } + + if (bc.nodes.length <= this.maxWidth_) { // Start Resizeing Up the Tree + workingObject = {extent: bc.extent.concat()}; + } else { // Otherwise Split this Node + // linearSplit_() returns an array containing two new nodes + // formed from the split of the previous node's overflow + var a = this.linearSplit_(bc.nodes); + workingObject = a;//[1]; + + if (treeStack.length < 1) { // If are splitting the root.. + bc.nodes.push(a[0]); + treeStack.push(bc); // Reconsider the root element + workingObject = a[1]; + } + } + } else { // Otherwise Do Resize + //Just keep applying the new bounding rectangle to the parents.. + ol.extent.extend(bc.extent, workingObject.extent); + workingObject = ({extent: bc.extent.concat()}); + } + } while (treeStack.length > 0); +}; + + +/** + * Pick the "best" two starter nodes to use as seeds using the "linear" + * criteria. + * + * @param {Array.} nodes Array of source nodes. + * @private + * @return {Array.} An array of two new arrays + * of nodes. + */ +ol.structs.RTree.prototype.pickLinear_ = function(nodes) { + var lowestHighX = nodes.length - 1; + var highestLowX = 0; + var lowestHighY = nodes.length - 1; + var highestLowY = 0; + var t1, t2; + + for (var i = nodes.length - 2; i >= 0; --i) { + var l = nodes[i]; + if (l.extent[0] > nodes[highestLowX].extent[0]) { + highestLowX = i; + } else if (l.extent[1] < nodes[lowestHighX].extent[2]) { + lowestHighX = i; + } + if (l.extent[2] > nodes[highestLowY].extent[2]) { + highestLowY = i; + } else if (l.extent[3] < nodes[lowestHighY].extent[3]) { + lowestHighY = i; + } + } + 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]; + t2 = nodes.splice(highestLowX, 1)[0]; + } else { + t2 = nodes.splice(highestLowX, 1)[0]; + t1 = nodes.splice(lowestHighX, 1)[0]; + } + } else { + if (lowestHighY > highestLowY) { + t1 = nodes.splice(lowestHighY, 1)[0]; + t2 = nodes.splice(highestLowY, 1)[0]; + } else { + t2 = nodes.splice(highestLowY, 1)[0]; + t1 = nodes.splice(lowestHighY, 1)[0]; + } + } + return [ + /** @type {ol.structs.RTreeNode} */ + ({extent: t1.extent.concat(), nodes: [t1]}), + /** @type {ol.structs.RTreeNode} */ + ({extent: t2.extent.concat(), nodes: [t2]}) + ]; +}; + + +/** + * 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. + * @private + */ +ol.structs.RTree.prototype.pickNext_ = function(nodes, a, b) { + // Area of new enlarged rectangle + var areaA = ol.structs.RTree.squarifiedRatio_(a.extent[1] - a.extent[0], + a.extent[3] - a.extent[2], a.nodes.length + 1); + var areaB = ol.structs.RTree.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 = [ + 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(ol.structs.RTree.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(ol.structs.RTree.squarifiedRatio_( + newAreaB[1] - newAreaB[0], newAreaB[3] - newAreaB[2], + b.nodes.length + 2) - areaB); + + var changeNewAreaDelta = Math.abs(changeNewAreaB - changeNewAreaA); + if (!highAreaNode || !highAreaDelta || + changeNewAreaDelta < highAreaDelta) { + highAreaNode = i; + highAreaDelta = changeNewAreaDelta; + lowestGrowthGroup = changeNewAreaB < changeNewAreaA ? b : a; + } + } + var tempNode = nodes.splice(highAreaNode, 1)[0]; + if (a.nodes.length + nodes.length + 1 <= this.minWidth_) { + a.nodes.push(tempNode); + ol.extent.extend(a.extent, tempNode.extent); + } else if (b.nodes.length + nodes.length + 1 <= this.minWidth_) { + b.nodes.push(tempNode); + ol.extent.extend(b.extent, tempNode.extent); + } + else { + lowestGrowthGroup.nodes.push(tempNode); + ol.extent.extend(lowestGrowthGroup.extent, tempNode.extent); + } +}; + + +/** + * Find the best specific node(s) for object to be deleted from. + * + * @param {ol.structs.RTreeNode} rect Rectangle. + * @param {Object} obj Object. + * @param {ol.structs.RTreeNode} root Root to start search. + * @private + * @return {Array} Leaf node parent. + */ +ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { + var hitStack = []; // Contains the elements that overlap + var countStack = []; // Contains the elements that overlap + var returnArray = []; + var currentDepth = 1; + + if (!rect || !ol.extent.intersects(rect.extent, root.extent)) { + return returnArray; + } + + /** @type {ol.structs.RTreeNode} */ + var workingObject = /** @type {ol.structs.RTreeNode} */ + ({extent: rect.extent.concat(), target: obj}); + + countStack.push(root.nodes.length); + hitStack.push(root); + + do { + var tree = hitStack.pop(); + var i = countStack.pop() - 1; + + if (goog.isDef(workingObject.target)) { + // We are searching for a target + while (i >= 0) { + var lTree = tree.nodes[i]; + 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.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)) { + // If we are deleting a node not a leaf... + returnArray = this.searchSubtree_(lTree, true, [], lTree); + tree.nodes.splice(i, 1); + } else { + returnArray = tree.nodes.splice(i, 1); + } + // Resize MBR down... + ol.structs.RTree.makeMBR_(tree.nodes, tree); + workingObject.target = undefined; + if (tree.nodes.length < this.minWidth_) { // Underflow + workingObject.nodes = /** @type {Array} */ + (this.searchSubtree_(tree, true, [], tree)); + } + break; + } else if (goog.isDef(lTree.nodes)) { + // Not a Leaf + currentDepth += 1; + countStack.push(i); + hitStack.push(tree); + tree = lTree; + i = lTree.nodes.length; + } + } + i -= 1; + } + } else if (goog.isDef(workingObject.nodes)) { + // We are unsplitting + tree.nodes.splice(i + 1, 1); // Remove unsplit node + // workingObject.nodes contains a list of elements removed from the + // tree so far + if (tree.nodes.length > 0) { + ol.structs.RTree.makeMBR_(tree.nodes, tree); + } + for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t) { + this.insertSubtree_(workingObject.nodes[t], tree); + } + workingObject.nodes.length = 0; + if (hitStack.length === 0 && tree.nodes.length <= 1) { + // Underflow..on root! + workingObject.nodes = /** @type {Array} */ + (this.searchSubtree_(tree, true, workingObject.nodes, tree)); + tree.nodes.length = 0; + hitStack.push(tree); + countStack.push(1); + } else if (hitStack.length > 0 && tree.nodes.length < this.minWidth_) { + // Underflow..AGAIN! + workingObject.nodes = /** @type {Array} */ + (this.searchSubtree_(tree, true, workingObject.nodes, tree)); + tree.nodes.length = 0; + } else { + workingObject.nodes = undefined; // Just start resizing + } + } else { // we are just resizing + ol.structs.RTree.makeMBR_(tree.nodes, tree); + } + currentDepth -= 1; + } while (hitStack.length > 0); + + return returnArray; +}; + + +/** + * Split a set of nodes into two roughly equally-filled nodes. + * + * @param {Array.} nodes Array of nodes. + * @private + * @return {Array.>} An array of two new arrays + * of nodes. + */ +ol.structs.RTree.prototype.linearSplit_ = function(nodes) { + var n = this.pickLinear_(nodes); + while (nodes.length > 0) { + this.pickNext_(nodes, n[0], n[1]); + } + return n; +}; + + +/** + * Non-recursive internal search function + * + * @param {ol.structs.RTreeNode} rect Rectangle. + * @param {boolean} returnNode Do we return nodes? + * @param {Array|Object} result Result. + * @param {ol.structs.RTreeNode} root Root. + * @param {string=} opt_type Optional type to search for. + * @param {boolean=} opt_resultAsObject If set, result will be an object keyed + * by UID. + * @private + * @return {Array|Object} Result. + */ +ol.structs.RTree.prototype.searchSubtree_ = function( + rect, returnNode, result, root, opt_type, opt_resultAsObject) { + var resultObject = {}; + var hitStack = []; // Contains the elements that overlap + + if (!ol.extent.intersects(rect.extent, root.extent)) { + return result; + } + + hitStack.push(root.nodes); + + do { + var nodes = hitStack.pop(); + + for (var i = nodes.length - 1; i >= 0; --i) { + var lTree = nodes[i]; + 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 !! + if (!returnNode) { + // TODO keep track of type on all nodes so we don't have to + // walk all the way in to the leaf to know that we don't need it + if (!goog.isDef(opt_type) || lTree.type == opt_type) { + var obj = lTree.leaf; + if (goog.isDef(opt_resultAsObject)) { + resultObject[goog.getUid(obj).toString()] = obj; + } else { + result.push(obj); + } + } + } else { + result.push(lTree); + } + } + } + } + } while (hitStack.length > 0); + + if (goog.isDef(opt_resultAsObject)) { + return resultObject; + } else { + return result; + } +}; From ea1ee73392f30b44274587f0d1caa87a1cf4169d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 19:33:46 -0500 Subject: [PATCH 05/11] Move search and searchReturningObject into methods --- src/ol/structs/rtree.js | 72 ++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 1e188a2ce0..ec8c761633 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -62,39 +62,15 @@ ol.structs.RTree = function(opt_maxWidth) { */ this.minWidth_ = Math.floor(this.maxWidth_ / 2); - var that = this; // FIXME remove - - // Start with an empty root-tree - var rootTree = /** @type {ol.structs.RTreeNode} */ + /** + * Start with an empty root-tree. + * @private + * @type {ol.structs.RTreeNode} + */ + this.rootTree_ = /** @type {ol.structs.RTreeNode} */ ({extent: [0, 0, 0, 0], nodes: []}); - /** - * Non-recursive search function - * - * @param {ol.Extent} extent Extent. - * @param {string=} opt_type Optional type of the objects we want to find. - * @return {Array} Result. - * @this {ol.structs.RTree} - */ - this.search = function(extent, opt_type) { - var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return /** @type {Array} */ (that.searchSubtree_.apply(this, - [rect, false, [], rootTree, opt_type])); - }; - - /** - * Non-recursive search function - * - * @param {ol.Extent} extent Extent. - * @param {string=} opt_type Optional type of the objects we want to find. - * @return {Object} Result. Keys are UIDs of the values. - * @this {ol.structs.RTree} - */ - this.searchReturningObject = function(extent, opt_type) { - var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return /** @type {Object} */ (that.searchSubtree_.apply(this, - [rect, false, [], rootTree, opt_type, true])); - }; + var that = this; // FIXME remove /** * Non-recursive function that deletes a specific region. @@ -110,7 +86,7 @@ ol.structs.RTree = function(opt_maxWidth) { case 1: arguments[1] = false; // opt_obj == false for conditionals case 2: - arguments[2] = rootTree; // Add root node to end of argument list + arguments[2] = that.rootTree_; // Add root node to end of argument list default: arguments.length = 3; } @@ -140,7 +116,7 @@ ol.structs.RTree = function(opt_maxWidth) { if (goog.isDef(opt_type)) { node.type = opt_type; } - that.insertSubtree_(node, rootTree); + that.insertSubtree_(node, that.rootTree_); }; //End of RTree @@ -577,6 +553,36 @@ ol.structs.RTree.prototype.linearSplit_ = function(nodes) { }; +/** + * Non-recursive search function + * + * @param {ol.Extent} extent Extent. + * @param {string=} opt_type Optional type of the objects we want to find. + * @return {Array} Result. + * @this {ol.structs.RTree} + */ +ol.structs.RTree.prototype.search = function(extent, opt_type) { + var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); + return /** @type {Array} */ ( + this.searchSubtree_(rect, false, [], this.rootTree_, opt_type)); +}; + + +/** + * Non-recursive search function + * + * @param {ol.Extent} extent Extent. + * @param {string=} opt_type Optional type of the objects we want to find. + * @return {Object} Result. Keys are UIDs of the values. + * @this {ol.structs.RTree} + */ +ol.structs.RTree.prototype.searchReturningObject = function(extent, opt_type) { + var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); + return /** @type {Object} */ ( + this.searchSubtree_(rect, false, [], this.rootTree_, opt_type, true)); +}; + + /** * Non-recursive internal search function * From a871f29e75a6ce8120ca29517900d36f0d051150 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 19:35:59 -0500 Subject: [PATCH 06/11] Move remove into method --- src/ol/structs/rtree.js | 63 +++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index ec8c761633..d172de6d8a 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -72,37 +72,6 @@ ol.structs.RTree = function(opt_maxWidth) { var that = this; // FIXME remove - /** - * Non-recursive function that deletes a specific region. - * - * @param {ol.Extent} extent Extent. - * @param {Object=} opt_obj Object. - * @return {Array} Result. - * @this {ol.structs.RTree} - */ - this.remove = function(extent, opt_obj) { - arguments[0] = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - switch (arguments.length) { - case 1: - arguments[1] = false; // opt_obj == false for conditionals - case 2: - arguments[2] = that.rootTree_; // Add root node to end of argument list - default: - arguments.length = 3; - } - if (arguments[1] === false) { // Do area-wide † - var numberDeleted = 0; - var result = []; - do { - numberDeleted = result.length; - result = result.concat(this.removeSubtree_.apply(this, arguments)); - } while (numberDeleted != result.length); - return result; - } else { // Delete a specific item - return this.removeSubtree_.apply(this, arguments); - } - }; - /** * Non-recursive insert function. * @@ -553,6 +522,38 @@ ol.structs.RTree.prototype.linearSplit_ = function(nodes) { }; +/** + * Non-recursive function that deletes a specific region. + * + * @param {ol.Extent} extent Extent. + * @param {Object=} opt_obj Object. + * @return {Array} Result. + * @this {ol.structs.RTree} + */ +ol.structs.RTree.prototype.remove = function(extent, opt_obj) { + arguments[0] = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); + switch (arguments.length) { + case 1: + arguments[1] = false; // opt_obj == false for conditionals + case 2: + arguments[2] = this.rootTree_; // Add root node to end of argument list + default: + arguments.length = 3; + } + if (arguments[1] === false) { // Do area-wide † + var numberDeleted = 0; + var result = []; + do { + numberDeleted = result.length; + result = result.concat(this.removeSubtree_.apply(this, arguments)); + } while (numberDeleted != result.length); + return result; + } else { // Delete a specific item + return this.removeSubtree_.apply(this, arguments); + } +}; + + /** * Non-recursive search function * From aaa4fec14190359bbd026354581b8296d47f356a Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 19:39:51 -0500 Subject: [PATCH 07/11] Move insert into method --- src/ol/structs/rtree.js | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index d172de6d8a..a915adedb3 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -70,24 +70,6 @@ ol.structs.RTree = function(opt_maxWidth) { this.rootTree_ = /** @type {ol.structs.RTreeNode} */ ({extent: [0, 0, 0, 0], nodes: []}); - var that = this; // FIXME remove - - /** - * Non-recursive insert function. - * - * @param {ol.Extent} extent Extent. - * @param {Object} obj Object to insert. - * @param {string=} opt_type Optional type to store along with the object. - */ - this.insert = function(extent, obj, opt_type) { - var node = /** @type {ol.structs.RTreeNode} */ - ({extent: extent, leaf: obj}); - if (goog.isDef(opt_type)) { - node.type = opt_type; - } - that.insertSubtree_(node, that.rootTree_); - }; - //End of RTree }; @@ -200,6 +182,23 @@ ol.structs.RTree.prototype.chooseLeafSubtree_ = function(rect, root) { }; +/** + * Non-recursive insert function. + * + * @param {ol.Extent} extent Extent. + * @param {Object} obj Object to insert. + * @param {string=} opt_type Optional type to store along with the object. + */ +ol.structs.RTree.prototype.insert = function(extent, obj, opt_type) { + var node = /** @type {ol.structs.RTreeNode} */ + ({extent: extent, leaf: obj}); + if (goog.isDef(opt_type)) { + node.type = opt_type; + } + this.insertSubtree_(node, this.rootTree_); +}; + + /** * Non-recursive internal insert function. * From d80ded31eeb083dfc5fe88b151b3dfa0bafdfc59 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 19:46:40 -0500 Subject: [PATCH 08/11] Clean up some comments --- src/ol/structs/rtree.js | 63 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index a915adedb3..481b96a0c5 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -1,30 +1,28 @@ -/****************************************************************************** - rtree.js - General-Purpose Non-Recursive Javascript R-Tree Library - Version 0.6.2, December 5st 2009 - - Copyright (c) 2009 Jon-Carlos Rivera - - 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. - - Jon-Carlos Rivera - imbcmdth@hotmail.com -******************************************************************************/ +// rtree.js - General-Purpose Non-Recursive Javascript R-Tree Library +// Version 0.6.2, December 5st 2009 +// +// Copyright (c) 2009 Jon-Carlos Rivera +// +// 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. +// +// Jon-Carlos Rivera - imbcmdth@hotmail.com goog.provide('ol.structs.RTree'); @@ -34,17 +32,19 @@ goog.require('ol.extent'); /** - * @typedef {{extent: ol.Extent, leaf: (Object|undefined), - * nodes: (Array.|undefined), - * target: (Object|undefined), type: (string|undefined)}} + * @typedef {{extent: ol.Extent, + * leaf: (Object|undefined), + * nodes: (Array.|undefined), + * target: (Object|undefined), + * type: (string|undefined)}} */ ol.structs.RTreeNode; /** - * @param {number=} opt_maxWidth Width before a node is split. Default is 6. * @constructor + * @param {number=} opt_maxWidth Width before a node is split. Default is 6. */ ol.structs.RTree = function(opt_maxWidth) { @@ -70,7 +70,6 @@ ol.structs.RTree = function(opt_maxWidth) { this.rootTree_ = /** @type {ol.structs.RTreeNode} */ ({extent: [0, 0, 0, 0], nodes: []}); - //End of RTree }; From 18b17895120ec6f6df4599e9b13f6b7e5e5849ef Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 19:50:44 -0500 Subject: [PATCH 09/11] Sort methods alphabetically --- src/ol/structs/rtree.js | 100 ++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 481b96a0c5..902b298099 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -120,7 +120,7 @@ ol.structs.RTree.squarifiedRatio_ = function(l, w, fill) { /** - * Choose the best damn node for rectangle to be inserted into. + * Choose the best for rectangle to be inserted into. * * @param {ol.structs.RTreeNode} rect Rectangle. * @param {ol.structs.RTreeNode} root Root to start search. @@ -275,6 +275,23 @@ ol.structs.RTree.prototype.insertSubtree_ = function(node, root) { }; +/** + * Split a set of nodes into two roughly equally-filled nodes. + * + * @param {Array.} nodes Array of nodes. + * @private + * @return {Array.>} An array of two new arrays + * of nodes. + */ +ol.structs.RTree.prototype.linearSplit_ = function(nodes) { + var n = this.pickLinear_(nodes); + while (nodes.length > 0) { + this.pickNext_(nodes, n[0], n[1]); + } + return n; +}; + + /** * Pick the "best" two starter nodes to use as seeds using the "linear" * criteria. @@ -398,6 +415,38 @@ ol.structs.RTree.prototype.pickNext_ = function(nodes, a, b) { }; +/** + * Non-recursive function that deletes a specific region. + * + * @param {ol.Extent} extent Extent. + * @param {Object=} opt_obj Object. + * @return {Array} Result. + * @this {ol.structs.RTree} + */ +ol.structs.RTree.prototype.remove = function(extent, opt_obj) { + arguments[0] = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); + switch (arguments.length) { + case 1: + arguments[1] = false; // opt_obj == false for conditionals + case 2: + arguments[2] = this.rootTree_; // Add root node to end of argument list + default: + arguments.length = 3; + } + if (arguments[1] === false) { // Do area-wide † + var numberDeleted = 0; + var result = []; + do { + numberDeleted = result.length; + result = result.concat(this.removeSubtree_.apply(this, arguments)); + } while (numberDeleted != result.length); + return result; + } else { // Delete a specific item + return this.removeSubtree_.apply(this, arguments); + } +}; + + /** * Find the best specific node(s) for object to be deleted from. * @@ -503,55 +552,6 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { }; -/** - * Split a set of nodes into two roughly equally-filled nodes. - * - * @param {Array.} nodes Array of nodes. - * @private - * @return {Array.>} An array of two new arrays - * of nodes. - */ -ol.structs.RTree.prototype.linearSplit_ = function(nodes) { - var n = this.pickLinear_(nodes); - while (nodes.length > 0) { - this.pickNext_(nodes, n[0], n[1]); - } - return n; -}; - - -/** - * Non-recursive function that deletes a specific region. - * - * @param {ol.Extent} extent Extent. - * @param {Object=} opt_obj Object. - * @return {Array} Result. - * @this {ol.structs.RTree} - */ -ol.structs.RTree.prototype.remove = function(extent, opt_obj) { - arguments[0] = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - switch (arguments.length) { - case 1: - arguments[1] = false; // opt_obj == false for conditionals - case 2: - arguments[2] = this.rootTree_; // Add root node to end of argument list - default: - arguments.length = 3; - } - if (arguments[1] === false) { // Do area-wide † - var numberDeleted = 0; - var result = []; - do { - numberDeleted = result.length; - result = result.concat(this.removeSubtree_.apply(this, arguments)); - } while (numberDeleted != result.length); - return result; - } else { // Delete a specific item - return this.removeSubtree_.apply(this, arguments); - } -}; - - /** * Non-recursive search function * From f0baf2efc2dcf3f2aafc050135e23669214808f2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 20:38:11 -0500 Subject: [PATCH 10/11] Add ol.extent.empty --- src/ol/extent.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index dc937c2df2..745247579f 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -111,6 +111,18 @@ ol.extent.createOrUpdate = function(minX, maxX, minY, maxY, extent) { }; +/** + * Empties extent in place. + * @param {ol.Extent} extent Extent. + * @return {ol.Extent} Extent. + */ +ol.extent.empty = function(extent) { + extent[0] = extent[2] = Infinity; + extent[1] = extent[3] = -Infinity; + return extent; +}; + + /** * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent 2. From a162b6fab3673f4ee23e12e059ec5d099968abe0 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 25 May 2013 20:39:55 -0500 Subject: [PATCH 11/11] Use empty extents and refactor for clarity --- src/ol/structs/rtree.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 902b298099..41af87fcf3 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -68,31 +68,31 @@ ol.structs.RTree = function(opt_maxWidth) { * @type {ol.structs.RTreeNode} */ this.rootTree_ = /** @type {ol.structs.RTreeNode} */ - ({extent: [0, 0, 0, 0], nodes: []}); + ({extent: ol.extent.createEmpty(), nodes: []}); }; /** - * 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. + * @param {ol.structs.RTreeNode} node Node. * @private - * @return {ol.structs.RTreeNode} Rectangle. */ -ol.structs.RTree.makeMBR_ = function(nodes, rect) { - if (nodes.length < 1) { - return {extent: [0, 0, 0, 0]}; +ol.structs.RTree.recalculateExtent_ = function(node) { + var n = node.nodes.length; + var extent = node.extent; + if (n === 0) { + ol.extent.empty(extent); + } else { + var firstNodeExtent = node.nodes[0].extent; + extent[0] = firstNodeExtent[0]; + extent[1] = firstNodeExtent[1]; + extent[2] = firstNodeExtent[2]; + extent[3] = firstNodeExtent[3]; + var i; + for (i = 1; i < n; ++i) { + ol.extent.extend(extent, node.nodes[i].extent); + } } - rect.extent = nodes[0].extent.concat(); - - for (var i = nodes.length - 1; i > 0; --i) { - ol.extent.extend(rect.extent, nodes[i].extent); - } - - return rect; }; @@ -497,7 +497,7 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { returnArray = tree.nodes.splice(i, 1); } // Resize MBR down... - ol.structs.RTree.makeMBR_(tree.nodes, tree); + ol.structs.RTree.recalculateExtent_(tree); workingObject.target = undefined; if (tree.nodes.length < this.minWidth_) { // Underflow workingObject.nodes = /** @type {Array} */ @@ -521,7 +521,7 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { // workingObject.nodes contains a list of elements removed from the // tree so far if (tree.nodes.length > 0) { - ol.structs.RTree.makeMBR_(tree.nodes, tree); + ol.structs.RTree.recalculateExtent_(tree); } for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t) { this.insertSubtree_(workingObject.nodes[t], tree); @@ -543,7 +543,7 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { workingObject.nodes = undefined; // Just start resizing } } else { // we are just resizing - ol.structs.RTree.makeMBR_(tree.nodes, tree); + ol.structs.RTree.recalculateExtent_(tree); } currentDepth -= 1; } while (hitStack.length > 0);