From e00a9cf06797336f1d5deb385889aba3b082a40e Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 29 Aug 2014 16:55:30 -0700 Subject: [PATCH] Update to rbush 1.3.3 --- js/lib/rbush.js | 382 +++++++++++++++++++++++++----------------------- 1 file changed, 197 insertions(+), 185 deletions(-) diff --git a/js/lib/rbush.js b/js/lib/rbush.js index 794ec9255..19b841a8a 100644 --- a/js/lib/rbush.js +++ b/js/lib/rbush.js @@ -9,7 +9,7 @@ function rbush(maxEntries, format) { // jshint newcap: false, validthis: true - if (!(this instanceof rbush)) { return new rbush(maxEntries, format); } + if (!(this instanceof rbush)) return new rbush(maxEntries, format); // max entries in a node is 9 by default; min node fill is 40% for best performance this._maxEntries = Math.max(4, maxEntries || 9); @@ -31,32 +31,26 @@ rbush.prototype = { search: function (bbox) { var node = this.data, - result = []; + result = [], + toBBox = this.toBBox; - if (!this._intersects(bbox, node.bbox)) { return result; } + if (!intersects(bbox, node.bbox)) return result; var nodesToSearch = [], i, len, child, childBBox; while (node) { for (i = 0, len = node.children.length; i < len; i++) { + child = node.children[i]; - childBBox = node.leaf ? this.toBBox(child) : child.bbox; + childBBox = node.leaf ? toBBox(child) : child.bbox; - if (this._intersects(bbox, childBBox)) { - - if (node.leaf) { - result.push(child); - - } else if (this._contains(bbox, childBBox)) { - this._all(child, result); - - } else { - nodesToSearch.push(child); - } + if (intersects(bbox, childBBox)) { + if (node.leaf) result.push(child); + else if (contains(bbox, childBBox)) this._all(child, result); + else nodesToSearch.push(child); } } - node = nodesToSearch.pop(); } @@ -64,7 +58,7 @@ rbush.prototype = { }, load: function (data) { - if (!(data && data.length)) { return this; } + if (!(data && data.length)) return this; if (data.length < this._minEntries) { for (var i = 0, len = data.length; i < len; i++) { @@ -74,7 +68,7 @@ rbush.prototype = { } // recursively build the tree with the given data from stratch using OMT algorithm - var node = this._build(data.slice(), 0); + var node = this._build(data.slice(), 0, data.length - 1, 0); if (!this.data.children.length) { // save as is if tree is empty @@ -100,24 +94,22 @@ rbush.prototype = { }, insert: function (item) { - if (item) { - this._insert(item, this.data.height - 1); - } + if (item) this._insert(item, this.data.height - 1); return this; }, clear: function () { this.data = { children: [], - leaf: true, - bbox: this._empty(), - height: 1 + height: 1, + bbox: empty(), + leaf: true }; return this; }, remove: function (item) { - if (!item) { return this; } + if (!item) return this; var node = this.data, bbox = this.toBBox(item), @@ -147,7 +139,7 @@ rbush.prototype = { } } - if (!goingUp && !node.leaf && this._intersects(bbox, node.bbox)) { // go down + if (!goingUp && !node.leaf && contains(node.bbox, bbox)) { // go down path.push(node); indexes.push(i); i = 0; @@ -159,9 +151,7 @@ rbush.prototype = { node = parent.children[i]; goingUp = false; - } else { // nothing found - node = null; - } + } else node = null; // nothing found } return this; @@ -182,29 +172,28 @@ rbush.prototype = { _all: function (node, result) { var nodesToSearch = []; while (node) { - if (node.leaf) { - result.push.apply(result, node.children); - } else { - nodesToSearch.push.apply(nodesToSearch, node.children); - } + if (node.leaf) result.push.apply(result, node.children); + else nodesToSearch.push.apply(nodesToSearch, node.children); + node = nodesToSearch.pop(); } return result; }, - _build: function (items, level, height) { + _build: function (items, left, right, level, height) { - var N = items.length, + var N = right - left + 1, M = this._maxEntries, node; if (N <= M) { node = { - children: items, - leaf: true, - height: 1 + children: items.slice(left, right + 1), + height: 1, + bbox: null, + leaf: true }; - this._calcBBox(node); + calcBBox(node, this.toBBox); return node; } @@ -214,34 +203,37 @@ rbush.prototype = { // target number of root entries to maximize storage utilization M = Math.ceil(N / Math.pow(M, height - 1)); - - items.sort(this.compareMinX); } // TODO eliminate recursion? node = { children: [], - height: height + height: height, + bbox: null }; - var N1 = Math.ceil(N / M) * Math.ceil(Math.sqrt(M)), - N2 = Math.ceil(N / M), - compare = level % 2 === 1 ? this.compareMinX : this.compareMinY, - i, j, slice, sliceLen, childNode; + var N2 = Math.ceil(N / M), + N1 = N2 * Math.ceil(Math.sqrt(M)), + i, j, right2, childNode; // split the items into M mostly square tiles - for (i = 0; i < N; i += N1) { - slice = items.slice(i, i + N1).sort(compare); + for (i = left; i <= right; i += N1) { + + if (i + N1 <= right) partitionSort(items, i, right, i + N1, this.compareMinX); + right2 = Math.min(i + N1 - 1, right); + + for (j = i; j <= right2; j += N2) { + + if (j + N2 <= right2) partitionSort(items, j, right2, j + N2, this.compareMinY); - for (j = 0, sliceLen = slice.length; j < sliceLen; j += N2) { // pack each entry recursively - childNode = this._build(slice.slice(j, j + N2), level + 1, height - 1); + childNode = this._build(items, j, Math.min(j + N2 - 1, right2), level + 1, height - 1); node.children.push(childNode); } } - this._calcBBox(node); + calcBBox(node, this.toBBox); return node; }, @@ -253,14 +245,14 @@ rbush.prototype = { while (true) { path.push(node); - if (node.leaf || path.length - 1 === level) { break; } + if (node.leaf || path.length - 1 === level) break; minArea = minEnlargement = Infinity; for (i = 0, len = node.children.length; i < len; i++) { child = node.children[i]; - area = this._area(child.bbox); - enlargement = this._enlargedArea(bbox, child.bbox) - area; + area = bboxArea(child.bbox); + enlargement = enlargedArea(bbox, child.bbox) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { @@ -283,28 +275,26 @@ rbush.prototype = { return node; }, - _insert: function (item, level, isNode, root) { + _insert: function (item, level, isNode) { - var bbox = isNode ? item.bbox : this.toBBox(item), + var toBBox = this.toBBox, + bbox = isNode ? item.bbox : toBBox(item), insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too - var node = this._chooseSubtree(bbox, root || this.data, level, insertPath), - splitOccured; + var node = this._chooseSubtree(bbox, this.data, level, insertPath); // put the item into the node node.children.push(item); - this._extend(node.bbox, bbox); + extend(node.bbox, bbox); // split on node overflow; propagate upwards if necessary - do { - splitOccured = false; + while (level >= 0) { if (insertPath[level].children.length > this._maxEntries) { this._split(insertPath, level); - splitOccured = true; level--; - } - } while (level >= 0 && splitOccured); + } else break; + } // adjust bboxes along the insertion path this._adjustParentBBoxes(bbox, insertPath, level); @@ -324,26 +314,22 @@ rbush.prototype = { height: node.height }; - if (node.leaf) { - newNode.leaf = true; - } + if (node.leaf) newNode.leaf = true; - this._calcBBox(node); - this._calcBBox(newNode); + calcBBox(node, this.toBBox); + calcBBox(newNode, this.toBBox); - if (level) { - insertPath[level - 1].children.push(newNode); - } else { - this._splitRoot(node, newNode); - } + if (level) insertPath[level - 1].children.push(newNode); + else this._splitRoot(node, newNode); }, _splitRoot: function (node, newNode) { // split root node - this.data = {}; - this.data.children = [node, newNode]; - this.data.height = node.height + 1; - this._calcBBox(this.data); + this.data = { + children: [node, newNode], + height: node.height + 1 + }; + calcBBox(this.data, this.toBBox); }, _chooseSplitIndex: function (node, m, M) { @@ -353,11 +339,11 @@ rbush.prototype = { minOverlap = minArea = Infinity; for (i = m; i <= M - m; i++) { - bbox1 = this._distBBox(node, 0, i); - bbox2 = this._distBBox(node, i, M); + bbox1 = distBBox(node, 0, i, this.toBBox); + bbox2 = distBBox(node, i, M, this.toBBox); - overlap = this._intersectionArea(bbox1, bbox2); - area = this._area(bbox1) + this._area(bbox2); + overlap = intersectionArea(bbox1, bbox2); + area = bboxArea(bbox1) + bboxArea(bbox2); // choose distribution with minimum overlap if (overlap < minOverlap) { @@ -381,17 +367,14 @@ rbush.prototype = { // sorts node children by the best axis for split _chooseSplitAxis: function (node, m, M) { - var compareMinX = node.leaf ? this.compareMinX : this._compareNodeMinX, - compareMinY = node.leaf ? this.compareMinY : this._compareNodeMinY, + var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : compareNodeMinY, xMargin = this._allDistMargin(node, m, M, compareMinX), yMargin = this._allDistMargin(node, m, M, compareMinY); // if total distributions margin value is minimal for x, sort by minX, // otherwise it's already sorted by minY - - if (xMargin < yMargin) { - node.children.sort(compareMinX); - } + if (xMargin < yMargin) node.children.sort(compareMinX); }, // total margin of all possible split distributions where each node is at least m full @@ -399,116 +382,48 @@ rbush.prototype = { node.children.sort(compare); - var leftBBox = this._distBBox(node, 0, m), - rightBBox = this._distBBox(node, M - m, M), - margin = this._margin(leftBBox) + this._margin(rightBBox), + var toBBox = this.toBBox, + leftBBox = distBBox(node, 0, m, toBBox), + rightBBox = distBBox(node, M - m, M, toBBox), + margin = bboxMargin(leftBBox) + bboxMargin(rightBBox), i, child; for (i = m; i < M - m; i++) { child = node.children[i]; - this._extend(leftBBox, node.leaf ? this.toBBox(child) : child.bbox); - margin += this._margin(leftBBox); + extend(leftBBox, node.leaf ? toBBox(child) : child.bbox); + margin += bboxMargin(leftBBox); } - for (i = M - m - 1; i >= 0; i--) { + for (i = M - m - 1; i >= m; i--) { child = node.children[i]; - this._extend(rightBBox, node.leaf ? this.toBBox(child) : child.bbox); - margin += this._margin(rightBBox); + extend(rightBBox, node.leaf ? toBBox(child) : child.bbox); + margin += bboxMargin(rightBBox); } return margin; }, - // min bounding rectangle of node children from k to p-1 - _distBBox: function (node, k, p) { - var bbox = this._empty(); - - for (var i = k, child; i < p; i++) { - child = node.children[i]; - this._extend(bbox, node.leaf ? this.toBBox(child) : child.bbox); - } - - return bbox; - }, - - // calculate node's bbox from bboxes of its children - _calcBBox: function (node) { - node.bbox = this._empty(); - - for (var i = 0, len = node.children.length, child; i < len; i++) { - child = node.children[i]; - this._extend(node.bbox, node.leaf ? this.toBBox(child) : child.bbox); - } - }, - _adjustParentBBoxes: function (bbox, path, level) { // adjust bboxes along the given tree path for (var i = level; i >= 0; i--) { - this._extend(path[i].bbox, bbox); + extend(path[i].bbox, bbox); } }, _condense: function (path) { // go through the path, removing empty nodes and updating bboxes - for (var i = path.length - 1, parent; i >= 0; i--) { + for (var i = path.length - 1, siblings; i >= 0; i--) { if (path[i].children.length === 0) { if (i > 0) { - parent = path[i - 1].children; - parent.splice(parent.indexOf(path[i]), 1); - } else { - this.clear(); - } - } else { - this._calcBBox(path[i]); - } + siblings = path[i - 1].children; + siblings.splice(siblings.indexOf(path[i]), 1); + + } else this.clear(); + + } else calcBBox(path[i], this.toBBox); } }, - _contains: function(a, b) { - return a[0] <= b[0] && - a[1] <= b[1] && - b[2] <= a[2] && - b[3] <= a[3]; - }, - - _intersects: function (a, b) { - return b[0] <= a[2] && - b[1] <= a[3] && - b[2] >= a[0] && - b[3] >= a[1]; - }, - - _extend: function (a, b) { - a[0] = Math.min(a[0], b[0]); - a[1] = Math.min(a[1], b[1]); - a[2] = Math.max(a[2], b[2]); - a[3] = Math.max(a[3], b[3]); - return a; - }, - - _area: function (a) { return (a[2] - a[0]) * (a[3] - a[1]); }, - _margin: function (a) { return (a[2] - a[0]) + (a[3] - a[1]); }, - - _enlargedArea: function (a, b) { - return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * - (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); - }, - - _intersectionArea: function (a, b) { - var minX = Math.max(a[0], b[0]), - minY = Math.max(a[1], b[1]), - maxX = Math.min(a[2], b[2]), - maxY = Math.min(a[3], b[3]); - - return Math.max(0, maxX - minX) * - Math.max(0, maxY - minY); - }, - - _empty: function () { return [Infinity, Infinity, -Infinity, -Infinity]; }, - - _compareNodeMinX: function (a, b) { return a.bbox[0] - b.bbox[0]; }, - _compareNodeMinY: function (a, b) { return a.bbox[1] - b.bbox[1]; }, - _initFormat: function (format) { // data format (minX, minY, maxX, maxY accessors) @@ -527,16 +442,113 @@ rbush.prototype = { } }; -if (typeof define === 'function' && define.amd) { - define(function() { - return rbush; - }); -} else if (typeof module !== 'undefined') { - module.exports = rbush; -} else if (typeof self !== 'undefined') { - self.rbush = rbush; -} else { - window.rbush = rbush; +// calculate node's bbox from bboxes of its children +function calcBBox(node, toBBox) { + node.bbox = distBBox(node, 0, node.children.length, toBBox); } -})(); +// min bounding rectangle of node children from k to p-1 +function distBBox(node, k, p, toBBox) { + var bbox = empty(); + + for (var i = k, child; i < p; i++) { + child = node.children[i]; + extend(bbox, node.leaf ? toBBox(child) : child.bbox); + } + + return bbox; +} + + +function empty() { return [Infinity, Infinity, -Infinity, -Infinity]; } + +function extend(a, b) { + a[0] = Math.min(a[0], b[0]); + a[1] = Math.min(a[1], b[1]); + a[2] = Math.max(a[2], b[2]); + a[3] = Math.max(a[3], b[3]); + return a; +} + +function compareNodeMinX(a, b) { return a.bbox[0] - b.bbox[0]; } +function compareNodeMinY(a, b) { return a.bbox[1] - b.bbox[1]; } + +function bboxArea(a) { return (a[2] - a[0]) * (a[3] - a[1]); } +function bboxMargin(a) { return (a[2] - a[0]) + (a[3] - a[1]); } + +function enlargedArea(a, b) { + return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) * + (Math.max(b[3], a[3]) - Math.min(b[1], a[1])); +} + +function intersectionArea (a, b) { + var minX = Math.max(a[0], b[0]), + minY = Math.max(a[1], b[1]), + maxX = Math.min(a[2], b[2]), + maxY = Math.min(a[3], b[3]); + + return Math.max(0, maxX - minX) * + Math.max(0, maxY - minY); +} + +function contains(a, b) { + return a[0] <= b[0] && + a[1] <= b[1] && + b[2] <= a[2] && + b[3] <= a[3]; +} + +function intersects (a, b) { + return b[0] <= a[2] && + b[1] <= a[3] && + b[2] >= a[0] && + b[3] >= a[1]; +} + + +function partitionSort(arr, left, right, k, compare) { + var pivot; + + while (true) { + pivot = Math.floor((left + right) / 2); + pivot = partition(arr, left, right, pivot, compare); + + if (k === pivot) break; + else if (k < pivot) right = pivot - 1; + else left = pivot + 1; + } + + partition(arr, left, right, k, compare); +} + +function partition(arr, left, right, pivot, compare) { + var k = left, + value = arr[pivot]; + + swap(arr, pivot, right); + + for (var i = left; i < right; i++) { + if (compare(arr[i], value) < 0) { + swap(arr, k, i); + k++; + } + } + swap(arr, right, k); + + return k; +} + +function swap(arr, i, j) { + var tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; +} + + +// export as AMD/CommonJS module or global variable +if (typeof define === 'function' && define.amd) define(function() { return rbush; }); +else if (typeof module !== 'undefined') module.exports = rbush; +else if (typeof self !== 'undefined') self.rbush = rbush; +else window.rbush = rbush; + +})(); \ No newline at end of file