UNPKG

rbush-full

Version:

High-performance 2D spatial index for rectangles (based on R*-tree with bulk loading and bulk insertion algorithms)

852 lines (769 loc) 25.9 kB
import boxIntersect$1 from 'box-intersect'; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; // Generated by CoffeeScript 2.5.1 var GrowingArray, GrowingArrayPool, LeafNodes, MAX_REMOVALS_BEFORE_SWAP, ObjectStorage, RBush, SortableStack, TmpBbox, bboxArea, bboxMargin, boxIntersect, calcBBox, contains, createNode, distBBox, enlargedArea, extend, intersectionArea, intersects, rayBboxDistance; boxIntersect = boxIntersect$1; MAX_REMOVALS_BEFORE_SWAP = 50; TmpBbox = (function() { var CACHE_SIZE, bboxes, i, j, nextBbox, ref; var TmpBbox = function TmpBbox () {}; TmpBbox.get = function get () { return bboxes[nextBbox++ & (CACHE_SIZE - 1)]; }; CACHE_SIZE = 20; bboxes = []; nextBbox = 0; for (i = j = 0, ref = CACHE_SIZE; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) { bboxes.push([2e308, 2e308, -2e308, -2e308]); } return TmpBbox; }).call(commonjsGlobal); GrowingArray = /*@__PURE__*/(function () { function GrowingArray(size1, ref) { if ( size1 === void 0 ) size1 = 8; if ( ref === void 0 ) ref = {}; var double = ref.double; if ( double === void 0 ) double = false; this.size = size1; this.double = double; this.current = new Array(this.size); if (this.double) { this.other = new Array(this.size); } this.currentLen = 0; this[Symbol.iterator] = function() { var cur, i, max; i = 0; max = this.currentLen; cur = this.current; return { next: function() { if (i < max) { return { value: cur[i++], done: false }; } else { return { done: true }; } } }; }; } GrowingArray.prototype.push = function push (item, items2) { if (this.currentLen === this.size) { this.enlarge(); } if (typeof item2 !== "undefined" && item2 !== null) { this.other[this.currentLen] = item2; } return this.current[this.currentLen++] = item; }; GrowingArray.prototype.pop = function pop () { if (this.currentLen > 0) { return this.current[--this.currentLen]; } }; GrowingArray.prototype.enlarge = function enlarge () { var ref; this.size = Math.floor(this.size * 1.5); this.current.length = this.size; return (ref = this.other) != null ? ref.length = this.size : void 0; }; GrowingArray.prototype.clear = function clear () { return this.currentLen = 0; }; return GrowingArray; }()); GrowingArrayPool = /*@__PURE__*/(function (GrowingArray) { function GrowingArrayPool(size, innerSize) { GrowingArray.call(this, size); this.innerSize = innerSize; } if ( GrowingArray ) GrowingArrayPool.__proto__ = GrowingArray; GrowingArrayPool.prototype = Object.create( GrowingArray && GrowingArray.prototype ); GrowingArrayPool.prototype.constructor = GrowingArrayPool; GrowingArrayPool.prototype.get = function get () { return this.pop() || new GrowingArray(this.innerSize); }; GrowingArrayPool.prototype.release = function release (item) { return this.push(item); }; return GrowingArrayPool; }(GrowingArray)); SortableStack = /*@__PURE__*/(function (GrowingArray) { function SortableStack(size) { GrowingArray.call(this, size, { double: true }); } if ( GrowingArray ) SortableStack.__proto__ = GrowingArray; SortableStack.prototype = Object.create( GrowingArray && GrowingArray.prototype ); SortableStack.prototype.constructor = SortableStack; SortableStack.prototype.push = function push (item, value) { var assign, assign$1; var newItem, newValue, pos; if (this.currentLen === this.size) { this.enlarge(); } pos = 0; while (pos < this.currentLen) { if (value > this.other[pos]) { break; } else { pos++; } } while (pos <= this.currentLen) { (assign = [this.current[pos], this.other[pos]], newItem = assign[0], newValue = assign[1]); this.current[pos] = item; this.other[pos] = value; (assign$1 = [newItem, newValue], item = assign$1[0], value = assign$1[1]); pos++; } return this.currentLen++; }; return SortableStack; }(GrowingArray)); ObjectStorage = /*@__PURE__*/(function (GrowingArray) { function ObjectStorage(size) { GrowingArray.call(this, size, { double: true }); this.removalsCount = 0; this[Symbol.iterator] = function() { var cur, i, max; i = 0; max = this.currentLen; cur = this.current; return { next: function() { var value; while (i < max) { value = cur[i++]; if (!value._removed) { return { value: value, done: false }; } } return { done: true }; } }; }; } if ( GrowingArray ) ObjectStorage.__proto__ = GrowingArray; ObjectStorage.prototype = Object.create( GrowingArray && GrowingArray.prototype ); ObjectStorage.prototype.constructor = ObjectStorage; ObjectStorage.prototype.remove = function remove (item) { item._removed = true; return this.removalsCount++; }; ObjectStorage.prototype.maybeCondense = function maybeCondense (threshold) { if (this.removalsCount > (threshold != null ? threshold : Math.max(this.currentLen, 50) * 0.1)) { return this.condense(); } }; ObjectStorage.prototype.condense = function condense () { var index, item, j, newIndex, ref; newIndex = 0; for (index = j = 0, ref = this.currentLen; (0 <= ref ? j < ref : j > ref); index = 0 <= ref ? ++j : --j) { item = this.current[index]; if (item._removed) { item._removed = null; continue; } this.other[newIndex++] = item; } return this.swap(newIndex); }; ObjectStorage.prototype.swap = function swap (currentLen1) { var assign; this.currentLen = currentLen1; (assign = [this.other, this.current, 0], this.current = assign[0], this.other = assign[1], this.removalsCount = assign[2]); return null; }; return ObjectStorage; }(GrowingArray)); LeafNodes = /*@__PURE__*/(function (ObjectStorage) { function LeafNodes() { ObjectStorage.apply(this, arguments); this.overlappingPool = new GrowingArrayPool(this.size, Math.ceil(this.size / 4)); } if ( ObjectStorage ) LeafNodes.__proto__ = ObjectStorage; LeafNodes.prototype = Object.create( ObjectStorage && ObjectStorage.prototype ); LeafNodes.prototype.constructor = LeafNodes; LeafNodes.prototype.push = function push (item) { ObjectStorage.prototype.push.apply(this, arguments); return item.overlapping != null ? item.overlapping : item.overlapping = this.overlappingPool.get(); }; LeafNodes.prototype.remove = function remove (item) { ObjectStorage.prototype.remove.apply(this, arguments); this.overlappingPool.release(item.overlapping); return item.overlapping = null; }; return LeafNodes; }(ObjectStorage)); RBush = /*@__PURE__*/(function () { function RBush(maxEntries) { if ( maxEntries === void 0 ) maxEntries = 9; // max entries in a node is 9 by default; min node fill is 40% for best performance this._maxEntries = Math.max(4, maxEntries); this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); this._collisionRunId = 0; this._stacks = [0, 1, 2, 3, 4, 5, 6, 7, 8].map(function() { return new SortableStack(maxEntries); }); this.raycastResponse = { dist: 2e308, item: null }; this.nonStatic = new ObjectStorage(64); this.result = new GrowingArray(32); this.searchPath = new GrowingArray(32); this.leafNodes = new LeafNodes(16); this.clear(); } RBush.prototype.all = function all (predicate, result) { if ( result === void 0 ) result = this.result; result.currentLen = 0; this.searchPath.currentLen = 0; return this._all(this.data, predicate, result); }; RBush.prototype.search = function search (bbox, predicate, result) { if ( result === void 0 ) result = this.result; var child, j, len, node, ref; node = this.data; result.currentLen = 0; if (!intersects(bbox, node.bbox)) { return result; } this.searchPath.currentLen = 0; while (node) { ref = node.children; for (j = 0, len = ref.length; j < len; j++) { child = ref[j]; if (!child._ignore) { if (intersects(bbox, child.bbox)) { if (node.leaf) { if ((predicate == null) || predicate(child)) { result.push(child); } } else if (contains(bbox, child.bbox)) { this._all(child, predicate, result); } else { this.searchPath.push(child); } } } } node = this.searchPath.pop(); } return result; }; RBush.prototype.collides = function collides (bbox) { var child, j, len, node, ref; node = this.data; if (!intersects(bbox, node)) { return false; } this.searchPath.currentLen = 0; while (node) { ref = node.children; for (j = 0, len = ref.length; j < len; j++) { child = ref[j]; if (intersects(bbox, child.bbox)) { if (node.leaf || contains(bbox, child.bbox)) { return true; } this.searchPath.push(child); } } node = this.searchPath.pop(); } return false; }; RBush.prototype.update = function update (item) { var reinsert; if (contains(item.parent.bbox, item.bbox)) { return; } reinsert = true; this.remove(item, reinsert); return this.insert(item, reinsert); }; RBush.prototype.insert = function insert (item, reinsert) { if (!(item != null ? item.bbox : void 0)) { log("[RBush::insert] can't add without bbox", item); return; } if (!item.isStatic && item._removed) { item._removed = null; this.nonStatic.removalsCount--; reinsert = true; } this._insert(item); if (!(item.isStatic || reinsert)) { this.nonStatic.push(item); } return this; }; RBush.prototype.clear = function clear () { this.data = createNode([]); this.leafNodes.currentLen = 0; this.leafNodes.push(this.data); return this; }; RBush.prototype.remove = function remove (item, reinsert) { var index, parent; if (item == null) { return; } parent = item.parent; index = parent.children.indexOf(item); if (index === -1) { throw "[RBush remove] ERROR: parent doesn't have that item"; } parent.children.splice(index, 1); if (!(item.isStatic || reinsert)) { this.nonStatic.remove(item); } this._condense(parent); return this; }; RBush.prototype.checkCollisions = function checkCollisions () { var assign; var c, current, currentLen, i, index, item, j, l, leaf, leafs, len, len1, n, newIndex, o, other, otherLeaf, overlapping, q, ref, ref1, ref2, ref3, ref4, removalsCount, swapping; this.result.currentLen = 0; if (++this._collisionRunId > 99999) { this._collisionRunId = 0; } ((assign = this.nonStatic, other = assign.other, current = assign.current, currentLen = assign.currentLen, removalsCount = assign.removalsCount)); if (removalsCount > MAX_REMOVALS_BEFORE_SWAP) { swapping = true; newIndex = 0; } leafs = this.leafNodes.current; for (i = j = 0, ref = this.leafNodes.currentLen; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) { leaf = leafs[i]; if (!leaf._removed) { leaf.overlapping.currentLen = 0; } } boxIntersect(leafs, this.leafNodes.currentLen, function(leaf1, leaf2) { leaf1.overlapping.push(leaf2); leaf2.overlapping.push(leaf1); return void 0; }); for (index = l = 0, ref1 = currentLen; (0 <= ref1 ? l < ref1 : l > ref1); index = 0 <= ref1 ? ++l : --l) { item = current[index]; if (item._removed) { continue; } if (swapping) { other[newIndex++] = item; } if (item._ignore) { continue; } item._colRunId = this._collisionRunId; leaf = item.parent; ref2 = leaf.children; for (i = n = 0, len = ref2.length; n < len; i = ++n) { c = ref2[i]; if (!c._ignore && c._colRunId !== this._collisionRunId) { if (intersects(item.bbox, c.bbox)) { this.result.push(item); this.result.push(c); } } } // using iterators here is much slower overlapping = leaf.overlapping; if (overlapping.currentLen) { for (i = o = 0, ref3 = overlapping.currentLen; (0 <= ref3 ? o < ref3 : o > ref3); i = 0 <= ref3 ? ++o : --o) { otherLeaf = overlapping.current[i]; if (intersects(item.bbox, otherLeaf.bbox)) { ref4 = otherLeaf.children; for (q = 0, len1 = ref4.length; q < len1; q++) { c = ref4[q]; if (!c._ignore && c._colRunId !== this._collisionRunId) { if (intersects(item.bbox, c.bbox)) { this.result.push(item); this.result.push(c); } } } } } } } if (swapping) { this.nonStatic.swap(newIndex); } return this.result; }; // _rayObjectDistance: (distToBbox, origin, dir, dstX, dstY, range, item) -> RBush.prototype.raycast = function raycast (origin, dir, range, predicate) { if ( range === void 0 ) range = 2e308; var child, dstX, dstY, invDirx, invDiry, item, j, l, len, len1, len2, n, node, popped, ref, ref1, ref2, stack, t, tmin; node = this.data; invDirx = 1 / dir.x; invDiry = 1 / dir.y; dstX = origin.x + dir.x * range; dstY = origin.y + dir.y * range; tmin = 2e308; item = null; ref = this._stacks; for (j = 0, len = ref.length; j < len; j++) { stack = ref[j]; stack.currentLen = 0; } while (node) { stack = this._stacks[node.height]; if (node.leaf) { ref1 = node.children; for (l = 0, len1 = ref1.length; l < len1; l++) { child = ref1[l]; if (!child._ignore) { if ((predicate == null) || predicate(child)) { t = rayBboxDistance(origin.x, origin.y, invDirx, invDiry, child.bbox); if (t < range && t < tmin) { if (this._rayObjectDistance != null) { t = this._rayObjectDistance(t, origin, dir, dstX, dstY, range, child); } if (t < range && t < tmin) { tmin = t; item = child; } } } } } } else { ref2 = node.children; for (n = 0, len2 = ref2.length; n < len2; n++) { child = ref2[n]; if (!(!child._ignore)) { continue; } t = rayBboxDistance(origin.x, origin.y, invDirx, invDiry, child.bbox); if (t < range && t < tmin) { stack.push(child, t); } } } while (node) { popped = this._stacks[node.height].pop(); if (popped) { node = popped; break; } node = node.parent; } } this.raycastResponse.dist = tmin; this.raycastResponse.item = item; return this.raycastResponse; }; RBush.prototype._all = function _all (node, predicate, result) { var child, i, j, l, len, len1, ref, ref1; i = this.searchPath.currentLen; while (node) { if (node.leaf) { ref = node.children; for (j = 0, len = ref.length; j < len; j++) { child = ref[j]; if (!child._ignore) { if ((predicate == null) || predicate(child)) { result.push(child); } } } } else { ref1 = node.children; for (l = 0, len1 = ref1.length; l < len1; l++) { child = ref1[l]; this.searchPath.push(child); } } if (this.searchPath.currentLen === i) { break; } node = this.searchPath.pop(); } return result; }; RBush.prototype._chooseSubtree = function _chooseSubtree (bbox, node) { var area, child, enlargement, j, len, minArea, minEnlargement, ref, targetNode; while (true) { if (node.leaf) { break; } minArea = 2e308; minEnlargement = 2e308; targetNode = null; ref = node.children; for (j = 0, len = ref.length; j < len; j++) { child = ref[j]; area = bboxArea(child.bbox); enlargement = enlargedArea(bbox, child.bbox) - area; // choose entry with the least area enlargement if (enlargement < minEnlargement) { minEnlargement = enlargement; minArea = area < minArea ? area : minArea; targetNode = child; } else if (enlargement === minEnlargement) { // otherwise choose one with the smallest area if (area < minArea) { minArea = area; targetNode = child; } } } node = targetNode || node.children[0]; } return node; }; RBush.prototype._insert = function _insert (item) { var bbox, node, parent; bbox = item.bbox; // find the best node for accommodating the item, saving all nodes along the path too node = this._chooseSubtree(bbox, this.data); // put the item into the node node.children.push(item); item.parent = node; extend(node.bbox, bbox); // split on node overflow; propagate upwards if necessary parent = node; while (parent != null) { if (parent.children.length > this._maxEntries) { this._split(parent); } parent = parent.parent; } // adjust bboxes along the insertion path return this._adjustParentBBoxes(bbox, node.parent); }; // split overflowed node into two RBush.prototype._split = function _split (node) { var M, m, newNode, splitIndex; M = node.children.length; m = this._minEntries; this._chooseSplitAxis(node, m, M); splitIndex = this._chooseSplitIndex(node, m, M); newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex)); newNode.height = node.height; newNode.leaf = node.leaf; if (newNode.leaf) { this.leafNodes.push(newNode); } calcBBox(node); calcBBox(newNode); if (node.parent != null) { node.parent.children.push(newNode); return newNode.parent = node.parent; } else { return this._splitRoot(node, newNode); } }; RBush.prototype._splitRoot = function _splitRoot (node, newNode) { // split root node this.data = createNode([node, newNode]); this.data.height = node.height + 1; if (this.data.height === this._stacks.length) { this._stacks.push(new SortableStack(this._maxEntries)); } this.data.leaf = false; return calcBBox(this.data); }; RBush.prototype._chooseSplitIndex = function _chooseSplitIndex (node, m, M) { var area, bbox1, bbox2, i, index, j, minArea, minOverlap, overlap, ref, ref1; index = null; minOverlap = 2e308; minArea = 2e308; i = m - 1; for (i = j = ref = m, ref1 = M - m; (ref <= ref1 ? j <= ref1 : j >= ref1); i = ref <= ref1 ? ++j : --j) { bbox1 = distBBox(node, 0, i); bbox2 = distBBox(node, i, M); overlap = intersectionArea(bbox1, bbox2); area = bboxArea(bbox1) + bboxArea(bbox2); // choose distribution with minimum overlap if (overlap < minOverlap) { minOverlap = overlap; index = i; minArea = area < minArea ? area : minArea; } else if (overlap === minOverlap) { // otherwise choose distribution with minimum area if (area < minArea) { minArea = area; index = i; } } } return index || M - m; }; // sorts node children by the best axis for split RBush.prototype._chooseSplitAxis = function _chooseSplitAxis (node, m, M) { var xMargin, yMargin; // all of these sorts could be done with Timsort // two options: // 1. Typed Arrays implementation: https://github.com/LXSMNSYC/TimSort // - this requires children to be indexed separetely (separete order array) // - splitting as well as chooseSplitIndex would have to traverse the order array instead // - this could be difficult // 2. Drop-in replacement implementation: https://github.com/mziccard/node-timsort xMargin = this._allDistMargin(node, m, M, function(a, b) { return a.bbox[0] - b.bbox[0]; }); yMargin = this._allDistMargin(node, m, M, function(a, b) { return a.bbox[1] - b.bbox[1]; }); // if total distributions margin value is minimal for x, sort by minX, // otherwise it's already sorted by minY if (xMargin < yMargin) { return node.children.sort(function(a, b) { return a.bbox[0] - b.bbox[0]; }); } }; // total margin of all possible split distributions where each node is at least m full RBush.prototype._allDistMargin = function _allDistMargin (node, m, M, compare) { var child, i, j, l, leftBbox, margin, ref, ref1, ref2, ref3, rightBbox; node.children.sort(compare); leftBbox = distBBox(node, 0, m); rightBbox = distBBox(node, M - m, M); margin = bboxMargin(leftBbox) + bboxMargin(rightBbox); for (i = j = ref = m, ref1 = M - m; (ref <= ref1 ? j < ref1 : j > ref1); i = ref <= ref1 ? ++j : --j) { child = node.children[i]; extend(leftBbox, child.bbox); margin += bboxMargin(leftBbox); } for (i = l = ref2 = M - m - 1, ref3 = m; (ref2 <= ref3 ? l <= ref3 : l >= ref3); i = ref2 <= ref3 ? ++l : --l) { child = node.children[i]; extend(rightBbox, child.bbox); margin += bboxMargin(rightBbox); } return margin; }; RBush.prototype._adjustParentBBoxes = function _adjustParentBBoxes (bbox, node) { while (node) { extend(node.bbox, bbox); node = node.parent; } return null; }; RBush.prototype._condense = function _condense (node) { var siblings; // go upward, removing empty while (node) { if (node.children.length === 0) { if (node.parent != null) { siblings = node.parent.children; siblings.splice(siblings.indexOf(node), 1); if (node.leaf) { this.leafNodes.remove(node); this.leafNodes.maybeCondense(); } } else { return this.clear(); } } else { calcBBox(node); } node = node.parent; } return null; }; return RBush; }()); // calculate node's bbox from bboxes of its children calcBBox = function(node) { return distBBox(node, 0, node.children.length, node); }; // min bounding rectangle of node children from k to p-1 distBBox = function(node, k, p, destNode) { var bbox, i, j, ref, ref1, ref2; bbox = (ref = destNode != null ? destNode.bbox : void 0) != null ? ref : TmpBbox.get(); bbox[0] = 2e308; bbox[1] = 2e308; bbox[2] = -2e308; bbox[3] = -2e308; for (i = j = ref1 = k, ref2 = p; (ref1 <= ref2 ? j < ref2 : j > ref2); i = ref1 <= ref2 ? ++j : --j) { extend(bbox, node.children[i].bbox); } return bbox; }; 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; }; bboxArea = function(a) { return (a[2] - a[0]) * (a[3] - a[1]); }; bboxMargin = 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 maxX, maxY, minX, minY; 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); }; 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]; }; rayBboxDistance = function(x, y, invdx, invdy, bbox) { var tmax, tmin, tx1, tx2, ty1, ty2; tx1 = (bbox[0] - x) * invdx; tx2 = (bbox[2] - x) * invdx; tmin = Math.min(tx1, tx2); tmax = Math.max(tx1, tx2); ty1 = (bbox[1] - y) * invdy; ty2 = (bbox[3] - y) * invdy; tmin = Math.max(tmin, Math.min(ty1, ty2)); tmax = Math.min(tmax, Math.max(ty1, ty2)); if (tmax > Math.max(tmin, 0)) { return tmin; } else { return 2e308; } }; createNode = function(children) { var c, j, len, node; node = { children: children, height: 1, leaf: true, bbox: [2e308, 2e308, -2e308, -2e308] }; for (j = 0, len = children.length; j < len; j++) { c = children[j]; c.parent = node; } return node; }; var rbush = {RBush: RBush, boxIntersect: boxIntersect, rayBboxDistance: rayBboxDistance, GrowingArray: GrowingArray, GrowingArrayPool: GrowingArrayPool, ObjectStorage: ObjectStorage}; var rbush_1 = rbush.RBush; var rbush_2 = rbush.boxIntersect; var rbush_3 = rbush.rayBboxDistance; var rbush_4 = rbush.GrowingArray; var rbush_5 = rbush.GrowingArrayPool; var rbush_6 = rbush.ObjectStorage; export default rbush; export { rbush_4 as GrowingArray, rbush_5 as GrowingArrayPool, rbush_6 as ObjectStorage, rbush_1 as RBush, rbush_2 as boxIntersect, rbush_3 as rayBboxDistance };