diff --git a/js/lib/rbush.js b/js/lib/rbush.js index 4105e9951..794ec9255 100644 --- a/js/lib/rbush.js +++ b/js/lib/rbush.js @@ -11,16 +11,23 @@ function rbush(maxEntries, format) { // jshint newcap: false, validthis: true 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); this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); - this._initFormat(format); + if (format) { + this._initFormat(format); + } this.clear(); } rbush.prototype = { + all: function () { + return this._all(this.data, []); + }, + search: function (bbox) { var node = this.data, @@ -34,10 +41,19 @@ rbush.prototype = { 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 ? this.toBBox(child) : child.bbox; if (this._intersects(bbox, childBBox)) { - (node.leaf ? result : nodesToSearch).push(child); + + if (node.leaf) { + result.push(child); + + } else if (this._contains(bbox, childBBox)) { + this._all(child, result); + + } else { + nodesToSearch.push(child); + } } } @@ -59,7 +75,6 @@ rbush.prototype = { // recursively build the tree with the given data from stratch using OMT algorithm var node = this._build(data.slice(), 0); - this._calcBBoxes(node, true); if (!this.data.children.length) { // save as is if tree is empty @@ -95,7 +110,7 @@ rbush.prototype = { this.data = { children: [], leaf: true, - bbox: this._infinite(), + bbox: this._empty(), height: 1 }; return this; @@ -105,7 +120,7 @@ rbush.prototype = { if (!item) { return this; } var node = this.data, - bbox = this._toBBox(item), + bbox = this.toBBox(item), path = [], indexes = [], i, parent, index, goingUp; @@ -152,6 +167,11 @@ rbush.prototype = { return this; }, + toBBox: function (item) { return item; }, + + compareMinX: function (a, b) { return a[0] - b[0]; }, + compareMinY: function (a, b) { return a[1] - b[1]; }, + toJSON: function () { return this.data; }, fromJSON: function (data) { @@ -159,17 +179,33 @@ rbush.prototype = { return this; }, + _all: function (node, result) { + var nodesToSearch = []; + while (node) { + 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) { var N = items.length, - M = this._maxEntries; + M = this._maxEntries, + node; if (N <= M) { - return { + node = { children: items, leaf: true, height: 1 }; + this._calcBBox(node); + return node; } if (!level) { @@ -179,19 +215,19 @@ rbush.prototype = { // target number of root entries to maximize storage utilization M = Math.ceil(N / Math.pow(M, height - 1)); - items.sort(this._compareMinX); + items.sort(this.compareMinX); } // TODO eliminate recursion? - var node = { + node = { children: [], height: height }; var N1 = Math.ceil(N / M) * Math.ceil(Math.sqrt(M)), N2 = Math.ceil(N / M), - compare = level % 2 === 1 ? this._compareMinX : this._compareMinY, + compare = level % 2 === 1 ? this.compareMinX : this.compareMinY, i, j, slice, sliceLen, childNode; // split the items into M mostly square tiles @@ -205,6 +241,8 @@ rbush.prototype = { } } + this._calcBBox(node); + return node; }, @@ -247,7 +285,7 @@ rbush.prototype = { _insert: function (item, level, isNode, root) { - var bbox = isNode ? item.bbox : this._toBBox(item), + var bbox = isNode ? item.bbox : this.toBBox(item), insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too @@ -290,8 +328,8 @@ rbush.prototype = { newNode.leaf = true; } - this._calcBBoxes(node); - this._calcBBoxes(newNode); + this._calcBBox(node); + this._calcBBox(newNode); if (level) { insertPath[level - 1].children.push(newNode); @@ -305,7 +343,7 @@ rbush.prototype = { this.data = {}; this.data.children = [node, newNode]; this.data.height = node.height + 1; - this._calcBBoxes(this.data); + this._calcBBox(this.data); }, _chooseSplitIndex: function (node, m, M) { @@ -343,8 +381,8 @@ 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 : this._compareNodeMinX, + compareMinY = node.leaf ? this.compareMinY : this._compareNodeMinY, xMargin = this._allDistMargin(node, m, M, compareMinX), yMargin = this._allDistMargin(node, m, M, compareMinY); @@ -368,13 +406,13 @@ rbush.prototype = { for (i = m; i < M - m; i++) { child = node.children[i]; - this._extend(leftBBox, node.leaf ? this._toBBox(child) : child.bbox); + this._extend(leftBBox, node.leaf ? this.toBBox(child) : child.bbox); margin += this._margin(leftBBox); } for (i = M - m - 1; i >= 0; i--) { child = node.children[i]; - this._extend(rightBBox, node.leaf ? this._toBBox(child) : child.bbox); + this._extend(rightBBox, node.leaf ? this.toBBox(child) : child.bbox); margin += this._margin(rightBBox); } @@ -383,31 +421,23 @@ rbush.prototype = { // min bounding rectangle of node children from k to p-1 _distBBox: function (node, k, p) { - var bbox = this._infinite(); + 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); + this._extend(bbox, node.leaf ? this.toBBox(child) : child.bbox); } return bbox; }, - _calcBBoxes: function (node, recursive) { - // TODO eliminate recursion - node.bbox = this._infinite(); + // 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]; - - if (node.leaf) { - this._extend(node.bbox, this._toBBox(child)); - } else { - if (recursive) { - this._calcBBoxes(child, recursive); - } - this._extend(node.bbox, child.bbox); - } + this._extend(node.bbox, node.leaf ? this.toBBox(child) : child.bbox); } }, @@ -421,15 +451,26 @@ rbush.prototype = { _condense: function (path) { // go through the path, removing empty nodes and updating bboxes for (var i = path.length - 1, parent; i >= 0; i--) { - if (i > 0 && path[i].children.length === 0) { - parent = path[i - 1].children; - parent.splice(parent.indexOf(path[i]), 1); + 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._calcBBoxes(path[i]); + this._calcBBox(path[i]); } } }, + _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] && @@ -463,14 +504,13 @@ rbush.prototype = { Math.max(0, maxY - minY); }, - _infinite: function () { return [Infinity, Infinity, -Infinity, -Infinity]; }, + _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) - format = format || ['[0]', '[1]', '[2]', '[3]']; // uses eval-type function compilation instead of just accepting a toBBox function // because the algorithms are very sensitive to sorting functions performance, @@ -480,15 +520,21 @@ rbush.prototype = { var compareArr = ['return a', ' - b', ';']; - this._compareMinX = new Function('a', 'b', compareArr.join(format[0])); - this._compareMinY = new Function('a', 'b', compareArr.join(format[1])); + this.compareMinX = new Function('a', 'b', compareArr.join(format[0])); + this.compareMinY = new Function('a', 'b', compareArr.join(format[1])); - this._toBBox = new Function('a', 'return [a' + format.join(', a') + '];'); + this.toBBox = new Function('a', 'return [a' + format.join(', a') + '];'); } }; -if (typeof module !== 'undefined') { +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; }