fastquadtree 1.3.0__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.

Files changed (80) hide show
  1. fastquadtree-1.3.2/.github/workflows/test.yml +65 -0
  2. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/Cargo.lock +1 -1
  3. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/Cargo.toml +1 -1
  4. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/PKG-INFO +1 -1
  5. fastquadtree-1.3.2/docs/api/pyqtree.md +4 -0
  6. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/_base_quadtree.py +15 -3
  7. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/point_quadtree.py +2 -2
  8. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/pyqtree.py +47 -41
  9. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/rect_quadtree.py +2 -2
  10. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_insert_many_numpy.py +42 -0
  11. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_point_quadtree_dtypes.py +2 -2
  12. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_pyqtree_shim_compat.py +70 -0
  13. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_rect_quadtree.py +2 -2
  14. fastquadtree-1.3.0/docs/api/pyqtree.md +0 -4
  15. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/.github/workflows/docs.yml +0 -0
  16. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/.github/workflows/release.yml +0 -0
  17. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/.gitignore +0 -0
  18. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/.pre-commit-config.yaml +0 -0
  19. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/LICENSE +0 -0
  20. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/README.md +0 -0
  21. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/assets/ballpit.png +0 -0
  22. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/assets/interactive_v2_rect_screenshot.png +0 -0
  23. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/assets/interactive_v2_screenshot.png +0 -0
  24. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/assets/quadtree_bench_throughput.png +0 -0
  25. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/assets/quadtree_bench_time.png +0 -0
  26. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/benchmark_native_vs_shim.py +0 -0
  27. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/benchmark_np_vs_list.py +0 -0
  28. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/benchmark_serialization_vs_rebuild.py +0 -0
  29. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/cross_library_bench.py +0 -0
  30. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/__init__.py +0 -0
  31. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/engines.py +0 -0
  32. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/main.py +0 -0
  33. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/plotting.py +0 -0
  34. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/quadtree_bench/runner.py +0 -0
  35. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/requirements.txt +0 -0
  36. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/runner.py +0 -0
  37. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/benchmarks/system_info_collector.py +0 -0
  38. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/api/point_item.md +0 -0
  39. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/api/quadtree.md +0 -0
  40. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/api/rect_item.md +0 -0
  41. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/api/rect_quadtree.md +0 -0
  42. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/benchmark.md +0 -0
  43. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/future_features.md +0 -0
  44. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/index.md +0 -0
  45. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/quickstart.md +0 -0
  46. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/runnables.md +0 -0
  47. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/docs/styles/overrides.css +0 -0
  48. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/interactive/ballpit.py +0 -0
  49. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/interactive/interactive.py +0 -0
  50. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/interactive/interactive_v2.py +0 -0
  51. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/interactive/interactive_v2_rect.py +0 -0
  52. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/interactive/requirements.txt +0 -0
  53. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/mkdocs.yml +0 -0
  54. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pyproject.toml +0 -0
  55. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/__init__.py +0 -0
  56. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/_item.py +0 -0
  57. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/_obj_store.py +0 -0
  58. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/pysrc/fastquadtree/py.typed +0 -0
  59. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/src/geom.rs +0 -0
  60. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/src/lib.rs +0 -0
  61. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/src/quadtree.rs +0 -0
  62. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/src/rect_quadtree.rs +0 -0
  63. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/insertions.rs +0 -0
  64. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/nearest_neighbor.rs +0 -0
  65. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/query.rs +0 -0
  66. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/rect_quadtree.rs +0 -0
  67. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/rectangle_traversal.rs +0 -0
  68. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/serialization.rs +0 -0
  69. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_base_quadtree.py +0 -0
  70. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_clear.py +0 -0
  71. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_delete.rs +0 -0
  72. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_delete_by_object.py +0 -0
  73. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_delete_python.py +0 -0
  74. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_obj_store.py +0 -0
  75. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_point_quadtree_nn_runtime.py +0 -0
  76. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_python.py +0 -0
  77. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_serialization.py +0 -0
  78. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_unconventional_bounds.py +0 -0
  79. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/test_wrapper_edges.py +0 -0
  80. {fastquadtree-1.3.0 → fastquadtree-1.3.2}/tests/unconventional_bounds.rs +0 -0
