UNPKG

static-range-query

Version:

Fast n-dimensional orthogonal range searches for static point sets

300 lines (276 loc) 7.22 kB
"use strict" var bfs = require("bfs-tree-layout") , bits = require("bit-twiddle") , inorder = require("inorder-tree-layout") , lowerBound = require("lower-bound") , upperBound = require("upper-bound") var TREE_CUTOFF = 32 var SCRATCH = [new Uint32Array(1024)] , BUFFER = [new Uint32Array(1024)] , TEMP = [new Uint32Array(SCRATCH[0].buffer)] function merge(d, begin, pivot, end) { var a_ptr = begin|0, a_end = pivot|0, a , b_ptr = pivot|0, b_end = (pivot+1)|0, b , c_ptr = (pivot+1)|0, c_end = end|0, c , d_ptr = 0, d_end = (end - begin)|0 , next, i, j, src, dst, data = BUFFER[d] for(d_ptr=0; d_ptr<d_end; ++d_ptr) { a = a_ptr < a_end ? data[a_ptr] : Infinity b = b_ptr < b_end ? data[b_ptr] : Infinity c = c_ptr < c_end ? data[c_ptr] : Infinity if(a <= b) { if(a <= c) { next = a_ptr++ } else if(b <= c) { next = b_ptr++ } else { next = c_ptr++ } } else if(b <= c) { next = b_ptr++ } else { next = c_ptr++ } for(i=0; i<=d; ++i) { SCRATCH[i][d_ptr] = BUFFER[i][next] } } for(j=0; j<=d; ++j) { src = SCRATCH[j] dst = BUFFER[j] for(i=0; i<d_end; ++i) { dst[i+begin] = src[i] } } } function TerminalNode(points) { this.points = points } TerminalNode.prototype.search = function(DIMENSION, lo, hi, cb) { var points = this.points , n = points.length|0 , i, j, p i_loop: for(i=0; i<n; ++i) { p = points[i] for(j=0; j<DIMENSION; ++j) { if(!(lo[j] <= p[j+1] && p[j+1] <= hi[j])) { continue i_loop } } if(cb(p[0])) { return true } } return false } function RangeTree1D(coords, indices) { this.coords = coords this.indices = indices } //Terminal tree is easy: just binary search and iterate over interval RangeTree1D.prototype.search = function(DIMENSION, lo, hi, cb) { var coords = this.coords , begin = lowerBound(coords, lo[0]) , end = upperBound(coords, hi[0]) , indices = this.indices if(begin <= end) { //Edge case if(begin < 0) { begin = 0 } if(coords[begin] < lo[0]) { ++begin } //Search over range for(var i=begin; i<end; ++i) { if(cb(indices[i])) { return true } } } return false } function RangeTree(coords, indices, children) { this.coords = coords this.indices = indices this.children = children } RangeTree.prototype.search = function(DIMENSION, bounds_lo, bounds_hi, cb) { DIMENSION = (DIMENSION-1)|0 var coords = this.coords , children = this.children , n = coords.length , lo = +bounds_lo[DIMENSION] , hi = +bounds_hi[DIMENSION] , v = bfs.root(n), l, r , vsplit , x , vsplit = -1 if(hi < lo) { return } while(v < n) { x = coords[v] if(lo < x && hi < x) { v = bfs.left(n, v) } else if(lo > x && hi > x) { v = bfs.right(n, v) } else { //Handle case where tree is completely contained if(lo <= coords[bfs.lo(n, v)] && coords[bfs.hi(n,v)] <= hi) { return children[v].search(DIMENSION, bounds_lo, bounds_hi, cb) } //Save split node vsplit = v //Visit left trees v = bfs.left(n, vsplit) while(v < n) { l = bfs.left(n, v) r = l + 1 if(lo <= coords[bfs.lo(n, v)]) { if(children[v].search(DIMENSION, bounds_lo, bounds_hi, cb)) { return true } break } if(lo <= coords[v]) { if(r < n) { if(children[r].search(DIMENSION, bounds_lo, bounds_hi, cb)) { return true } } v = l } else { v = r } } //Visit split node if(cb(this.indices[vsplit])) { return true } //Visit right trees v = bfs.right(n, vsplit) while(v < n) { l = bfs.left(n, v) r = l + 1 if(coords[bfs.hi(n, v)] <= hi) { return children[v].search(DIMENSION, bounds_lo, bounds_hi, cb) } if(coords[v] <= hi) { if(l < n) { if(children[l].search(DIMENSION, bounds_lo, bounds_hi, cb)) { return true } } v = r } else { v = l } } return false } } return false } function comparePoints(a, b) { var i, d for(i=a.length-1; i>0; --i) { d = a[i] - b[i] if(d) { return d } } return a[0] - b[0] } function makeTerminal(DIMENSION, begin, end) { DIMENSION = DIMENSION|0 begin = begin|0 end = end|0 var n = (end - begin)|0 , points = new Array(n) , i, j, p for(i=0; i<n; ++i) { p = new Array(DIMENSION+1) for(j=0; j<=DIMENSION; ++j) { p[j] = BUFFER[j][i+begin] } points[i] = p } points.sort(comparePoints) return new TerminalNode(points) } function makeRangeTree(DIMENSION, begin, end) { DIMENSION = DIMENSION|0 begin = begin|0 end = end|0 if(DIMENSION === 1) { return new RangeTree1D(new Float64Array(BUFFER[1].buffer.slice(begin*8, end*8)), new Uint32Array(BUFFER[0].buffer.slice(begin*4, end*4))) } var n = (end-begin)|0 if(n < TREE_CUTOFF) { return makeTerminal(DIMENSION, begin, end) } var coords = new Float64Array(n) , indices = new Uint32Array(n) , children = new Array(n) , carray = BUFFER[DIMENSION] , iarray = BUFFER[0] , b2i = TEMP[DIMENSION] , i, j, k, lo, hi for(i=0, j=bfs.begin(n); i<n; ++i, j=bfs.next(n, j)) { k = i + begin coords[j] = carray[k] indices[j] = iarray[k] b2i[j] = k } for(j=n-1; j>=0; --j) { lo = b2i[bfs.lo(n, j)] hi = b2i[bfs.hi(n, j)] + 1 merge(DIMENSION-1, lo, b2i[j], hi) children[j] = makeRangeTree(DIMENSION-1, lo, hi) } return new RangeTree(coords, indices, children) } function buildTree(points) { var n = points.length if(n === 0) { return noop } //Create decorated points array var d = points[0].length , dec_points = new Array(n) , i, j, p, q, dst for(i=0; i<n; ++i) { p = new Array(d+1) q = points[i] p[0] = i for(j=0; j<d; ++j) { p[j+1] = q[j] } dec_points[i] = p } dec_points.sort(function(a, b) { return a[d] - b[d] }) //Unpack points into buffers for(i=0; i<=d; ++i) { if(BUFFER.length <= i) { BUFFER.push(new Float64Array(Math.max(1024, bits.nextPow2(n)))) SCRATCH.push(new Float64Array(Math.max(1024, bits.nextPow2(n)))) TEMP.push(new Uint32Array(SCRATCH[i].buffer)) } else if(BUFFER[i].length < n) { if(i === 0) { BUFFER[i] = new Uint32Array(bits.nextPow2(n)) SCRATCH[i] = new Uint32Array(bits.nextPow2(n)) } else { BUFFER[i] = new Float64Array(bits.nextPow2(n)) SCRATCH[i] = new Float64Array(bits.nextPow2(n)) } TEMP[i] = new Uint32Array(SCRATCH[i].buffer) } dst = BUFFER[i] for(j=0; j<n; ++j) { dst[j] = dec_points[j][i] } } var tree = makeRangeTree(d, 0, n) return tree.search.bind(tree, d) } module.exports = buildTree