fastquadtree 1.3.1__tar.gz → 1.3.2__tar.gz
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.
Potentially problematic release.
This version of fastquadtree might be problematic. Click here for more details.
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/Cargo.lock +1 -1
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/Cargo.toml +1 -1
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/PKG-INFO +1 -1
- fastquadtree-1.3.2/docs/api/pyqtree.md +4 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/pyqtree.py +47 -41
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_pyqtree_shim_compat.py +70 -0
- fastquadtree-1.3.1/docs/api/pyqtree.md +0 -4
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/.github/workflows/docs.yml +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/.github/workflows/release.yml +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/.github/workflows/test.yml +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/.gitignore +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/.pre-commit-config.yaml +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/LICENSE +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/README.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/assets/ballpit.png +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/assets/interactive_v2_rect_screenshot.png +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/assets/interactive_v2_screenshot.png +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/assets/quadtree_bench_throughput.png +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/assets/quadtree_bench_time.png +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/benchmark_native_vs_shim.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/benchmark_np_vs_list.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/benchmark_serialization_vs_rebuild.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/cross_library_bench.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/__init__.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/engines.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/main.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/plotting.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/runner.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/requirements.txt +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/runner.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/benchmarks/system_info_collector.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/api/point_item.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/api/quadtree.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/api/rect_item.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/api/rect_quadtree.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/benchmark.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/future_features.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/index.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/quickstart.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/runnables.md +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/docs/styles/overrides.css +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/interactive/ballpit.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/interactive/interactive.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/interactive/interactive_v2.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/interactive/interactive_v2_rect.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/interactive/requirements.txt +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/mkdocs.yml +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pyproject.toml +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/__init__.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/_base_quadtree.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/_item.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/_obj_store.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/point_quadtree.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/py.typed +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/pysrc/fastquadtree/rect_quadtree.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/src/geom.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/src/lib.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/src/quadtree.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/src/rect_quadtree.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/insertions.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/nearest_neighbor.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/query.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/rect_quadtree.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/rectangle_traversal.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/serialization.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_base_quadtree.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_clear.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_delete.rs +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_delete_by_object.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_delete_python.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_insert_many_numpy.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_obj_store.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_point_quadtree_dtypes.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_point_quadtree_nn_runtime.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_python.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_rect_quadtree.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_serialization.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_unconventional_bounds.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/test_wrapper_edges.py +0 -0
- {fastquadtree-1.3.1 → fastquadtree-1.3.2}/tests/unconventional_bounds.rs +0 -0
|
@@ -5,8 +5,9 @@ drop-in replacement to fastquadtree.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
from collections.abc import Iterable
|
|
8
9
|
from operator import itemgetter
|
|
9
|
-
from typing import Any, Tuple
|
|
10
|
+
from typing import Any, SupportsFloat, Tuple
|
|
10
11
|
|
|
11
12
|
from ._native import RectQuadTree
|
|
12
13
|
|
|
@@ -34,18 +35,18 @@ def gather_objs(objs, ids, chunk=2048):
|
|
|
34
35
|
|
|
35
36
|
class Index:
|
|
36
37
|
"""
|
|
37
|
-
The class below is taken from the pyqtree package, but the implementation
|
|
38
|
+
The interface of the class below is taken from the pyqtree package, but the implementation
|
|
38
39
|
has been modified to use the fastquadtree package as a backend instead of
|
|
39
40
|
the original pure-python implementation.
|
|
40
41
|
Based on the benchmarks, this gives a overall performance boost of 6.514x.
|
|
41
42
|
See the benchmark section of the docs for more details and the latest numbers.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
Index is the top-level class for creating and using a quadtree spatial index
|
|
45
|
+
with the original pyqtree interface. If you are not migrating from pyqtree,
|
|
46
|
+
consider using the RectQuadTree class for detailed control and better performance.
|
|
47
|
+
|
|
48
|
+
This class wraps a RectQuadTree instance and provides methods to insert items with bounding boxes,
|
|
49
|
+
remove items, and query for items intersecting a given bounding box.
|
|
49
50
|
|
|
50
51
|
Example usage:
|
|
51
52
|
```python
|
|
@@ -65,32 +66,36 @@ class Index:
|
|
|
65
66
|
|
|
66
67
|
def __init__(
|
|
67
68
|
self,
|
|
68
|
-
bbox=None,
|
|
69
|
-
x=None,
|
|
70
|
-
y=None,
|
|
71
|
-
width=None,
|
|
72
|
-
height=None,
|
|
73
|
-
max_items=MAX_ITEMS,
|
|
74
|
-
max_depth=MAX_DEPTH,
|
|
69
|
+
bbox: Iterable[SupportsFloat] | None = None,
|
|
70
|
+
x: float | int | None = None,
|
|
71
|
+
y: float | int | None = None,
|
|
72
|
+
width: float | int | None = None,
|
|
73
|
+
height: float | int | None = None,
|
|
74
|
+
max_items: int = MAX_ITEMS,
|
|
75
|
+
max_depth: int = MAX_DEPTH,
|
|
75
76
|
):
|
|
76
77
|
"""
|
|
77
78
|
Initiate by specifying either 1) a bbox to keep track of, or 2) with an xy centerpoint and a width and height.
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
Args:
|
|
81
|
+
bbox: The coordinate system bounding box of the area that the quadtree should
|
|
81
82
|
keep track of, as a 4-length sequence (xmin,ymin,xmax,ymax)
|
|
82
|
-
|
|
83
|
+
x:
|
|
83
84
|
The x center coordinate of the area that the quadtree should keep track of.
|
|
84
|
-
|
|
85
|
+
y:
|
|
85
86
|
The y center coordinate of the area that the quadtree should keep track of.
|
|
86
|
-
|
|
87
|
+
width:
|
|
87
88
|
How far from the xcenter that the quadtree should look when keeping track.
|
|
88
|
-
|
|
89
|
+
height:
|
|
89
90
|
How far from the ycenter that the quadtree should look when keeping track
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
max_items (optional): The maximum number of items allowed per quad before splitting
|
|
92
|
+
up into four new subquads. Default is 10.
|
|
93
|
+
max_depth (optional): The maximum levels of nested subquads, after which no more splitting
|
|
93
94
|
occurs and the bottommost quad nodes may grow indefinately. Default is 20.
|
|
95
|
+
|
|
96
|
+
Note:
|
|
97
|
+
Either the bbox argument must be set, or the x, y, width, and height
|
|
98
|
+
arguments must be set.
|
|
94
99
|
"""
|
|
95
100
|
if bbox is not None:
|
|
96
101
|
x1, y1, x2, y2 = bbox
|
|
@@ -114,15 +119,15 @@ class Index:
|
|
|
114
119
|
self._free = []
|
|
115
120
|
self._item_to_id = {}
|
|
116
121
|
|
|
117
|
-
def insert(self, item: Any, bbox
|
|
122
|
+
def insert(self, item: Any, bbox: Iterable[SupportsFloat]):
|
|
118
123
|
"""
|
|
119
124
|
Inserts an item into the quadtree along with its bounding box.
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
Args:
|
|
127
|
+
item: The item to insert into the index, which will be returned by the intersection method
|
|
128
|
+
bbox: The spatial bounding box tuple of the item, with four members (xmin,ymin,xmax,ymax)
|
|
124
129
|
"""
|
|
125
|
-
if type(bbox) is
|
|
130
|
+
if type(bbox) is not tuple: # Handle non-tuple input
|
|
126
131
|
bbox = tuple(bbox)
|
|
127
132
|
|
|
128
133
|
if self._free:
|
|
@@ -134,17 +139,18 @@ class Index:
|
|
|
134
139
|
self._qt.insert(rid, bbox)
|
|
135
140
|
self._item_to_id[id(item)] = rid
|
|
136
141
|
|
|
137
|
-
def remove(self, item, bbox):
|
|
142
|
+
def remove(self, item: Any, bbox: Iterable[SupportsFloat]):
|
|
138
143
|
"""
|
|
139
144
|
Removes an item from the quadtree.
|
|
140
145
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
Args:
|
|
147
|
+
item: The item to remove from the index
|
|
148
|
+
bbox: The spatial bounding box tuple of the item, with four members (xmin,ymin,xmax,ymax)
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
Note:
|
|
151
|
+
Both parameters need to exactly match the parameters provided to the insert method.
|
|
146
152
|
"""
|
|
147
|
-
if type(bbox) is
|
|
153
|
+
if type(bbox) is not tuple: # Handle non-tuple input
|
|
148
154
|
bbox = tuple(bbox)
|
|
149
155
|
|
|
150
156
|
rid = self._item_to_id.pop(id(item))
|
|
@@ -152,18 +158,18 @@ class Index:
|
|
|
152
158
|
self._objects[rid] = None
|
|
153
159
|
self._free.append(rid)
|
|
154
160
|
|
|
155
|
-
def intersect(self, bbox):
|
|
161
|
+
def intersect(self, bbox: Iterable[SupportsFloat]) -> list:
|
|
156
162
|
"""
|
|
157
|
-
Intersects an input
|
|
163
|
+
Intersects an input bounding box rectangle with all of the items
|
|
158
164
|
contained in the quadtree.
|
|
159
165
|
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
Args:
|
|
167
|
+
bbox: A spatial bounding box tuple with four members (xmin,ymin,xmax,ymax)
|
|
162
168
|
|
|
163
169
|
Returns:
|
|
164
|
-
|
|
170
|
+
A list of inserted items whose bounding boxes intersect with the input bbox.
|
|
165
171
|
"""
|
|
166
|
-
if type(bbox) is
|
|
172
|
+
if type(bbox) is not tuple: # Handle non-tuple input
|
|
167
173
|
bbox = tuple(bbox)
|
|
168
174
|
result = self._qt.query_ids(bbox)
|
|
169
175
|
# result = [id1, id2, ...]
|
|
@@ -401,3 +401,73 @@ def test_insert_list_and_tuple_equivalence():
|
|
|
401
401
|
# Both objects should be present
|
|
402
402
|
results = idx.intersect((0.0, 0.0, 100.0, 100.0))
|
|
403
403
|
assert set(results) == {obj1, obj2}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def test_insert_non_list_non_tuple_iterator():
|
|
407
|
+
"""Test that any iterable (not just list/tuple) works for bbox in insert."""
|
|
408
|
+
idx = FQTIndex(bbox=WORLD)
|
|
409
|
+
|
|
410
|
+
obj1, box1 = "obj1", (10.0, 10.0, 20.0, 20.0)
|
|
411
|
+
|
|
412
|
+
obj2 = "obj2"
|
|
413
|
+
|
|
414
|
+
def obj2_box2_iterator():
|
|
415
|
+
yield 30.0
|
|
416
|
+
yield 30.0
|
|
417
|
+
yield 40.0
|
|
418
|
+
yield 40.0
|
|
419
|
+
|
|
420
|
+
# Insert using tuple
|
|
421
|
+
idx.insert(obj1, box1)
|
|
422
|
+
|
|
423
|
+
# Insert using range iterator
|
|
424
|
+
idx.insert(obj2, obj2_box2_iterator())
|
|
425
|
+
|
|
426
|
+
# Both objects should be present
|
|
427
|
+
results = idx.intersect((0.0, 0.0, 100.0, 100.0))
|
|
428
|
+
assert set(results) == {obj1, obj2}
|
|
429
|
+
|
|
430
|
+
# Try Range
|
|
431
|
+
obj3 = "obj3"
|
|
432
|
+
box3_range = range(50, 54) # 50, 51, 52, 53
|
|
433
|
+
idx.insert(obj3, box3_range)
|
|
434
|
+
results = idx.intersect((0.0, 0.0, 100.0, 100.0))
|
|
435
|
+
assert set(results) == {obj1, obj2, obj3}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def test_insert_fails_on_tuple_too_long():
|
|
439
|
+
"""Test that insert fails when bbox tuple is too long."""
|
|
440
|
+
idx = FQTIndex(bbox=WORLD)
|
|
441
|
+
|
|
442
|
+
obj1 = "obj1"
|
|
443
|
+
box1 = (10.0, 10.0, 20.0, 20.0, 30.0) # This should fail
|
|
444
|
+
|
|
445
|
+
with pytest.raises(ValueError):
|
|
446
|
+
idx.insert(obj1, box1)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def non_tuple_intersect_and_non_tuple_remove_handled():
|
|
450
|
+
"""Test that intersect and remove accept any iterable (not just list/tuple)."""
|
|
451
|
+
idx = FQTIndex(bbox=WORLD)
|
|
452
|
+
|
|
453
|
+
obj1, box1 = "obj1", (10.0, 10.0, 20.0, 20.0)
|
|
454
|
+
|
|
455
|
+
idx.insert(obj1, box1)
|
|
456
|
+
|
|
457
|
+
def query_iterator():
|
|
458
|
+
yield 15.0
|
|
459
|
+
yield 15.0
|
|
460
|
+
yield 25.0
|
|
461
|
+
yield 25.0
|
|
462
|
+
|
|
463
|
+
results = idx.intersect(query_iterator())
|
|
464
|
+
assert results == [obj1]
|
|
465
|
+
|
|
466
|
+
def remove_box_iterator():
|
|
467
|
+
yield 10.0
|
|
468
|
+
yield 10.0
|
|
469
|
+
yield 20.0
|
|
470
|
+
yield 20.0
|
|
471
|
+
|
|
472
|
+
idx.remove(obj1, remove_box_iterator())
|
|
473
|
+
assert idx.intersect((0.0, 0.0, 100.0, 100.0)) == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|