@@ -0,0 +1,65 @@
1
+ # .github/workflows/test.yml
2
+ name: Test
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+
7
+ permissions:
8
+ contents: write
9
+ pages: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ test:
14
+ name: Test build (Python ${{ matrix.python-version }})
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.9", "3.14"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ allow-prereleases: true
29
+ cache: "pip"
30
+
31
+ - name: Create venv for maturin develop
32
+ run: python -m venv .venv
33
+
34
+ - name: Build with maturin into this Python
35
+ uses: PyO3/maturin-action@v1
36
+ with:
37
+ command: develop
38
+ args: --release
39
+ manylinux: manylinux2014
40
+
41
+ - name: Install Python test deps into .venv
42
+ run: |
43
+ . .venv/bin/activate
44
+ pip install -e '.[dev]'
45
+
46
+ - name: Run Python tests
47
+ run: |
48
+ . .venv/bin/activate
49
+ pytest
50
+
51
+ - name: Run Python tests
52
+ run: |
53
+ . .venv/bin/activate
54
+ pytest
55
+
56
+ - name: Install Rust toolchain
57
+ uses: dtolnay/rust-toolchain@stable
58
+
59
+ - name: Run Rust tests
60
+ run: cargo test
61
+
62
+ - name: Upload coverage to Codecov
63
+ uses: codecov/codecov-action@v5
64
+ with:
65
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -30,7 +30,7 @@ dependencies = [
30
30
 
31
31
  [[package]]
32
32
  name = "fastquadtree"
33
- version = "1.3.0"
33
+ version = "1.3.2"
34
34
  dependencies = [
35
35
  "bincode",
36
36
  "num-traits",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fastquadtree"
3
- version = "1.3.0"
3
+ version = "1.3.2"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastquadtree
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Rust
@@ -0,0 +1,4 @@
1
+ # fastquadtree.pyqtree.Index
2
+ ::: fastquadtree.pyqtree.Index
3
+ options:
4
+ inherited_members: true
@@ -29,6 +29,14 @@ G = TypeVar("G") # geometry type, e.g. Point or Bounds
29
29
  HitT = TypeVar("HitT") # raw native tuple, e.g. (id,x,y) or (id,x0,y0,x1,y1)
30
30
  ItemType = TypeVar("ItemType", bound=Item) # e.g. PointItem or RectItem
31
31
 
32
+ # Quadtree dtype to numpy dtype mapping
33
+ QUADTREE_DTYPE_TO_NP_DTYPE = {
34
+ "f32": "float32",
35
+ "f64": "float64",
36
+ "i32": "int32",
37
+ "i64": "int64",
38
+ }
39
+
32
40
 
33
41
  def _is_np_array(x: Any) -> bool:
34
42
  mod = getattr(x.__class__, "__module__", "")
@@ -250,7 +258,7 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
250
258
  ) -> int:
251
259
  """
252
260
  Bulk insert with auto-assigned contiguous ids. Faster than inserting one-by-one.<br>
253
- Can accept either a Python sequence of geometries or a NumPy array of shape (N,2) or (N,4) with dtype float32.
261
+ Can accept either a Python sequence of geometries or a NumPy array of shape (N,2) or (N,4) with a dtype that matches the quadtree's dtype.
254
262
 
255
263
  If tracking is enabled, the objects will be bulk stored internally.
256
264
  If no objects are provided, the items will have obj=None (if tracking).
@@ -289,8 +297,12 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
289
297
  if geoms.size == 0:
290
298
  return 0
291
299
 
292
- if geoms.dtype != _np.float32:
293
- raise TypeError("Numpy array must use dtype float32")
300
+ # Check if dtype matches quadtree dtype
301
+ expected_np_dtype = QUADTREE_DTYPE_TO_NP_DTYPE.get(self._dtype)
302
+ if geoms.dtype != expected_np_dtype:
303
+ raise TypeError(
304
+ f"Numpy array dtype {geoms.dtype} does not match quadtree dtype {self._dtype}"
305
+ )
294
306
 
295
307
  if self._store is None:
296
308
  # Simple contiguous path with native bulk insert
@@ -161,7 +161,7 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
161
161
  """Create the native engine instance."""
162
162
  rust_cls = DTYPE_MAP.get(self._dtype)
163
163
  if rust_cls is None:
164
- raise ValueError(f"Unsupported dtype: {self._dtype}")
164
+ raise TypeError(f"Unsupported dtype: {self._dtype}")
165
165
  return rust_cls(bounds, capacity, max_depth)
166
166
 
167
167
  @classmethod
@@ -169,7 +169,7 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
169
169
  """Create a new native engine instance from serialized bytes."""
170
170
  rust_cls = DTYPE_MAP.get(dtype)
171
171
  if rust_cls is None:
172
- raise ValueError(f"Unsupported dtype: {dtype}")
172
+ raise TypeError(f"Unsupported dtype: {dtype}")
173
173
  return rust_cls.from_bytes(data)
174
174
 
175
175
  @staticmethod
@@ -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
- Original docstring from pyqtree follows:
44
- The top spatial index to be created by the user. Once created it can be
45
- populated with geographically placed members that can later be tested for
46
- intersection with a user inputted geographic bounding box. Note that the
47
- index can be iterated through in a for-statement, which loops through all
48
- all the quad instances and lets you access their properties.
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
- Parameters:
80
- - **bbox**: The coordinate system bounding box of the area that the quadtree should
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
- - **x**:
83
+ x:
83
84
  The x center coordinate of the area that the quadtree should keep track of.
84
- - **y**
85
+ y:
85
86
  The y center coordinate of the area that the quadtree should keep track of.
86
- - **width**:
87
+ width:
87
88
  How far from the xcenter that the quadtree should look when keeping track.
88
- - **height**:
89
+ height:
89
90
  How far from the ycenter that the quadtree should look when keeping track
90
- - **max_items** (optional): The maximum number of items allowed per quad before splitting
91
- up into four new subquads. Default is 10.
92
- - **max_depth** (optional): The maximum levels of nested subquads, after which no more splitting
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): # pyright: ignore[reportIncompatibleMethodOverride]
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
- Parameters:
122
- - **item**: The item to insert into the index, which will be returned by the intersection method
123
- - **bbox**: The spatial bounding box tuple of the item, with four members (xmin,ymin,xmax,ymax)
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 list: # Handle list input
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
- Parameters:
142
- - **item**: The item to remove from the index
143
- - **bbox**: The spatial bounding box tuple of the item, with four members (xmin,ymin,xmax,ymax)
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
- Both parameters need to exactly match the parameters provided to the insert method.
150
+ Note:
151
+ Both parameters need to exactly match the parameters provided to the insert method.
146
152
  """
147
- if type(bbox) is list: # Handle list input
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 boundingbox rectangle with all of the items
163
+ Intersects an input bounding box rectangle with all of the items
158
164
  contained in the quadtree.
159
165
 
160
- Parameters:
161
- - **bbox**: A spatial bounding box tuple with four members (xmin,ymin,xmax,ymax)
166
+ Args:
167
+ bbox: A spatial bounding box tuple with four members (xmin,ymin,xmax,ymax)
162
168
 
163
169
  Returns:
164
- - A list of inserted items whose bounding boxes intersect with the input bbox.
170
+ A list of inserted items whose bounding boxes intersect with the input bbox.
165
171
  """
166
- if type(bbox) is list: # Handle list input
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, ...]
@@ -101,7 +101,7 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
101
101
  """Create the native engine instance."""
102
102
  rust_cls = DTYPE_MAP.get(self._dtype)
103
103
  if rust_cls is None:
104
- raise ValueError(f"Unsupported dtype: {self._dtype}")
104
+ raise TypeError(f"Unsupported dtype: {self._dtype}")
105
105
  return rust_cls(bounds, capacity, max_depth)
106
106
 
107
107
  @classmethod
@@ -109,7 +109,7 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
109
109
  """Create a new native engine instance from serialized bytes."""
110
110
  rust_cls = DTYPE_MAP.get(dtype)
111
111
  if rust_cls is None:
112
- raise ValueError(f"Unsupported dtype: {dtype}")
112
+ raise TypeError(f"Unsupported dtype: {dtype}")
113
113
  return rust_cls.from_bytes(data)
114
114
 
115
115
  @staticmethod
@@ -52,6 +52,48 @@ def test_type_error_on_wrong_dtype():
52
52
  assert len(qt) == 0
53
53
 
54
54
 
55
+ def test_non_default_dtype_insert_many():
56
+ qt = QuadTree(BOUNDS, capacity=8, track_objects=True, dtype="f64")
57
+ points = np.array([[10, 10], [20, 20], [30, 30]], dtype=np.float64)
58
+ n = qt.insert_many(points)
59
+ assert n == 3
60
+ assert len(qt) == 3
61
+
62
+ raw = qt.query((0, 0, 40, 40), as_items=False)
63
+
64
+ assert len(raw) == 3
65
+ # ids and positions match
66
+ m_raw = {t[0]: (t[1], t[2]) for t in raw}
67
+ for t in raw:
68
+ assert (t[1], t[2]) == m_raw[t[0]]
69
+
70
+
71
+ def test_non_default_quadtree_dtype_with_default_numpy_dtype_raises():
72
+ qt = QuadTree(BOUNDS, capacity=8, track_objects=True, dtype="f64")
73
+ points = np.array([[10, 10], [20, 20], [30, 30]], dtype=np.float32) # Wrong dtype
74
+ with pytest.raises(TypeError):
75
+ qt.insert_many(points)
76
+ assert len(qt) == 0
77
+
78
+
79
+ def test_unspported_quadtree_dtype_insert_many_raises():
80
+ qt = QuadTree(BOUNDS, capacity=8, track_objects=True, dtype="i32")
81
+ points = np.array([[10, 10], [20, 20], [30, 30]], dtype=np.float32) # Wrong dtype
82
+ with pytest.raises(TypeError):
83
+ qt.insert_many(points)
84
+ assert len(qt) == 0
85
+
86
+ points = np.array(
87
+ [[10, 10], [20, 20], [30, 30]], dtype=np.uint32
88
+ ) # unsupported dtype
89
+ with pytest.raises(TypeError):
90
+ qt.insert_many(points)
91
+
92
+ # QT is also unsupported
93
+ with pytest.raises(TypeError):
94
+ qt = QuadTree(BOUNDS, capacity=8, track_objects=True, dtype="u32")
95
+
96
+
55
97
  def test_insert_empty_numpy_array():
56
98
  qt = QuadTree(BOUNDS, capacity=8, track_objects=True)
57
99
  points = np.empty((0, 2), dtype=np.float32)
@@ -5,13 +5,13 @@ from fastquadtree import QuadTree
5
5
 
6
6
  def test_unsupported_dtype():
7
7
  """Test that providing an unsupported dtype raises ValueError."""
8
- with pytest.raises(ValueError):
8
+ with pytest.raises(TypeError):
9
9
  QuadTree((0, 0, 100, 100), capacity=4, track_objects=True, dtype="f128") # type: ignore
10
10
 
11
11
  # From bytes
12
12
  qt = QuadTree((0, 0, 100, 100), capacity=4, track_objects=True, dtype="f32")
13
13
  data = qt.to_bytes()
14
- with pytest.raises(ValueError):
14
+ with pytest.raises(TypeError):
15
15
  QuadTree.from_bytes(data, dtype="f128") # type: ignore
16
16
 
17
17
 
@@ -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)) == []
@@ -195,7 +195,7 @@ def test_accurate_obj_output_with_tracking():
195
195
 
196
196
  def test_unsupported_dtype():
197
197
  """Test that providing an unsupported dtype raises ValueError."""
198
- with pytest.raises(ValueError):
198
+ with pytest.raises(TypeError):
199
199
  rq.RectQuadTree(
200
200
  b_to_float(0, 0, 100, 100), capacity=4, track_objects=True, dtype="f128"
201
201
  ) # type: ignore
@@ -205,7 +205,7 @@ def test_unsupported_dtype():
205
205
  b_to_float(0, 0, 100, 100), capacity=4, track_objects=True, dtype="f32"
206
206
  )
207
207
  data = qt.to_bytes()
208
- with pytest.raises(ValueError):
208
+ with pytest.raises(TypeError):
209
209
  rq.RectQuadTree.from_bytes(data, dtype="f128") # type: ignore
210
210
 
211
211
 
@@ -1,4 +0,0 @@
1
- # fastquadtree.pyqtree
2
- ::: fastquadtree.pyqtree.Index
3
- options:
4
- inherited_members: true
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes