fastquadtree 0.5.0__cp38-abi3-win_amd64.whl → 0.6.0__cp38-abi3-win_amd64.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 +63 -70
- fastquadtree/__init__.pyi +24 -21
- fastquadtree/_bimap.py +12 -11
- fastquadtree/_item.py +5 -4
- fastquadtree/_native.pyd +0 -0
- {fastquadtree-0.5.0.dist-info → fastquadtree-0.6.0.dist-info}/METADATA +32 -14
- fastquadtree-0.6.0.dist-info/RECORD +10 -0
- fastquadtree-0.5.0.dist-info/RECORD +0 -10
- {fastquadtree-0.5.0.dist-info → fastquadtree-0.6.0.dist-info}/WHEEL +0 -0
- {fastquadtree-0.5.0.dist-info → fastquadtree-0.6.0.dist-info}/licenses/LICENSE +0 -0
fastquadtree/__init__.py
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import Any,
|
4
|
-
from typing import Literal
|
3
|
+
from typing import Any, Literal, Tuple, overload
|
5
4
|
|
6
|
-
# Compiled Rust module is provided by maturin (tool.maturin.module-name)
|
7
|
-
from ._native import QuadTree as _RustQuadTree
|
8
5
|
from ._bimap import BiMap # type: ignore[attr-defined]
|
9
6
|
from ._item import Item
|
10
7
|
|
8
|
+
# Compiled Rust module is provided by maturin (tool.maturin.module-name)
|
9
|
+
from ._native import QuadTree as _RustQuadTree
|
10
|
+
|
11
11
|
Bounds = Tuple[float, float, float, float]
|
12
12
|
"""Axis-aligned rectangle as (min_x, min_y, max_x, max_y)."""
|
13
13
|
|
@@ -45,14 +45,14 @@ class QuadTree:
|
|
45
45
|
ValueError: If parameters are invalid or inserts are out of bounds.
|
46
46
|
"""
|
47
47
|
|
48
|
-
__slots__ = ("
|
48
|
+
__slots__ = ("_bounds", "_count", "_items", "_native", "_next_id")
|
49
49
|
|
50
50
|
def __init__(
|
51
51
|
self,
|
52
52
|
bounds: Bounds,
|
53
53
|
capacity: int,
|
54
54
|
*,
|
55
|
-
max_depth:
|
55
|
+
max_depth: int | None = None,
|
56
56
|
track_objects: bool = False,
|
57
57
|
start_id: int = 1,
|
58
58
|
):
|
@@ -60,14 +60,14 @@ class QuadTree:
|
|
60
60
|
self._native = _RustQuadTree(bounds, capacity)
|
61
61
|
else:
|
62
62
|
self._native = _RustQuadTree(bounds, capacity, max_depth=max_depth)
|
63
|
-
self._items:
|
63
|
+
self._items: BiMap | None = BiMap() if track_objects else None
|
64
64
|
self._next_id: int = int(start_id)
|
65
65
|
self._count: int = 0
|
66
66
|
self._bounds = bounds
|
67
67
|
|
68
68
|
# ---------- inserts ----------
|
69
69
|
|
70
|
-
def insert(self, xy: Point, *,
|
70
|
+
def insert(self, xy: Point, *, id_: int | None = None, obj: Any = None) -> int:
|
71
71
|
"""
|
72
72
|
Insert a single point.
|
73
73
|
|
@@ -83,15 +83,14 @@ class QuadTree:
|
|
83
83
|
Raises:
|
84
84
|
ValueError: If the point is outside tree bounds.
|
85
85
|
"""
|
86
|
-
if
|
87
|
-
|
86
|
+
if id_ is None:
|
87
|
+
id_ = self._next_id
|
88
88
|
self._next_id += 1
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
self._next_id = id + 1
|
89
|
+
# ensure future auto-ids do not collide
|
90
|
+
elif id_ >= self._next_id:
|
91
|
+
self._next_id = id_ + 1
|
93
92
|
|
94
|
-
if not self._native.insert(
|
93
|
+
if not self._native.insert(id_, xy):
|
95
94
|
x, y = xy
|
96
95
|
bx0, by0, bx1, by1 = self._bounds
|
97
96
|
raise ValueError(
|
@@ -99,76 +98,72 @@ class QuadTree:
|
|
99
98
|
)
|
100
99
|
|
101
100
|
if self._items is not None:
|
102
|
-
self._items.add(Item(
|
101
|
+
self._items.add(Item(id_, xy[0], xy[1], obj))
|
103
102
|
|
104
103
|
self._count += 1
|
105
|
-
return
|
104
|
+
return id_
|
106
105
|
|
107
|
-
def insert_many_points(self, points:
|
106
|
+
def insert_many_points(self, points: list[Point]) -> int:
|
108
107
|
"""
|
109
108
|
Bulk insert points with auto-assigned ids.
|
110
109
|
|
111
110
|
Args:
|
112
|
-
points:
|
111
|
+
points: List of (x, y) points.
|
113
112
|
|
114
113
|
Returns:
|
115
|
-
|
116
|
-
|
117
|
-
Raises:
|
118
|
-
ValueError: If any point is outside tree bounds.
|
114
|
+
The number of points inserted
|
119
115
|
"""
|
120
|
-
|
121
|
-
|
122
|
-
inserted = 0
|
123
|
-
bx0, by0, bx1, by1 = self._bounds
|
124
|
-
for xy in points:
|
125
|
-
id_ = nid
|
126
|
-
nid += 1
|
127
|
-
if not ins(id_, xy):
|
128
|
-
x, y = xy
|
129
|
-
raise ValueError(
|
130
|
-
f"Point ({x}, {y}) is outside bounds ({bx0}, {by0}, {bx1}, {by1})"
|
131
|
-
)
|
132
|
-
inserted += 1
|
133
|
-
if self._items is not None:
|
134
|
-
self._items.add(Item(id_, xy[0], xy[1], None))
|
135
|
-
self._next_id = nid
|
136
|
-
self._count += inserted
|
137
|
-
return inserted
|
116
|
+
start_id = self._next_id
|
117
|
+
last_id = self._native.insert_many_points(start_id, points)
|
138
118
|
|
139
|
-
|
119
|
+
num_inserted = last_id - start_id + 1
|
120
|
+
|
121
|
+
if num_inserted < len(points):
|
122
|
+
raise ValueError("One or more points are outside tree bounds")
|
123
|
+
|
124
|
+
self._next_id = last_id + 1
|
125
|
+
|
126
|
+
# Update the item tracker if needed
|
127
|
+
if self._items is not None:
|
128
|
+
for i, id_ in enumerate(range(start_id, last_id + 1)):
|
129
|
+
x, y = points[i]
|
130
|
+
self._items.add(Item(id_, x, y, None))
|
131
|
+
|
132
|
+
return num_inserted
|
133
|
+
|
134
|
+
def attach(self, id_: int, obj: Any) -> None:
|
140
135
|
"""
|
141
136
|
Attach or replace the Python object for an existing id.
|
142
137
|
Tracking must be enabled.
|
143
138
|
|
144
139
|
Args:
|
145
|
-
|
140
|
+
id_: Target id.
|
146
141
|
obj: Object to associate with id.
|
147
142
|
"""
|
148
143
|
if self._items is None:
|
149
144
|
raise ValueError("Cannot attach objects when track_objects=False")
|
150
145
|
|
151
|
-
item = self._items.by_id(
|
146
|
+
item = self._items.by_id(id_)
|
152
147
|
if item is None:
|
153
|
-
raise KeyError(f"Id {
|
154
|
-
self._items.add(Item(
|
148
|
+
raise KeyError(f"Id {id_} not found in quadtree")
|
149
|
+
self._items.add(Item(id_, item.x, item.y, obj))
|
155
150
|
|
156
|
-
def delete(self,
|
151
|
+
def delete(self, id_: int, xy: Point) -> bool:
|
157
152
|
"""
|
158
153
|
Delete an item by id and exact coordinates.
|
159
154
|
|
160
155
|
Args:
|
161
|
-
|
156
|
+
id_: Integer id to remove.
|
162
157
|
xy: Coordinates (x, y) of the item.
|
163
158
|
|
164
159
|
Returns:
|
165
160
|
True if the item was found and deleted, else False.
|
166
161
|
"""
|
167
|
-
deleted = self._native.delete(
|
162
|
+
deleted = self._native.delete(id_, xy)
|
168
163
|
if deleted:
|
169
164
|
self._count -= 1
|
170
165
|
if self._items is not None:
|
171
|
-
self._items.pop_id(
|
166
|
+
self._items.pop_id(id_) # ignore result
|
172
167
|
return deleted
|
173
168
|
|
174
169
|
def delete_by_object(self, obj: Any) -> bool:
|
@@ -196,21 +191,21 @@ class QuadTree:
|
|
196
191
|
if item is None:
|
197
192
|
return False
|
198
193
|
|
199
|
-
return self.delete(item.
|
194
|
+
return self.delete(item.id_, (item.x, item.y))
|
200
195
|
|
201
196
|
# ---------- queries ----------
|
202
197
|
|
203
198
|
@overload
|
204
199
|
def query(
|
205
200
|
self, rect: Bounds, *, as_items: Literal[False] = ...
|
206
|
-
) ->
|
201
|
+
) -> list[_IdCoord]: ...
|
207
202
|
|
208
203
|
@overload
|
209
|
-
def query(self, rect: Bounds, *, as_items: Literal[True]) ->
|
204
|
+
def query(self, rect: Bounds, *, as_items: Literal[True]) -> list[Item]: ...
|
210
205
|
|
211
206
|
def query(
|
212
207
|
self, rect: Bounds, *, as_items: bool = False
|
213
|
-
) ->
|
208
|
+
) -> list[_IdCoord] | list[Item]:
|
214
209
|
"""
|
215
210
|
Return all points inside an axis-aligned rectangle.
|
216
211
|
|
@@ -228,8 +223,8 @@ class QuadTree:
|
|
228
223
|
|
229
224
|
if self._items is None:
|
230
225
|
raise ValueError("Cannot return results as items with track_objects=False")
|
231
|
-
out:
|
232
|
-
for id_,
|
226
|
+
out: list[Item] = []
|
227
|
+
for id_, _, _ in raw:
|
233
228
|
item = self._items.by_id(id_)
|
234
229
|
if item is None:
|
235
230
|
raise RuntimeError(
|
@@ -242,12 +237,10 @@ class QuadTree:
|
|
242
237
|
@overload
|
243
238
|
def nearest_neighbor(
|
244
239
|
self, xy: Point, *, as_item: Literal[False] = ...
|
245
|
-
) ->
|
240
|
+
) -> _IdCoord | None: ...
|
246
241
|
|
247
242
|
@overload
|
248
|
-
def nearest_neighbor(
|
249
|
-
self, xy: Point, *, as_item: Literal[True]
|
250
|
-
) -> Optional[Item]: ...
|
243
|
+
def nearest_neighbor(self, xy: Point, *, as_item: Literal[True]) -> Item | None: ...
|
251
244
|
|
252
245
|
def nearest_neighbor(self, xy: Point, *, as_item: bool = False):
|
253
246
|
"""
|
@@ -266,7 +259,7 @@ class QuadTree:
|
|
266
259
|
|
267
260
|
if self._items is None:
|
268
261
|
raise ValueError("Cannot return result as item with track_objects=False")
|
269
|
-
id_,
|
262
|
+
id_, _x, _y = t
|
270
263
|
item = self._items.by_id(id_)
|
271
264
|
if item is None:
|
272
265
|
raise RuntimeError(
|
@@ -278,12 +271,12 @@ class QuadTree:
|
|
278
271
|
@overload
|
279
272
|
def nearest_neighbors(
|
280
273
|
self, xy: Point, k: int, *, as_items: Literal[False] = ...
|
281
|
-
) ->
|
274
|
+
) -> list[_IdCoord]: ...
|
282
275
|
|
283
276
|
@overload
|
284
277
|
def nearest_neighbors(
|
285
278
|
self, xy: Point, k: int, *, as_items: Literal[True]
|
286
|
-
) ->
|
279
|
+
) -> list[Item]: ...
|
287
280
|
|
288
281
|
def nearest_neighbors(self, xy: Point, k: int, *, as_items: bool = False):
|
289
282
|
"""
|
@@ -303,7 +296,7 @@ class QuadTree:
|
|
303
296
|
if self._items is None:
|
304
297
|
raise ValueError("Cannot return results as items with track_objects=False")
|
305
298
|
|
306
|
-
out:
|
299
|
+
out: list[Item] = []
|
307
300
|
for id_, _, _ in raw:
|
308
301
|
item = self._items.by_id(id_)
|
309
302
|
if item is None:
|
@@ -316,7 +309,7 @@ class QuadTree:
|
|
316
309
|
|
317
310
|
# ---------- misc ----------
|
318
311
|
|
319
|
-
def get(self,
|
312
|
+
def get(self, id_: int) -> Any | None:
|
320
313
|
"""
|
321
314
|
Return the object associated with id.
|
322
315
|
|
@@ -325,12 +318,12 @@ class QuadTree:
|
|
325
318
|
"""
|
326
319
|
if self._items is None:
|
327
320
|
raise ValueError("Cannot get objects when track_objects=False")
|
328
|
-
item = self._items.by_id(
|
321
|
+
item = self._items.by_id(id_)
|
329
322
|
if item is None:
|
330
323
|
return None
|
331
324
|
return item.obj
|
332
325
|
|
333
|
-
def get_all_rectangles(self) ->
|
326
|
+
def get_all_rectangles(self) -> list[Bounds]:
|
334
327
|
"""
|
335
328
|
Return all node rectangles in the current quadtree.
|
336
329
|
|
@@ -339,7 +332,7 @@ class QuadTree:
|
|
339
332
|
"""
|
340
333
|
return self._native.get_all_rectangles()
|
341
334
|
|
342
|
-
def get_all_objects(self) ->
|
335
|
+
def get_all_objects(self) -> list[Any]:
|
343
336
|
"""
|
344
337
|
Return all tracked objects.
|
345
338
|
|
@@ -350,7 +343,7 @@ class QuadTree:
|
|
350
343
|
raise ValueError("Cannot get objects when track_objects=False")
|
351
344
|
return [t.obj for t in self._items.items() if t.obj is not None]
|
352
345
|
|
353
|
-
def get_all_items(self) ->
|
346
|
+
def get_all_items(self) -> list[Item]:
|
354
347
|
"""
|
355
348
|
Return all tracked items.
|
356
349
|
|
@@ -385,4 +378,4 @@ class QuadTree:
|
|
385
378
|
NativeQuadTree = _RustQuadTree
|
386
379
|
|
387
380
|
|
388
|
-
__all__ = ["
|
381
|
+
__all__ = ["Bounds", "Item", "Point", "QuadTree"]
|
fastquadtree/__init__.pyi
CHANGED
@@ -1,67 +1,70 @@
|
|
1
|
-
from typing import
|
2
|
-
|
1
|
+
from typing import (
|
2
|
+
Any,
|
3
|
+
Iterable,
|
4
|
+
Literal as _Literal, # avoid polluting public namespace
|
5
|
+
overload,
|
6
|
+
)
|
3
7
|
|
4
|
-
Bounds =
|
5
|
-
Point =
|
8
|
+
Bounds = tuple[float, float, float, float]
|
9
|
+
Point = tuple[float, float]
|
6
10
|
|
7
11
|
class Item:
|
8
|
-
|
12
|
+
id_: int
|
9
13
|
x: float
|
10
14
|
y: float
|
11
15
|
obj: Any | None
|
12
16
|
|
13
17
|
class QuadTree:
|
14
18
|
# Expose the raw native class for power users
|
15
|
-
NativeQuadTree:
|
19
|
+
NativeQuadTree: type
|
16
20
|
|
17
21
|
def __init__(
|
18
22
|
self,
|
19
23
|
bounds: Bounds,
|
20
24
|
capacity: int,
|
21
25
|
*,
|
22
|
-
max_depth:
|
26
|
+
max_depth: int | None = None,
|
23
27
|
track_objects: bool = False,
|
24
28
|
start_id: int = 1,
|
25
29
|
) -> None: ...
|
26
30
|
|
27
31
|
# Inserts
|
28
|
-
def insert(self, xy: Point, *,
|
32
|
+
def insert(self, xy: Point, *, id_: int | None = ..., obj: Any = ...) -> int: ...
|
29
33
|
def insert_many_points(self, points: Iterable[Point]) -> int: ...
|
30
|
-
def
|
31
|
-
def attach(self, id: int, obj: Any) -> None: ...
|
34
|
+
def attach(self, id_: int, obj: Any) -> None: ...
|
32
35
|
|
33
36
|
# Deletions
|
34
|
-
def delete(self,
|
37
|
+
def delete(self, id_: int, xy: Point) -> bool: ...
|
35
38
|
def delete_by_object(self, obj: Any) -> bool: ...
|
36
39
|
|
37
40
|
# Queries
|
38
41
|
@overload
|
39
42
|
def query(
|
40
43
|
self, rect: Bounds, *, as_items: _Literal[False] = ...
|
41
|
-
) ->
|
44
|
+
) -> list[tuple[int, float, float]]: ...
|
42
45
|
@overload
|
43
|
-
def query(self, rect: Bounds, *, as_items: _Literal[True]) ->
|
46
|
+
def query(self, rect: Bounds, *, as_items: _Literal[True]) -> list[Item]: ...
|
44
47
|
@overload
|
45
48
|
def nearest_neighbor(
|
46
49
|
self, xy: Point, *, as_item: _Literal[False] = ...
|
47
|
-
) ->
|
50
|
+
) -> tuple[int, float, float] | None: ...
|
48
51
|
@overload
|
49
52
|
def nearest_neighbor(
|
50
53
|
self, xy: Point, *, as_item: _Literal[True]
|
51
|
-
) ->
|
54
|
+
) -> Item | None: ...
|
52
55
|
@overload
|
53
56
|
def nearest_neighbors(
|
54
57
|
self, xy: Point, k: int, *, as_items: _Literal[False] = ...
|
55
|
-
) ->
|
58
|
+
) -> list[tuple[int, float, float]]: ...
|
56
59
|
@overload
|
57
60
|
def nearest_neighbors(
|
58
61
|
self, xy: Point, k: int, *, as_items: _Literal[True]
|
59
|
-
) ->
|
62
|
+
) -> list[Item]: ...
|
60
63
|
|
61
64
|
# Misc
|
62
|
-
def get(self,
|
63
|
-
def get_all_rectangles(self) ->
|
64
|
-
def get_all_objects(self) ->
|
65
|
-
def get_all_items(self) ->
|
65
|
+
def get(self, id_: int) -> Any | None: ...
|
66
|
+
def get_all_rectangles(self) -> list[Bounds]: ...
|
67
|
+
def get_all_objects(self) -> list[Any]: ...
|
68
|
+
def get_all_items(self) -> list[Item]: ...
|
66
69
|
def count_items(self) -> int: ...
|
67
70
|
def __len__(self) -> int: ...
|
fastquadtree/_bimap.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# _bimap.py
|
2
2
|
from __future__ import annotations
|
3
|
-
|
3
|
+
|
4
|
+
from typing import Any, Iterable, Iterator
|
4
5
|
|
5
6
|
from ._item import Item
|
6
7
|
|
@@ -33,7 +34,7 @@ class BiMap:
|
|
33
34
|
Insert or replace mapping for this Item.
|
34
35
|
Handles conflicts so that both id and obj point to this exact Item.
|
35
36
|
"""
|
36
|
-
id_ = item.
|
37
|
+
id_ = item.id_
|
37
38
|
obj = item.obj
|
38
39
|
|
39
40
|
# Unlink any old item currently bound to this id
|
@@ -47,20 +48,20 @@ class BiMap:
|
|
47
48
|
if obj is not None:
|
48
49
|
prev = self._objid_to_item.get(id(obj))
|
49
50
|
if prev is not None and prev is not item:
|
50
|
-
self._id_to_item.pop(prev.
|
51
|
+
self._id_to_item.pop(prev.id_, None)
|
51
52
|
|
52
53
|
# Link new
|
53
54
|
self._id_to_item[id_] = item
|
54
55
|
if obj is not None:
|
55
56
|
self._objid_to_item[id(obj)] = item
|
56
57
|
|
57
|
-
def by_id(self, id_: int) ->
|
58
|
+
def by_id(self, id_: int) -> Item | None:
|
58
59
|
return self._id_to_item.get(id_)
|
59
60
|
|
60
|
-
def by_obj(self, obj: Any) ->
|
61
|
+
def by_obj(self, obj: Any) -> Item | None:
|
61
62
|
return self._objid_to_item.get(id(obj))
|
62
63
|
|
63
|
-
def pop_id(self, id_: int) ->
|
64
|
+
def pop_id(self, id_: int) -> Item | None:
|
64
65
|
it = self._id_to_item.pop(id_, None)
|
65
66
|
if it is not None:
|
66
67
|
obj = it.obj
|
@@ -68,19 +69,19 @@ class BiMap:
|
|
68
69
|
self._objid_to_item.pop(id(obj), None)
|
69
70
|
return it
|
70
71
|
|
71
|
-
def pop_obj(self, obj: Any) ->
|
72
|
+
def pop_obj(self, obj: Any) -> Item | None:
|
72
73
|
it = self._objid_to_item.pop(id(obj), None)
|
73
74
|
if it is not None:
|
74
|
-
self._id_to_item.pop(it.
|
75
|
+
self._id_to_item.pop(it.id_, None)
|
75
76
|
return it
|
76
77
|
|
77
|
-
def pop_item(self, item: Item) ->
|
78
|
+
def pop_item(self, item: Item) -> Item | None:
|
78
79
|
"""
|
79
80
|
Remove this exact Item if present on either side.
|
80
81
|
"""
|
81
82
|
removed = None
|
82
83
|
# Remove by id first
|
83
|
-
removed = self._id_to_item.pop(item.
|
84
|
+
removed = self._id_to_item.pop(item.id_)
|
84
85
|
|
85
86
|
# Remove by obj side
|
86
87
|
obj = item.obj
|
@@ -104,7 +105,7 @@ class BiMap:
|
|
104
105
|
def contains_obj(self, obj: Any) -> bool:
|
105
106
|
return id(obj) in self._objid_to_item
|
106
107
|
|
107
|
-
def items_by_id(self) -> Iterator[
|
108
|
+
def items_by_id(self) -> Iterator[tuple[int, Item]]:
|
108
109
|
return iter(self._id_to_item.items())
|
109
110
|
|
110
111
|
def items(self) -> Iterator[Item]:
|
fastquadtree/_item.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# item.py
|
2
2
|
from __future__ import annotations
|
3
|
+
|
3
4
|
from typing import Any
|
4
5
|
|
5
6
|
|
@@ -8,16 +9,16 @@ class Item:
|
|
8
9
|
Lightweight view of an index entry.
|
9
10
|
|
10
11
|
Attributes:
|
11
|
-
|
12
|
+
id_: Integer identifier.
|
12
13
|
x: X coordinate.
|
13
14
|
y: Y coordinate.
|
14
15
|
obj: The attached Python object if available, else None.
|
15
16
|
"""
|
16
17
|
|
17
|
-
__slots__ = ("
|
18
|
+
__slots__ = ("id_", "obj", "x", "y")
|
18
19
|
|
19
|
-
def __init__(self,
|
20
|
-
self.
|
20
|
+
def __init__(self, id_: int, x: float, y: float, obj: Any | None = None):
|
21
|
+
self.id_ = id_
|
21
22
|
self.x = x
|
22
23
|
self.y = y
|
23
24
|
self.obj = obj
|
fastquadtree/_native.pyd
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fastquadtree
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
5
5
|
Classifier: Programming Language :: Python :: 3 :: Only
|
6
6
|
Classifier: Programming Language :: Rust
|
@@ -11,6 +11,13 @@ Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
11
11
|
Classifier: Topic :: Software Development :: Libraries
|
12
12
|
Classifier: Typing :: Typed
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Requires-Dist: ruff>=0.6.0 ; extra == 'dev'
|
15
|
+
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
16
|
+
Requires-Dist: pytest-cov>=4.1 ; 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
|
+
Provides-Extra: dev
|
14
21
|
License-File: LICENSE
|
15
22
|
Summary: Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search.
|
16
23
|
Keywords: quadtree,spatial-index,geometry,rust,pyo3,nearest-neighbor,k-nn
|
@@ -28,15 +35,15 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
|
|
28
35
|
[](https://pypi.org/project/fastquadtree/#files)
|
29
36
|
[](LICENSE)
|
30
37
|
|
31
|
-
[](https://pepy.tech/projects/fastquadtree)
|
38
|
+
[](https://pepy.tech/projects/fastquadtree)
|
33
39
|
|
34
40
|
[](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
|
35
41
|
[](https://codecov.io/gh/Elan456/fastquadtree)
|
36
42
|
|
37
43
|
[](https://pyo3.rs/)
|
38
44
|
[](https://www.maturin.rs/)
|
39
|
-
[](https://github.com/astral-sh/ruff)
|
46
|
+
|
40
47
|
|
41
48
|
|
42
49
|

|
@@ -58,18 +65,27 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
|
|
58
65
|

|
59
66
|
|
60
67
|
### Summary (largest dataset, PyQtree baseline)
|
61
|
-
- Points: **
|
68
|
+
- Points: **250,000**, Queries: **500**
|
62
69
|
--------------------
|
63
|
-
- Fastest total: **fastquadtree** at **
|
70
|
+
- Fastest total: **fastquadtree** at **0.120 s**
|
64
71
|
|
65
72
|
| Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
|
66
73
|
|---|---:|---:|---:|---:|
|
67
|
-
| fastquadtree | 0.
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
| fastquadtree | 0.031 | 0.089 | 0.120 | 14.64× |
|
75
|
+
| Shapely STRtree | 0.179 | 0.100 | 0.279 | 6.29× |
|
76
|
+
| nontree-QuadTree | 0.595 | 0.605 | 1.200 | 1.46× |
|
77
|
+
| Rtree | 0.961 | 0.300 | 1.261 | 1.39× |
|
78
|
+
| e-pyquadtree | 1.005 | 0.660 | 1.665 | 1.05× |
|
79
|
+
| PyQtree | 1.492 | 0.263 | 1.755 | 1.00× |
|
80
|
+
| quads | 1.407 | 0.484 | 1.890 | 0.93× |
|
81
|
+
|
82
|
+
#### Benchmark Configuration
|
83
|
+
| Parameter | Value |
|
84
|
+
|---|---:|
|
85
|
+
| Bounds | (0, 0, 1000, 1000) |
|
86
|
+
| Max points per node | 128 |
|
87
|
+
| Max depth | 16 |
|
88
|
+
| Queries per experiment | 500 |
|
73
89
|
|
74
90
|
## Install
|
75
91
|
|
@@ -273,7 +289,7 @@ MIT. See `LICENSE`.
|
|
273
289
|
|
274
290
|
## Acknowledgments
|
275
291
|
|
276
|
-
* Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads]
|
292
|
+
* Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads], [Shapely]
|
277
293
|
* Built with [PyO3] and [maturin]
|
278
294
|
|
279
295
|
[PyQtree]: https://pypi.org/project/pyqtree/
|
@@ -282,4 +298,6 @@ MIT. See `LICENSE`.
|
|
282
298
|
[maturin]: https://www.maturin.rs/
|
283
299
|
[Rtree]: https://pypi.org/project/Rtree/
|
284
300
|
[nontree]: https://pypi.org/project/nontree/
|
285
|
-
[quads]: https://pypi.org/project/quads/
|
301
|
+
[quads]: https://pypi.org/project/quads/
|
302
|
+
[Shapely]: https://pypi.org/project/Shapely/
|
303
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
fastquadtree-0.6.0.dist-info/METADATA,sha256=NRMlg6YFAuT7fwSjEfV2F_r3Yw71JDwKibvRVPaQyHI,10746
|
2
|
+
fastquadtree-0.6.0.dist-info/WHEEL,sha256=7bfl5v0wbVhXZba613g0x-n2obNNfpQuN8I1cQ4oaU8,94
|
3
|
+
fastquadtree-0.6.0.dist-info/licenses/LICENSE,sha256=46IVFhoCIwMo-ocq4olyEB1eBvvtaKic5yGLeKXnDuc,1092
|
4
|
+
fastquadtree/__init__.py,sha256=o7zH6lOu2lTxMafl9FNVZiecM0KVxqhC3FNSRbpaaqY,12435
|
5
|
+
fastquadtree/__init__.pyi,sha256=_HGJ9lpIZ9-JTSfMKSn8aif_tu60m2vgOXR4d0uXnRY,2062
|
6
|
+
fastquadtree/_bimap.py,sha256=SPFFw9hWAVRKZVXyDGYFFNw4MLV8qUlOq8gDbLcTqkg,3402
|
7
|
+
fastquadtree/_item.py,sha256=p-ymnE9S-c8Lc00KhNKN9Pt4bd05nZ6g-DB8q8D9zYk,533
|
8
|
+
fastquadtree/_native.pyd,sha256=72nxQfcx-JZkSZM3Jm-r7qHbwrL6Rhd5lrvftuuioeQ,254464
|
9
|
+
fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
fastquadtree-0.6.0.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
fastquadtree-0.5.0.dist-info/METADATA,sha256=7TPGeXEmmg20TEZc2Wx3gAiGMzdTuklhapwtEbn8IrQ,10119
|
2
|
-
fastquadtree-0.5.0.dist-info/WHEEL,sha256=7bfl5v0wbVhXZba613g0x-n2obNNfpQuN8I1cQ4oaU8,94
|
3
|
-
fastquadtree-0.5.0.dist-info/licenses/LICENSE,sha256=46IVFhoCIwMo-ocq4olyEB1eBvvtaKic5yGLeKXnDuc,1092
|
4
|
-
fastquadtree/__init__.py,sha256=rqttCrFBaqMjD7BDZlcwkU9eeOQh2gzRi81xKHHLwLk,12652
|
5
|
-
fastquadtree/__init__.pyi,sha256=4lKaqtnvylRzhj322ue_y_DO_xxrkzOzjpYnH5SN1Fg,2166
|
6
|
-
fastquadtree/_bimap.py,sha256=zMp20NJkvVuO3jn2OeVApf7j4siBI4r2kU6SHyb7EZE,3428
|
7
|
-
fastquadtree/_item.py,sha256=imyiwnbmY21mHsOvNqB4j-TsOPpAmsvb38LKxEL8q4o,526
|
8
|
-
fastquadtree/_native.pyd,sha256=84XbRhl0Lj2ofRahwDK4Pjw_JiXvOVHfEFGD8hHbbJ0,247808
|
9
|
-
fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
fastquadtree-0.5.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|