fastquadtree 1.1.1__cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 1.2.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.
Potentially problematic release.
This version of fastquadtree might be problematic. Click here for more details.
- fastquadtree/_base_quadtree.py +68 -1
- fastquadtree/_item.py +29 -0
- fastquadtree/_native.abi3.so +0 -0
- fastquadtree/_obj_store.py +39 -5
- fastquadtree/point_quadtree.py +19 -1
- fastquadtree/pyqtree.py +6 -0
- fastquadtree/rect_quadtree.py +19 -1
- {fastquadtree-1.1.1.dist-info → fastquadtree-1.2.0.dist-info}/METADATA +2 -1
- fastquadtree-1.2.0.dist-info/RECORD +13 -0
- fastquadtree-1.1.1.dist-info/RECORD +0 -13
- {fastquadtree-1.1.1.dist-info → fastquadtree-1.2.0.dist-info}/WHEEL +0 -0
- {fastquadtree-1.1.1.dist-info → fastquadtree-1.2.0.dist-info}/licenses/LICENSE +0 -0
fastquadtree/_base_quadtree.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# _abc_quadtree.py
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import pickle
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
@@ -58,8 +59,9 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
|
|
|
58
59
|
def _new_native(self, bounds: Bounds, capacity: int, max_depth: int | None) -> Any:
|
|
59
60
|
"""Create the native engine instance."""
|
|
60
61
|
|
|
62
|
+
@staticmethod
|
|
61
63
|
@abstractmethod
|
|
62
|
-
def _make_item(
|
|
64
|
+
def _make_item(id_: int, geom: G, obj: Any | None) -> ItemType:
|
|
63
65
|
"""Build an ItemType from id, geometry, and optional object."""
|
|
64
66
|
|
|
65
67
|
# ---- ctor ----
|
|
@@ -84,6 +86,71 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
|
|
|
84
86
|
self._next_id = 0
|
|
85
87
|
self._count = 0
|
|
86
88
|
|
|
89
|
+
# ---- serialization ----
|
|
90
|
+
|
|
91
|
+
def to_dict(self) -> dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Serialize the quadtree to a dict suitable for JSON or other serialization.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Includes a binary 'core' key for the native engine state, plus other metadata such as bounds and capacity and the obj store if tracking is enabled.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
core_bytes = self._native.to_bytes()
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
"core": core_bytes,
|
|
103
|
+
"store": self._store.to_dict() if self._store is not None else None,
|
|
104
|
+
"bounds": self._bounds,
|
|
105
|
+
"capacity": self._capacity,
|
|
106
|
+
"max_depth": self._max_depth,
|
|
107
|
+
"track_objects": self._track_objects,
|
|
108
|
+
"next_id": self._next_id,
|
|
109
|
+
"count": self._count,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
def to_bytes(self) -> bytes:
|
|
113
|
+
"""
|
|
114
|
+
Serialize the quadtree to bytes.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Bytes representing the serialized quadtree. Can be saved as a file or loaded with `from_bytes()`.
|
|
118
|
+
"""
|
|
119
|
+
return pickle.dumps(self.to_dict())
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_bytes(cls, data: bytes) -> _BaseQuadTree[G, HitT, ItemType]:
|
|
123
|
+
"""
|
|
124
|
+
Deserialize a quadtree from bytes.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
data: Bytes representing the serialized quadtree from `to_bytes()`.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
A new quadtree instance with the same state as when serialized.
|
|
131
|
+
"""
|
|
132
|
+
in_dict = pickle.loads(data)
|
|
133
|
+
core_bytes = in_dict["core"]
|
|
134
|
+
store_dict = in_dict["store"]
|
|
135
|
+
|
|
136
|
+
qt = cls.__new__(cls) # type: ignore[call-arg]
|
|
137
|
+
qt._native = cls._new_native_from_bytes(core_bytes)
|
|
138
|
+
|
|
139
|
+
if store_dict is not None:
|
|
140
|
+
qt._store = ObjStore.from_dict(store_dict, qt._make_item)
|
|
141
|
+
else:
|
|
142
|
+
qt._store = None
|
|
143
|
+
|
|
144
|
+
# Extract bounds, capacity, max_depth from native
|
|
145
|
+
qt._bounds = in_dict["bounds"]
|
|
146
|
+
qt._capacity = in_dict["capacity"]
|
|
147
|
+
qt._max_depth = in_dict["max_depth"]
|
|
148
|
+
qt._next_id = in_dict["next_id"]
|
|
149
|
+
qt._count = in_dict["count"]
|
|
150
|
+
qt._track_objects = in_dict["track_objects"]
|
|
151
|
+
|
|
152
|
+
return qt
|
|
153
|
+
|
|
87
154
|
# ---- internal helper ----
|
|
88
155
|
|
|
89
156
|
def _ids_to_objects(self, ids: Iterable[int]) -> list[Any]:
|
fastquadtree/_item.py
CHANGED
|
@@ -27,6 +27,35 @@ class Item:
|
|
|
27
27
|
self.geom: Point | Bounds = geom
|
|
28
28
|
self.obj: Any | None = obj
|
|
29
29
|
|
|
30
|
+
def to_dict(self) -> dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Serialize the item to a dictionary.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A dictionary with 'id', 'geom', and 'obj' keys.
|
|
36
|
+
"""
|
|
37
|
+
return {
|
|
38
|
+
"id": self.id_,
|
|
39
|
+
"geom": self.geom,
|
|
40
|
+
"obj": self.obj,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_dict(cls, data: dict[str, Any]) -> Item:
|
|
45
|
+
"""
|
|
46
|
+
Deserialize an item from a dictionary.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
data: A dictionary with 'id', 'geom', and 'obj' keys.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
An Item instance populated with the deserialized data.
|
|
53
|
+
"""
|
|
54
|
+
id_ = data["id"]
|
|
55
|
+
geom = data["geom"]
|
|
56
|
+
obj = data["obj"]
|
|
57
|
+
return cls(id_, geom, obj)
|
|
58
|
+
|
|
30
59
|
|
|
31
60
|
class PointItem(Item):
|
|
32
61
|
"""
|
fastquadtree/_native.abi3.so
CHANGED
|
Binary file
|
fastquadtree/_obj_store.py
CHANGED
|
@@ -35,11 +35,40 @@ class ObjStore(Generic[TItem]):
|
|
|
35
35
|
|
|
36
36
|
if items:
|
|
37
37
|
for it in items:
|
|
38
|
-
self.add(it)
|
|
38
|
+
self.add(it, handle_out_of_order=True)
|
|
39
|
+
|
|
40
|
+
# ---- Serialization ----
|
|
41
|
+
def to_dict(self) -> dict[str, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Serialize to a dict suitable for JSON or other serialization.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A dict with 'items' key containing list of serialized items.
|
|
47
|
+
"""
|
|
48
|
+
items = [it.to_dict() for it in self._arr if it is not None]
|
|
49
|
+
return {"items": items}
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_dict(cls, data: dict[str, Any], item_factory: Any) -> ObjStore[TItem]:
|
|
53
|
+
"""
|
|
54
|
+
Deserialize from a dict.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
data: A dict with 'items' key containing list of serialized items.
|
|
58
|
+
item_factory: A callable that takes (id, obj) and returns an Item.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
An ObjStore instance populated with the deserialized items.
|
|
62
|
+
"""
|
|
63
|
+
items = []
|
|
64
|
+
for item_data in data.get("items", []):
|
|
65
|
+
item = Item.from_dict(item_data)
|
|
66
|
+
items.append(item_factory(item.id_, item.geom, item.obj))
|
|
67
|
+
return cls(items)
|
|
39
68
|
|
|
40
69
|
# -------- core --------
|
|
41
70
|
|
|
42
|
-
def add(self, item: TItem) -> None:
|
|
71
|
+
def add(self, item: TItem, handle_out_of_order: bool = False) -> None:
|
|
43
72
|
"""
|
|
44
73
|
Insert or replace the mapping at item.id_. Reverse map updated so obj points to id.
|
|
45
74
|
"""
|
|
@@ -48,9 +77,14 @@ class ObjStore(Generic[TItem]):
|
|
|
48
77
|
|
|
49
78
|
# ids must be dense and assigned by the caller
|
|
50
79
|
if id_ > len(self._arr):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
80
|
+
if not handle_out_of_order:
|
|
81
|
+
raise AssertionError(
|
|
82
|
+
"ObjStore.add received an out-of-order id, use alloc_id() to get the next available id"
|
|
83
|
+
)
|
|
84
|
+
# fill holes with None
|
|
85
|
+
while len(self._arr) < id_:
|
|
86
|
+
self._arr.append(None)
|
|
87
|
+
self._objs.append(None)
|
|
54
88
|
|
|
55
89
|
if id_ == len(self._arr):
|
|
56
90
|
# append
|
fastquadtree/point_quadtree.py
CHANGED
|
@@ -49,6 +49,18 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
|
|
|
49
49
|
track_objects=track_objects,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_bytes(cls, data: bytes) -> QuadTree:
|
|
54
|
+
"""
|
|
55
|
+
Create a QuadTree instance from serialized bytes.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
data: Serialized byte data from `to_bytes()`.
|
|
59
|
+
Returns:
|
|
60
|
+
A QuadTree instance.
|
|
61
|
+
"""
|
|
62
|
+
return super().from_bytes(data)
|
|
63
|
+
|
|
52
64
|
@overload
|
|
53
65
|
def query(
|
|
54
66
|
self, rect: Bounds, *, as_items: Literal[False] = ...
|
|
@@ -145,5 +157,11 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
|
|
|
145
157
|
return _RustQuadTree(bounds, capacity)
|
|
146
158
|
return _RustQuadTree(bounds, capacity, max_depth=max_depth)
|
|
147
159
|
|
|
148
|
-
|
|
160
|
+
@classmethod
|
|
161
|
+
def _new_native_from_bytes(cls, data: bytes) -> Any:
|
|
162
|
+
"""Create a new native engine instance from serialized bytes."""
|
|
163
|
+
return _RustQuadTree.from_bytes(data)
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _make_item(id_: int, geom: Point, obj: Any | None) -> PointItem:
|
|
149
167
|
return PointItem(id_, geom, obj)
|
fastquadtree/pyqtree.py
CHANGED
|
@@ -122,6 +122,9 @@ class Index:
|
|
|
122
122
|
- **item**: The item to insert into the index, which will be returned by the intersection method
|
|
123
123
|
- **bbox**: The spatial bounding box tuple of the item, with four members (xmin,ymin,xmax,ymax)
|
|
124
124
|
"""
|
|
125
|
+
if type(bbox) is list: # Handle list input
|
|
126
|
+
bbox = tuple(bbox)
|
|
127
|
+
|
|
125
128
|
if self._free:
|
|
126
129
|
rid = self._free.pop()
|
|
127
130
|
self._objects[rid] = item
|
|
@@ -141,6 +144,9 @@ class Index:
|
|
|
141
144
|
|
|
142
145
|
Both parameters need to exactly match the parameters provided to the insert method.
|
|
143
146
|
"""
|
|
147
|
+
if type(bbox) is list: # Handle list input
|
|
148
|
+
bbox = tuple(bbox)
|
|
149
|
+
|
|
144
150
|
rid = self._item_to_id.pop(id(item))
|
|
145
151
|
self._qt.delete(rid, bbox)
|
|
146
152
|
self._objects[rid] = None
|
fastquadtree/rect_quadtree.py
CHANGED
|
@@ -50,6 +50,18 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
|
|
|
50
50
|
track_objects=track_objects,
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_bytes(cls, data: bytes) -> RectQuadTree:
|
|
55
|
+
"""
|
|
56
|
+
Create a RectQuadTree instance from serialized bytes.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
data: Serialized byte data from `to_bytes()`.
|
|
60
|
+
Returns:
|
|
61
|
+
A RectQuadTree instance.
|
|
62
|
+
"""
|
|
63
|
+
return super().from_bytes(data)
|
|
64
|
+
|
|
53
65
|
@overload
|
|
54
66
|
def query(
|
|
55
67
|
self, rect: Bounds, *, as_items: Literal[False] = ...
|
|
@@ -81,5 +93,11 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
|
|
|
81
93
|
return _RustRectQuadTree(bounds, capacity)
|
|
82
94
|
return _RustRectQuadTree(bounds, capacity, max_depth=max_depth)
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
@classmethod
|
|
97
|
+
def _new_native_from_bytes(cls, data: bytes) -> Any:
|
|
98
|
+
"""Create a new native engine instance from serialized bytes."""
|
|
99
|
+
return _RustRectQuadTree.from_bytes(data)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _make_item(id_: int, geom: Bounds, obj: Any | None) -> RectItem:
|
|
85
103
|
return RectItem(id_, geom, obj)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastquadtree
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
5
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
6
6
|
Classifier: Programming Language :: Rust
|
|
@@ -70,6 +70,7 @@ Rust-optimized quadtree with a clean Python API
|
|
|
70
70
|
- Support for [inserting bounding boxes](https://elan456.github.io/fastquadtree/api/rect_quadtree/) or points
|
|
71
71
|
- Fast KNN and range queries
|
|
72
72
|
- Optional object tracking for id ↔ object mapping
|
|
73
|
+
- Fast [serialization](https://elan456.github.io/fastquadtree/benchmark/#serialization-vs-rebuild) to/from bytes
|
|
73
74
|
- [100% test coverage](https://codecov.io/gh/Elan456/fastquadtree) and CI on GitHub Actions
|
|
74
75
|
- 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
|
|
75
76
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
fastquadtree-1.2.0.dist-info/METADATA,sha256=MUUv_93xtLD1Vbsm3t0JelXhuKt0VP1OmFlkQPcioHU,9614
|
|
2
|
+
fastquadtree-1.2.0.dist-info/WHEEL,sha256=cqfH6P_NujaeOc1olR46J5a7YgoxWJnrr5iZ1_DMqps,129
|
|
3
|
+
fastquadtree-1.2.0.dist-info/licenses/LICENSE,sha256=pRuvcuqIMtEUBMgvP1Bc4fOHydzeuA61c6DQoQ1pb1w,1071
|
|
4
|
+
fastquadtree/__init__.py,sha256=rtkveNz7rScRasTRGu1yEqzeoJfLfreJNxg21orPL-U,195
|
|
5
|
+
fastquadtree/_base_quadtree.py,sha256=ukiCKjsOPmnrkkKNLNJS5ad0X2HURYAWp9W60pk-c9Q,12045
|
|
6
|
+
fastquadtree/_item.py,sha256=EDS3nJHdVtjDsuTqTZKGTZH8iWJIQ-TKxLXqvMScNPA,2405
|
|
7
|
+
fastquadtree/_native.abi3.so,sha256=vAF6BAZuSwIczxJMxMuWpkQtFuxZVO0GYvEzXIaWJlY,540600
|
|
8
|
+
fastquadtree/_obj_store.py,sha256=HeYFGUPYhvxBzL7Js0g0jsIxflpZS6RsXNk50rGeNlA,6522
|
|
9
|
+
fastquadtree/point_quadtree.py,sha256=5ic-ZPq2AC20f9GkunKQAoH-W_doRFePOlJwqhST0FI,5697
|
|
10
|
+
fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
fastquadtree/pyqtree.py,sha256=2Khh1gCPalD4z0gb3EmqtzMoga08E9BkB0j5bwkiRPU,6076
|
|
12
|
+
fastquadtree/rect_quadtree.py,sha256=9JXqvh9WZGy7ZcfbXIRCWUf8oYpQz2QLB39f6XdP7u8,3559
|
|
13
|
+
fastquadtree-1.2.0.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
fastquadtree-1.1.1.dist-info/METADATA,sha256=szgCbwJaeD_tIire0P01nYD6Iy4_3E-XUXRhh4S6loQ,9501
|
|
2
|
-
fastquadtree-1.1.1.dist-info/WHEEL,sha256=cqfH6P_NujaeOc1olR46J5a7YgoxWJnrr5iZ1_DMqps,129
|
|
3
|
-
fastquadtree-1.1.1.dist-info/licenses/LICENSE,sha256=pRuvcuqIMtEUBMgvP1Bc4fOHydzeuA61c6DQoQ1pb1w,1071
|
|
4
|
-
fastquadtree/__init__.py,sha256=rtkveNz7rScRasTRGu1yEqzeoJfLfreJNxg21orPL-U,195
|
|
5
|
-
fastquadtree/_base_quadtree.py,sha256=o5T0xqDM9i_JjpYF4U7VmLM0qUHkw0wr0bbRqb-CGt4,9901
|
|
6
|
-
fastquadtree/_item.py,sha256=LoTDr7zlsZyUrrKK6Ketzl5fxTcFF8YbcxEXbQ65st4,1679
|
|
7
|
-
fastquadtree/_native.abi3.so,sha256=SL_mhqWi7EybECrblpSPyNQyFJfONjv_yH29HE0NiGw,508576
|
|
8
|
-
fastquadtree/_obj_store.py,sha256=vmhZGdzEoTQHvRbFjTne_0X2Z1l48SXyB6I9SAjjbiM,5267
|
|
9
|
-
fastquadtree/point_quadtree.py,sha256=Pz8ZS7N3kYSYJJYGa3ghKzy7d3JCA1dbi9nfEwwpF_k,5178
|
|
10
|
-
fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
fastquadtree/pyqtree.py,sha256=m3ylDtG3bnxBJSLoEH7u0rLdzb6l3MLI_UrggAkesI4,5908
|
|
12
|
-
fastquadtree/rect_quadtree.py,sha256=7F-JceCHn5RLhztxSYCIJEZ_e2TV-NeobobbrdauJQA,3024
|
|
13
|
-
fastquadtree-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|