[Git][debian-gis-team/python-rtree][upstream] New upstream version 1.4.0
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Thu Mar 6 04:31:38 GMT 2025
Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-rtree
Commits:
e25d6645 by Bas Couwenberg at 2025-03-06T05:18:23+01:00
New upstream version 1.4.0
- - - - -
26 changed files:
- .github/workflows/deploy.yml
- .github/workflows/test.yml
- .gitignore
- .pre-commit-config.yaml
- .readthedocs.yaml
- CHANGES.rst
- DEPENDENCIES.txt
- README.md
- benchmarks/benchmarks.py
- docs/source/class.rst
- docs/source/install.rst
- docs/source/performance.rst
- environment.yml
- pyproject.toml
- rtree/__init__.py
- rtree/core.py
- rtree/finder.py
- rtree/index.py
- ci/install_libspatialindex.bat → scripts/install_libspatialindex.bat
- ci/install_libspatialindex.bash → scripts/install_libspatialindex.sh
- scripts/repair_wheel.py
- setup.py
- tests/conftest.py
- tests/test_index.py
- tests/test_tpr.py
- tox.ini
Changes:
=====================================
.github/workflows/deploy.yml
=====================================
@@ -18,40 +18,29 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [windows-latest, ubuntu-latest, macos-latest]
+ os:
+ - windows-latest
+ - ubuntu-latest
+ - ubuntu-24.04-arm
+ - macos-latest
steps:
- uses: actions/checkout at v4
- - name: Set up QEMU
- if: runner.os == 'Linux'
- uses: docker/setup-qemu-action at v3
- with:
- platforms: arm64
-
- uses: actions/setup-python at v5
name: Install Python
with:
python-version: '3.11'
- - name: Setup
- run: pip install wheel
-
- uses: ilammy/msvc-dev-cmd at v1
if: startsWith(matrix.os, 'windows')
- - name: Run Windows Preinstall Build
- if: startsWith(matrix.os, 'windows')
- run: |
- choco install vcpython27 -f -y
- ci\install_libspatialindex.bat
-
- name: Build wheels
- uses: pypa/cibuildwheel at v2.19.2
+ uses: pypa/cibuildwheel at v2.23.0
- uses: actions/upload-artifact at v4
with:
- name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
+ name: cibw-wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl
build_sdist:
=====================================
.github/workflows/test.yml
=====================================
@@ -4,21 +4,12 @@ on:
push:
branches:
- master
- paths:
- - '.github/workflows/test.yml'
pull_request:
workflow_dispatch:
schedule:
- cron: '0 6 * * 1'
jobs:
- pre-commit:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout at v4
- - uses: actions/setup-python at v5
- - uses: pre-commit/action at v3.0.1
-
conda:
name: Conda ${{ matrix.python-version }} - ${{ matrix.os }}
defaults:
@@ -26,12 +17,12 @@ jobs:
shell: bash -l {0}
runs-on: ${{ matrix.os }}
strategy:
- fail-fast: true
+ fail-fast: false
matrix:
os: ['ubuntu-latest', 'macos-latest', 'windows-latest']
- python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
- # test oldesst and newest libspatialindex versions
- sidx-version: ['1.8.5', '2.0.0']
+ # test oldest and newest versions of python and libspatialindex
+ python-version: ['3.9', '3.13']
+ sidx-version: ['1.8.5', '2.1.0']
exclude:
- os: 'macos-latest'
- sidx-version: '1.8.5'
@@ -43,16 +34,15 @@ jobs:
channels: conda-forge
auto-update-conda: true
python-version: ${{ matrix.python-version }}
+
- name: Setup
- run: |
- conda install -c conda-forge numpy libspatialindex=${{ matrix.sidx-version }} -y
+ run: conda install -c conda-forge numpy pytest libspatialindex=${{ matrix.sidx-version }} -y
+
- name: Install
- run: |
- pip install -e .
+ run: pip install -e .
+
- name: Test with pytest
- run: |
- pip install pytest
- python -m pytest --doctest-modules rtree tests
+ run: pytest --import-mode=importlib -Werror -v --doctest-modules rtree tests
ubuntu:
name: Ubuntu Python ${{ matrix.python-version }}
@@ -61,25 +51,26 @@ jobs:
shell: bash -l {0}
runs-on: ubuntu-latest
strategy:
- fail-fast: true
+ fail-fast: false
matrix:
- python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout at v4
- uses: actions/setup-python at v5
name: Install Python
with:
- python-version: '3.11'
+ python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
+
- name: Setup
run: |
- sudo apt install libspatialindex-c6 python3-pip
- python3 -m pip install --upgrade pip
- python3 -m pip install setuptools numpy pytest
+ sudo apt-get -y install libspatialindex-c6
+ pip install --upgrade pip
+ pip install numpy pytest
- name: Build
- run: |
- python3 -m pip install --user .
+ run: pip install --user .
+
- name: Test with pytest
- run: |
- python3 -m pytest --doctest-modules rtree tests
+ run: pytest --import-mode=importlib -Werror -v --doctest-modules rtree tests
=====================================
.gitignore
=====================================
@@ -1,4 +1,4 @@
-Rtree.egg-info/
+*.egg-info/
*.pyc
docs/build
build/
=====================================
.pre-commit-config.yaml
=====================================
@@ -2,26 +2,26 @@ ci:
autoupdate_schedule: quarterly
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.6.0
+ rev: v5.0.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.27.3
+ rev: 0.31.2
hooks:
- id: check-github-workflows
args: ["--verbose"]
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.5.0
+ rev: v0.9.9
hooks:
- # Run the linter.
+ # Run the linter
- id: ruff
args: [ --fix ]
- # Run the formatter.
+ # Run the formatter
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.5.1
+ rev: v1.15.0
hooks:
- id: mypy
exclude: 'docs/.'
=====================================
.readthedocs.yaml
=====================================
@@ -9,9 +9,9 @@ version: 2
build:
apt_packages:
- libspatialindex-dev
- os: ubuntu-20.04
+ os: ubuntu-lts-latest
tools:
- python: "3.11"
+ python: latest
# Build documentation in the docs/source directory with Sphinx
sphinx:
=====================================
CHANGES.rst
=====================================
@@ -1,3 +1,11 @@
+1.4.0: 2025-03-06
+=================
+
+- Python 3.9+ is now required (:PR:`321`)
+- Add support for array-based bulk insert with NumPy (:PR:`340` by :user:`FreddieWitherden`)
+- Upgrade binary wheels with libspatialindex-2.1.0 (:PR:`353`)
+- Rename project and other build components to "rtree" (:PR:`350`)
+
1.3.0: 2024-07-10
=================
=====================================
DEPENDENCIES.txt
=====================================
@@ -1,4 +1,4 @@
-- python 3.8+
+- python 3.9+
- setuptools
- libspatialindex C library 1.8.5+:
https://libspatialindex.org/
=====================================
README.md
=====================================
@@ -1,7 +1,7 @@
# Rtree: Spatial indexing for Python

