Move more functions into private methods
This commit is contained in:
@@ -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.<ol.structs.RTreeNode>} nodes Array of nodes.
|
||||
* @return {Array.<Array.<ol.structs.RTreeNode>>} 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.<ol.structs.RTreeNode>} 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.<ol.structs.RTreeNode>} nodes Array of source nodes.
|
||||
* @return {Array.<ol.structs.RTreeNode>} 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.<ol.structs.RTreeNode>} nodes Array of source nodes.
|
||||
* @private
|
||||
* @return {Array.<ol.structs.RTreeNode>} 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.<ol.structs.RTreeNode>} 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.<ol.structs.RTreeNode>} nodes Array of nodes.
|
||||
* @private
|
||||
* @return {Array.<Array.<ol.structs.RTreeNode>>} 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;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user