fastquadtree 0.4.1__tar.gz → 0.5.0__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.
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/.github/workflows/release.yml +29 -11
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/.pre-commit-config.yaml +2 -11
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/Cargo.lock +1 -1
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/Cargo.toml +7 -1
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/PKG-INFO +29 -47
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/README.md +28 -46
- fastquadtree-0.5.0/assets/quadtree_bench_throughput.png +0 -0
- fastquadtree-0.5.0/assets/quadtree_bench_time.png +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/quadtree_bench/engines.py +1 -1
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/quadtree_bench/runner.py +1 -1
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/pyproject.toml +15 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/pysrc/fastquadtree/_bimap.py +3 -3
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/src/geom.rs +8 -8
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/src/lib.rs +8 -8
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/src/quadtree.rs +24 -27
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/insertions.rs +3 -3
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/nearest_neighbor.rs +6 -6
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/query.rs +4 -4
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_wrapper_edges.py +17 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/unconventional_bounds.rs +5 -5
- fastquadtree-0.4.1/assets/quadtree_bench_throughput.png +0 -0
- fastquadtree-0.4.1/assets/quadtree_bench_time.png +0 -0
- fastquadtree-0.4.1/examples/delete_demo.rs +0 -63
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/.gitignore +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/LICENSE +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/assets/interactive_v2_screenshot.png +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/benchmark_native_vs_shim.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/cross_library_bench.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/quadtree_bench/__init__.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/quadtree_bench/main.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/quadtree_bench/plotting.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/requirements.txt +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/benchmarks/runner.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/interactive/interactive.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/interactive/interactive_v2.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/interactive/requirements.txt +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/pysrc/fastquadtree/__init__.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/pysrc/fastquadtree/__init__.pyi +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/pysrc/fastquadtree/_item.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/pysrc/fastquadtree/py.typed +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/rectangle_traversal.rs +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_bimap.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_delete.rs +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_delete_by_object.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_delete_python.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_python.py +0 -0
- {fastquadtree-0.4.1 → fastquadtree-0.5.0}/tests/test_unconventional_bounds.py +0 -0
@@ -2,8 +2,7 @@ name: Publish to PyPI
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
push:
|
5
|
-
tags:
|
6
|
-
- "v*"
|
5
|
+
tags: ["v*"]
|
7
6
|
release:
|
8
7
|
types: [published]
|
9
8
|
workflow_dispatch:
|
@@ -18,21 +17,41 @@ jobs:
|
|
18
17
|
uses: actions/setup-python@v5
|
19
18
|
with:
|
20
19
|
python-version: "3.10"
|
21
|
-
|
20
|
+
|
21
|
+
- name: Create venv for maturin develop
|
22
|
+
run: python -m venv .venv
|
23
|
+
|
24
|
+
- name: Build with maturin & install into .venv
|
22
25
|
uses: PyO3/maturin-action@v1
|
23
26
|
with:
|
24
|
-
command:
|
27
|
+
command: develop
|
28
|
+
args: --release
|
25
29
|
manylinux: manylinux2014
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
|
31
|
+
- name: Install Python test deps into .venv
|
32
|
+
run: |
|
33
|
+
. .venv/bin/activate
|
34
|
+
pip install -U pip pytest pytest-cov
|
35
|
+
|
36
|
+
- name: Run Python tests
|
37
|
+
run: |
|
38
|
+
. .venv/bin/activate
|
39
|
+
pytest
|
40
|
+
|
41
|
+
- name: Install Rust toolchain
|
42
|
+
uses: dtolnay/rust-toolchain@stable
|
43
|
+
|
44
|
+
- name: Run Rust tests
|
45
|
+
run: cargo test
|
46
|
+
|
30
47
|
- name: Upload coverage to Codecov
|
31
48
|
uses: codecov/codecov-action@v5
|
32
49
|
with:
|
33
50
|
token: ${{ secrets.CODECOV_TOKEN }}
|
51
|
+
|
34
52
|
build:
|
35
53
|
name: Build wheels
|
54
|
+
needs: test
|
36
55
|
runs-on: ${{ matrix.os }}
|
37
56
|
strategy:
|
38
57
|
matrix:
|
@@ -45,8 +64,7 @@ jobs:
|
|
45
64
|
args: "--skip-existing"
|
46
65
|
steps:
|
47
66
|
- uses: actions/checkout@v4
|
48
|
-
-
|
49
|
-
uses: actions/setup-python@v5
|
67
|
+
- uses: actions/setup-python@v5
|
50
68
|
with:
|
51
69
|
python-version: "3.10"
|
52
70
|
- name: Build and publish with maturin
|
@@ -56,4 +74,4 @@ jobs:
|
|
56
74
|
with:
|
57
75
|
command: publish
|
58
76
|
args: ${{ matrix.args }}
|
59
|
-
manylinux: manylinux2014
|
77
|
+
manylinux: manylinux2014
|
@@ -31,16 +31,7 @@ repos:
|
|
31
31
|
# This both runs pytest and generates .coverage data
|
32
32
|
- id: pytest
|
33
33
|
name: pytest (under coverage)
|
34
|
-
entry:
|
34
|
+
entry: pytest
|
35
35
|
language: system
|
36
36
|
pass_filenames: false
|
37
|
-
always_run: true
|
38
|
-
|
39
|
-
# 4) Coverage threshold check
|
40
|
-
# Change FAIL_UNDER to your target percent
|
41
|
-
- id: coverage-report
|
42
|
-
name: coverage report
|
43
|
-
entry: bash -c 'coverage report --omit="tests/*" --fail-under=85'
|
44
|
-
language: system
|
45
|
-
pass_filenames: false
|
46
|
-
always_run: true
|
37
|
+
always_run: true
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[package]
|
2
2
|
name = "fastquadtree"
|
3
|
-
version = "0.
|
3
|
+
version = "0.5.0"
|
4
4
|
edition = "2021"
|
5
5
|
readme = "README.md"
|
6
6
|
|
@@ -9,3 +9,9 @@ crate-type = ["rlib", "cdylib"]
|
|
9
9
|
|
10
10
|
[dependencies]
|
11
11
|
pyo3 = { version = "0.21", features = ["extension-module", "abi3-py38"] }
|
12
|
+
|
13
|
+
[profile.release]
|
14
|
+
opt-level = 3
|
15
|
+
lto = "thin"
|
16
|
+
codegen-units = 1
|
17
|
+
panic = "abort"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fastquadtree
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
5
5
|
Classifier: Programming Language :: Python :: 3 :: Only
|
6
6
|
Classifier: Programming Language :: Rust
|
@@ -37,7 +37,6 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
|
|
37
37
|
[](https://pyo3.rs/)
|
38
38
|
[](https://www.maturin.rs/)
|
39
39
|
[](https://github.com/psf/black)
|
40
|
-
[](http://mypy-lang.org/)
|
41
40
|
|
42
41
|
|
43
42
|

|
@@ -49,6 +48,29 @@ Rust-optimized quadtree with a simple Python API.
|
|
49
48
|
- Python ≥ 3.8
|
50
49
|
- Import path: `from fastquadtree import QuadTree`
|
51
50
|
|
51
|
+
## Benchmarks
|
52
|
+
|
53
|
+
fastquadtree **outperforms** all other quadtree Python packages, including the Rtree spatial index.
|
54
|
+
|
55
|
+
### Library comparison
|
56
|
+
|
57
|
+

|
58
|
+

|
59
|
+
|
60
|
+
### Summary (largest dataset, PyQtree baseline)
|
61
|
+
- Points: **500,000**, Queries: **500**
|
62
|
+
--------------------
|
63
|
+
- Fastest total: **fastquadtree** at **1.591 s**
|
64
|
+
|
65
|
+
| Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
|
66
|
+
|---|---:|---:|---:|---:|
|
67
|
+
| fastquadtree | 0.165 | 1.427 | 1.591 | 5.09× |
|
68
|
+
| Rtree | 1.320 | 2.369 | 3.688 | 2.20× |
|
69
|
+
| PyQtree | 2.687 | 5.415 | 8.102 | 1.00× |
|
70
|
+
| nontree-QuadTree | 1.284 | 9.891 | 11.175 | 0.73× |
|
71
|
+
| quads | 2.346 | 10.129 | 12.475 | 0.65× |
|
72
|
+
| e-pyquadtree | 1.795 | 11.855 | 13.650 | 0.59× |
|
73
|
+
|
52
74
|
## Install
|
53
75
|
|
54
76
|
```bash
|
@@ -109,25 +131,7 @@ print(f"Deleted player: {deleted}") # True
|
|
109
131
|
|
110
132
|
You can keep the tree pure and manage your own id → object map, or let the wrapper manage it.
|
111
133
|
|
112
|
-
**
|
113
|
-
|
114
|
-
```python
|
115
|
-
from fastquadtree import QuadTree
|
116
|
-
|
117
|
-
qt = QuadTree((0, 0, 1000, 1000), capacity=16)
|
118
|
-
objects: dict[int, object] = {}
|
119
|
-
|
120
|
-
def add(obj) -> int:
|
121
|
-
obj_id = qt.insert(obj.position) # auto id
|
122
|
-
objects[obj_id] = obj
|
123
|
-
return obj_id
|
124
|
-
|
125
|
-
# Later, resolve ids back to objects
|
126
|
-
ids = [obj_id for (obj_id, x, y) in qt.query((100, 100, 300, 300))]
|
127
|
-
selected = [objects[i] for i in ids]
|
128
|
-
```
|
129
|
-
|
130
|
-
**Option B: Ask the wrapper to track objects**
|
134
|
+
**Wrapper Managed Objects**
|
131
135
|
|
132
136
|
```python
|
133
137
|
from fastquadtree import QuadTree
|
@@ -137,7 +141,7 @@ qt = QuadTree((0, 0, 1000, 1000), capacity=16, track_objects=True)
|
|
137
141
|
# Store the object alongside the point
|
138
142
|
qt.insert((25, 40), obj={"name": "apple"})
|
139
143
|
|
140
|
-
# Ask for Item objects
|
144
|
+
# Ask for Item objects within a bounding box
|
141
145
|
items = qt.query((0, 0, 100, 100), as_items=True)
|
142
146
|
for it in items:
|
143
147
|
print(it.id, it.x, it.y, it.obj)
|
@@ -151,7 +155,7 @@ qt.attach(123, my_object) # binds object to id 123
|
|
151
155
|
|
152
156
|
## API
|
153
157
|
|
154
|
-
### `QuadTree(bounds, capacity,
|
158
|
+
### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
|
155
159
|
|
156
160
|
* `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
|
157
161
|
* `capacity` — max number of points kept in a leaf before splitting
|
@@ -204,30 +208,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
204
208
|
* For fastest local runs, use `maturin develop --release`.
|
205
209
|
* The wrapper keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
|
206
210
|
|
207
|
-
## Benchmarks
|
208
|
-
|
209
|
-
fastquadtree outperforms all other quadtree python packages (at least all the ones I could find and install via pip.)
|
210
|
-
|
211
|
-
### Library comparison
|
212
|
-
|
213
|
-

|
214
|
-

|
215
|
-
|
216
|
-
### Summary (largest dataset, PyQtree baseline)
|
217
|
-
- Points: **500,000**, Queries: **500**
|
218
|
-
- Fastest total: **fastquadtree** at **2.207 s**
|
219
|
-
|
220
|
-
| Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
|
221
|
-
|---|---:|---:|---:|---:|
|
222
|
-
| fastquadtree | 0.321 | 1.885 | 2.207 | 4.27× |
|
223
|
-
| Rtree | 1.718 | 4.376 | 6.095 | 1.55× |
|
224
|
-
| nontree-QuadTree | 1.617 | 7.643 | 9.260 | 1.02× |
|
225
|
-
| PyQtree | 4.349 | 5.082 | 9.431 | 1.00× |
|
226
|
-
| quads | 3.874 | 9.058 | 12.932 | 0.73× |
|
227
|
-
| e-pyquadtree | 2.732 | 10.598 | 13.330 | 0.71× |
|
228
|
-
| Brute force | 0.019 | 19.986 | 20.005 | 0.47× |
|
229
211
|
|
230
|
-
### Native vs Shim
|
212
|
+
### Native vs Shim Benchmark
|
231
213
|
|
232
214
|
**Setup**
|
233
215
|
- Points: 500,000
|
@@ -277,7 +259,7 @@ Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_
|
|
277
259
|
Allowed. For k-nearest, duplicates are de-duplicated by id. For range queries you will see every inserted point.
|
278
260
|
|
279
261
|
**Can I delete items from the quadtree?**
|
280
|
-
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
|
262
|
+
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.
|
281
263
|
|
282
264
|
**Can I store rectangles or circles?**
|
283
265
|
The core stores points. To index objects with extent, insert whatever representative point you choose. For rectangles you can insert centers or build an AABB tree separately.
|
@@ -14,7 +14,6 @@
|
|
14
14
|
[](https://pyo3.rs/)
|
15
15
|
[](https://www.maturin.rs/)
|
16
16
|
[](https://github.com/psf/black)
|
17
|
-
[](http://mypy-lang.org/)
|
18
17
|
|
19
18
|
|
20
19
|

|
@@ -26,6 +25,29 @@ Rust-optimized quadtree with a simple Python API.
|
|
26
25
|
- Python ≥ 3.8
|
27
26
|
- Import path: `from fastquadtree import QuadTree`
|
28
27
|
|
28
|
+
## Benchmarks
|
29
|
+
|
30
|
+
fastquadtree **outperforms** all other quadtree Python packages, including the Rtree spatial index.
|
31
|
+
|
32
|
+
### Library comparison
|
33
|
+
|
34
|
+

|
35
|
+

|
36
|
+
|
37
|
+
### Summary (largest dataset, PyQtree baseline)
|
38
|
+
- Points: **500,000**, Queries: **500**
|
39
|
+
--------------------
|
40
|
+
- Fastest total: **fastquadtree** at **1.591 s**
|
41
|
+
|
42
|
+
| Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
|
43
|
+
|---|---:|---:|---:|---:|
|
44
|
+
| fastquadtree | 0.165 | 1.427 | 1.591 | 5.09× |
|
45
|
+
| Rtree | 1.320 | 2.369 | 3.688 | 2.20× |
|
46
|
+
| PyQtree | 2.687 | 5.415 | 8.102 | 1.00× |
|
47
|
+
| nontree-QuadTree | 1.284 | 9.891 | 11.175 | 0.73× |
|
48
|
+
| quads | 2.346 | 10.129 | 12.475 | 0.65× |
|
49
|
+
| e-pyquadtree | 1.795 | 11.855 | 13.650 | 0.59× |
|
50
|
+
|
29
51
|
## Install
|
30
52
|
|
31
53
|
```bash
|
@@ -86,25 +108,7 @@ print(f"Deleted player: {deleted}") # True
|
|
86
108
|
|
87
109
|
You can keep the tree pure and manage your own id → object map, or let the wrapper manage it.
|
88
110
|
|
89
|
-
**
|
90
|
-
|
91
|
-
```python
|
92
|
-
from fastquadtree import QuadTree
|
93
|
-
|
94
|
-
qt = QuadTree((0, 0, 1000, 1000), capacity=16)
|
95
|
-
objects: dict[int, object] = {}
|
96
|
-
|
97
|
-
def add(obj) -> int:
|
98
|
-
obj_id = qt.insert(obj.position) # auto id
|
99
|
-
objects[obj_id] = obj
|
100
|
-
return obj_id
|
101
|
-
|
102
|
-
# Later, resolve ids back to objects
|
103
|
-
ids = [obj_id for (obj_id, x, y) in qt.query((100, 100, 300, 300))]
|
104
|
-
selected = [objects[i] for i in ids]
|
105
|
-
```
|
106
|
-
|
107
|
-
**Option B: Ask the wrapper to track objects**
|
111
|
+
**Wrapper Managed Objects**
|
108
112
|
|
109
113
|
```python
|
110
114
|
from fastquadtree import QuadTree
|
@@ -114,7 +118,7 @@ qt = QuadTree((0, 0, 1000, 1000), capacity=16, track_objects=True)
|
|
114
118
|
# Store the object alongside the point
|
115
119
|
qt.insert((25, 40), obj={"name": "apple"})
|
116
120
|
|
117
|
-
# Ask for Item objects
|
121
|
+
# Ask for Item objects within a bounding box
|
118
122
|
items = qt.query((0, 0, 100, 100), as_items=True)
|
119
123
|
for it in items:
|
120
124
|
print(it.id, it.x, it.y, it.obj)
|
@@ -128,7 +132,7 @@ qt.attach(123, my_object) # binds object to id 123
|
|
128
132
|
|
129
133
|
## API
|
130
134
|
|
131
|
-
### `QuadTree(bounds, capacity,
|
135
|
+
### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
|
132
136
|
|
133
137
|
* `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
|
134
138
|
* `capacity` — max number of points kept in a leaf before splitting
|
@@ -181,30 +185,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
181
185
|
* For fastest local runs, use `maturin develop --release`.
|
182
186
|
* The wrapper keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
|
183
187
|
|
184
|
-
## Benchmarks
|
185
|
-
|
186
|
-
fastquadtree outperforms all other quadtree python packages (at least all the ones I could find and install via pip.)
|
187
|
-
|
188
|
-
### Library comparison
|
189
|
-
|
190
|
-

|
191
|
-

|
192
|
-
|
193
|
-
### Summary (largest dataset, PyQtree baseline)
|
194
|
-
- Points: **500,000**, Queries: **500**
|
195
|
-
- Fastest total: **fastquadtree** at **2.207 s**
|
196
|
-
|
197
|
-
| Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
|
198
|
-
|---|---:|---:|---:|---:|
|
199
|
-
| fastquadtree | 0.321 | 1.885 | 2.207 | 4.27× |
|
200
|
-
| Rtree | 1.718 | 4.376 | 6.095 | 1.55× |
|
201
|
-
| nontree-QuadTree | 1.617 | 7.643 | 9.260 | 1.02× |
|
202
|
-
| PyQtree | 4.349 | 5.082 | 9.431 | 1.00× |
|
203
|
-
| quads | 3.874 | 9.058 | 12.932 | 0.73× |
|
204
|
-
| e-pyquadtree | 2.732 | 10.598 | 13.330 | 0.71× |
|
205
|
-
| Brute force | 0.019 | 19.986 | 20.005 | 0.47× |
|
206
188
|
|
207
|
-
### Native vs Shim
|
189
|
+
### Native vs Shim Benchmark
|
208
190
|
|
209
191
|
**Setup**
|
210
192
|
- Points: 500,000
|
@@ -254,7 +236,7 @@ Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_
|
|
254
236
|
Allowed. For k-nearest, duplicates are de-duplicated by id. For range queries you will see every inserted point.
|
255
237
|
|
256
238
|
**Can I delete items from the quadtree?**
|
257
|
-
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
|
239
|
+
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.
|
258
240
|
|
259
241
|
**Can I store rectangles or circles?**
|
260
242
|
The core stores points. To index objects with extent, insert whatever representative point you choose. For rectangles you can insert centers or build an AABB tree separately.
|
Binary file
|
Binary file
|
@@ -247,7 +247,7 @@ def get_engines(
|
|
247
247
|
"fastquadtree": _create_fastquadtree_engine(bounds, max_points, max_depth),
|
248
248
|
"e-pyquadtree": _create_e_pyquadtree_engine(bounds, max_points, max_depth),
|
249
249
|
"PyQtree": _create_pyqtree_engine(bounds, max_points, max_depth),
|
250
|
-
|
250
|
+
# "Brute force": _create_brute_force_engine(bounds, max_points, max_depth), # Brute force doesn't scale well on the graphs so omit it from the main set
|
251
251
|
}
|
252
252
|
|
253
253
|
# Optional engines (only include if import succeeded)
|
@@ -34,7 +34,7 @@ class BenchmarkConfig:
|
|
34
34
|
"""Generate experiment point sizes."""
|
35
35
|
self.experiments = [2, 4, 8, 16]
|
36
36
|
while self.experiments[-1] < self.max_experiment_points:
|
37
|
-
self.experiments.append(int(self.experiments[-1] *
|
37
|
+
self.experiments.append(int(self.experiments[-1] * 2))
|
38
38
|
if self.experiments[-1] > self.max_experiment_points:
|
39
39
|
self.experiments[-1] = self.max_experiment_points
|
40
40
|
|
@@ -39,3 +39,18 @@ module-name = "fastquadtree._native"
|
|
39
39
|
# Choose a wide wheel tag for Linux. Adjust if your build images are newer.
|
40
40
|
# Options include: manylinux2014, manylinux_2_28, musllinux_1_2, or "off".
|
41
41
|
compatibility = "manylinux2014"
|
42
|
+
|
43
|
+
[tool.pytest.ini_options]
|
44
|
+
addopts = "--cov=fastquadtree --cov-branch --cov-report=xml --cov-fail-under=95"
|
45
|
+
testpaths = ["tests"] # still run tests
|
46
|
+
|
47
|
+
[tool.coverage.run]
|
48
|
+
source = ["fastquadtree"]
|
49
|
+
omit = [
|
50
|
+
"tests/*",
|
51
|
+
]
|
52
|
+
|
53
|
+
[tool.coverage.report]
|
54
|
+
omit = [
|
55
|
+
"tests/*",
|
56
|
+
]
|
@@ -80,11 +80,11 @@ class BiMap:
|
|
80
80
|
"""
|
81
81
|
removed = None
|
82
82
|
# Remove by id first
|
83
|
-
|
84
|
-
|
83
|
+
removed = self._id_to_item.pop(item.id)
|
84
|
+
|
85
85
|
# Remove by obj side
|
86
86
|
obj = item.obj
|
87
|
-
if obj is not None
|
87
|
+
if obj is not None:
|
88
88
|
self._objid_to_item.pop(id(obj), None)
|
89
89
|
removed = removed or item
|
90
90
|
return removed
|
@@ -1,15 +1,15 @@
|
|
1
1
|
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
2
2
|
pub struct Point {
|
3
|
-
pub x:
|
4
|
-
pub y:
|
3
|
+
pub x: f32,
|
4
|
+
pub y: f32,
|
5
5
|
}
|
6
6
|
|
7
7
|
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
8
8
|
pub struct Rect {
|
9
|
-
pub min_x:
|
10
|
-
pub min_y:
|
11
|
-
pub max_x:
|
12
|
-
pub max_y:
|
9
|
+
pub min_x: f32,
|
10
|
+
pub min_y: f32,
|
11
|
+
pub max_x: f32,
|
12
|
+
pub max_y: f32,
|
13
13
|
}
|
14
14
|
|
15
15
|
impl Rect {
|
@@ -23,7 +23,7 @@ impl Rect {
|
|
23
23
|
}
|
24
24
|
}
|
25
25
|
|
26
|
-
pub fn dist_sq_point_to_rect(p: &Point, r: &Rect) ->
|
26
|
+
pub fn dist_sq_point_to_rect(p: &Point, r: &Rect) -> f32 {
|
27
27
|
let dx = if p.x < r.min_x {
|
28
28
|
r.min_x - p.x
|
29
29
|
} else if p.x > r.max_x {
|
@@ -43,7 +43,7 @@ pub fn dist_sq_point_to_rect(p: &Point, r: &Rect) -> f64 {
|
|
43
43
|
dx * dx + dy * dy
|
44
44
|
}
|
45
45
|
|
46
|
-
pub fn dist_sq_points(a: &Point, b: &Point) ->
|
46
|
+
pub fn dist_sq_points(a: &Point, b: &Point) -> f32 {
|
47
47
|
let dx = a.x - b.x;
|
48
48
|
let dy = a.y - b.y;
|
49
49
|
dx * dx + dy * dy
|
@@ -8,7 +8,7 @@ pub use crate::quadtree::{Item, QuadTree};
|
|
8
8
|
|
9
9
|
use pyo3::prelude::*;
|
10
10
|
|
11
|
-
fn item_to_tuple(it: Item) -> (u64,
|
11
|
+
fn item_to_tuple(it: Item) -> (u64, f32, f32) {
|
12
12
|
(it.id, it.point.x, it.point.y)
|
13
13
|
}
|
14
14
|
|
@@ -20,7 +20,7 @@ pub struct PyQuadTree {
|
|
20
20
|
#[pymethods]
|
21
21
|
impl PyQuadTree {
|
22
22
|
#[new]
|
23
|
-
pub fn new(bounds: (
|
23
|
+
pub fn new(bounds: (f32, f32, f32, f32), capacity: usize, max_depth: Option<usize>) -> Self {
|
24
24
|
let (min_x, min_y, max_x, max_y) = bounds;
|
25
25
|
let rect = Rect { min_x, min_y, max_x, max_y };
|
26
26
|
let inner = match max_depth {
|
@@ -30,17 +30,17 @@ impl PyQuadTree {
|
|
30
30
|
Self { inner }
|
31
31
|
}
|
32
32
|
|
33
|
-
pub fn insert(&mut self, id: u64, xy: (
|
33
|
+
pub fn insert(&mut self, id: u64, xy: (f32, f32)) -> bool {
|
34
34
|
let (x, y) = xy;
|
35
35
|
self.inner.insert(Item { id, point: Point { x, y } })
|
36
36
|
}
|
37
37
|
|
38
|
-
pub fn delete(&mut self, id: u64, xy: (
|
38
|
+
pub fn delete(&mut self, id: u64, xy: (f32, f32)) -> bool {
|
39
39
|
let (x, y) = xy;
|
40
40
|
self.inner.delete(id, Point { x, y })
|
41
41
|
}
|
42
42
|
|
43
|
-
pub fn query(&self, rect: (
|
43
|
+
pub fn query(&self, rect: (f32, f32, f32, f32)) -> Vec<(u64, f32, f32)> {
|
44
44
|
let (min_x, min_y, max_x, max_y) = rect;
|
45
45
|
self.inner
|
46
46
|
.query(Rect { min_x, min_y, max_x, max_y })
|
@@ -49,12 +49,12 @@ impl PyQuadTree {
|
|
49
49
|
.collect()
|
50
50
|
}
|
51
51
|
|
52
|
-
pub fn nearest_neighbor(&self, xy: (
|
52
|
+
pub fn nearest_neighbor(&self, xy: (f32, f32)) -> Option<(u64, f32, f32)> {
|
53
53
|
let (x, y) = xy;
|
54
54
|
self.inner.nearest_neighbor(Point { x, y }).map(item_to_tuple)
|
55
55
|
}
|
56
56
|
|
57
|
-
pub fn nearest_neighbors(&self, xy: (
|
57
|
+
pub fn nearest_neighbors(&self, xy: (f32, f32), k: usize) -> Vec<(u64, f32, f32)> {
|
58
58
|
let (x, y) = xy;
|
59
59
|
self.inner
|
60
60
|
.nearest_neighbors(Point { x, y }, k)
|
@@ -64,7 +64,7 @@ impl PyQuadTree {
|
|
64
64
|
}
|
65
65
|
|
66
66
|
/// Returns all rectangle boundaries in the quadtree for visualization
|
67
|
-
pub fn get_all_rectangles(&self) -> Vec<(
|
67
|
+
pub fn get_all_rectangles(&self) -> Vec<(f32, f32, f32, f32)> {
|
68
68
|
self.inner
|
69
69
|
.get_all_rectangles()
|
70
70
|
.into_iter()
|
@@ -22,6 +22,7 @@ pub struct QuadTree {
|
|
22
22
|
// 1: (x >= cx, y < cy)
|
23
23
|
// 2: (x < cx, y >= cy)
|
24
24
|
// 3: (x >= cx, y >= cy)
|
25
|
+
#[inline(always)]
|
25
26
|
fn child_index_for_point(b: &Rect, p: &Point) -> usize {
|
26
27
|
let cx = 0.5 * (b.min_x + b.max_x);
|
27
28
|
let cy = 0.5 * (b.min_y + b.max_y);
|
@@ -34,7 +35,7 @@ impl QuadTree {
|
|
34
35
|
pub fn new(boundary: Rect, capacity: usize) -> Self {
|
35
36
|
QuadTree {
|
36
37
|
boundary,
|
37
|
-
items: Vec::
|
38
|
+
items: Vec::with_capacity(capacity),
|
38
39
|
capacity,
|
39
40
|
children: None,
|
40
41
|
depth: 0,
|
@@ -45,7 +46,7 @@ impl QuadTree {
|
|
45
46
|
pub fn new_with_max_depth(boundary: Rect, capacity: usize, max_depth: usize) -> Self {
|
46
47
|
QuadTree {
|
47
48
|
boundary,
|
48
|
-
items: Vec::
|
49
|
+
items: Vec::with_capacity(capacity),
|
49
50
|
capacity,
|
50
51
|
children: None,
|
51
52
|
depth: 0,
|
@@ -56,7 +57,7 @@ impl QuadTree {
|
|
56
57
|
pub fn new_child(boundary: Rect, capacity: usize, depth: usize, max_depth: usize) -> Self {
|
57
58
|
QuadTree {
|
58
59
|
boundary,
|
59
|
-
items: Vec::
|
60
|
+
items: Vec::with_capacity(capacity),
|
60
61
|
capacity,
|
61
62
|
children: None,
|
62
63
|
depth: depth,
|
@@ -121,42 +122,38 @@ impl QuadTree {
|
|
121
122
|
|
122
123
|
pub fn query(&self, range: Rect) -> Vec<Item> {
|
123
124
|
let mut out = Vec::new();
|
124
|
-
|
125
|
-
|
126
|
-
}
|
125
|
+
let mut stack: Vec<&QuadTree> = Vec::new();
|
126
|
+
stack.push(self);
|
127
127
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
// check items stored at this node
|
135
|
-
for it in &self.items {
|
136
|
-
if range.contains(&it.point) {
|
137
|
-
out.push(*it); // Item is Copy
|
128
|
+
while let Some(node) = stack.pop() {
|
129
|
+
for it in &node.items {
|
130
|
+
if range.contains(&it.point) {
|
131
|
+
out.push(*it);
|
132
|
+
}
|
138
133
|
}
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
134
|
+
if let Some(children) = node.children.as_ref() {
|
135
|
+
// Push children that intersect the query range
|
136
|
+
for child in children.iter() {
|
137
|
+
if range.intersects(&child.boundary) {
|
138
|
+
stack.push(child);
|
139
|
+
}
|
140
|
+
}
|
145
141
|
}
|
146
142
|
}
|
143
|
+
out
|
147
144
|
}
|
148
145
|
|
149
146
|
pub fn nearest_neighbor(&self, point: Point) -> Option<Item> {
|
150
|
-
self.nearest_neighbors_within(point, 1,
|
147
|
+
self.nearest_neighbors_within(point, 1, f32::INFINITY)
|
151
148
|
.into_iter()
|
152
149
|
.next()
|
153
150
|
}
|
154
151
|
|
155
152
|
pub fn nearest_neighbors(&self, point: Point, k: usize) -> Vec<Item> {
|
156
|
-
self.nearest_neighbors_within(point, k,
|
153
|
+
self.nearest_neighbors_within(point, k, f32::INFINITY)
|
157
154
|
}
|
158
155
|
|
159
|
-
pub fn nearest_neighbors_within(&self, point: Point, k: usize, max_distance:
|
156
|
+
pub fn nearest_neighbors_within(&self, point: Point, k: usize, max_distance: f32) -> Vec<Item> {
|
160
157
|
if k == 0 {
|
161
158
|
return Vec::new();
|
162
159
|
}
|
@@ -167,7 +164,7 @@ impl QuadTree {
|
|
167
164
|
|
168
165
|
for _ in 0..k {
|
169
166
|
// stack holds (node_ref, bbox_distance_sq)
|
170
|
-
let mut stack: Vec<(&QuadTree,
|
167
|
+
let mut stack: Vec<(&QuadTree, f32)> = Vec::new();
|
171
168
|
stack.push((self, dist_sq_point_to_rect(&point, &self.boundary)));
|
172
169
|
|
173
170
|
let mut best: Option<Item> = None;
|
@@ -181,7 +178,7 @@ impl QuadTree {
|
|
181
178
|
|
182
179
|
if let Some(children) = node.children.as_ref() {
|
183
180
|
// compute and sort children by bbox distance, push farthest first
|
184
|
-
let mut kids: Vec<(&QuadTree,
|
181
|
+
let mut kids: Vec<(&QuadTree, f32)> = children
|
185
182
|
.iter()
|
186
183
|
.map(|c| (c, dist_sq_point_to_rect(&point, &c.boundary)))
|
187
184
|
.filter(|(_, d2)| *d2 < best_d2)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
use fastquadtree::{Point, Rect, Item, QuadTree};
|
2
2
|
|
3
|
-
fn r(x0:
|
3
|
+
fn r(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
|
4
4
|
Rect { min_x: x0, min_y: y0, max_x: x1, max_y: y1 }
|
5
5
|
}
|
6
6
|
|
7
|
-
fn pt(x:
|
7
|
+
fn pt(x: f32, y: f32) -> Point { Point { x, y } }
|
8
8
|
|
9
9
|
#[test]
|
10
10
|
fn rect_contains_half_open() {
|
@@ -73,7 +73,7 @@ fn many_inserts_all_succeed_when_inside() {
|
|
73
73
|
let mut id = 1u64;
|
74
74
|
for x in (10..1000).step_by(100) {
|
75
75
|
for y in (10..1000).step_by(100) {
|
76
|
-
if qt.insert(Item { id, point: pt(x as
|
76
|
+
if qt.insert(Item { id, point: pt(x as f32, y as f32) }) {
|
77
77
|
ok += 1;
|
78
78
|
}
|
79
79
|
id += 1;
|
@@ -1,15 +1,15 @@
|
|
1
1
|
use fastquadtree::{Point, Rect, Item, QuadTree};
|
2
2
|
|
3
|
-
fn r(x0:
|
3
|
+
fn r(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
|
4
4
|
Rect { min_x: x0, min_y: y0, max_x: x1, max_y: y1 }
|
5
5
|
}
|
6
|
-
fn pt(x:
|
6
|
+
fn pt(x: f32, y: f32) -> Point { Point { x, y } }
|
7
7
|
fn ids(v: &[Item]) -> Vec<u64> {
|
8
8
|
let mut out: Vec<u64> = v.iter().map(|it| it.id).collect();
|
9
9
|
out.sort_unstable();
|
10
10
|
out
|
11
11
|
}
|
12
|
-
fn dist2(a: Point, b: Point) ->
|
12
|
+
fn dist2(a: Point, b: Point) -> f32 {
|
13
13
|
let dx = a.x - b.x;
|
14
14
|
let dy = a.y - b.y;
|
15
15
|
dx * dx + dy * dy
|
@@ -69,7 +69,7 @@ fn knn_basic_ordering_no_split() {
|
|
69
69
|
assert_eq!(order, vec![2, 1, 4, 3, 5]);
|
70
70
|
|
71
71
|
// distances should be nondecreasing
|
72
|
-
let d: Vec<
|
72
|
+
let d: Vec<f32> = res.iter().map(|it| dist2(q, it.point)).collect();
|
73
73
|
for w in d.windows(2) {
|
74
74
|
assert!(w[0] <= w[1] + 1e-12);
|
75
75
|
}
|
@@ -176,7 +176,7 @@ fn ordering_is_by_distance_even_after_splits() {
|
|
176
176
|
let mut id = 1u64;
|
177
177
|
for y in (4..=60).step_by(8) {
|
178
178
|
for x in (4..=60).step_by(8) {
|
179
|
-
qt.insert(Item { id, point: pt(x as
|
179
|
+
qt.insert(Item { id, point: pt(x as f32, y as f32) });
|
180
180
|
id += 1;
|
181
181
|
}
|
182
182
|
}
|
@@ -185,7 +185,7 @@ fn ordering_is_by_distance_even_after_splits() {
|
|
185
185
|
assert!(!res.is_empty());
|
186
186
|
|
187
187
|
// distances should be nondecreasing
|
188
|
-
let d: Vec<
|
188
|
+
let d: Vec<f32> = res.iter().map(|it| dist2(q, it.point)).collect();
|
189
189
|
for w in d.windows(2) {
|
190
190
|
assert!(w[0] <= w[1] + 1e-12);
|
191
191
|
}
|
@@ -1,9 +1,9 @@
|
|
1
1
|
use fastquadtree::{Point, Rect, Item, QuadTree};
|
2
2
|
|
3
|
-
fn r(x0:
|
3
|
+
fn r(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
|
4
4
|
Rect { min_x: x0, min_y: y0, max_x: x1, max_y: y1 }
|
5
5
|
}
|
6
|
-
fn pt(x:
|
6
|
+
fn pt(x: f32, y: f32) -> Point { Point { x, y } }
|
7
7
|
fn ids(v: &[Item]) -> Vec<u64> {
|
8
8
|
let mut out: Vec<u64> = v.iter().map(|it| it.id).collect();
|
9
9
|
out.sort_unstable();
|
@@ -101,8 +101,8 @@ fn query_range_covering_multiple_children_returns_union() {
|
|
101
101
|
fn full_range_query_returns_all_items() {
|
102
102
|
let mut qt = QuadTree::new(r(0.0, 0.0, 100.0, 100.0), 3);
|
103
103
|
for i in 0..9 {
|
104
|
-
let x = 10.0 + 10.0 * (i as
|
105
|
-
let y = 20.0 + 5.0 * (i as
|
104
|
+
let x = 10.0 + 10.0 * (i as f32);
|
105
|
+
let y = 20.0 + 5.0 * (i as f32);
|
106
106
|
assert!(qt.insert(Item { id: i + 1, point: pt(x, y) }));
|
107
107
|
}
|
108
108
|
let hits = qt.query(r(0.0, 0.0, 100.0, 100.0));
|
@@ -207,3 +207,20 @@ def test_insert_many_points_exception_for_out_of_bounds():
|
|
207
207
|
|
208
208
|
with pytest.raises(ValueError):
|
209
209
|
qt.insert_many_points(points)
|
210
|
+
|
211
|
+
|
212
|
+
def test_auto_id_collision_prevention():
|
213
|
+
qt = QuadTree(BOUNDS, capacity=8, track_objects=True)
|
214
|
+
id1 = qt.insert((10, 10)) # auto ID
|
215
|
+
|
216
|
+
id2 = qt.insert((20, 20), id=200) # Large ID
|
217
|
+
|
218
|
+
# Next auto ID should be 201
|
219
|
+
id3 = qt.insert((30, 30)) # auto ID
|
220
|
+
|
221
|
+
assert id3 == 201
|
222
|
+
assert len(qt) == 3
|
223
|
+
assert id1 != id2 != id3
|
224
|
+
|
225
|
+
id4 = qt.insert((40, 40), id=150) # Manual ID lower than current auto ID
|
226
|
+
assert id4 == 150
|
@@ -1,14 +1,14 @@
|
|
1
1
|
use fastquadtree::{Point, Rect, Item, QuadTree};
|
2
2
|
|
3
|
-
fn r(x0:
|
3
|
+
fn r(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
|
4
4
|
Rect { min_x: x0, min_y: y0, max_x: x1, max_y: y1 }
|
5
5
|
}
|
6
6
|
|
7
|
-
fn pt(x:
|
7
|
+
fn pt(x: f32, y: f32) -> Point {
|
8
8
|
Point { x, y }
|
9
9
|
}
|
10
10
|
|
11
|
-
fn item(id: u64, x:
|
11
|
+
fn item(id: u64, x: f32, y: f32) -> Item {
|
12
12
|
Item { id, point: pt(x, y) }
|
13
13
|
}
|
14
14
|
|
@@ -226,8 +226,8 @@ fn stress_test_negative_coordinates() {
|
|
226
226
|
let mut inserted_count = 0;
|
227
227
|
for i in 0..50 {
|
228
228
|
for j in 0..50 {
|
229
|
-
let x = -20.0 * (i as
|
230
|
-
let y = -20.0 * (j as
|
229
|
+
let x = -20.0 * (i as f32) - 10.0; // Range from -10 to -990
|
230
|
+
let y = -20.0 * (j as f32) - 10.0; // Range from -10 to -990
|
231
231
|
let id = (i * 50 + j) as u64 + 1;
|
232
232
|
if x >= -1000.0 && y >= -1000.0 && x < 0.0 && y < 0.0 {
|
233
233
|
assert!(qt.insert(item(id, x, y)));
|
Binary file
|
Binary file
|
@@ -1,63 +0,0 @@
|
|
1
|
-
use fastquadtree::{QuadTree, Item, Point, Rect};
|
2
|
-
|
3
|
-
fn main() {
|
4
|
-
println!("=== QuadTree Delete by ID+Location Demo ===\n");
|
5
|
-
|
6
|
-
// Create a quadtree
|
7
|
-
let mut tree = QuadTree::new(Rect { min_x: 0.0, min_y: 0.0, max_x: 100.0, max_y: 100.0 }, 4);
|
8
|
-
|
9
|
-
// Insert multiple items at the same location
|
10
|
-
let shared_location = Point { x: 50.0, y: 50.0 };
|
11
|
-
tree.insert(Item { id: 1, point: shared_location });
|
12
|
-
tree.insert(Item { id: 2, point: shared_location });
|
13
|
-
tree.insert(Item { id: 3, point: shared_location });
|
14
|
-
|
15
|
-
// Insert items at different locations
|
16
|
-
tree.insert(Item { id: 4, point: Point { x: 25.0, y: 25.0 } });
|
17
|
-
tree.insert(Item { id: 5, point: Point { x: 75.0, y: 75.0 } });
|
18
|
-
|
19
|
-
println!("Initial tree with {} items", tree.count_items());
|
20
|
-
|
21
|
-
// Query the area around the shared location
|
22
|
-
let query_rect = Rect { min_x: 45.0, min_y: 45.0, max_x: 55.0, max_y: 55.0 };
|
23
|
-
let items_at_location = tree.query(query_rect);
|
24
|
-
println!("Items at (50,50): {:?}", items_at_location.iter().map(|i| i.id).collect::<Vec<_>>());
|
25
|
-
|
26
|
-
// Delete specific item by ID+location - only removes that specific item
|
27
|
-
println!("\nDeleting item with ID=2 at (50,50)...");
|
28
|
-
let deleted = tree.delete(2, shared_location);
|
29
|
-
println!("Delete successful: {}", deleted);
|
30
|
-
println!("Tree now has {} items", tree.count_items());
|
31
|
-
|
32
|
-
// Verify the other items at the same location are still there
|
33
|
-
let remaining_items = tree.query(query_rect);
|
34
|
-
println!("Remaining items at (50,50): {:?}", remaining_items.iter().map(|i| i.id).collect::<Vec<_>>());
|
35
|
-
|
36
|
-
// Try to delete the same item again - should fail
|
37
|
-
println!("\nTrying to delete ID=2 again...");
|
38
|
-
let deleted_again = tree.delete(2, shared_location);
|
39
|
-
println!("Delete successful: {}", deleted_again);
|
40
|
-
|
41
|
-
// Try to delete with wrong ID - should fail
|
42
|
-
println!("\nTrying to delete ID=999 at (50,50)...");
|
43
|
-
let wrong_id = tree.delete(999, shared_location);
|
44
|
-
println!("Delete successful: {}", wrong_id);
|
45
|
-
|
46
|
-
// Try to delete with wrong location - should fail
|
47
|
-
println!("\nTrying to delete ID=1 at wrong location (60,60)...");
|
48
|
-
let wrong_location = tree.delete(1, Point { x: 60.0, y: 60.0 });
|
49
|
-
println!("Delete successful: {}", wrong_location);
|
50
|
-
|
51
|
-
// Delete the remaining items at the shared location
|
52
|
-
println!("\nDeleting remaining items at (50,50)...");
|
53
|
-
tree.delete(1, shared_location);
|
54
|
-
tree.delete(3, shared_location);
|
55
|
-
|
56
|
-
println!("Final tree has {} items", tree.count_items());
|
57
|
-
|
58
|
-
// Verify the items at other locations are still there
|
59
|
-
let all_items = tree.query(Rect { min_x: 0.0, min_y: 0.0, max_x: 100.0, max_y: 100.0 });
|
60
|
-
println!("All remaining items: {:?}", all_items.iter().map(|i| (i.id, i.point.x, i.point.y)).collect::<Vec<_>>());
|
61
|
-
|
62
|
-
println!("\n=== Demo Complete ===");
|
63
|
-
}
|
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
|