-[](https://badge.fury.io/py/Rtree)
+[](https://badge.fury.io/py/rtree)
Rtree is a [ctypes](https://docs.python.org/3/library/ctypes.html) Python wrapper of [libspatialindex](https://libspatialindex.org/) that provides a
=====================================
benchmarks/benchmarks.py
=====================================
@@ -164,7 +164,7 @@ hits = disk_index.intersection(bbox, objects="raw")
t = timeit.Timer(
stmt=s, setup="from __main__ import points, disk_index, bbox, insert_object"
)
- print("Disk-based Rtree Intersection " "without Item() wrapper (objects='raw'):")
+ print("Disk-based Rtree Intersection without Item() wrapper (objects='raw'):")
result = list(disk_index.intersection(bbox, objects="raw"))
print(len(result), "raw hits")
print(f"{1e6 * t.timeit(number=TEST_TIMES) / TEST_TIMES:.2f} usec/pass")
=====================================
docs/source/class.rst
=====================================
@@ -4,7 +4,7 @@ Class Documentation
------------------------------------------------------------------------------
.. autoclass:: rtree.index.Index
- :members: __init__, insert, intersection, nearest, delete, bounds, count, close, dumps, loads
+ :members: __init__, insert, intersection, intersection_v, nearest, nearest_v, delete, bounds, count, close, dumps, loads
.. autoclass:: rtree.index.Property
:members:
=====================================
docs/source/install.rst
=====================================
@@ -22,7 +22,7 @@ ensure that applications can find it at startup time.
Rtree can be easily installed via pip::
- $ pip install Rtree
+ $ pip install rtree
or by running in a local source directory::
@@ -39,8 +39,8 @@ The Windows DLLs of `libspatialindex`_ are pre-compiled in
windows installers that are available from `PyPI`_. Installation on Windows
is as easy as::
- pip install Rtree
+ pip install rtree
-.. _`PyPI`: https://pypi.org/project/Rtree/
+.. _`PyPI`: https://pypi.org/project/rtree/
.. _`libspatialindex`: https://libspatialindex.org
=====================================
docs/source/performance.rst
=====================================
@@ -80,4 +80,5 @@ Use the correct query method
Use :py:meth:`~rtree.index.Index.count` if you only need a count and
:py:meth:`~rtree.index.Index.intersection` if you only need the ids.
-Otherwise, lots of data may potentially be copied.
+Otherwise, lots of data may potentially be copied. If possible also
+make use of the bulk query methods suffixed with `_v`.
=====================================
environment.yml
=====================================
@@ -3,5 +3,5 @@ channels:
- defaults
- conda-forge
dependencies:
-- python>=3.8
+- python>=3.9
- libspatialindex>=1.8.5
=====================================
pyproject.toml
=====================================
@@ -3,7 +3,7 @@ requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[project]
-name = "Rtree"
+name = "rtree"
authors = [
{name = "Sean Gillies", email = "sean.gillies at gmail.com"},
]
@@ -13,7 +13,7 @@ maintainers = [
]
description = "R-Tree spatial index for Python GIS"
readme = "README.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
keywords = ["gis", "spatial", "index", "r-tree"]
license = {text = "MIT"}
classifiers = [
@@ -23,11 +23,11 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: GIS",
"Topic :: Database",
]
@@ -49,28 +49,28 @@ version = {attr = "rtree.__version__"}
rtree = ["py.typed"]
[tool.cibuildwheel]
-build = "cp38-*"
+build = "cp39-*"
build-verbosity = 3
+before-all = "pip install wheel"
repair-wheel-command = "python scripts/repair_wheel.py -w {dest_dir} {wheel}"
test-requires = "tox"
test-command = "tox --conf {project} --installpkg {wheel}"
test-skip = [
- "*aarch64", # slow!
"*-macosx_arm64",
]
[tool.cibuildwheel.linux]
-archs = ["auto", "aarch64"]
+archs = ["auto"]
before-build = [
"yum install -y cmake libffi-devel",
- "bash {project}/ci/install_libspatialindex.bash",
+ "sh {project}/scripts/install_libspatialindex.sh",
]
[[tool.cibuildwheel.overrides]]
select = "*-musllinux*"
before-build = [
"apk add cmake libffi-dev",
- "bash {project}/ci/install_libspatialindex.bash",
+ "sh {project}/scripts/install_libspatialindex.sh",
]
[tool.cibuildwheel.macos]
@@ -78,11 +78,14 @@ archs = ["x86_64", "arm64"]
environment = { MACOSX_DEPLOYMENT_TARGET="10.9" }
before-build = [
"brew install coreutils cmake",
- "bash {project}/ci/install_libspatialindex.bash",
+ "sh {project}/scripts/install_libspatialindex.sh",
]
[tool.cibuildwheel.windows]
archs = ["AMD64"]
+before-build = [
+ "call {project}\\scripts\\install_libspatialindex.bat",
+]
[tool.coverage.report]
# Ignore warnings for overloads
=====================================
rtree/__init__.py
=====================================
@@ -7,6 +7,6 @@ hyperrectangular intersection queries.
from __future__ import annotations
-__version__ = "1.3.0"
+__version__ = "1.4.0"
from .index import Index, Rtree # noqa
=====================================
rtree/core.py
=====================================
@@ -125,6 +125,23 @@ rt.Index_CreateWithStream.argtypes = [ctypes.c_void_p, NEXTFUNC]
rt.Index_CreateWithStream.restype = ctypes.c_void_p
rt.Index_CreateWithStream.errcheck = check_void # type: ignore
+try:
+ rt.Index_CreateWithArray.argtypes = [
+ ctypes.c_void_p,
+ ctypes.c_uint64,
+ ctypes.c_uint32,
+ ctypes.c_uint64,
+ ctypes.c_uint64,
+ ctypes.c_uint64,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ]
+ rt.Index_CreateWithArray.restype = ctypes.c_void_p
+ rt.Index_CreateWithArray.errcheck = check_void # type: ignore
+except AttributeError:
+ pass
+
rt.Index_Destroy.argtypes = [ctypes.c_void_p]
rt.Index_Destroy.restype = None
rt.Index_Destroy.errcheck = check_void_done # type: ignore
@@ -221,6 +238,44 @@ rt.Index_NearestNeighbors_id.argtypes = [
rt.Index_NearestNeighbors_id.restype = ctypes.c_int
rt.Index_NearestNeighbors_id.errcheck = check_return # type: ignore
+try:
+ rt.Index_NearestNeighbors_id_v.argtypes = [
+ ctypes.c_void_p,
+ ctypes.c_int64,
+ ctypes.c_int64,
+ ctypes.c_uint32,
+ ctypes.c_uint64,
+ ctypes.c_uint64,
+ ctypes.c_uint64,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.POINTER(ctypes.c_int64),
+ ]
+ rt.Index_NearestNeighbors_id_v.restype = ctypes.c_int
+ rt.Index_NearestNeighbors_id_v.errcheck = check_return # type: ignore
+
+ rt.Index_Intersects_id_v.argtypes = [
+ ctypes.c_void_p,
+ ctypes.c_int64,
+ ctypes.c_uint32,
+ ctypes.c_uint64,
+ ctypes.c_uint64,
+ ctypes.c_uint64,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.POINTER(ctypes.c_int64),
+ ]
+ rt.Index_Intersects_id_v.restype = ctypes.c_int
+ rt.Index_Intersects_id_v.errcheck = check_return # type: ignore
+except AttributeError:
+ pass
+
+
rt.Index_GetLeaves.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_uint32),
=====================================
rtree/finder.py
=====================================
@@ -77,7 +77,7 @@ def load() -> ctypes.CDLL:
if pkg_files is not None:
for file in pkg_files: # type: ignore
if (
- file.parent.name == "Rtree.libs"
+ file.parent.name == "rtree.libs"
and file.stem.startswith("libspatialindex")
and ".so" in file.suffixes
):
=====================================
rtree/index.py
=====================================
@@ -6,7 +6,8 @@ import os.path
import pickle
import pprint
import warnings
-from typing import Any, Iterator, Literal, Sequence, overload
+from collections.abc import Iterator, Sequence
+from typing import Any, Literal, overload
from . import core
from .exceptions import RTreeError
@@ -206,20 +207,26 @@ class Index:
self.interleaved = bool(kwargs.get("interleaved", True))
stream = None
+ arrays = None
basename = None
storage = None
if args:
if isinstance(args[0], str) or isinstance(args[0], bytes):
# they sent in a filename
basename = args[0]
- # they sent in a filename, stream
+ # they sent in a filename, stream or filename, buffers
if len(args) > 1:
- stream = args[1]
+ if isinstance(args[1], tuple):
+ arrays = args[1]
+ else:
+ stream = args[1]
elif isinstance(args[0], ICustomStorage):
storage = args[0]
# they sent in a storage, stream
if len(args) > 1:
stream = args[1]
+ elif isinstance(args[0], tuple):
+ arrays = args[0]
else:
stream = args[0]
@@ -271,6 +278,18 @@ class Index:
if stream and self.properties.type == RT_RTree:
self._exception = None
self.handle = self._create_idx_from_stream(stream)
+ if self._exception:
+ raise self._exception
+ elif arrays and self.properties.type == RT_RTree:
+ self._exception = None
+
+ try:
+ self.handle = self._create_idx_from_array(*arrays)
+ except NameError:
+ raise NotImplementedError(
+ "libspatialindex >= 2.1 needed for bulk insert"
+ )
+
if self._exception:
raise self._exception
else:
@@ -278,6 +297,8 @@ class Index:
if stream: # Bulk insert not supported, so add one by one
for item in stream:
self.insert(*item)
+ elif arrays:
+ raise NotImplementedError("Bulk insert only supported for RTrees")
def get_size(self) -> int:
warnings.warn(
@@ -296,7 +317,7 @@ class Index:
return 0
def __repr__(self) -> str:
- return f"rtree.index.Index(bounds={self.bounds}, size={self.get_size()})"
+ return f"rtree.index.Index(bounds={self.bounds}, size={len(self)})"
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
@@ -330,42 +351,38 @@ class Index:
def get_coordinate_pointers(
self, coordinates: Sequence[float]
) -> tuple[float, float]:
- try:
- iter(coordinates)
- except TypeError:
- raise TypeError("Bounds must be a sequence")
dimension = self.properties.dimension
+ coordinates = list(coordinates)
- mins = ctypes.c_double * dimension
- maxs = ctypes.c_double * dimension
-
- if not self.interleaved:
- coordinates = Index.interleave(coordinates)
+ arr = ctypes.c_double * dimension
+ mins = arr()
- # it's a point make it into a bbox. [x, y] => [x, y, x, y]
+ # Point
if len(coordinates) == dimension:
- coordinates = *coordinates, *coordinates
+ mins[:] = coordinates
+ maxs = mins
+ # Bounding box
+ else:
+ maxs = arr()
- if len(coordinates) != dimension * 2:
- raise RTreeError(
- "Coordinates must be in the form "
- "(minx, miny, maxx, maxy) or (x, y) for 2D indexes"
- )
+ # Interleaved box
+ if self.interleaved:
+ p = coordinates[:dimension]
+ q = coordinates[dimension:]
+ # Non-interleaved box
+ else:
+ p = coordinates[::2]
+ q = coordinates[1::2]
- # so here all coords are in the form:
- # [xmin, ymin, zmin, xmax, ymax, zmax]
- for i in range(dimension):
- if not coordinates[i] <= coordinates[i + dimension]:
+ mins[:] = p
+ maxs[:] = q
+
+ if not p <= q:
raise RTreeError(
"Coordinates must not have minimums more than maximums"
)
- p_mins = mins(*[ctypes.c_double(coordinates[i]) for i in range(dimension)])
- p_maxs = maxs(
- *[ctypes.c_double(coordinates[i + dimension]) for i in range(dimension)]
- )
-
- return (p_mins, p_maxs)
+ return mins, maxs
@staticmethod
def _get_time_doubles(times):
@@ -1029,6 +1046,162 @@ class Index:
return self._get_ids(it, p_num_results.contents.value)
+ def intersection_v(self, mins, maxs):
+ """Bulk intersection query for obtaining the ids of entries
+ which intersect with the provided bounding boxes. The return
+ value is a tuple consisting of two 1D NumPy arrays: one of
+ intersecting ids and another containing the counts for each
+ bounding box.
+
+ :param mins: A NumPy array of shape `(n, d)` containing the
+ minima to query.
+
+ :param maxs: A NumPy array of shape `(n, d)` containing the
+ maxima to query.
+ """
+ import numpy as np
+
+ assert mins.shape == maxs.shape
+ assert mins.strides == maxs.strides
+
+ # Cast
+ mins = mins.astype(np.float64)
+ maxs = maxs.astype(np.float64)
+
+ # Extract counts
+ n, d = mins.shape
+
+ # Compute strides
+ d_i_stri = mins.strides[0] // mins.itemsize
+ d_j_stri = mins.strides[1] // mins.itemsize
+
+ ids = np.empty(2 * n, dtype=np.int64)
+ counts = np.empty(n, dtype=np.uint64)
+ nr = ctypes.c_int64(0)
+ offn, offi = 0, 0
+
+ while True:
+ core.rt.Index_Intersects_id_v(
+ self.handle,
+ n - offn,
+ d,
+ len(ids),
+ d_i_stri,
+ d_j_stri,
+ mins[offn:].ctypes.data,
+ maxs[offn:].ctypes.data,
+ ids[offi:].ctypes.data,
+ counts[offn:].ctypes.data,
+ ctypes.byref(nr),
+ )
+
+ # If we got the expected nuber of results then return
+ if nr.value == n - offn:
+ return ids[: counts.sum()], counts
+ # Otherwise, if our array is too small then resize
+ else:
+ offi += counts[offn : offn + nr.value].sum()
+ offn += nr.value
+
+ ids = ids.resize(2 * len(ids), refcheck=False)
+
+ def nearest_v(
+ self,
+ mins,
+ maxs,
+ num_results=1,
+ max_dists=None,
+ strict=False,
+ return_max_dists=False,
+ ):
+ """Bulk ``k``-nearest query for the given bounding boxes. The
+ return value is a tuple consisting of, by default, two 1D NumPy
+ arrays: one of intersecting ids and another containing the
+ counts for each bounding box.
+
+ :param mins: A NumPy array of shape `(n, d)` containing the
+ minima to query.
+
+ :param maxs: A NumPy array of shape `(n, d)` containing the
+ maxima to query.
+
+ :param num_results: The maximum number of neighbors to return
+ for each bounding box. If there are multiple equidistant
+ furthest neighbors then, by default, they are *all*
+ returned. Hence, the actual number of results can be
+ greater than requested.
+
+ :param max_dists: Optional; a NumPy array of shape `(n,)`
+ containing the maximum distance to consider for each
+ bounding box.
+
+ :param strict: If True then each point will never return more
+ than `num_results` even in cases of equidistant furthest
+ neighbors.
+
+ :param return_max_dists: If True, the distance of the furthest
+ neighbor for each bounding box will also be returned.
+ """
+ import numpy as np
+
+ assert mins.shape == maxs.shape
+ assert mins.strides == maxs.strides
+
+ # Cast
+ mins = mins.astype(np.float64)
+ maxs = maxs.astype(np.float64)
+
+ # Extract counts
+ n, d = mins.shape
+
+ # Compute strides
+ d_i_stri = mins.strides[0] // mins.itemsize
+ d_j_stri = mins.strides[1] // mins.itemsize
+
+ ids = np.empty(n * num_results, dtype=np.int64)
+ counts = np.empty(n, dtype=np.uint64)
+ nr = ctypes.c_int64(0)
+ offn, offi = 0, 0
+
+ if max_dists is not None:
+ assert len(max_dists) == n
+
+ dists = max_dists.astype(np.float64).copy()
+ elif return_max_dists:
+ dists = np.zeros(n)
+ else:
+ dists = None
+
+ while True:
+ core.rt.Index_NearestNeighbors_id_v(
+ self.handle,
+ num_results if not strict else -num_results,
+ n - offn,
+ d,
+ len(ids),
+ d_i_stri,
+ d_j_stri,
+ mins[offn:].ctypes.data,
+ maxs[offn:].ctypes.data,
+ ids[offi:].ctypes.data,
+ counts[offn:].ctypes.data,
+ dists[offn:].ctypes.data if dists is not None else None,
+ ctypes.byref(nr),
+ )
+
+ # If we got the expected nuber of results then return
+ if nr.value == n - offn:
+ if return_max_dists:
+ return ids[: counts.sum()], counts, dists
+ else:
+ return ids[: counts.sum()], counts
+ # Otherwise, if our array is too small then resize
+ else:
+ offi += counts[offn : offn + nr.value].sum()
+ offn += nr.value
+
+ ids = ids.resize(2 * len(ids), refcheck=False)
+
def _nearestTP(self, coordinates, velocities, times, num_results=1, objects=False):
p_mins, p_maxs = self.get_coordinate_pointers(coordinates)
pv_mins, pv_maxs = self.get_coordinate_pointers(velocities)
@@ -1230,16 +1403,14 @@ class Index:
return -1
if self.interleaved:
- coordinates = Index.deinterleave(coordinates)
-
- # this code assumes the coords are not interleaved.
- # xmin, xmax, ymin, ymax, zmin, zmax
- for i in range(dimension):
- mins[i] = coordinates[i * 2]
- maxs[i] = coordinates[(i * 2) + 1]
+ mins[:] = coordinates[:dimension]
+ maxs[:] = coordinates[dimension:]
+ else:
+ mins[:] = coordinates[::2]
+ maxs[:] = coordinates[1::2]
- p_mins[0] = ctypes.cast(mins, ctypes.POINTER(ctypes.c_double))
- p_maxs[0] = ctypes.cast(maxs, ctypes.POINTER(ctypes.c_double))
+ p_mins[0] = mins
+ p_maxs[0] = maxs
# set the dimension
p_dimension[0] = dimension
@@ -1255,6 +1426,36 @@ class Index:
stream = core.NEXTFUNC(py_next_item)
return IndexStreamHandle(self.properties.handle, stream)
+ def _create_idx_from_array(self, ibuf, minbuf, maxbuf):
+ assert len(ibuf) == len(minbuf)
+ assert len(ibuf) == len(maxbuf)
+ assert minbuf.strides == maxbuf.strides
+
+ # Cast
+ ibuf = ibuf.astype(int)
+ minbuf = minbuf.astype(float)
+ maxbuf = maxbuf.astype(float)
+
+ # Extract counts
+ n, d = minbuf.shape
+
+ # Compute strides
+ i_stri = ibuf.strides[0] // 8
+ d_i_stri = minbuf.strides[0] // 8
+ d_j_stri = minbuf.strides[1] // 8
+
+ return IndexArrayHandle(
+ self.properties.handle,
+ n,
+ d,
+ i_stri,
+ d_i_stri,
+ d_j_stri,
+ ibuf.ctypes.data,
+ minbuf.ctypes.data,
+ maxbuf.ctypes.data,
+ )
+
def leaves(self):
leaf_node_count = ctypes.c_uint32()
p_leafsizes = ctypes.pointer(ctypes.c_uint32())
@@ -1436,6 +1637,14 @@ class IndexStreamHandle(IndexHandle):
_create = core.rt.Index_CreateWithStream
+try:
+
+ class IndexArrayHandle(IndexHandle):
+ _create = core.rt.Index_CreateWithArray
+except AttributeError:
+ pass
+
+
class PropertyHandle(Handle):
_create = core.rt.IndexProperty_Create
_destroy = core.rt.IndexProperty_Destroy
@@ -1485,6 +1694,13 @@ class Property:
if v is not None:
setattr(self, k, v)
+ # Consistency checks
+ if "near_minimum_overlap_factor" not in state:
+ nmof = self.near_minimum_overlap_factor
+ ilc = min(self.index_capacity, self.leaf_capacity)
+ if nmof >= ilc:
+ self.near_minimum_overlap_factor = ilc // 3 + 1
+
def __getstate__(self) -> dict[Any, Any]:
return self.as_dict()
@@ -1509,9 +1725,15 @@ class Property:
return pprint.pformat(self.as_dict())
def get_index_type(self) -> int:
- return core.rt.IndexProperty_GetIndexType(self.handle)
+ try:
+ return self._type
+ except AttributeError:
+ type = core.rt.IndexProperty_GetIndexType(self.handle)
+ self._type: int = type
+ return type
def set_index_type(self, value: int) -> None:
+ self._type = value
return core.rt.IndexProperty_SetIndexType(self.handle, value)
type = property(get_index_type, set_index_type)
@@ -1530,11 +1752,17 @@ class Property:
:data:`RT_Linear`, :data:`RT_Quadratic`, and :data:`RT_Star`"""
def get_dimension(self) -> int:
- return core.rt.IndexProperty_GetDimension(self.handle)
+ try:
+ return self._dimension
+ except AttributeError:
+ dim = core.rt.IndexProperty_GetDimension(self.handle)
+ self._dimension: int = dim
+ return dim
def set_dimension(self, value: int) -> None:
if value <= 0:
raise RTreeError("Negative or 0 dimensional indexes are not allowed")
+ self._dimension = value
return core.rt.IndexProperty_SetDimension(self.handle, value)
dimension = property(get_dimension, set_dimension)
=====================================
ci/install_libspatialindex.bat → scripts/install_libspatialindex.bat
=====================================
@@ -1,6 +1,6 @@
python -c "import sys; print(sys.version)"
-set SIDX_VERSION=2.0.0
+set SIDX_VERSION=2.1.0
curl -LO --retry 5 --retry-max-time 120 "https://github.com/libspatialindex/libspatialindex/archive/%SIDX_VERSION%.zip"
=====================================
ci/install_libspatialindex.bash → scripts/install_libspatialindex.sh
=====================================
@@ -1,9 +1,9 @@
-#!/bin/bash
+#!/bin/sh
set -xe
# A simple script to install libspatialindex from a Github Release
-VERSION=2.0.0
-SHA256=8caa4564c4592824acbf63a2b883aa2d07e75ccd7e9bf64321c455388a560579
+VERSION=2.1.0
+SHA256=86aa0925dd151ff9501a5965c4f8d7fb3dcd8accdc386a650dbdd62660399926
# where to copy resulting files
# this has to be run before `cd`-ing anywhere
@@ -32,7 +32,12 @@ rm -f $VERSION.zip
curl -LOs --retry 5 --retry-max-time 120 https://github.com/libspatialindex/libspatialindex/archive/${VERSION}.zip
# check the file hash
-echo "${SHA256} ${VERSION}.zip" | sha256sum -c -
+if [ "$(uname)" = "Darwin" ]
+then
+ echo "${SHA256} ${VERSION}.zip" | shasum -a 256 -c -
+else
+ echo "${SHA256} ${VERSION}.zip" | sha256sum -c -
+fi
rm -rf "libspatialindex-${VERSION}"
unzip -q $VERSION
@@ -43,7 +48,7 @@ cd build
printenv
-if [ "$(uname)" == "Darwin" ]; then
+if [ "$(uname)" = "Darwin" ]; then
CMAKE_ARGS="-D CMAKE_OSX_ARCHITECTURES=${ARCHFLAGS##* } \
-D CMAKE_INSTALL_RPATH=@loader_path"
fi
=====================================
scripts/repair_wheel.py
=====================================
@@ -45,7 +45,7 @@ def main():
# use the platform specific repair tool first
if os_ == "linux":
# use path from cibuildwheel which allows auditwheel to create
- # Rtree.libs/libspatialindex-*.so.*
+ # rtree.libs/libspatialindex-*.so.*
cibw_lib_path = "/project/rtree/lib"
if os.environ.get("LD_LIBRARY_PATH"): # append path
os.environ["LD_LIBRARY_PATH"] += f"{os.pathsep}{cibw_lib_path}"
@@ -96,7 +96,7 @@ def main():
if os_ == "linux":
# This is auditwheel's libs, which needs post-processing
- libs_dir = unpackdir / "Rtree.libs"
+ libs_dir = unpackdir / "rtree.libs"
lsidx_list = list(libs_dir.glob("libspatialindex*.so*"))
assert len(lsidx_list) == 1, list(libs_dir.iterdir())
lsidx = lsidx_list[0]
=====================================
setup.py
=====================================
@@ -57,7 +57,7 @@ class InstallPlatlib(install): # type: ignore[misc]
# See pyproject.toml for other project metadata
setup(
- name="Rtree",
+ name="rtree",
distclass=BinaryDistribution,
cmdclass={"bdist_wheel": bdist_wheel, "install": InstallPlatlib},
)
=====================================
tests/conftest.py
=====================================
@@ -2,11 +2,14 @@ from __future__ import annotations
import os
import shutil
-from typing import Iterator
+from collections.abc import Iterator
+import numpy
import py
import pytest
+import rtree
+
data_files = ["boxes_15x15.data"]
@@ -17,3 +20,12 @@ def temporary_working_directory(tmpdir: py.path.local) -> Iterator[None]:
shutil.copy(filename, str(tmpdir))
with tmpdir.as_cwd():
yield
+
+
+def pytest_report_header(config):
+ """Header for pytest."""
+ vers = [
+ f"SIDX version: {rtree.core.rt.SIDX_Version().decode()}",
+ f"NumPy version: {numpy.__version__}",
+ ]
+ return "\n".join(vers)
=====================================
tests/test_index.py
=====================================
@@ -5,7 +5,7 @@ import pickle
import sys
import tempfile
import unittest
-from typing import Iterator
+from collections.abc import Iterator
import numpy as np
import pytest
@@ -240,7 +240,7 @@ class IndexIntersection(IndexTestCase):
self.assertTrue(0 in self.idx.intersection((0, 0, 60, 60)))
hits = list(self.idx.intersection((0, 0, 60, 60)))
- self.assertTrue(len(hits), 10)
+ self.assertEqual(len(hits), 10)
self.assertEqual(hits, [0, 4, 16, 27, 35, 40, 47, 50, 76, 80])
def test_objects(self) -> None:
@@ -436,14 +436,14 @@ class IndexSerialization(unittest.TestCase):
idx.add(i, coords)
hits = list(idx.intersection((0, 0, 60, 60)))
- self.assertTrue(len(hits), 10)
+ self.assertEqual(len(hits), 10)
self.assertEqual(hits, [0, 4, 16, 27, 35, 40, 47, 50, 76, 80])
del idx
# Check we can reopen the index and get the same results
idx2 = index.Index(tname, properties=p)
hits = list(idx2.intersection((0, 0, 60, 60)))
- self.assertTrue(len(hits), 10)
+ self.assertEqual(len(hits), 10)
self.assertEqual(hits, [0, 4, 16, 27, 35, 40, 47, 50, 76, 80])
@pytest.mark.skipif(not sys.maxsize > 2**32, reason="Fails on 32bit systems")
@@ -465,7 +465,7 @@ class IndexSerialization(unittest.TestCase):
tname, data_gen(interleaved=False), properties=p, interleaved=False
)
hits1 = sorted(list(idx.intersection((0, 60, 0, 60))))
- self.assertTrue(len(hits1), 10)
+ self.assertEqual(len(hits1), 10)
self.assertEqual(hits1, [0, 4, 16, 27, 35, 40, 47, 50, 76, 80])
leaves = idx.leaves()
@@ -591,7 +591,7 @@ class IndexSerialization(unittest.TestCase):
)
hits2 = sorted(list(idx.intersection((0, 60, 0, 60), objects=True)))
- self.assertTrue(len(hits2), 10)
+ self.assertEqual(len(hits2), 10)
self.assertEqual(hits2[0].object, 42)
def test_overwrite(self) -> None:
@@ -846,15 +846,11 @@ class IndexCustomStorage(unittest.TestCase):
"""Reopening custom index storage works as expected"""
storage = DictStorage()
- settings = index.Property()
- settings.writethrough = True
- settings.buffering_capacity = 1
-
- r1 = index.Index(storage, properties=settings, overwrite=True)
+ r1 = index.Index(storage, overwrite=True)
r1.add(555, (2, 2))
del r1
self.assertTrue(storage.hasData)
- r2 = index.Index(storage, properly=settings, overwrite=False)
+ r2 = index.Index(storage, overwrite=False)
count = r2.count((0, 0, 10, 10))
self.assertEqual(count, 1)
=====================================
tests/test_tpr.py
=====================================
@@ -3,8 +3,9 @@ from __future__ import annotations
import os
import unittest
from collections import defaultdict, namedtuple
+from collections.abc import Iterator
from math import ceil
-from typing import Any, Iterator
+from typing import Any
import numpy as np
from numpy.random import default_rng
=====================================
tox.ini
=====================================
@@ -1,7 +1,7 @@
[tox]
requires =
tox>=4
-env_list = py{38,39,310,311,312}
+env_list = py{39,310,311,312,313}
[testenv]
description = run unit tests
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-rtree/-/commit/e25d66454c78db34491de4241745c8b4c57c2df4
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-rtree/-/commit/e25d66454c78db34491de4241745c8b4c57c2df4
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20250306/1848ee27/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list