import RBush from 'rbush';
import knn from 'rbush-knn';
import SpacialIndex from './SpacialIndex.js';
/**
* 2D spatial indexing for rectangles
* @see https://www.npmjs.com/package/rbush
* @see https://www.npmjs.com/package/rbush-knn
*/
class SpacialRectIndex extends SpacialIndex {
constructor(getBBox) {
super(null, null);
this.items = [];
this.getBBox = getBBox;
this.dirty = true;
}
_update() {
let rects = this.items.map((r, i) => {
return {
...this.getBBox(r),
index: i
};
});
this.tree = new RBush();
this.tree.load(rects);
this.dirty = false;
}
/**
* Get all rects that intersect the given rectangle
* @param {Number} minX
* @param {Number} minY
* @param {Number} maxX
* @param {Number} maxY
* @returns {Array} array of items in the rectangle
*/
getInRect(minX, minY, maxX, maxY) {
if (this.dirty) this._update();
return this.tree.search({ minX, minY, maxX, maxY }).map(i => this.items[i.index]);
}
/**
* Get all rects that intersect the circle around x|y
* @param {Number} x coordinate
* @param {Number} y coordinate
* @param {Number} radius radius
* @returns {Array} array of items in the radius
*/
getInRadius(x, y, radius) {
return this.getInRect(x - radius, y - radius, x + radius, y + radius).filter(i => {
let dx = Math.abs(x - i.x1);
let dy = Math.abs(x - i.x1);
if (dx > i.w * 0.5 + radius || dy > i.h * 0.5 + radius) return false;
if (dx <= i.w * 0.5 || dy <= i.h * 0.5) return true;
let corner_sq = Math.pow(dx - i.w * 0.5, 2) + Math.pow(dy - i.h * 0.5, 2);
return corner_sq <= radius * radius;
});
}
/**
* Get the nearest rectangle(s) (k-nearest-neighbour)
* @param {Number} x coordinate
* @param {Number} y coordinate
* @param {Number} n amount of items to return
* @returns {*|Array} an item if n = 1, otherwise an array
*/
getNearest(x, y, n = 1, filter) {
if (this.dirty) this._update();
let result = knn(this.tree, x, y, n, filter);
if (n === 1) return result.length ? this.items[result[0].index] : null
return result.map(i => this.items[i.index]);
}
}
export default SpacialRectIndex