一、空间索引
1.1 简介
空间索引是指依据空间对象的位置和形状或空间对象之间的某种空间关系按一定的顺序排列的一种数据结构 ,其中包含空间对象的概要信息,如对象的标识、外接矩形及指向空间对象实体的指针。
空间数据查询即空间索引,是对存储在介质上的数据位置信息的描述,是用来提高系统对数据获取的效率,也称为空间访问方法(Spatial Access Method SAM)。是指依据空间对象的位置和形状或空间对象之间的某种空间关系按一定的顺序排列的一种数据结构其中包含空间对象的概要信息如对象的标识外接矩形及指向空间对象实体的指针。
作为一种辅助性的空间数据结构空间索引介于空间操作算法和空间对象之间它通过筛选作用 [1]大量与特定空间操作无关的空间对象被排除从而提高空间操作的速度和效率。
1.2 索引
对一个数据集做“索引”,是为了提高对这个数据集检索的效率。书的”目录“就是这本书内容的”索引“,当我们拿到一本新书,想查看感兴趣内容的时候,我们会先查看目录,确定感兴趣的内容会在哪些页里 ,直接翻到那些页,就OK了,而不是从第一章节开始翻,一个字一个字地找我们感兴趣的内容,直到找到为止,这种检索内容的效率也太低了,如果一本书没有目录,可以想象有多么不方便…指可空间在索引查找中的重要性 可见书的目录有多重要索引有多重要啊
1.3 现状
作为一种辅助性的空间数据结构,空间索引介于空间操作算法和空间对象之间,它通过筛选作用,大量与特定空间操作无关的空间对象被排除,从而提高空间操作的速度和效率。
常见空间索引类型有BSP树、K-D-B树、R树、R+树和CELL树,空间索引的性能的优越直接影响空间数据库和地理信息系统的整体性能。
二、R树
2.1 概念
R树(R-tree)是一种平衡树数据结构,用于存储空间数据,如多维对象的边界框。它广泛应用于地理信息系统(GIS)、数据库索引、以及其他需要高效执行空间查询的领域。R树通过将对象组织到相互重叠的最小边界矩形(Minimum Bounding Rectangles, MBRs)中,来优化范围查询、最近邻查询等操作。
2.2 术语
- 节点:R树中的节点分为内部节点和叶子节点。内部节点存储其子节点所包含对象的最小边界矩形,而叶子节点直接存储空间对象及其边界。
- 最小边界矩形(MBR):对于每个节点,计算其所有子节点(或对象)边界框的最小外包矩形,作为该节点的表示。
- 分裂策略:当插入新对象导致节点容量超限时,需要进行节点分裂。常见的分裂策略有轴向分裂、最小面积增加分裂等。
三、RBush
3.1 说明
rbush 是一个高效、基于 R 树算法的 JavaScript 空间索引库,适用于处理大量的二维空间数据集,优化碰撞检测、地理空间查询等场景。
3.2 使用
1、npm install rbush
2、yarn add rbush
3.3 自定义
1、用于个人的代码设定
import RBush from "rbush";
export default class CustomRBush extends RBush {
constructor(maxEntries) {
super(maxEntries);
}
contains(a, b) {
return (
a.minX <= b.minX &&
a.minY <= b.minY &&
b.maxX <= a.maxX &&
b.maxY <= a.maxY
);
}
intersects(a, b) {
return (
b.minX <= a.maxX &&
b.minY <= a.maxY &&
b.maxX >= a.minX &&
b.maxY >= a.minY
);
}
findItem(item, items, equalsFn) {
let index = items.findIndex((item2) => item.id === item2.id);
return index;
}
/**
*
* @param bbox
* @param { CullingVolume } cullingVolume
* @return {*[]}
*/
search(bbox) {
let node = this.data;
const result = [];
if (!this.intersects(bbox, node)) return result;
const toBBox = this.toBBox;
const nodesToSearch = [];
while (node) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const childBBox = node.leaf ? toBBox(child) : child;
if (this.intersects(bbox, childBBox)) {
if (node.leaf) result.push(child);
else if (this.contains(bbox, childBBox)) this._all(child, result);
else nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return result;
}
update(item) {
if (!item) return this;
let node = this.data;
const bbox = this.toBBox(item);
const path = [];
const indexes = [];
let i, parent, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) {
// go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) {
// check current node
const index = this.findItem(item, node.children);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1, item);
path.push(node);
this._condense(path);
break;
}
}
if (!goingUp && !node.leaf && this.contains(node, bbox)) {
// go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) {
// go right
i++;
node = parent.children[i];
goingUp = false;
} else node = null; // nothing found
}
return this;
}
remove(item) {
if (!item) {
return this;
}
var node = this.data;
var bbox = this.toBBox(item);
var path = [];
var indexes = [];
var i, parent, goingUp;
// depth-first iterative tree traversal
while (node || path.length) {
if (!node) {
// go up
node = path.pop();
parent = path[path.length - 1];
i = indexes.pop();
goingUp = true;
}
if (node.leaf) {
// check current node
var index = this.findItem(item, node.children);
if (index !== -1) {
// item found, remove the item and condense tree upwards
node.children.splice(index, 1);
path.push(node);
this._condense(path);
return this;
}
}
if (!goingUp && !node.leaf && this.contains(node, bbox)) {
// go down
path.push(node);
indexes.push(i);
i = 0;
parent = node;
node = node.children[0];
} else if (parent) {
// go right
i++;
node = parent.children[i];
goingUp = false;
} else {
node = null;
} // nothing found
}
return this;
}
}
2、新增
/**
* 设备索引树
* @type {CustomRBush}
* @private
*/
this._spatialIndex = new CustomRBush(8);
let extent = {
//最小经度
minX: box[0],
//最小纬度
minY: box[1],
//最大经度
maxX: box[2],
//最大纬度
maxY: box[3],
};
this._spatialIndex.insert({
...extent,
//设备编号
id: device.id,
});
3、更新
let extent = {
//最小经度
minX: box[0],
//最小纬度
minY: box[1],
//最大经度
maxX: box[2],
//最大纬度
maxY: box[3],
};
this._spatialIndex.update({
...extent,
//设备编号
id: device.id,
});
4、查找
let extent = {
//最小经度
minX: box[0],
//最小纬度
minY: box[1],
//最大经度
maxX: box[2],
//最大纬度
maxY: box[3],
};
let deviceList = this._spatialIndex.search(extent);