rstar-python 0.0.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.
@@ -0,0 +1,210 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ branches: [ "**" ]
9
+ workflow_dispatch: # Allow manual trigger
10
+
11
+ concurrency:
12
+ group: wheel-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ env:
16
+ MACOSX_DEPLOYMENT_TARGET: "10.12"
17
+
18
+ jobs:
19
+ test:
20
+ runs-on: ${{ matrix.os }}
21
+ strategy:
22
+ matrix:
23
+ os: [ubuntu-latest, windows-latest, macos-latest]
24
+ python-version: ["3.10", "3.11", "3.12"]
25
+
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Set up Python ${{ matrix.python-version }}
30
+ uses: actions/setup-python@v5
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+
34
+ - name: Install Rust
35
+ uses: dtolnay/rust-toolchain@stable
36
+
37
+ - name: Create virtual environment
38
+ run: |
39
+ python -m venv .venv
40
+
41
+ - name: Install dependencies (Unix)
42
+ if: runner.os != 'Windows'
43
+ run: |
44
+ source .venv/bin/activate
45
+ python -m pip install --upgrade pip
46
+ pip install maturin pytest numpy
47
+
48
+ - name: Install dependencies (Windows)
49
+ if: runner.os == 'Windows'
50
+ run: |
51
+ .venv\Scripts\activate
52
+ python -m pip install --upgrade pip
53
+ pip install maturin pytest numpy
54
+
55
+ - name: Build and install (Unix)
56
+ if: runner.os != 'Windows'
57
+ run: |
58
+ source .venv/bin/activate
59
+ maturin develop --release
60
+
61
+ - name: Build and install (Windows)
62
+ if: runner.os == 'Windows'
63
+ run: |
64
+ .venv\Scripts\activate
65
+ maturin develop --release
66
+
67
+ - name: Run tests (Unix)
68
+ if: runner.os != 'Windows'
69
+ run: |
70
+ source .venv/bin/activate
71
+ pytest python/tests -v
72
+
73
+ - name: Run tests (Windows)
74
+ if: runner.os == 'Windows'
75
+ run: |
76
+ .venv\Scripts\activate
77
+ pytest python/tests -v
78
+
79
+ cargo-test:
80
+ name: Run cargo tests
81
+ runs-on: ubuntu-latest
82
+ steps:
83
+ - uses: actions/checkout@v4
84
+ - uses: actions/cache@v4
85
+ with:
86
+ path: |
87
+ ~/.cargo/bin/
88
+ ~/.cargo/registry/index/
89
+ ~/.cargo/registry/cache/
90
+ ~/.cargo/git/db/
91
+ target/
92
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
93
+ - name: Run tests
94
+ run: cargo test --all-features --workspace
95
+
96
+ make_sdist:
97
+ needs: [test, cargo-test]
98
+ name: Make SDist
99
+ runs-on: ubuntu-latest
100
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+ with:
104
+ fetch-depth: 0
105
+ submodules: true
106
+
107
+ - name: Build sdist
108
+ uses: PyO3/maturin-action@v1
109
+ with:
110
+ command: sdist
111
+ args: --out dist
112
+
113
+ - name: Check sdist
114
+ run: |
115
+ ls -la dist/
116
+ # Make twine check non-fatal for now
117
+ pip install twine
118
+ twine check dist/* || echo "Twine check failed but continuing"
119
+
120
+ - uses: actions/upload-artifact@v4
121
+ with:
122
+ name: pypi-sdist
123
+ path: dist/*.tar.gz
124
+
125
+ build_wheels:
126
+ name: Build wheels on ${{ matrix.os }}
127
+ needs: [test, cargo-test]
128
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
129
+ runs-on: ${{ matrix.os }}
130
+ strategy:
131
+ fail-fast: false
132
+ matrix:
133
+ os: [ubuntu-latest, windows-latest, macos-13, macos-14]
134
+
135
+ steps:
136
+ - uses: actions/checkout@v4
137
+ with:
138
+ fetch-depth: 0
139
+ submodules: true
140
+
141
+ - name: Build wheels - Linux
142
+ if: runner.os == 'Linux'
143
+ uses: PyO3/maturin-action@v1
144
+ with:
145
+ target: x86_64
146
+ args: --release --out dist
147
+ sccache: 'true'
148
+ manylinux: auto
149
+
150
+ - name: Build wheels - Windows
151
+ if: runner.os == 'Windows'
152
+ uses: PyO3/maturin-action@v1
153
+ with:
154
+ target: x64
155
+ args: --release --out dist
156
+ sccache: 'true'
157
+
158
+ - name: Build wheels - macOS (Intel)
159
+ if: matrix.os == 'macos-13'
160
+ uses: PyO3/maturin-action@v1
161
+ with:
162
+ target: x86_64
163
+ args: --release --out dist
164
+ sccache: 'true'
165
+
166
+ - name: Build wheels - macOS (Apple Silicon)
167
+ if: matrix.os == 'macos-14'
168
+ uses: PyO3/maturin-action@v1
169
+ with:
170
+ target: aarch64
171
+ args: --release --out dist
172
+ sccache: 'true'
173
+
174
+ - name: Upload wheels
175
+ uses: actions/upload-artifact@v4
176
+ with:
177
+ name: pypi-wheels-${{ matrix.os }}
178
+ path: dist/*.whl
179
+
180
+ upload_pypi:
181
+ name: Upload to PyPI
182
+ needs: [build_wheels, make_sdist]
183
+ environment:
184
+ name: pypi
185
+ url: https://pypi.org/p/rstar-python
186
+ permissions:
187
+ id-token: write
188
+ runs-on: ubuntu-latest
189
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
190
+
191
+ steps:
192
+ - uses: actions/download-artifact@v4
193
+ with:
194
+ pattern: pypi-*
195
+ path: dist
196
+ merge-multiple: true
197
+
198
+ - name: List artifacts
199
+ run: |
200
+ ls -la dist/
201
+ echo "Found $(ls dist/*.whl | wc -l) wheel files"
202
+ echo "Found $(ls dist/*.tar.gz | wc -l) source distributions"
203
+
204
+ - name: Publish to PyPI
205
+ uses: pypa/gh-action-pypi-publish@release/v1
206
+ with:
207
+ packages-dir: dist/
208
+ verify-metadata: true
209
+ skip-existing: false
210
+ verbose: true
@@ -0,0 +1,36 @@
1
+ name: Test PyPI Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: 'Version to release (e.g., 0.1.0rc1)'
8
+ required: true
9
+
10
+ jobs:
11
+ build_and_publish:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.11"
20
+
21
+ - name: Update version
22
+ run: |
23
+ sed -i 's/version = ".*"/version = "${{ github.event.inputs.version }}"/' pyproject.toml
24
+ sed -i 's/version = ".*"/version = "${{ github.event.inputs.version }}"/' Cargo.toml
25
+
26
+ - name: Build with maturin
27
+ uses: PyO3/maturin-action@v1
28
+ with:
29
+ command: build
30
+ args: --release --sdist
31
+
32
+ - name: Publish to Test PyPI
33
+ uses: pypa/gh-action-pypi-publish@release/v1
34
+ with:
35
+ repository-url: https://test.pypi.org/legacy/
36
+ password: ${{ secrets.TEST_PYPI_API_TOKEN }}
@@ -0,0 +1,10 @@
1
+ /target
2
+ *.so
3
+ *.pyd
4
+ *.dll
5
+ __pycache__
6
+ *.pyc
7
+ .pytest_cache
8
+ /venv
9
+ /dist
10
+ *.egg-info
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.0.1] - 2025-02-20
11
+
12
+ ### Added
13
+ - Initial release of rstar-python
14
+ - Python bindings for the rstar R*-tree spatial index library
15
+ - Support for 2D and 3D spatial indexing
16
+ - Methods: insert, bulk_load, nearest, locate_within_distance, remove
17
+ - Support for Python 3.10, 3.11, and 3.12
18
+ - Wheels didn't work!
@@ -0,0 +1,339 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
10
+
11
+ [[package]]
12
+ name = "bitflags"
13
+ version = "2.9.4"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
16
+
17
+ [[package]]
18
+ name = "byteorder"
19
+ version = "1.5.0"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
22
+
23
+ [[package]]
24
+ name = "cfg-if"
25
+ version = "1.0.3"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
28
+
29
+ [[package]]
30
+ name = "hash32"
31
+ version = "0.3.1"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
34
+ dependencies = [
35
+ "byteorder",
36
+ ]
37
+
38
+ [[package]]
39
+ name = "heapless"
40
+ version = "0.8.0"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
43
+ dependencies = [
44
+ "hash32",
45
+ "stable_deref_trait",
46
+ ]
47
+
48
+ [[package]]
49
+ name = "indoc"
50
+ version = "1.0.9"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
53
+
54
+ [[package]]
55
+ name = "libc"
56
+ version = "0.2.176"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
59
+
60
+ [[package]]
61
+ name = "libm"
62
+ version = "0.2.15"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
65
+
66
+ [[package]]
67
+ name = "lock_api"
68
+ version = "0.4.13"
69
+ source = "registry+https://github.com/rust-lang/crates.io-index"
70
+ checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
71
+ dependencies = [
72
+ "autocfg",
73
+ "scopeguard",
74
+ ]
75
+
76
+ [[package]]
77
+ name = "memoffset"
78
+ version = "0.9.1"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
81
+ dependencies = [
82
+ "autocfg",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "num-traits"
87
+ version = "0.2.19"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
90
+ dependencies = [
91
+ "autocfg",
92
+ "libm",
93
+ ]
94
+
95
+ [[package]]
96
+ name = "once_cell"
97
+ version = "1.21.3"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
100
+
101
+ [[package]]
102
+ name = "parking_lot"
103
+ version = "0.12.4"
104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
105
+ checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
106
+ dependencies = [
107
+ "lock_api",
108
+ "parking_lot_core",
109
+ ]
110
+
111
+ [[package]]
112
+ name = "parking_lot_core"
113
+ version = "0.9.11"
114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
115
+ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
116
+ dependencies = [
117
+ "cfg-if",
118
+ "libc",
119
+ "redox_syscall",
120
+ "smallvec",
121
+ "windows-targets",
122
+ ]
123
+
124
+ [[package]]
125
+ name = "proc-macro2"
126
+ version = "1.0.101"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
129
+ dependencies = [
130
+ "unicode-ident",
131
+ ]
132
+
133
+ [[package]]
134
+ name = "pyo3"
135
+ version = "0.19.2"
136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
137
+ checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38"
138
+ dependencies = [
139
+ "cfg-if",
140
+ "indoc",
141
+ "libc",
142
+ "memoffset",
143
+ "parking_lot",
144
+ "pyo3-build-config",
145
+ "pyo3-ffi",
146
+ "pyo3-macros",
147
+ "unindent",
148
+ ]
149
+
150
+ [[package]]
151
+ name = "pyo3-build-config"
152
+ version = "0.19.2"
153
+ source = "registry+https://github.com/rust-lang/crates.io-index"
154
+ checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5"
155
+ dependencies = [
156
+ "once_cell",
157
+ "target-lexicon",
158
+ ]
159
+
160
+ [[package]]
161
+ name = "pyo3-ffi"
162
+ version = "0.19.2"
163
+ source = "registry+https://github.com/rust-lang/crates.io-index"
164
+ checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9"
165
+ dependencies = [
166
+ "libc",
167
+ "pyo3-build-config",
168
+ ]
169
+
170
+ [[package]]
171
+ name = "pyo3-macros"
172
+ version = "0.19.2"
173
+ source = "registry+https://github.com/rust-lang/crates.io-index"
174
+ checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1"
175
+ dependencies = [
176
+ "proc-macro2",
177
+ "pyo3-macros-backend",
178
+ "quote",
179
+ "syn",
180
+ ]
181
+
182
+ [[package]]
183
+ name = "pyo3-macros-backend"
184
+ version = "0.19.2"
185
+ source = "registry+https://github.com/rust-lang/crates.io-index"
186
+ checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536"
187
+ dependencies = [
188
+ "proc-macro2",
189
+ "quote",
190
+ "syn",
191
+ ]
192
+
193
+ [[package]]
194
+ name = "quote"
195
+ version = "1.0.40"
196
+ source = "registry+https://github.com/rust-lang/crates.io-index"
197
+ checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
198
+ dependencies = [
199
+ "proc-macro2",
200
+ ]
201
+
202
+ [[package]]
203
+ name = "redox_syscall"
204
+ version = "0.5.17"
205
+ source = "registry+https://github.com/rust-lang/crates.io-index"
206
+ checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
207
+ dependencies = [
208
+ "bitflags",
209
+ ]
210
+
211
+ [[package]]
212
+ name = "rstar"
213
+ version = "0.12.2"
214
+ source = "registry+https://github.com/rust-lang/crates.io-index"
215
+ checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
216
+ dependencies = [
217
+ "heapless",
218
+ "num-traits",
219
+ "smallvec",
220
+ ]
221
+
222
+ [[package]]
223
+ name = "rstar-python"
224
+ version = "0.0.2"
225
+ dependencies = [
226
+ "pyo3",
227
+ "rstar",
228
+ ]
229
+
230
+ [[package]]
231
+ name = "scopeguard"
232
+ version = "1.2.0"
233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
234
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
235
+
236
+ [[package]]
237
+ name = "smallvec"
238
+ version = "1.15.1"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
241
+
242
+ [[package]]
243
+ name = "stable_deref_trait"
244
+ version = "1.2.0"
245
+ source = "registry+https://github.com/rust-lang/crates.io-index"
246
+ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
247
+
248
+ [[package]]
249
+ name = "syn"
250
+ version = "1.0.109"
251
+ source = "registry+https://github.com/rust-lang/crates.io-index"
252
+ checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
253
+ dependencies = [
254
+ "proc-macro2",
255
+ "quote",
256
+ "unicode-ident",
257
+ ]
258
+
259
+ [[package]]
260
+ name = "target-lexicon"
261
+ version = "0.12.16"
262
+ source = "registry+https://github.com/rust-lang/crates.io-index"
263
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
264
+
265
+ [[package]]
266
+ name = "unicode-ident"
267
+ version = "1.0.19"
268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
269
+ checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
270
+
271
+ [[package]]
272
+ name = "unindent"
273
+ version = "0.1.11"
274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
275
+ checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
276
+
277
+ [[package]]
278
+ name = "windows-targets"
279
+ version = "0.52.6"
280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
281
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
282
+ dependencies = [
283
+ "windows_aarch64_gnullvm",
284
+ "windows_aarch64_msvc",
285
+ "windows_i686_gnu",
286
+ "windows_i686_gnullvm",
287
+ "windows_i686_msvc",
288
+ "windows_x86_64_gnu",
289
+ "windows_x86_64_gnullvm",
290
+ "windows_x86_64_msvc",
291
+ ]
292
+
293
+ [[package]]
294
+ name = "windows_aarch64_gnullvm"
295
+ version = "0.52.6"
296
+ source = "registry+https://github.com/rust-lang/crates.io-index"
297
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
298
+
299
+ [[package]]
300
+ name = "windows_aarch64_msvc"
301
+ version = "0.52.6"
302
+ source = "registry+https://github.com/rust-lang/crates.io-index"
303
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
304
+
305
+ [[package]]
306
+ name = "windows_i686_gnu"
307
+ version = "0.52.6"
308
+ source = "registry+https://github.com/rust-lang/crates.io-index"
309
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
310
+
311
+ [[package]]
312
+ name = "windows_i686_gnullvm"
313
+ version = "0.52.6"
314
+ source = "registry+https://github.com/rust-lang/crates.io-index"
315
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
316
+
317
+ [[package]]
318
+ name = "windows_i686_msvc"
319
+ version = "0.52.6"
320
+ source = "registry+https://github.com/rust-lang/crates.io-index"
321
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
322
+
323
+ [[package]]
324
+ name = "windows_x86_64_gnu"
325
+ version = "0.52.6"
326
+ source = "registry+https://github.com/rust-lang/crates.io-index"
327
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
328
+
329
+ [[package]]
330
+ name = "windows_x86_64_gnullvm"
331
+ version = "0.52.6"
332
+ source = "registry+https://github.com/rust-lang/crates.io-index"
333
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
334
+
335
+ [[package]]
336
+ name = "windows_x86_64_msvc"
337
+ version = "0.52.6"
338
+ source = "registry+https://github.com/rust-lang/crates.io-index"
339
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -0,0 +1,17 @@
1
+ [package]
2
+ name = "rstar-python"
3
+ version = "0.0.2" # Update version to match
4
+ edition = "2021"
5
+ license = "MIT" # Add license field
6
+ description = "Python bindings for the rstar R*-tree spatial index"
7
+ repository = "https://github.com/kephale/rstar-python"
8
+ homepage = "https://github.com/kephale/rstar-python"
9
+ readme = "README.md"
10
+
11
+ [lib]
12
+ name = "rstar_python"
13
+ crate-type = ["cdylib"]
14
+
15
+ [dependencies]
16
+ pyo3 = { version = "0.19", features = ["extension-module"] }
17
+ rstar = "0.12"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kyle I S Harrington
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: rstar-python
3
+ Version: 0.0.2
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Rust
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Requires-Dist: numpy>=1.21.6
16
+ License-File: LICENSE
17
+ Summary: Python bindings for the rstar R*-tree spatial index
18
+ Home-Page: https://github.com/kephale/rstar-python
19
+ Author-email: Kyle Harrington <kyle@kyleharrington.com>
20
+ License: MIT
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
23
+ Project-URL: Homepage, https://github.com/kephale/rstar-python
24
+ Project-URL: Repository, https://github.com/kephale/rstar-python
25
+ Project-URL: Issues, https://github.com/kephale/rstar-python/issues
26
+
27
+ # rstar-python
28
+
29
+ Python bindings for the [rstar](https://github.com/georust/rstar) R*-tree spatial index library.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pip install rstar-python
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```python
40
+ from rstar_python import PyRTree
41
+
42
+ # Create a 3D R-tree
43
+ tree = PyRTree(dims=3)
44
+
45
+ # Insert points
46
+ tree.insert([1.0, 2.0, 3.0])
47
+ tree.insert([4.0, 5.0, 6.0])
48
+
49
+ # Bulk load points
50
+ points = [
51
+ [1.0, 2.0, 3.0],
52
+ [4.0, 5.0, 6.0],
53
+ [7.0, 8.0, 9.0]
54
+ ]
55
+ tree.bulk_load(points)
56
+
57
+ # Find nearest neighbor
58
+ nearest = tree.nearest_neighbor([1.1, 2.1, 3.1])
59
+
60
+ # Find k nearest neighbors
61
+ k_nearest = tree.k_nearest_neighbors([1.1, 2.1, 3.1], k=2)
62
+
63
+ # Find neighbors within radius
64
+ neighbors = tree.neighbors_within_radius([1.0, 2.0, 3.0], radius=1.0)
65
+
66
+ # Query points within a bounding box
67
+ points = tree.locate_in_envelope(
68
+ min_corner=[0.0, 0.0, 0.0],
69
+ max_corner=[2.0, 2.0, 2.0]
70
+ )
71
+
72
+ # Get tree size
73
+ size = tree.size()
74
+
75
+ # Remove a point
76
+ tree.remove([1.0, 2.0, 3.0])
77
+ ```
78
+
79
+ ## Features
80
+
81
+ - Supports 1D to 4D points
82
+ - Fast nearest neighbor queries
83
+ - Radius search
84
+ - Bounding box queries
85
+ - Bulk loading for faster initialization
86
+ - Built on top of the fast Rust rstar library
87
+
88
+ ## Development
89
+
90
+ Requirements:
91
+ - Rust
92
+ - Python 3.7+
93
+ - maturin
94
+
95
+ ```bash
96
+ # Clone repository
97
+ git clone https://github.com/kephale/rstar-python
98
+ cd rstar-python
99
+
100
+ # Create virtual environment
101
+ python -m venv venv
102
+ source venv/bin/activate # or `venv\Scripts\activate` on Windows
103
+
104
+ # Install development dependencies
105
+ pip install maturin pytest
106
+
107
+ # Build and install in development mode
108
+ maturin develop
109
+
110
+ # Run tests
111
+ pytest
112
+ ```
113
+
114
+ ## License
115
+
116
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,90 @@
1
+ # rstar-python
2
+
3
+ Python bindings for the [rstar](https://github.com/georust/rstar) R*-tree spatial index library.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install rstar-python
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from rstar_python import PyRTree
15
+
16
+ # Create a 3D R-tree
17
+ tree = PyRTree(dims=3)
18
+
19
+ # Insert points
20
+ tree.insert([1.0, 2.0, 3.0])
21
+ tree.insert([4.0, 5.0, 6.0])
22
+
23
+ # Bulk load points
24
+ points = [
25
+ [1.0, 2.0, 3.0],
26
+ [4.0, 5.0, 6.0],
27
+ [7.0, 8.0, 9.0]
28
+ ]
29
+ tree.bulk_load(points)
30
+
31
+ # Find nearest neighbor
32
+ nearest = tree.nearest_neighbor([1.1, 2.1, 3.1])
33
+
34
+ # Find k nearest neighbors
35
+ k_nearest = tree.k_nearest_neighbors([1.1, 2.1, 3.1], k=2)
36
+
37
+ # Find neighbors within radius
38
+ neighbors = tree.neighbors_within_radius([1.0, 2.0, 3.0], radius=1.0)
39
+
40
+ # Query points within a bounding box
41
+ points = tree.locate_in_envelope(
42
+ min_corner=[0.0, 0.0, 0.0],
43
+ max_corner=[2.0, 2.0, 2.0]
44
+ )
45
+
46
+ # Get tree size
47
+ size = tree.size()
48
+
49
+ # Remove a point
50
+ tree.remove([1.0, 2.0, 3.0])
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - Supports 1D to 4D points
56
+ - Fast nearest neighbor queries
57
+ - Radius search
58
+ - Bounding box queries
59
+ - Bulk loading for faster initialization
60
+ - Built on top of the fast Rust rstar library
61
+
62
+ ## Development
63
+
64
+ Requirements:
65
+ - Rust
66
+ - Python 3.7+
67
+ - maturin
68
+
69
+ ```bash
70
+ # Clone repository
71
+ git clone https://github.com/kephale/rstar-python
72
+ cd rstar-python
73
+
74
+ # Create virtual environment
75
+ python -m venv venv
76
+ source venv/bin/activate # or `venv\Scripts\activate` on Windows
77
+
78
+ # Install development dependencies
79
+ pip install maturin pytest
80
+
81
+ # Build and install in development mode
82
+ maturin develop
83
+
84
+ # Run tests
85
+ pytest
86
+ ```
87
+
88
+ ## License
89
+
90
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "rstar-python"
7
+ version = "0.0.2" # Update version
8
+ description = "Python bindings for the rstar R*-tree spatial index"
9
+ readme = "README.md"
10
+ license = {text = "MIT"} # Modern license field
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Kyle Harrington", email = "kyle@kyleharrington.com" }
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Rust",
24
+ "Operating System :: POSIX :: Linux",
25
+ "Operating System :: MacOS",
26
+ "Operating System :: Microsoft :: Windows",
27
+ ]
28
+ dependencies = [
29
+ "numpy>=1.21.6",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/kephale/rstar-python"
34
+ Repository = "https://github.com/kephale/rstar-python"
35
+ Issues = "https://github.com/kephale/rstar-python/issues"
36
+
37
+ [tool.maturin]
38
+ features = ["pyo3/extension-module"]
39
+ python-source = "python"
40
+ module-name = "rstar_python.rstar_python"
@@ -0,0 +1,3 @@
1
+ from rstar_python import PyRTree
2
+
3
+ __all__ = ['PyRTree']
@@ -0,0 +1,64 @@
1
+ import pytest
2
+ from rstar_python import PyRTree
3
+ import numpy as np
4
+
5
+ def test_create_rtree():
6
+ tree = PyRTree(dims=3)
7
+ assert tree.size() == 0
8
+
9
+ def test_invalid_dimensions():
10
+ with pytest.raises(ValueError):
11
+ PyRTree(dims=5)
12
+ with pytest.raises(ValueError):
13
+ PyRTree(dims=0)
14
+
15
+ def test_insert_and_size():
16
+ tree = PyRTree(dims=2)
17
+ tree.insert([1.0, 2.0])
18
+ assert tree.size() == 1
19
+ tree.insert([3.0, 4.0])
20
+ assert tree.size() == 2
21
+
22
+ def test_nearest_neighbor():
23
+ tree = PyRTree(dims=2)
24
+ points = [
25
+ [0.0, 0.0],
26
+ [1.0, 1.0],
27
+ [2.0, 2.0]
28
+ ]
29
+ for point in points:
30
+ tree.insert(point)
31
+
32
+ nearest = tree.nearest_neighbor([0.1, 0.1])
33
+ assert nearest is not None
34
+ assert np.allclose(nearest, [0.0, 0.0])
35
+
36
+ def test_k_nearest_neighbors():
37
+ tree = PyRTree(dims=2)
38
+ points = [
39
+ [0.0, 0.0],
40
+ [1.0, 1.0],
41
+ [2.0, 2.0]
42
+ ]
43
+ for point in points:
44
+ tree.insert(point)
45
+
46
+ k_nearest = tree.k_nearest_neighbors([0.1, 0.1], k=2)
47
+ assert len(k_nearest) == 2
48
+ assert np.allclose(k_nearest[0], [0.0, 0.0])
49
+ assert np.allclose(k_nearest[1], [1.0, 1.0])
50
+
51
+ def test_neighbors_within_radius():
52
+ tree = PyRTree(dims=2)
53
+ points = [
54
+ [0.0, 0.0],
55
+ [1.0, 0.0],
56
+ [2.0, 0.0]
57
+ ]
58
+ for point in points:
59
+ tree.insert(point)
60
+
61
+ neighbors = tree.neighbors_within_radius([0.0, 0.0], radius=1.5)
62
+ assert len(neighbors) == 2
63
+ assert any(np.allclose(n, [0.0, 0.0]) for n in neighbors)
64
+ assert any(np.allclose(n, [1.0, 0.0]) for n in neighbors)
@@ -0,0 +1,405 @@
1
+ use pyo3::prelude::*;
2
+ use pyo3::types::{PyList, PyTuple};
3
+ use rstar::{Point, RTree, RTreeObject, AABB};
4
+ use std::fmt::Debug;
5
+
6
+ // Type aliases for different dimensional points
7
+ type Point1D = [f64; 1];
8
+ type Point2D = [f64; 2];
9
+ type Point3D = [f64; 3];
10
+ type Point4D = [f64; 4];
11
+
12
+ // Generic wrapper around RTree
13
+ #[pyclass]
14
+ struct PyRTree {
15
+ tree_1d: Option<RTree<Point1D>>,
16
+ tree_2d: Option<RTree<Point2D>>,
17
+ tree_3d: Option<RTree<Point3D>>,
18
+ tree_4d: Option<RTree<Point4D>>,
19
+ dims: usize,
20
+ }
21
+
22
+ #[pymethods]
23
+ impl PyRTree {
24
+ #[new]
25
+ fn new(dims: usize) -> PyResult<Self> {
26
+ if dims < 1 || dims > 4 {
27
+ return Err(pyo3::exceptions::PyValueError::new_err(
28
+ "Dimensions must be between 1 and 4",
29
+ ));
30
+ }
31
+
32
+ let mut rtree = PyRTree {
33
+ tree_1d: None,
34
+ tree_2d: None,
35
+ tree_3d: None,
36
+ tree_4d: None,
37
+ dims,
38
+ };
39
+
40
+ match dims {
41
+ 1 => rtree.tree_1d = Some(RTree::new()),
42
+ 2 => rtree.tree_2d = Some(RTree::new()),
43
+ 3 => rtree.tree_3d = Some(RTree::new()),
44
+ 4 => rtree.tree_4d = Some(RTree::new()),
45
+ _ => unreachable!(),
46
+ }
47
+
48
+ Ok(rtree)
49
+ }
50
+
51
+ fn insert(&mut self, point: Vec<f64>) -> PyResult<()> {
52
+ if point.len() != self.dims {
53
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
54
+ "Expected {} dimensions, got {}",
55
+ self.dims,
56
+ point.len()
57
+ )));
58
+ }
59
+
60
+ match self.dims {
61
+ 1 => {
62
+ if let Some(tree) = &mut self.tree_1d {
63
+ tree.insert([point[0]]);
64
+ }
65
+ }
66
+ 2 => {
67
+ if let Some(tree) = &mut self.tree_2d {
68
+ tree.insert([point[0], point[1]]);
69
+ }
70
+ }
71
+ 3 => {
72
+ if let Some(tree) = &mut self.tree_3d {
73
+ tree.insert([point[0], point[1], point[2]]);
74
+ }
75
+ }
76
+ 4 => {
77
+ if let Some(tree) = &mut self.tree_4d {
78
+ tree.insert([point[0], point[1], point[2], point[3]]);
79
+ }
80
+ }
81
+ _ => unreachable!(),
82
+ }
83
+ Ok(())
84
+ }
85
+
86
+ fn bulk_load(&mut self, points: Vec<Vec<f64>>) -> PyResult<()> {
87
+ if points.is_empty() {
88
+ return Ok(());
89
+ }
90
+
91
+ // Validate dimensions
92
+ if points[0].len() != self.dims {
93
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
94
+ "Expected {} dimensions, got {}",
95
+ self.dims,
96
+ points[0].len()
97
+ )));
98
+ }
99
+
100
+ match self.dims {
101
+ 1 => {
102
+ let points_1d: Vec<Point1D> = points.into_iter().map(|p| [p[0]]).collect();
103
+ self.tree_1d = Some(RTree::bulk_load(points_1d));
104
+ }
105
+ 2 => {
106
+ let points_2d: Vec<Point2D> = points.into_iter().map(|p| [p[0], p[1]]).collect();
107
+ self.tree_2d = Some(RTree::bulk_load(points_2d));
108
+ }
109
+ 3 => {
110
+ let points_3d: Vec<Point3D> = points
111
+ .into_iter()
112
+ .map(|p| [p[0], p[1], p[2]])
113
+ .collect();
114
+ self.tree_3d = Some(RTree::bulk_load(points_3d));
115
+ }
116
+ 4 => {
117
+ let points_4d: Vec<Point4D> = points
118
+ .into_iter()
119
+ .map(|p| [p[0], p[1], p[2], p[3]])
120
+ .collect();
121
+ self.tree_4d = Some(RTree::bulk_load(points_4d));
122
+ }
123
+ _ => unreachable!(),
124
+ }
125
+ Ok(())
126
+ }
127
+
128
+ fn nearest_neighbor(&self, point: Vec<f64>) -> PyResult<Option<Vec<f64>>> {
129
+ if point.len() != self.dims {
130
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
131
+ "Expected {} dimensions, got {}",
132
+ self.dims,
133
+ point.len()
134
+ )));
135
+ }
136
+
137
+ let result = match self.dims {
138
+ 1 => self
139
+ .tree_1d
140
+ .as_ref()
141
+ .and_then(|tree| tree.nearest_neighbor(&[point[0]]))
142
+ .map(|p| vec![p[0]]),
143
+ 2 => self
144
+ .tree_2d
145
+ .as_ref()
146
+ .and_then(|tree| tree.nearest_neighbor(&[point[0], point[1]]))
147
+ .map(|p| vec![p[0], p[1]]),
148
+ 3 => self
149
+ .tree_3d
150
+ .as_ref()
151
+ .and_then(|tree| tree.nearest_neighbor(&[point[0], point[1], point[2]]))
152
+ .map(|p| vec![p[0], p[1], p[2]]),
153
+ 4 => self
154
+ .tree_4d
155
+ .as_ref()
156
+ .and_then(|tree| {
157
+ tree.nearest_neighbor(&[point[0], point[1], point[2], point[3]])
158
+ })
159
+ .map(|p| vec![p[0], p[1], p[2], p[3]]),
160
+ _ => unreachable!(),
161
+ };
162
+
163
+ Ok(result)
164
+ }
165
+
166
+ fn k_nearest_neighbors(&self, point: Vec<f64>, k: usize) -> PyResult<Vec<Vec<f64>>> {
167
+ if point.len() != self.dims {
168
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
169
+ "Expected {} dimensions, got {}",
170
+ self.dims,
171
+ point.len()
172
+ )));
173
+ }
174
+
175
+ let mut result = match self.dims {
176
+ 1 => self
177
+ .tree_1d
178
+ .as_ref()
179
+ .map(|tree| {
180
+ tree.nearest_neighbor_iter(&[point[0]])
181
+ .take(k)
182
+ .map(|p| vec![p[0]])
183
+ .collect()
184
+ })
185
+ .unwrap_or_default(),
186
+ 2 => self
187
+ .tree_2d
188
+ .as_ref()
189
+ .map(|tree| {
190
+ tree.nearest_neighbor_iter(&[point[0], point[1]])
191
+ .take(k)
192
+ .map(|p| vec![p[0], p[1]])
193
+ .collect()
194
+ })
195
+ .unwrap_or_default(),
196
+ 3 => self
197
+ .tree_3d
198
+ .as_ref()
199
+ .map(|tree| {
200
+ tree.nearest_neighbor_iter(&[point[0], point[1], point[2]])
201
+ .take(k)
202
+ .map(|p| vec![p[0], p[1], p[2]])
203
+ .collect()
204
+ })
205
+ .unwrap_or_default(),
206
+ 4 => self
207
+ .tree_4d
208
+ .as_ref()
209
+ .map(|tree| {
210
+ tree.nearest_neighbor_iter(&[point[0], point[1], point[2], point[3]])
211
+ .take(k)
212
+ .map(|p| vec![p[0], p[1], p[2], p[3]])
213
+ .collect()
214
+ })
215
+ .unwrap_or_default(),
216
+ _ => unreachable!(),
217
+ };
218
+
219
+ Ok(result)
220
+ }
221
+
222
+ fn neighbors_within_radius(&self, point: Vec<f64>, radius: f64) -> PyResult<Vec<Vec<f64>>> {
223
+ if point.len() != self.dims {
224
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
225
+ "Expected {} dimensions, got {}",
226
+ self.dims,
227
+ point.len()
228
+ )));
229
+ }
230
+
231
+ if radius < 0.0 {
232
+ return Err(pyo3::exceptions::PyValueError::new_err(
233
+ "Radius must be non-negative"
234
+ ));
235
+ }
236
+
237
+ let squared_radius = radius * radius;
238
+ let result = match self.dims {
239
+ 1 => self
240
+ .tree_1d
241
+ .as_ref()
242
+ .map(|tree| {
243
+ tree.locate_within_distance([point[0]], squared_radius)
244
+ .map(|p| vec![p[0]])
245
+ .collect()
246
+ })
247
+ .unwrap_or_default(),
248
+ 2 => self
249
+ .tree_2d
250
+ .as_ref()
251
+ .map(|tree| {
252
+ tree.locate_within_distance([point[0], point[1]], squared_radius)
253
+ .map(|p| vec![p[0], p[1]])
254
+ .collect()
255
+ })
256
+ .unwrap_or_default(),
257
+ 3 => self
258
+ .tree_3d
259
+ .as_ref()
260
+ .map(|tree| {
261
+ tree.locate_within_distance([point[0], point[1], point[2]], squared_radius)
262
+ .map(|p| vec![p[0], p[1], p[2]])
263
+ .collect()
264
+ })
265
+ .unwrap_or_default(),
266
+ 4 => self
267
+ .tree_4d
268
+ .as_ref()
269
+ .map(|tree| {
270
+ tree.locate_within_distance(
271
+ [point[0], point[1], point[2], point[3]],
272
+ squared_radius,
273
+ )
274
+ .map(|p| vec![p[0], p[1], p[2], p[3]])
275
+ .collect()
276
+ })
277
+ .unwrap_or_default(),
278
+ _ => unreachable!(),
279
+ };
280
+
281
+ Ok(result)
282
+ }
283
+
284
+ fn size(&self) -> usize {
285
+ match self.dims {
286
+ 1 => self.tree_1d.as_ref().map(|tree| tree.size()).unwrap_or(0),
287
+ 2 => self.tree_2d.as_ref().map(|tree| tree.size()).unwrap_or(0),
288
+ 3 => self.tree_3d.as_ref().map(|tree| tree.size()).unwrap_or(0),
289
+ 4 => self.tree_4d.as_ref().map(|tree| tree.size()).unwrap_or(0),
290
+ _ => unreachable!(),
291
+ }
292
+ }
293
+
294
+ fn remove(&mut self, point: Vec<f64>) -> PyResult<bool> {
295
+ if point.len() != self.dims {
296
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
297
+ "Expected {} dimensions, got {}",
298
+ self.dims,
299
+ point.len()
300
+ )));
301
+ }
302
+
303
+ let removed = match self.dims {
304
+ 1 => self
305
+ .tree_1d
306
+ .as_mut()
307
+ .and_then(|tree| tree.remove(&[point[0]]))
308
+ .is_some(),
309
+ 2 => self
310
+ .tree_2d
311
+ .as_mut()
312
+ .and_then(|tree| tree.remove(&[point[0], point[1]]))
313
+ .is_some(),
314
+ 3 => self
315
+ .tree_3d
316
+ .as_mut()
317
+ .and_then(|tree| tree.remove(&[point[0], point[1], point[2]]))
318
+ .is_some(),
319
+ 4 => self
320
+ .tree_4d
321
+ .as_mut()
322
+ .and_then(|tree| tree.remove(&[point[0], point[1], point[2], point[3]]))
323
+ .is_some(),
324
+ _ => unreachable!(),
325
+ };
326
+
327
+ Ok(removed)
328
+ }
329
+
330
+ fn locate_in_envelope(&self, min_corner: Vec<f64>, max_corner: Vec<f64>) -> PyResult<Vec<Vec<f64>>> {
331
+ if min_corner.len() != self.dims || max_corner.len() != self.dims {
332
+ return Err(pyo3::exceptions::PyValueError::new_err(format!(
333
+ "Expected {} dimensions for corners, got {} and {}",
334
+ self.dims,
335
+ min_corner.len(),
336
+ max_corner.len()
337
+ )));
338
+ }
339
+
340
+ let result = match self.dims {
341
+ 1 => {
342
+ let envelope = AABB::from_corners([min_corner[0]], [max_corner[0]]);
343
+ self.tree_1d
344
+ .as_ref()
345
+ .map(|tree| {
346
+ tree.locate_in_envelope(&envelope)
347
+ .map(|p| vec![p[0]])
348
+ .collect()
349
+ })
350
+ .unwrap_or_default()
351
+ }
352
+ 2 => {
353
+ let envelope = AABB::from_corners(
354
+ [min_corner[0], min_corner[1]],
355
+ [max_corner[0], max_corner[1]],
356
+ );
357
+ self.tree_2d
358
+ .as_ref()
359
+ .map(|tree| {
360
+ tree.locate_in_envelope(&envelope)
361
+ .map(|p| vec![p[0], p[1]])
362
+ .collect()
363
+ })
364
+ .unwrap_or_default()
365
+ }
366
+ 3 => {
367
+ let envelope = AABB::from_corners(
368
+ [min_corner[0], min_corner[1], min_corner[2]],
369
+ [max_corner[0], max_corner[1], max_corner[2]],
370
+ );
371
+ self.tree_3d
372
+ .as_ref()
373
+ .map(|tree| {
374
+ tree.locate_in_envelope(&envelope)
375
+ .map(|p| vec![p[0], p[1], p[2]])
376
+ .collect()
377
+ })
378
+ .unwrap_or_default()
379
+ }
380
+ 4 => {
381
+ let envelope = AABB::from_corners(
382
+ [min_corner[0], min_corner[1], min_corner[2], min_corner[3]],
383
+ [max_corner[0], max_corner[1], max_corner[2], max_corner[3]],
384
+ );
385
+ self.tree_4d
386
+ .as_ref()
387
+ .map(|tree| {
388
+ tree.locate_in_envelope(&envelope)
389
+ .map(|p| vec![p[0], p[1], p[2], p[3]])
390
+ .collect()
391
+ })
392
+ .unwrap_or_default()
393
+ }
394
+ _ => unreachable!(),
395
+ };
396
+
397
+ Ok(result)
398
+ }
399
+ }
400
+
401
+ #[pymodule]
402
+ fn rstar_python(_py: Python, m: &PyModule) -> PyResult<()> {
403
+ m.add_class::<PyRTree>()?;
404
+ Ok(())
405
+ }