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.
- rstar_python-0.0.2/.github/workflows/test_and_deploy.yml +210 -0
- rstar_python-0.0.2/.github/workflows/test_pypi.yml +36 -0
- rstar_python-0.0.2/.gitignore +10 -0
- rstar_python-0.0.2/CHANGELOG.md +18 -0
- rstar_python-0.0.2/Cargo.lock +339 -0
- rstar_python-0.0.2/Cargo.toml +17 -0
- rstar_python-0.0.2/LICENSE +21 -0
- rstar_python-0.0.2/PKG-INFO +116 -0
- rstar_python-0.0.2/README.md +90 -0
- rstar_python-0.0.2/pyproject.toml +40 -0
- rstar_python-0.0.2/python/__init__.py +3 -0
- rstar_python-0.0.2/python/tests/test_rtree.py +64 -0
- rstar_python-0.0.2/src/lib.rs +405 -0
|
@@ -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,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,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
|
+
}
|