文章目录
一、关于 Rtree
Rtree 是基于 ctypes 的 Python 封装库,它实现了 libspatialindex 的核心功能,为需要处理空间数据的 Python 开发者提供了一系列高级空间索引特性,包括:
- 最近邻搜索
- 交叉区域搜索
- 多维索引
- 聚簇索引(可直接将 Python 对象序列化存储于索引条目中)
- 批量加载
- 数据删除
- 磁盘序列化
- 自定义存储实现(例如可在 ZODB 中实现空间索引)
- GitHub:https://github.com/Toblerity/rtree
- 官方文档:https://rtree.readthedocs.io/en/latest/
- PyPI:https://badge.fury.io/py/rtree
二、安装
1、*nix 系统
首先,从以下网址下载并安装 1.8.5 及以上版本的 libspatialindex 库:
https://libspatialindex.org
该库支持 CMake 构建,因此只需执行以下操作:
$ mkdir build && cd build
$ cmake ..
$ cmake --build . -j
$ cmake --install .
安装库后,你可能需要运行 ldconfig
命令,以确保应用程序在启动时能找到它。
Rtree 可以通过 pip 轻松安装:
$ pip install rtree
或者在本地源代码目录中运行:
$ pip install -e .
你可以像这样就地构建和测试:
$ pytest
2、Windows
libspatialindex 的 Windows DLL 文件已预编译为 Windows 安装包,可通过 PyPI 获取。在 Windows 系统上的安装非常简单:
pip install rtree
为您的受众变现: 通过EthicalAds(一个隐私至上的广告网络)为开源项目或网站筹集资金
三、教程
本教程演示如何利用 Rtree 来查询具有空间组件(可建模为边界框)的数据。
1、创建索引
以下部分介绍 Rtree 的基本实例化及使用方法。
1.1 导入
在完成 Rtree 的安装后,您应该能够打开 Python 提示符并执行以下操作:
from rtree import index
rtree
以 Python 包的形式组织,包含若干模块和两个主要类—— rtree.index.Index
和 rtree.index.Property
。用户通过操作这些类来与索引进行交互。
1.2 构建实例
导入索引模块后,使用默认构造方法构建索引:
idx = index.Index()
注意:虽然默认构造方式在许多情况下很有用,但如果您需要控制索引的构建方式,在创建索引时必须传入一个 rtree.index.Property
实例。
1.3 创建边界框
实例化索引后,创建一个可插入到索引中的边界框:
left, bottom, right, top = (0.0, 0.0, 1.0, 1.0)
注意:所有函数的坐标顺序对索引的interleaved
数据成员敏感。如果interleaved
为False,坐标必须采用 [xmin, xmax, ymin, ymax, …, …, kmin, kmax]
的形式。
如果interleaved
为True,坐标必须采用 [xmin, ymin, …, kmin, xmax, ymax, …, kmax]
的形式。
1.4 向索引中插入记录
向索引插入一条记录:
idx.insert(0, (left, bottom, right, top))
注意:插入索引的条目在ID或与索引条目一起插入的边界框方面都不是唯一的。如果需要保持唯一性,必须在将条目插入Rtree之前自行管理这一点。
注意:插入一个点(即left == right && top == bottom的情况)实际上会向索引中插入一个单点条目,而不是复制额外坐标再插入。但目前没有直接插入单点的快捷方式。
1.5 查询索引
查询索引主要有三种方法。 rtree.index.Index.intersection()
会返回与给定查询窗口相交或被包含在内的索引条目。
1.5.1 交集
给定一个查询窗口,返回包含在该窗口内的ID:
list(idx.intersection((1.0, 1.0, 2.0, 2.0)))
[0]
给定一个超出索引数据范围的查询窗口:
list(idx.intersection((1.0000001, 1.0000001, 2.0, 2.0)))
[]
1.5.2 最近邻搜索
以下代码用于查找与给定边界距离最近的1个项。如果存在多个项与边界的距离相等,则全部返回:
idx.insert(1, (left, bottom, right, top))
list(idx.nearest((1.0000001, 1.0000001, 2.0, 2.0), 1))
[0, 1]
2、将Rtree作为简易空间数据库使用
Rtree还支持将任何可序列化(pickle)的对象插入索引中(在libspatialindex术语中称为聚集索引)。
以下示例展示了如何将可序列化对象42
以指定ID插入索引:
idx.insert(id=id, coordinates=(left, bottom, right, top), obj=42)
然后你可以通过给 intersection 方法传入 objects=True
参数来返回一个对象列表:
[n.object for n in idx.intersection((left, bottom, right, top), objects=True)]
[None, None, 42]
警告 : libspatialindex 的聚类索引并非设计用作数据库。它不具备数据库应有的数据完整性保护机制,但 Rtree 的这种特性仍然有其应用价值。请务必注意这一限制。现在,去用它实现些酷炫的功能吧。
3、将索引序列化到文件
Rtree 最有用的特性之一是将 Rtree 索引序列化到磁盘的能力。这包括此处描述的聚集索引。
file_idx = index.Rtree('rtree')
file_idx.insert(1, (left, bottom, right, top))
file_idx.insert(2, (left - 1.0, bottom - 1.0, right + 1.0, top + 1.0))
[n for n in file_idx.intersection((left, bottom, right, top))]
[1, 2]
注意:默认情况下,如果文件系统中已存在指定名称的索引文件(如上例中的rtree),系统将以追加模式打开该文件而不会重新创建。
您可以通过 rtree.index.Property.overwrite
属性来控制此行为,该属性可在 rtree.index.Index
构造函数中设置。
另请参阅 : 性能优化 文档描述了一些可调参数,这些参数可以提升基于文件的索引运行速度。参数的选择完全取决于您的具体使用场景。
修改文件名
Rtree在将索引数据序列化到磁盘时,默认会创建两个扩展名为.dat
和.idx
的索引文件。可以通过以下索引属性控制这些文件扩展名:
p = rtree.index.Property()
p.dat_extension = 'data'
p.idx_extension = 'index'
file_idx = index.Index('rtree', properties = p)
4、3D索引
从Rtree 0.5.0版本开始,您可以创建3D(实际上是 kD)索引。
以下是一个将存储在磁盘上的3D索引示例。持久化索引使用两个文件存储在磁盘上——索引文件(.idx)和数据文件(.dat
)。
您可以通过在实例化时修改索引的属性来更改这些文件使用的扩展名。
以下代码创建一个3D索引,该索引将以3d_index.data
和3d_index.index
文件名存储在磁盘上:
from rtree import index
p = index.Property()
p.dimension = 3
p.dat_extension = 'data'
p.idx_extension = 'index'
idx3d = index.Index('3d_index',properties=p)
idx3d.insert(1, (0, 60, 23.0, 0, 60, 42.0))
idx3d.intersection( (-1, 62, 22, -1, 62, 43))
[1L]
5、ZODB与自定义存储
https://mail.zope.org/pipermail/zodb-dev/2010-June/013491.html 包含了一个针对ZODB的自定义存储后端实现,示例Python代码可在此处查看here。
需要注意的是,该代码写于2011年且未再更新,仅为alpha版本。
四、性能
查看 benchmarks.py 文件,了解不同查询方法的对比以及使用 Rtree 能获得多少加速效果。
这里有几个简单的技巧可以提升性能。
1、使用流式加载
相比 insert()
方法,这种方式通过允许数据预排序,能显著提升性能(在许多情况下可达数量级提升)。
def generator_function(somedata):
for i, obj in enumerate(somedata):
yield (i, (obj.xmin, obj.ymin, obj.xmax, obj.ymax), obj)
r = index.Index(generator_function(somedata))
批量加载索引后,您可以使用 insert()
方法向索引中插入额外的记录。
2、重写 dumps
方法以使用最高 pickle 协议
import cPickle, rtree
class FastRtree(rtree.Rtree):
def dumps(self, obj):
return cPickle.dumps(obj, -1)
r = FastRtree()
2024年1月更新说明
当前Pickling功能存在故障,正在等待合并拉取请求进行修复。更多详情请参阅GitHub上的拉取请求。
3、使用 objects=‘raw’
在任何 intersection()
或 nearest()
查询中,使用 objects=‘raw’ 关键字参数
objs = r.intersection((xmin, ymin, xmax, ymax), objects="raw")
4、调整索引属性
根据您的索引需求调整 rtree.index.Property
。
- 将
leaf_capacity
设置为高于默认值100的数值。对于默认4096的页面大小,1000+通常是不错的选择。 - 将
fill_factor
提高到接近0.9的值。较小的填充因子会导致更多分裂,从而产生更多节点。这可能是好事也可能是坏事,具体取决于您的使用场景。
5、将维度限制在所需范围内
不要使用超过实际需要的维度数量。如果只需要2个维度,就只用两个。否则会浪费大量存储空间,并在索引的每次查询、搜索和插入操作中增加大量浮点数比较。
6、使用正确的查询方法
如果只需要计数,请使用 count()
;如果只需要ID,请使用 intersection()
。否则可能会复制大量数据。如果可能,还应使用以 _v 结尾的批量查询方法。
六、Rtree 发展历史
Rtree 最初由 Sean Gillies 开发,作为 QGIS 维护的 libspatialindex 链接库的移植版本,用于为 GUI 操作提供实时索引支持。R-tree 的一个显著特点是无需全局分区边界即可向结构中插入数据,这一特性促使 Sean 采用了该代码。Howard Butler 后来接手了 Rtree 项目,通过为 libspatialindex 编写 C API 并将其重写为 ctypes 封装器来使用该 C API,从而添加了包括磁盘序列化和批量加载在内的多项功能。Brent Pedersen 随后加入,增加了支持替代坐标排序、增强 pickle 存储功能以及大量文档。Mattias (http://dr-code.org) 添加了自定义存储后端支持,使 Rtree 能够作为 ZODB 中的索引类型使用。
Rtree 经历了多次迭代,在 0.5.0 版本时进行了彻底重构,采用了基于 libspatialindex 的 ctypes + C API 新内部架构。这次重构带来了许多新功能和更高的灵活性。详见 变更日志。
注意 : 在 1.6.1+ 版本的 libspatialindex C API 中发现了一个严重错误:它使用无符号整数而非有符号整数作为索引条目 ID。由于当时 Rtree 似乎是该 C API 的唯一重要用户,此问题被立即修复。如果这对您的应用很重要,请立即更新并重新将数据插入新索引。
Rtree 0.5.0 包含的 C 库现已成为 libspatialindex 的 C API 并纳入其源代码树。两个代码库彼此独立,现在可以分别演进。从 0.6.0+ 版本开始,Rtree 是纯 Python 实现。
2025-08-04(一)