UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

424 lines (423 loc) 14.7 kB
import FlatQueue from "../flatqueue/index.js"; const ARRAY_TYPES = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, ]; const VERSION = 3; export default class Flatbush { numItems; nodeSize; byteOffset; ArrayType; IndexArrayType; data; minX; minY; maxX; maxY; _levelBounds; _boxes; _indices; _pos; _queue; static from(data, byteOffset = 0) { if (byteOffset % 8 !== 0) { throw new Error('byteOffset must be 8-byte aligned.'); } if (data.byteLength === 0) { throw new Error('Flatbush data buffer is detached (byteLength=0). ' + 'This usually means the buffer was transferred and is no longer usable.'); } const [magic, versionAndType] = new Uint8Array(data, byteOffset + 0, 2); if (magic !== 0xfb) { throw new Error('Data does not appear to be in a Flatbush format.'); } const version = versionAndType >> 4; if (version !== VERSION) { throw new Error(`Got v${version} data when expected v${VERSION}.`); } const ArrayType = ARRAY_TYPES[versionAndType & 0x0f]; if (!ArrayType) { throw new Error('Unrecognized array type.'); } const [nodeSize] = new Uint16Array(data, byteOffset + 2, 1); const [numItems] = new Uint32Array(data, byteOffset + 4, 1); return new Flatbush(numItems, nodeSize, ArrayType, undefined, data, byteOffset); } constructor(numItems, nodeSize = 16, ArrayType = Float64Array, ArrayBufferType = ArrayBuffer, data, byteOffset = 0) { if (isNaN(numItems) || numItems <= 0) { throw new Error(`Unexpected numItems value: ${numItems}.`); } this.numItems = +numItems; this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535); this.byteOffset = byteOffset; let n = numItems; let numNodes = n; this._levelBounds = [n * 4]; do { n = Math.ceil(n / this.nodeSize); numNodes += n; this._levelBounds.push(numNodes * 4); } while (n !== 1); this.ArrayType = ArrayType; this.IndexArrayType = numNodes < 16384 ? Uint16Array : Uint32Array; const arrayTypeIndex = ARRAY_TYPES.indexOf(ArrayType); const nodesByteSize = numNodes * 4 * ArrayType.BYTES_PER_ELEMENT; if (arrayTypeIndex === -1) { throw new Error(`Unexpected typed array class: ${ArrayType}.`); } if (data) { this.data = data; this._boxes = new ArrayType(data, byteOffset + 8, numNodes * 4); this._indices = new this.IndexArrayType(data, byteOffset + 8 + nodesByteSize, numNodes); this._pos = numNodes * 4; this.minX = this._boxes[this._pos - 4]; this.minY = this._boxes[this._pos - 3]; this.maxX = this._boxes[this._pos - 2]; this.maxY = this._boxes[this._pos - 1]; } else { const newData = (this.data = new ArrayBufferType(8 + nodesByteSize + numNodes * this.IndexArrayType.BYTES_PER_ELEMENT)); this._boxes = new ArrayType(newData, 8, numNodes * 4); this._indices = new this.IndexArrayType(newData, 8 + nodesByteSize, numNodes); this._pos = 0; this.minX = Infinity; this.minY = Infinity; this.maxX = -Infinity; this.maxY = -Infinity; new Uint8Array(newData, 0, 2).set([0xfb, (VERSION << 4) + arrayTypeIndex]); new Uint16Array(newData, 2, 1)[0] = nodeSize; new Uint32Array(newData, 4, 1)[0] = numItems; } this._queue = new FlatQueue(); } add(minX, minY, maxX = minX, maxY = minY) { const index = this._pos >> 2; const boxes = this._boxes; this._indices[index] = index; boxes[this._pos++] = minX; boxes[this._pos++] = minY; boxes[this._pos++] = maxX; boxes[this._pos++] = maxY; if (minX < this.minX) { this.minX = minX; } if (minY < this.minY) { this.minY = minY; } if (maxX > this.maxX) { this.maxX = maxX; } if (maxY > this.maxY) { this.maxY = maxY; } return index; } finish() { if (this._pos >> 2 !== this.numItems) { throw new Error(`Added ${this._pos >> 2} items when expected ${this.numItems}.`); } const boxes = this._boxes; if (this.numItems <= this.nodeSize) { boxes[this._pos++] = this.minX; boxes[this._pos++] = this.minY; boxes[this._pos++] = this.maxX; boxes[this._pos++] = this.maxY; return; } const width = this.maxX - this.minX || 1; const height = this.maxY - this.minY || 1; const hilbertValues = new Uint32Array(this.numItems); const hilbertMax = (1 << 16) - 1; const scaleX = hilbertMax / width; const scaleY = hilbertMax / height; const offsetX = this.minX; const offsetY = this.minY; for (let i = 0, pos = 0; i < this.numItems; i++) { const minX = boxes[pos++]; const minY = boxes[pos++]; const maxX = boxes[pos++]; const maxY = boxes[pos++]; const x = Math.floor(((minX + maxX) * 0.5 - offsetX) * scaleX); const y = Math.floor(((minY + maxY) * 0.5 - offsetY) * scaleY); hilbertValues[i] = hilbert(x, y); } sort(hilbertValues, boxes, this._indices, 0, this.numItems - 1, this.nodeSize); for (let i = 0, pos = 0; i < this._levelBounds.length - 1; i++) { const end = this._levelBounds[i]; while (pos < end) { const nodeIndex = pos; let nodeMinX = boxes[pos++]; let nodeMinY = boxes[pos++]; let nodeMaxX = boxes[pos++]; let nodeMaxY = boxes[pos++]; for (let j = 1; j < this.nodeSize && pos < end; j++) { const x0 = boxes[pos++]; const y0 = boxes[pos++]; const x1 = boxes[pos++]; const y1 = boxes[pos++]; if (x0 < nodeMinX) { nodeMinX = x0; } if (y0 < nodeMinY) { nodeMinY = y0; } if (x1 > nodeMaxX) { nodeMaxX = x1; } if (y1 > nodeMaxY) { nodeMaxY = y1; } } this._indices[this._pos >> 2] = nodeIndex; boxes[this._pos++] = nodeMinX; boxes[this._pos++] = nodeMinY; boxes[this._pos++] = nodeMaxX; boxes[this._pos++] = nodeMaxY; } } } search(minX, minY, maxX, maxY, filterFn) { if (this._pos !== this._boxes.length) { throw new Error('Data not yet indexed - call index.finish().'); } let nodeIndex = this._boxes.length - 4; const queue = []; const results = []; while (nodeIndex !== undefined) { const end = Math.min(nodeIndex + this.nodeSize * 4, upperBound(nodeIndex, this._levelBounds)); for (let pos = nodeIndex; pos < end; pos += 4) { const x0 = this._boxes[pos]; if (maxX < x0) { continue; } const y0 = this._boxes[pos + 1]; if (maxY < y0) { continue; } const x1 = this._boxes[pos + 2]; if (minX > x1) { continue; } const y1 = this._boxes[pos + 3]; if (minY > y1) { continue; } const index = this._indices[pos >> 2] | 0; if (nodeIndex >= this.numItems * 4) { queue.push(index); } else if (filterFn === undefined || filterFn(index, x0, y0, x1, y1)) { results.push(index); } } nodeIndex = queue.pop(); } return results; } neighbors(x, y, maxResults = Infinity, maxDistance = Infinity, filterFn) { if (this._pos !== this._boxes.length) { throw new Error('Data not yet indexed - call index.finish().'); } let nodeIndex = this._boxes.length - 4; const q = this._queue; const results = []; const maxDistSquared = maxDistance * maxDistance; outer: while (nodeIndex !== undefined) { const end = Math.min(nodeIndex + this.nodeSize * 4, upperBound(nodeIndex, this._levelBounds)); for (let pos = nodeIndex; pos < end; pos += 4) { const index = this._indices[pos >> 2] | 0; const minX = this._boxes[pos]; const minY = this._boxes[pos + 1]; const maxX = this._boxes[pos + 2]; const maxY = this._boxes[pos + 3]; const dx = x < minX ? minX - x : x > maxX ? x - maxX : 0; const dy = y < minY ? minY - y : y > maxY ? y - maxY : 0; const dist = dx * dx + dy * dy; if (dist > maxDistSquared) { continue; } if (nodeIndex >= this.numItems * 4) { q.push(index << 1, dist); } else if (filterFn === undefined || filterFn(index)) { q.push((index << 1) + 1, dist); } } while (q.length && q.peek() & 1) { const dist = q.peekValue(); if (dist > maxDistSquared) { break outer; } results.push(q.pop() >> 1); if (results.length === maxResults) { break outer; } } nodeIndex = q.length ? q.pop() >> 1 : undefined; } q.clear(); return results; } } function upperBound(value, arr) { let i = 0; let j = arr.length - 1; while (i < j) { const m = (i + j) >> 1; if (arr[m] > value) { j = m; } else { i = m + 1; } } return arr[i]; } function sort(values, boxes, indices, left, right, nodeSize) { const stack = [left, right]; while (stack.length > 0) { const r = stack.pop(); const l = stack.pop(); if (Math.floor(l / nodeSize) >= Math.floor(r / nodeSize)) { continue; } if (r - l < 10) { insertionSort(values, boxes, indices, l, r); continue; } const start = values[l]; const mid = values[(l + r) >> 1]; const end = values[r]; let pivot = end; const x = Math.max(start, mid); if (end > x) { pivot = x; } else if (x === start) { pivot = Math.max(mid, end); } else if (x === mid) { pivot = Math.max(start, end); } let i = l - 1; let j = r + 1; while (true) { do { i++; } while (values[i] < pivot); do { j--; } while (values[j] > pivot); if (i >= j) { break; } swap(values, boxes, indices, i, j); } stack.push(l, j, j + 1, r); } } function insertionSort(values, boxes, indices, left, right) { for (let i = left + 1; i <= right; i++) { const tempVal = values[i]; const tempIdx = indices[i]; const k = i * 4; const tempA = boxes[k]; const tempB = boxes[k + 1]; const tempC = boxes[k + 2]; const tempD = boxes[k + 3]; let j = i - 1; while (j >= left && values[j] > tempVal) { values[j + 1] = values[j]; indices[j + 1] = indices[j]; const src = j * 4; const dst = (j + 1) * 4; boxes[dst] = boxes[src]; boxes[dst + 1] = boxes[src + 1]; boxes[dst + 2] = boxes[src + 2]; boxes[dst + 3] = boxes[src + 3]; j--; } values[j + 1] = tempVal; indices[j + 1] = tempIdx; const dst = (j + 1) * 4; boxes[dst] = tempA; boxes[dst + 1] = tempB; boxes[dst + 2] = tempC; boxes[dst + 3] = tempD; } } function swap(values, boxes, indices, i, j) { const temp = values[i]; values[i] = values[j]; values[j] = temp; const k = 4 * i; const m = 4 * j; const a = boxes[k]; const b = boxes[k + 1]; const c = boxes[k + 2]; const d = boxes[k + 3]; boxes[k] = boxes[m]; boxes[k + 1] = boxes[m + 1]; boxes[k + 2] = boxes[m + 2]; boxes[k + 3] = boxes[m + 3]; boxes[m] = a; boxes[m + 1] = b; boxes[m + 2] = c; boxes[m + 3] = d; const e = indices[i]; indices[i] = indices[j]; indices[j] = e; } function hilbert(x, y) { let a = x ^ y; let b = 0xffff ^ a; let c = 0xffff ^ (x | y); let d = x & (y ^ 0xffff); let A = a | (b >> 1); let B = (a >> 1) ^ a; let C = (c >> 1) ^ (b & (d >> 1)) ^ c; let D = (a & (c >> 1)) ^ (d >> 1) ^ d; a = A; b = B; c = C; d = D; A = (a & (a >> 2)) ^ (b & (b >> 2)); B = (a & (b >> 2)) ^ (b & ((a ^ b) >> 2)); C ^= (a & (c >> 2)) ^ (b & (d >> 2)); D ^= (b & (c >> 2)) ^ ((a ^ b) & (d >> 2)); a = A; b = B; c = C; d = D; A = (a & (a >> 4)) ^ (b & (b >> 4)); B = (a & (b >> 4)) ^ (b & ((a ^ b) >> 4)); C ^= (a & (c >> 4)) ^ (b & (d >> 4)); D ^= (b & (c >> 4)) ^ ((a ^ b) & (d >> 4)); a = A; b = B; c = C; d = D; C ^= (a & (c >> 8)) ^ (b & (d >> 8)); D ^= (b & (c >> 8)) ^ ((a ^ b) & (d >> 8)); a = C ^ (C >> 1); b = D ^ (D >> 1); let i0 = x ^ y; let i1 = b | (0xffff ^ (i0 | a)); i0 = (i0 | (i0 << 8)) & 0x00ff00ff; i0 = (i0 | (i0 << 4)) & 0x0f0f0f0f; i0 = (i0 | (i0 << 2)) & 0x33333333; i0 = (i0 | (i0 << 1)) & 0x55555555; i1 = (i1 | (i1 << 8)) & 0x00ff00ff; i1 = (i1 | (i1 << 4)) & 0x0f0f0f0f; i1 = (i1 | (i1 << 2)) & 0x33333333; i1 = (i1 | (i1 << 1)) & 0x55555555; return ((i1 << 1) | i0) >>> 0; }