fqtree
Version:
a flexible quadtree for JavaScript/TypeScript
350 lines • 14 kB
JavaScript
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuadTree = void 0;
var onsreo_1 = require("onsreo");
var evjkit_1 = require("evjkit");
var range_1 = require("./range");
var caches_1 = require("./caches");
var _next_qt_id = 0;
var QuadTree = /** @class */ (function () {
/**
* A Quadtree for spatial partitioning and searching.
* @param boundary - this QuadTree<Spatial>'s bounds
* @param capacity - how many points this quadtree can hold
* @param root - QuadTree<Spatial>'s root Instance (only used for subdivisions)
*/
function QuadTree(boundary, capacity, maxDepth, root) {
if (capacity === void 0) { capacity = 4; }
if (maxDepth === void 0) { maxDepth = 10; }
this.id = -1;
this.capacity = 4;
this.divided = false;
this.boundary = boundary;
this.capacity = capacity;
this.id = _next_qt_id++;
this.root = root !== null && root !== void 0 ? root : this.id;
this.maxDepth = maxDepth;
this.data = [];
var qt_cache = (0, caches_1.get_qt_cache)();
qt_cache.add(this, this.id);
}
/**
* clears this quadtree and its children (if any).
*/
QuadTree.prototype._clear = function () {
var _this = this;
var _a, _b, _c, _d;
this.data.forEach(function (id) { return _this.data_cache.remove(id); });
this.data = [];
this.qt_cache.remove(this.id);
this.root = null;
if (this.divided) {
(_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a._clear();
(_b = this.qt_cache.get(this.nw)) === null || _b === void 0 ? void 0 : _b._clear();
(_c = this.qt_cache.get(this.se)) === null || _c === void 0 ? void 0 : _c._clear();
(_d = this.qt_cache.get(this.sw)) === null || _d === void 0 ? void 0 : _d._clear();
}
this.ne = null;
this.nw = null;
this.se = null;
this.sw = null;
this.divided = false;
(0, evjkit_1.nullify)(this, 'boundary');
};
QuadTree.prototype.destroy = function () {
this._clear();
_next_qt_id = 0;
this.data_cache.destroy();
this.qt_cache.destroy();
};
Object.defineProperty(QuadTree.prototype, "qt_cache", {
get: function () {
return (0, caches_1.get_qt_cache)();
},
enumerable: false,
configurable: true
});
Object.defineProperty(QuadTree.prototype, "data_cache", {
get: function () {
return (0, caches_1.get_data_cache)();
},
enumerable: false,
configurable: true
});
// /**
// * resets all the internal data for this tree and its children.
// */
// _reset(): void {
// this.data.forEach(id => this.data_cache.remove(id));
// this.data = [];
// this.qt_cache.remove(this.id);
// this.root = null;
// if (this.divided) {
// this.qt_cache.get(this.ne)?._reset();
// this.qt_cache.get(this.nw)?._reset();
// this.qt_cache.get(this.se)?._reset();
// this.qt_cache.get(this.sw)?._reset();
// }
// this.ne = null;
// this.nw = null;
// this.se = null;
// this.sw = null;
// this.divided = false;
// nullify(this, 'boundary');
// }
/**
* resets the quadtree and optionally sets new bounds.
* @param boundary - this QuadTree<Spatial>'s bounds
*/
QuadTree.prototype.reset = function (bounds) {
if (bounds === void 0) { bounds = this.boundary; }
// store bounds temporarily so they can be persisted
var tempBounds = bounds;
this._clear();
_next_qt_id = 0;
this.id = _next_qt_id++;
this.root = this.id;
this.qt_cache.add(this, this.id);
this.boundary = tempBounds;
};
/**
* subdivide this tree into 4 new quadrants.
*/
QuadTree.prototype.subdivide = function () {
var _this = this;
var _a = this.boundary.bounds, x = _a.x, y = _a.y, w = _a.w, h = _a.h;
var hw = Math.ceil(w / 2), hh = Math.ceil(h / 2);
var ne = new range_1.RectangleRange(x + hw, y - hh, hw, hh);
var nw = new range_1.RectangleRange(x - hw, y - hh, hw, hh);
var se = new range_1.RectangleRange(x + hw, y + hh, hw, hh);
var sw = new range_1.RectangleRange(x - hw, y + hh, hw, hh);
// we insert things this way so that the points will cause the
// appropriate number of subdivisions, that way ranges and lines
// will be inserted most efficiently. We don't do this separation
// in insertMany as it would have too large of a performance
// penalty
this.ne = new QuadTree(ne, this.capacity, this.maxDepth - 1, this.root).id;
this.nw = new QuadTree(nw, this.capacity, this.maxDepth - 1, this.root).id;
this.se = new QuadTree(se, this.capacity, this.maxDepth - 1, this.root).id;
this.sw = new QuadTree(sw, this.capacity, this.maxDepth - 1, this.root).id;
// now we've successfully divided
this.divided = true;
// now collect and re-insert this quadrants data
// into its sub-quadrants
this.getData().forEach(function (spatial) {
_this.data_cache.remove(spatial.id);
_this.insert(spatial);
});
// clear this node's internal data as we only want it
// to exist on the leaves
this.data = [];
};
QuadTree.prototype._insert = function (spatial) {
// insert and divide based on type of spatial
this.data.push(spatial.id);
// if we've exceeded capacity, subdivide
if (this.data.length > this.capacity && this.maxDepth) {
this.subdivide();
}
else {
this.data_cache.add([spatial, this.id], spatial.id);
}
};
/**
* insert a spatial into this tree.
* @param spatial - spatial to insert.
*/
QuadTree.prototype.insert = function (spatial) {
var _a, _b, _c, _d;
if (!this.contains(spatial))
return;
if (this.divided) {
(_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a.insert(spatial);
(_b = this.qt_cache.get(this.nw)) === null || _b === void 0 ? void 0 : _b.insert(spatial);
(_c = this.qt_cache.get(this.se)) === null || _c === void 0 ? void 0 : _c.insert(spatial);
(_d = this.qt_cache.get(this.sw)) === null || _d === void 0 ? void 0 : _d.insert(spatial);
}
else {
this._insert(spatial);
}
};
/**
* insert all the provided spatials.
* @param spatials - spatials to insert.
*/
QuadTree.prototype.insertMany = function (spatials) {
var _this = this;
spatials.forEach(function (spatial) { return _this.insert(spatial); });
};
/**
* tests if this quadtree's boundary completely contains the spatial.
* @param spatial - spatial to test.
* @returns true if contained within the bounds, else false.
*/
QuadTree.prototype.contains = function (spatial) {
return this.boundary.containsPoint(spatial);
};
/**
* tests if this quadtree's boundary intersects with the boundary.
* @param spatial - spatial to test.
* @returns true if intersects, else false.
*/
QuadTree.prototype.intersects = function (range) {
return this.boundary.intersects(range);
};
QuadTree.prototype.getData = function () {
var _this = this;
return this.data.reduce(function (data, id) {
var maybe_data = _this.data_cache.get(id);
if ((0, onsreo_1.is_none)(maybe_data))
return data;
return data.addItem(maybe_data[0]);
}, []);
};
/**
* query insertain spatial using provided range.
* @param range - range for query
* @param found - set to fill [default: empty Set].
* @returns query results as a set
*/
QuadTree.prototype.query = function (range, found) {
var _a, _b, _c, _d;
if (found === void 0) { found = new Set(); }
if (!this.intersects(range))
return found;
if (this.divided) {
(_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a.query(range, found);
(_b = this.qt_cache.get(this.nw)) === null || _b === void 0 ? void 0 : _b.query(range, found);
(_c = this.qt_cache.get(this.se)) === null || _c === void 0 ? void 0 : _c.query(range, found);
(_d = this.qt_cache.get(this.sw)) === null || _d === void 0 ? void 0 : _d.query(range, found);
}
else {
this.getData().forEach(function (spatial) { return range.containsPoint(spatial) && found.add(spatial); });
}
return found;
};
QuadTree.prototype._remove = function (id) {
var _a;
var maybe_data = this.data_cache.get(id);
if ((0, onsreo_1.is_none)(maybe_data))
return;
var _b = __read(maybe_data, 2), data = _b[0], parent_id = _b[1];
(_a = this.qt_cache.get(parent_id)) === null || _a === void 0 ? void 0 : _a.remove(data);
};
/**
* remove the given spatial from the quadtree.
* @param spatial - spatial to remove.
*/
QuadTree.prototype.remove = function (spatial) {
this._remove(spatial.id);
};
/**
* remove a spatial by id.
* @param id - id of spatial to remove.
*/
QuadTree.prototype.removeById = function (id) {
this._remove(id);
};
/**
* remove all spatials with the provided IDs.
* @param removedIds - array of uuids to remove.
*/
QuadTree.prototype.removeByIds = function (removedIds) {
var _this = this;
removedIds.forEach(function (id) { return _this._remove(id); });
};
/**
* remove many spatials from the quadtree.
* @param spatials - spatials to remove.
*/
QuadTree.prototype.removeMany = function (spatials) {
var _this = this;
spatials.forEach(function (spatial) { return _this._remove(spatial.id); });
};
/**
* update a spatial point.
* @param spatial - spatial point to update.
*/
QuadTree.prototype.update = function (spatial) {
var _a, _b;
var maybe_data = this.data_cache.get(spatial.id);
if ((0, onsreo_1.is_none)(maybe_data)) {
// if no parents, then we only need to insert at root
(_a = this.qt_cache.get(this.root)) === null || _a === void 0 ? void 0 : _a.insert(spatial);
return;
}
var _c = __read(maybe_data, 2), _data = _c[0], parent_id = _c[1];
var maybe_parent = this.qt_cache.get(parent_id);
var invalid = (0, onsreo_1.is_none)(maybe_parent) ||
!maybe_parent.boundary ||
!maybe_parent.boundary.containsPoint(spatial);
// IF parent is NONE
// OR has a bad boundary
// OR is not contained within this boundary
// THEN remove the point from its parent
// AND reinsert from root
// ELSE do nothing, since no change requried
if (invalid) {
this._remove(spatial.id);
(_b = this.qt_cache.get(this.root)) === null || _b === void 0 ? void 0 : _b.insert(spatial);
}
};
/**
* returns all spatials within the tree.
* @param spatials - optional starting set [default: empty Set].
* @returns All contained spatials within the tree.
*/
QuadTree.prototype.getAllSpatials = function (spatials) {
var _a, _b, _c, _d, _e, _f, _g, _h;
if (spatials === void 0) { spatials = new Set(); }
if (this.divided) {
spatials =
(_b = (_a = this.qt_cache.get(this.ne)) === null || _a === void 0 ? void 0 : _a.getAllSpatials(spatials)) !== null && _b !== void 0 ? _b : spatials;
spatials =
(_d = (_c = this.qt_cache.get(this.nw)) === null || _c === void 0 ? void 0 : _c.getAllSpatials(spatials)) !== null && _d !== void 0 ? _d : spatials;
spatials =
(_f = (_e = this.qt_cache.get(this.se)) === null || _e === void 0 ? void 0 : _e.getAllSpatials(spatials)) !== null && _f !== void 0 ? _f : spatials;
spatials =
(_h = (_g = this.qt_cache.get(this.sw)) === null || _g === void 0 ? void 0 : _g.getAllSpatials(spatials)) !== null && _h !== void 0 ? _h : spatials;
}
else {
this.getData().forEach(function (spatial) { return spatials.add(spatial); });
}
return spatials;
};
/**
* update the bounds of the current quadtree
* @param newBounds - the bounds for the quadtree.
* @param spatials - optional spatials to insert [default: all previously inserted spatials]
*/
QuadTree.prototype.updateBounds = function (newBounds, spatials) {
var _this = this;
if (spatials === void 0) { spatials = this.getAllSpatials(); }
// clear structure, update bounds, and insert all the exising or
// provided spatials while also clearing their parents
this.reset(newBounds);
spatials.forEach(function (spatial) {
_this.remove(spatial);
_this.insert(spatial);
});
};
return QuadTree;
}());
exports.QuadTree = QuadTree;
//# sourceMappingURL=quadtree.js.map