fastquadtree 1.5.0__cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastquadtree/__init__.py +5 -0
- fastquadtree/_base_quadtree.py +543 -0
- fastquadtree/_item.py +91 -0
- fastquadtree/_native.abi3.so +0 -0
- fastquadtree/_obj_store.py +194 -0
- fastquadtree/point_quadtree.py +206 -0
- fastquadtree/py.typed +0 -0
- fastquadtree/pyqtree.py +176 -0
- fastquadtree/rect_quadtree.py +230 -0
- fastquadtree-1.5.0.dist-info/METADATA +206 -0
- fastquadtree-1.5.0.dist-info/RECORD +13 -0
- fastquadtree-1.5.0.dist-info/WHEEL +5 -0
- fastquadtree-1.5.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# rect_quadtree.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Literal, SupportsFloat, Tuple, overload
|
|
5
|
+
|
|
6
|
+
from ._base_quadtree import Bounds, _BaseQuadTree
|
|
7
|
+
from ._item import Point, RectItem
|
|
8
|
+
from ._native import (
|
|
9
|
+
RectQuadTree as RectQuadTreeF32,
|
|
10
|
+
RectQuadTreeF64,
|
|
11
|
+
RectQuadTreeI32,
|
|
12
|
+
RectQuadTreeI64,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_IdRect = Tuple[int, SupportsFloat, SupportsFloat, SupportsFloat, SupportsFloat]
|
|
16
|
+
|
|
17
|
+
DTYPE_MAP = {
|
|
18
|
+
"f32": RectQuadTreeF32,
|
|
19
|
+
"f64": RectQuadTreeF64,
|
|
20
|
+
"i32": RectQuadTreeI32,
|
|
21
|
+
"i64": RectQuadTreeI64,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
|
|
26
|
+
"""
|
|
27
|
+
Rectangle version of the quadtree. All geometries are axis-aligned rectangles. (min_x, min_y, max_x, max_y)
|
|
28
|
+
High-level Python wrapper over the Rust quadtree engine.
|
|
29
|
+
|
|
30
|
+
Performance characteristics:
|
|
31
|
+
Inserts: average O(log n) <br>
|
|
32
|
+
Rect queries: average O(log n + k) where k is matches returned <br>
|
|
33
|
+
Nearest neighbor: average O(log n) <br>
|
|
34
|
+
|
|
35
|
+
Thread-safety:
|
|
36
|
+
Instances are not thread-safe. Use external synchronization if you
|
|
37
|
+
mutate the same tree from multiple threads.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
bounds: World bounds as (min_x, min_y, max_x, max_y).
|
|
41
|
+
capacity: Max number of points per node before splitting.
|
|
42
|
+
max_depth: Optional max tree depth. If omitted, engine decides.
|
|
43
|
+
track_objects: Enable id <-> object mapping inside Python.
|
|
44
|
+
dtype: Data type for coordinates and ids in the native engine. Default is 'f32'. Options are 'f32', 'f64', 'i32', 'i64'.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ValueError: If parameters are invalid or inserts are out of bounds.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
bounds: Bounds,
|
|
53
|
+
capacity: int,
|
|
54
|
+
*,
|
|
55
|
+
max_depth: int | None = None,
|
|
56
|
+
track_objects: bool = False,
|
|
57
|
+
dtype: str = "f32",
|
|
58
|
+
):
|
|
59
|
+
super().__init__(
|
|
60
|
+
bounds,
|
|
61
|
+
capacity,
|
|
62
|
+
max_depth=max_depth,
|
|
63
|
+
track_objects=track_objects,
|
|
64
|
+
dtype=dtype,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@overload
|
|
68
|
+
def query(
|
|
69
|
+
self, rect: Bounds, *, as_items: Literal[False] = ...
|
|
70
|
+
) -> list[_IdRect]: ...
|
|
71
|
+
@overload
|
|
72
|
+
def query(self, rect: Bounds, *, as_items: Literal[True]) -> list[RectItem]: ...
|
|
73
|
+
def query(
|
|
74
|
+
self, rect: Bounds, *, as_items: bool = False
|
|
75
|
+
) -> list[_IdRect] | list[RectItem]:
|
|
76
|
+
"""
|
|
77
|
+
Query the tree for all items that intersect the given rectangle.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
rect: Query rectangle as (min_x, min_y, max_x, max_y).
|
|
81
|
+
as_items: If True, return Item wrappers. If False, return raw tuples.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
If as_items is False: list of (id, x0, y0, x1, y1) tuples.
|
|
85
|
+
If as_items is True: list of Item objects.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
```python
|
|
89
|
+
results = rqt.query((10.0, 10.0, 20.0, 20.0), as_items=True)
|
|
90
|
+
for item in results:
|
|
91
|
+
print(f"Found rect id={item.id_} at {item.geom} with obj={item.obj}")
|
|
92
|
+
```
|
|
93
|
+
"""
|
|
94
|
+
if not as_items:
|
|
95
|
+
return self._native.query(rect)
|
|
96
|
+
if self._store is None:
|
|
97
|
+
raise ValueError("Cannot return results as items with track_objects=False")
|
|
98
|
+
return self._store.get_many_by_ids(self._native.query_ids(rect))
|
|
99
|
+
|
|
100
|
+
def query_np(self, rect: Bounds) -> tuple[Any, Any]:
|
|
101
|
+
"""
|
|
102
|
+
Return all points inside an axis-aligned rectangle as NumPy arrays.
|
|
103
|
+
The first array is an array of IDs, and the second is a corresponding array of rectangle coordinates.
|
|
104
|
+
|
|
105
|
+
Requirements:
|
|
106
|
+
NumPy must be installed to use this method.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
rect: Query rectangle as (min_x, min_y, max_x, max_y).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Tuple of (ids, locations) where:
|
|
113
|
+
ids: NDArray[np.uint64] with shape (N,)
|
|
114
|
+
locations: NDArray[np.floating] with shape (N, 4)
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
```python
|
|
118
|
+
ids, locations = rqt.query_np((10.0, 10.0, 20.0, 20.0))
|
|
119
|
+
for id_, (x0, y0, x1, y1) in zip(ids, locations):
|
|
120
|
+
print(f"Found rect id={id_} at ({x0}, {y0}, {x1}, {y1})")
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
return self._native.query_np(rect)
|
|
125
|
+
|
|
126
|
+
@overload
|
|
127
|
+
def nearest_neighbor(
|
|
128
|
+
self, xy: Point, *, as_item: Literal[False] = ...
|
|
129
|
+
) -> _IdRect | None: ...
|
|
130
|
+
@overload
|
|
131
|
+
def nearest_neighbor(
|
|
132
|
+
self, xy: Point, *, as_item: Literal[True]
|
|
133
|
+
) -> RectItem | None: ...
|
|
134
|
+
def nearest_neighbor(
|
|
135
|
+
self, xy: Point, *, as_item: bool = False
|
|
136
|
+
) -> RectItem | _IdRect | None:
|
|
137
|
+
"""
|
|
138
|
+
Return the single nearest neighbor to the query point.
|
|
139
|
+
Utilizes euclidean distance to the nearest edge of rectangles.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
xy: Query point (x, y).
|
|
143
|
+
as_item: If True, return Item. If False, return (id, x0, y0, x1, y1).
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
The nearest neighbor or None if the tree is empty.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
```python
|
|
150
|
+
nn = rqt.nearest_neighbor((15.0, 15.0), as_item=True)
|
|
151
|
+
if nn is not None:
|
|
152
|
+
print(f"Nearest rect id={nn.id_} at {nn.geom} with obj={nn.obj}")
|
|
153
|
+
else:
|
|
154
|
+
print("No rectangles in the tree.")
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
157
|
+
t = self._native.nearest_neighbor(xy)
|
|
158
|
+
if t is None or not as_item:
|
|
159
|
+
return t
|
|
160
|
+
if self._store is None:
|
|
161
|
+
raise ValueError("Cannot return result as item with track_objects=False")
|
|
162
|
+
id_, _x0, _y0, _x1, _y1 = t
|
|
163
|
+
it = self._store.by_id(id_)
|
|
164
|
+
if it is None:
|
|
165
|
+
raise RuntimeError("Internal error: missing tracked item")
|
|
166
|
+
return it
|
|
167
|
+
|
|
168
|
+
@overload
|
|
169
|
+
def nearest_neighbors(
|
|
170
|
+
self, xy: Point, k: int, *, as_items: Literal[False] = ...
|
|
171
|
+
) -> list[_IdRect]: ...
|
|
172
|
+
@overload
|
|
173
|
+
def nearest_neighbors(
|
|
174
|
+
self, xy: Point, k: int, *, as_items: Literal[True]
|
|
175
|
+
) -> list[RectItem]: ...
|
|
176
|
+
def nearest_neighbors(
|
|
177
|
+
self, xy: Point, k: int, *, as_items: bool = False
|
|
178
|
+
) -> list[RectItem] | list[_IdRect]:
|
|
179
|
+
"""
|
|
180
|
+
Return the k nearest neighbors to the query point in order of increasing distance.
|
|
181
|
+
Utilizes euclidean distance to the nearest edge of rectangles.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
xy: Query point (x, y).
|
|
185
|
+
k: Number of neighbors to return.
|
|
186
|
+
as_items: If True, return Item wrappers. If False, return raw tuples.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
If as_items is False: list of (id, x0, y0, x1, y1) tuples. <br>
|
|
190
|
+
If as_items is True: list of Item objects. <br>
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
```python
|
|
194
|
+
# Gets the 3 nearest rectangles to point (15.0, 15.0)
|
|
195
|
+
results = rqt.nearest_neighbors((15.0, 15.0), 3, as_items=True)
|
|
196
|
+
for item in results:
|
|
197
|
+
print(f"Found rect id={item.id_} at {item.geom} with obj={item.obj}")
|
|
198
|
+
```
|
|
199
|
+
"""
|
|
200
|
+
raw = self._native.nearest_neighbors(xy, k)
|
|
201
|
+
if not as_items:
|
|
202
|
+
return raw
|
|
203
|
+
if self._store is None:
|
|
204
|
+
raise ValueError("Cannot return results as items with track_objects=False")
|
|
205
|
+
out: list[RectItem] = []
|
|
206
|
+
for id_, _x0, _y0, _x1, _y1 in raw:
|
|
207
|
+
it = self._store.by_id(id_)
|
|
208
|
+
if it is None:
|
|
209
|
+
raise RuntimeError("Internal error: missing tracked item")
|
|
210
|
+
out.append(it)
|
|
211
|
+
return out
|
|
212
|
+
|
|
213
|
+
def _new_native(self, bounds: Bounds, capacity: int, max_depth: int | None) -> Any:
|
|
214
|
+
"""Create the native engine instance."""
|
|
215
|
+
rust_cls = DTYPE_MAP.get(self._dtype)
|
|
216
|
+
if rust_cls is None:
|
|
217
|
+
raise TypeError(f"Unsupported dtype: {self._dtype}")
|
|
218
|
+
return rust_cls(bounds, capacity, max_depth)
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def _new_native_from_bytes(cls, data: bytes, dtype: str = "f32") -> Any:
|
|
222
|
+
"""Create a new native engine instance from serialized bytes."""
|
|
223
|
+
rust_cls = DTYPE_MAP.get(dtype)
|
|
224
|
+
if rust_cls is None:
|
|
225
|
+
raise TypeError(f"Unsupported dtype: {dtype}")
|
|
226
|
+
return rust_cls.from_bytes(data)
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def _make_item(id_: int, geom: Bounds, obj: Any | None) -> RectItem:
|
|
230
|
+
return RectItem(id_, geom, obj)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastquadtree
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
6
|
+
Classifier: Programming Language :: Rust
|
|
7
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Requires-Dist: ruff>=0.6.0 ; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.4.2 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-cov>=7.0.0 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: coverage>=7.5 ; extra == 'dev'
|
|
18
|
+
Requires-Dist: mypy>=1.10 ; extra == 'dev'
|
|
19
|
+
Requires-Dist: build>=1.2.1 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: mkdocs>=1.6 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: mkdocs-material ; extra == 'dev'
|
|
22
|
+
Requires-Dist: mkdocstrings[python] ; extra == 'dev'
|
|
23
|
+
Requires-Dist: mkdocs-autorefs ; extra == 'dev'
|
|
24
|
+
Requires-Dist: mkdocs-git-revision-date-localized-plugin ; extra == 'dev'
|
|
25
|
+
Requires-Dist: mkdocs-minify-plugin ; extra == 'dev'
|
|
26
|
+
Requires-Dist: maturin>=1.5 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: pyqtree==1.0.0 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: numpy ; extra == 'dev'
|
|
29
|
+
Requires-Dist: pre-commit ; extra == 'dev'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Summary: Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search.
|
|
33
|
+
Keywords: quadtree,spatial-index,geometry,rust,pyo3,nearest-neighbor,k-nn
|
|
34
|
+
Author: Ethan Anderson
|
|
35
|
+
Requires-Python: >=3.9
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
Project-URL: Homepage, https://github.com/Elan456/fastquadtree
|
|
38
|
+
Project-URL: Repository, https://github.com/Elan456/fastquadtree
|
|
39
|
+
Project-URL: Documentation, https://elan456.github.io/fastquadtree/
|
|
40
|
+
Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
|
|
41
|
+
|
|
42
|
+
# fastquadtree
|
|
43
|
+
|
|
44
|
+
<img src="https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/interactive_v2_screenshot.png"
|
|
45
|
+
alt="Interactive Screenshot" align="right" width="420">
|
|
46
|
+
|
|
47
|
+
Rust-optimized quadtree with a clean Python API
|
|
48
|
+
|
|
49
|
+
👉 **Check out the Docs:** https://elan456.github.io/fastquadtree/
|
|
50
|
+
|
|
51
|
+
[](https://pypi.org/project/fastquadtree/)
|
|
52
|
+
[](https://pypi.org/project/fastquadtree/)
|
|
53
|
+
[](https://pepy.tech/projects/fastquadtree)
|
|
54
|
+
[](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml)
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
[](https://pyo3.rs/)
|
|
58
|
+
[](https://www.maturin.rs/)
|
|
59
|
+
[](https://github.com/astral-sh/ruff)
|
|
60
|
+
|
|
61
|
+
[](https://elan456.github.io/fastquadtree/)
|
|
62
|
+
[](https://pypi.org/project/fastquadtree/#files)
|
|
63
|
+
[](https://codecov.io/gh/Elan456/fastquadtree)
|
|
64
|
+
[](LICENSE)
|
|
65
|
+
|
|
66
|
+
<br clear="right"/>
|
|
67
|
+
|
|
68
|
+
## Why use fastquadtree
|
|
69
|
+
|
|
70
|
+
- Clean [Python API](https://elan456.github.io/fastquadtree/api/quadtree/) with **no external dependencies** and modern typing hints
|
|
71
|
+
- The fastest quadtree Python package ([>10x faster](https://elan456.github.io/fastquadtree/benchmark/) than pyqtree)
|
|
72
|
+
- Prebuilt wheels for Windows, macOS, and Linux
|
|
73
|
+
- Support for [inserting bounding boxes](https://elan456.github.io/fastquadtree/api/rect_quadtree/) or points
|
|
74
|
+
- Fast KNN and range queries
|
|
75
|
+
- Optional object tracking for id ↔ object mapping
|
|
76
|
+
- Fast [serialization](https://elan456.github.io/fastquadtree/benchmark/#serialization-vs-rebuild) to/from bytes
|
|
77
|
+
- Support for multiple data types (f32, f64, i32, i64) for coordinates
|
|
78
|
+
- [100% test coverage](https://codecov.io/gh/Elan456/fastquadtree) and CI on GitHub Actions
|
|
79
|
+
- Offers a drop-in [pyqtree shim](https://elan456.github.io/fastquadtree/benchmark/#pyqtree-drop-in-shim-performance-gains) that is 6.567x faster while keeping the same API
|
|
80
|
+
|
|
81
|
+
----
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## Examples
|
|
85
|
+
See examples of how fastquadtree can be used in the [runnables](https://elan456.github.io/fastquadtree/runnables/) section.
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
```bash
|
|
90
|
+
pip install fastquadtree
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from fastquadtree import QuadTree # Point handling
|
|
95
|
+
from fastquadtree import RectQuadTree # Bounding box handling
|
|
96
|
+
from fastquadtree.pyqtree import Index # Drop-in pyqtree shim (6.567x faster while keeping the same API)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Benchmarks
|
|
100
|
+
|
|
101
|
+
fastquadtree **outperforms** all other quadtree Python packages, including the Rtree spatial index.
|
|
102
|
+
|
|
103
|
+
### Library comparison
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
### Summary (largest dataset, PyQtree baseline)
|
|
109
|
+
- Points: **250,000**, Queries: **500**
|
|
110
|
+
- Fastest total: **fastquadtree** at **0.030 s**
|
|
111
|
+
|
|
112
|
+
| Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
|
|
113
|
+
|---|---:|---:|---:|---:|
|
|
114
|
+
| fastquadtree | 0.023 | 0.007 | 0.030 | 41.31× |
|
|
115
|
+
| Shapely STRtree | 0.094 | 0.049 | 0.143 | 8.67× |
|
|
116
|
+
| nontree-QuadTree | 0.448 | 0.475 | 0.924 | 1.34× |
|
|
117
|
+
| Rtree | 0.801 | 0.225 | 1.025 | 1.21× |
|
|
118
|
+
| e-pyquadtree | 0.666 | 0.451 | 1.117 | 1.11× |
|
|
119
|
+
| PyQtree | 1.066 | 0.175 | 1.241 | 1.00× |
|
|
120
|
+
| quads | 0.969 | 0.330 | 1.299 | 0.96× |
|
|
121
|
+
|
|
122
|
+
See the [benchmark section](https://elan456.github.io/fastquadtree/benchmark/) for details, including configurations, system info, and native vs shim benchmarks.
|
|
123
|
+
|
|
124
|
+
## Quickstart
|
|
125
|
+
[See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/)
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
[See the full API](https://elan456.github.io/fastquadtree/api/quadtree/)
|
|
130
|
+
|
|
131
|
+
### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
|
|
132
|
+
|
|
133
|
+
* `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
|
|
134
|
+
* `capacity` — max number of points kept in a leaf before splitting
|
|
135
|
+
* `max_depth` — optional depth cap. If omitted, the tree can keep splitting as needed
|
|
136
|
+
* `track_objects` — if `True`, the wrapper maintains an id → object map for convenience.
|
|
137
|
+
* `start_id` — starting value for auto-assigned ids
|
|
138
|
+
|
|
139
|
+
### Key Methods
|
|
140
|
+
|
|
141
|
+
- `insert(xy, *, id=None, obj=None) -> int`
|
|
142
|
+
|
|
143
|
+
- `query(rect, *, as_items=False) -> list`
|
|
144
|
+
|
|
145
|
+
- `nearest_neighbor(xy, *, as_item=False) -> (id, x, y) | Item | None`
|
|
146
|
+
|
|
147
|
+
- `delete(id, xy) -> bool`
|
|
148
|
+
|
|
149
|
+
There are more methods and object tracking versions in the [docs](https://elan456.github.io/fastquadtree/api/quadtree/).
|
|
150
|
+
|
|
151
|
+
### Geometric conventions
|
|
152
|
+
|
|
153
|
+
* Rectangles are `(min_x, min_y, max_x, max_y)`.
|
|
154
|
+
* Containment rule is closed on the min edge and open on the max edge
|
|
155
|
+
`(x >= min_x and x < max_x and y >= min_y and y < max_y)`.
|
|
156
|
+
This only matters for points exactly on edges.
|
|
157
|
+
|
|
158
|
+
## Performance tips
|
|
159
|
+
|
|
160
|
+
* Choose `capacity` so that leaves keep a small batch of points. Typical values are 8 to 64.
|
|
161
|
+
* If your data is very skewed, set a `max_depth` to prevent long chains.
|
|
162
|
+
* For fastest local runs, use `maturin develop --release`.
|
|
163
|
+
* The wrapper maintains an object map only if the quadtree was constructed with `track_objects=True`. If you don't need it, leave it off for best performance.
|
|
164
|
+
* Refer to the [Native vs Shim Benchmark](https://elan456.github.io/fastquadtree/benchmark/#native-vs-shim-benchmark) for overhead details.
|
|
165
|
+
|
|
166
|
+
### Pygame Ball Pit Demo
|
|
167
|
+
|
|
168
|
+

|
|
169
|
+
|
|
170
|
+
A simple demo of moving objects with collision detection using **fastquadtree**.
|
|
171
|
+
You can toggle between quadtree mode and brute-force mode to see the performance difference.
|
|
172
|
+
|
|
173
|
+
See the [runnables guide](https://elan456.github.io/fastquadtree/runnables/) for setup instructions.
|
|
174
|
+
|
|
175
|
+
## FAQ
|
|
176
|
+
|
|
177
|
+
**Can I delete items from the quadtree?**
|
|
178
|
+
Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID and exact location for precise deletion. This handles cases where multiple items exist at the same location. If you're using `track_objects=True`, you can also use `delete_by_object(obj)` for convenient object-based deletion with O(1) lookup. The tree automatically merges nodes when item counts drop below capacity.
|
|
179
|
+
|
|
180
|
+
**Can I store rectangles or circles?**
|
|
181
|
+
Yes, you can store rectangles using the `RectQuadTree` class. Circles can be approximated with bounding boxes. See the [RectQuadTree docs](https://elan456.github.io/fastquadtree/api/rect_quadtree/) for details.
|
|
182
|
+
|
|
183
|
+
**Do I need NumPy installed?**
|
|
184
|
+
No, NumPy is a fully optional dependency. If you do have NumPy installed, you can use methods such as `query_np` and the NumPy array variant of `insert_many` for better performance. The Rust core is able to handle NumPy arrays faster than Python lists, so there's a lot of time savings in utilizing the NumPy functions. See the [Native vs Shim benchmark](https://elan456.github.io/fastquadtree/benchmark/#native-vs-shim) for details on how returing NumPy arrays can speed up queries.
|
|
185
|
+
|
|
186
|
+
**Does fastquadtree support multiprocessing?**
|
|
187
|
+
Yes, fastquadtree objects can be serialized to bytes using the `to_bytes()` method and deserialized back using `from_bytes()`. This allows you to share quadtree data across processes and even cache prebuilt trees to disk. See the [interactive v2 demo](https://github.com/Elan456/fastquadtree/blob/main/interactive/interactive_v2.py) for an example of saving and loading a quadtree.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT. See `LICENSE`.
|
|
192
|
+
|
|
193
|
+
## Acknowledgments
|
|
194
|
+
|
|
195
|
+
* Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads], [Shapely]
|
|
196
|
+
* Built with [PyO3] and [maturin]
|
|
197
|
+
|
|
198
|
+
[PyQtree]: https://pypi.org/project/pyqtree/
|
|
199
|
+
[e-pyquadtree]: https://pypi.org/project/e-pyquadtree/
|
|
200
|
+
[PyO3]: https://pyo3.rs/
|
|
201
|
+
[maturin]: https://www.maturin.rs/
|
|
202
|
+
[Rtree]: https://pypi.org/project/Rtree/
|
|
203
|
+
[nontree]: https://pypi.org/project/nontree/
|
|
204
|
+
[quads]: https://pypi.org/project/quads/
|
|
205
|
+
[Shapely]: https://pypi.org/project/Shapely/
|
|
206
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
fastquadtree-1.5.0.dist-info/METADATA,sha256=F-qLCl6F_1h7um_sHBLFUvY8EzVgiVgq774oe6Sd8JU,10526
|
|
2
|
+
fastquadtree-1.5.0.dist-info/WHEEL,sha256=fqFHtOCr25M-h-Mnj-0-ZSwuoI9vJMsUUAJdvV4MPAM,145
|
|
3
|
+
fastquadtree-1.5.0.dist-info/licenses/LICENSE,sha256=pRuvcuqIMtEUBMgvP1Bc4fOHydzeuA61c6DQoQ1pb1w,1071
|
|
4
|
+
fastquadtree/__init__.py,sha256=rtkveNz7rScRasTRGu1yEqzeoJfLfreJNxg21orPL-U,195
|
|
5
|
+
fastquadtree/_base_quadtree.py,sha256=lnblGEYIIlB34rssfdRcfCdc0FI_tL7E8x0dbqhfPgI,17380
|
|
6
|
+
fastquadtree/_item.py,sha256=0ioI1mm-TUPzuAV8Y6t35IOV-R_pSiSMqQDyWAjb4zU,2468
|
|
7
|
+
fastquadtree/_native.abi3.so,sha256=a7aFvTIJcrzDDtT7Fu33FjPfvilo-uSYdkKhtm-ycc0,796648
|
|
8
|
+
fastquadtree/_obj_store.py,sha256=tY8siqTCjr1JK3rigtljDVVYbQmPDRs1m8iUCCBHMO0,6337
|
|
9
|
+
fastquadtree/point_quadtree.py,sha256=-3G_JgbyH8mFRV0bTFygI1mpb3F09v2gr1vqTm4hdUA,7167
|
|
10
|
+
fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
fastquadtree/pyqtree.py,sha256=iAWAajViag4yTfiRXcGPha8bnNJOLRU7O9jdkjUeJKU,6450
|
|
12
|
+
fastquadtree/rect_quadtree.py,sha256=kTxMH7hQU5c4ocGkzLqd9w35IrUdbnkm1Hec1xjbyV8,8139
|
|
13
|
+
fastquadtree-1.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ethan Anderson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|