空间索引的实现方式:Rtree 和其变种树 GIST-Tree、quad-tree(四叉树)、bin(网格索引)
所有的空间索引都是先插入数据,把数据在内部数据结构进行划分,然后才能进行 query。
这里介绍 boost 的开源 R-tree 库。
boost R-tree
构造
// index 在 namespace boost::geometry 里
rtree<Value,
Parameters,
IndexableGetter = index::indexable<Value>,
EqualTo = index::equal_to<Value>,
Allocator = std::allocator<Value> >
# 创建一个 rtree 对象
index::rtree<Value, index::linear<16>> rt;
rtree 的第一个参数 value,必须要是能提取出 indexable 的对象,默认的有 boost 库的 point, box, segment。可以接受 pair 和 tuple, 但是这两个数据结构的第一个参数必须是 indexable 的。
第二个参数表示 R-tree 插入和分割的算法类型,有下面三种算法可选。
index::linear<16>: 使用线性复杂性的方法来维护 R-tree。在插入和删除操作时,它会尝试保持节点的均衡,使得树的高度保持较小。由于它的复杂性是线性的,所以在某些情况下可能比其他算法更快。但是,它在处理大量数据和频繁更新时可能会失去一些性能。
index::quadratic<16>: 使用平方复杂性的方法来维护 R-tree。在插入和删除操作时更加注重节点的均衡,相对于线性算法,它可能在某些情况下提供更好的查询性能。但是,它的更新操作可能比线性算法慢一些。
index::rstar<16>: R*-tree 算法,一种优化的算法,旨在最小化节点的重叠并通过强制重新插入来进行平衡。这可以提高查询性能并减少树的高度。它通常在需要高性能的查询场景中使用,但可能会在更新操作时变得更加复杂。
每种平衡算法都有其优缺点,最适合的选择取决于您的数据和应用程序的要求。如果您的数据集和查询方式是已知的,可以通过比较不同算法在实际情况下的性能表现来做出选择。
Query
三种 query 方式:
- spatial predicates - spatial conditions that must be met by stored Value and some Geometry
- distance predicates - distance conditions that must be met by stored Value and some Geometry
- user-defined unary predicate - function, function object or lambda expression checking user-defined condition
Spatial queries
第一种是空间查询,查询和某个区域相关的操作。
std::vector<Value> result;
rt.query(index::contains(box), std::back_inserter(result));
rt.query(index::covered_by(box), std::back_inserter(result));
rt.query(index::covers(box), std::back_inserter(result));
rt.query(index::disjont(box), std::back_inserter(result));
rt.query(index::intersects(box), std::back_inserter(result));
rt.query(index::overlaps(box), std::back_inserter(result));
rt.query(index::within(box), std::back_inserter(result));
除了 box,还可以查询 segment
Nearest neighbours queries
查询和某个区域最近的元素。
std::vector<Value> result;
Point pt(/*...*/);
# k 表示返回离点 pt 最近的 k 个元素
rt.query(bgi::nearest(pt, k), std::back_inserter(result));
Box box(/*...*/);
rt.query(bgi::nearest(box, k), std::back_inserter(result));
Segment seg(/*...*/);
rt.query(bgi::nearest(seg, k), std::back_inserter(result));
User-defined unary predicate
用户可以自定义额外的条件,查询的结果是:满足查询条件,并且满足用户自定义的条件的元素。
bool is_red(Value const& v)
{
return v.is_red();
}
struct is_red_o
{
template <typename Value>
bool operator()(Value const& v)
{
return v.is_red();
}
}
// ...
rt.query(index::intersects(box) && index::satisfies(is_red),
std::back_inserter(result));
rt.query(index::intersects(box) && index::satisfies(is_red_o()),
std::back_inserter(result));
rt.query(index::intersects(box) && index::satisfies([](Value const& v) { return v.is_red(); }),
std::back_inserter(result));
Passing set of predicates
可以传递多个查询条件
rt.query(index::intersects(box1) && !index::within(box2),
std::back_inserter(result));
rt.query(index::intersects(box1) && !index::within(box2) && index::overlaps(box3),
std::back_inserter(result));
不同类型的查询也可以放在一起
index::query(rt, index::nearest(pt, k) && index::within(b), std::back_inserter(returned_values));
BOOST_FOREACH(Value & v, rt | index::adaptors::queried(index::nearest(pt, k) && index::covered_by(b)))
; // do something with v
Iterative queries
可以用迭代器遍历查询的结果。
for ( Rtree::const_query_iterator it = tree.qbegin(bgi::nearest(pt, 10000)) ;
it != tree.qend() ; ++it )
{
// do something with value
if ( has_enough_nearest_values() )
break;
}
Inserting query results into another R-tree
查询的结果可以插入另一个 RTree
namespace bgi = boost::geometry::index;
typedef std::pair<Box, int> Value;
typedef bgi::rtree< Value, bgi::linear<32, 8> > RTree;
RTree rt1;
/* some inserting into the tree */
std::vector<Value> result;
rt1.query(bgi::intersects(Box(/*...*/)), std::back_inserter(result));
RTree rt2(result.begin(), result.end());
RTree rt3;
rt1.query(bgi::intersects(Box(/*...*/))), bgi::inserter(rt3));
RTree rt4(rt1 | bgi::adaptors::queried(bgi::intersects(Box(/*...*/)))));
bin
fastBin
对于 bin 算法的理解:
- 通过 add 获取到所有的数据
- 构建 bin,根据数据的多少,划分 bin 的格点。
- 遍历所有的数据,构建每个格点和在格点上的数据的联系。
- query 时,所有在 query 区域内部的格点的数据直接加入结果,只需要计算touch 的格点。
格点之间的关系可以分为 body x y corner, 这样可以简化计算
去重:smallbin 只返回包含左下角
fastbin: 每个shape 只返回一次
data structure
vector<vector<unsigned>> _nodes; // nx, ny * 4
// _nodes.resize(_nx), _nodes[0].resize(ny << 2, UINT_MAX);
// first is data, second is next on the corner list
mutable deque<pair<T, unsigned>> _data;
// first is data index, second is next on the list
deque<pair<unsigned, unsigned>> _indexes;
addBin
对每一个 shape addBin 的时候
- 先计算出这个 shape 所在的 bin 格点的范围。getRange
- 遍历所有 shape 所在的格点,计算 shape 和 每个格点的关系:getListType
- 然后 addList, 获取当前格点中已有的同类型的 shape
_nodes[x][(y << 2) + lt]
cur- 如果 shape 的 listType 是 corner: _data 中当前 shape 的下一个指向 cur, nodes 中当前格点的这个类型的对象变为当前 shape
- 如果 shape 中的 listTpe 不是 corner:
query
- 获取要查询的 box 属于 bin 中的哪些格点
- 遍历每一个格点
- 获取当前格点的 ListType: myType, 这个格点在所有和 box 有关的格点中的位置,body,x,y,还是 corner
- 如果当前格点属于box 的内部格点,don’tcheck
- 遍历当前格点的 4 种 ListType
不会重复计算:通过下面的比较方式
c check c, x, y, b
x chekc c, y
y chekc c, x
b check c
smallBin
query:
getRange(): 获取 box 在 bin 上的范围 x1~x2 y1~y2
遍历所有格点,获取每一个格点相关的data list; 遍历 data list,每一个数据再具体比较,判断是不是在 box 上
checkBox 只有在 corner 位置的data 才会加入查询结果,所以不会有重复的问题