Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-Rtree for openSUSE:Factory checked in at 2025-06-03 19:10:45 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Rtree (Old) and /work/SRC/openSUSE:Factory/.python-Rtree.new.16005 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Rtree" Tue Jun 3 19:10:45 2025 rev:5 rq:1282361 version:1.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Rtree/python-Rtree.changes 2025-04-20 20:06:59.278638093 +0200 +++ /work/SRC/openSUSE:Factory/.python-Rtree.new.16005/python-Rtree.changes 2025-06-03 19:10:47.158034417 +0200 @@ -1,0 +2,10 @@ +Tue Jun 3 07:16:33 UTC 2025 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 1.4.0 + * Python 3.9+ is now required (#321) + * Add support for array-based bulk insert with NumPy (#340) + * Upgrade binary wheels with libspatialindex-2.1.0 (#353) + * Rename project and other build components to “rtree” (#350) +- Refresh Rtree-opensuse-noarch.patch + +------------------------------------------------------------------- Old: ---- rtree-1.3.0.tar.gz New: ---- rtree-1.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Rtree.spec ++++++ --- /var/tmp/diff_new_pack.quPUdg/_old 2025-06-03 19:10:47.926066318 +0200 +++ /var/tmp/diff_new_pack.quPUdg/_new 2025-06-03 19:10:47.926066318 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-Rtree -Version: 1.3.0 +Version: 1.4.0 Release: 0 Summary: R-Tree spatial index for Python GIS License: MIT ++++++ Rtree-opensuse-noarch.patch ++++++ --- /var/tmp/diff_new_pack.quPUdg/_old 2025-06-03 19:10:47.950067315 +0200 +++ /var/tmp/diff_new_pack.quPUdg/_new 2025-06-03 19:10:47.954067481 +0200 @@ -1,8 +1,7 @@ -Index: rtree-1.3.0/setup.py -=================================================================== ---- rtree-1.3.0.orig/setup.py -+++ rtree-1.3.0/setup.py -@@ -4,60 +4,11 @@ from pathlib import Path +diff -Nru rtree-1.4.0.orig/setup.py rtree-1.4.0/setup.py +--- rtree-1.4.0.orig/setup.py 2025-03-06 00:23:54.000000000 +0100 ++++ rtree-1.4.0/setup.py 2025-06-03 09:15:31.930380106 +0200 +@@ -4,60 +4,11 @@ from setuptools import setup from setuptools.command.install import install from setuptools.dist import Distribution @@ -59,7 +58,7 @@ - # See pyproject.toml for other project metadata setup( - name="Rtree", + name="rtree", - distclass=BinaryDistribution, - cmdclass={"bdist_wheel": bdist_wheel, "install": InstallPlatlib}, ) ++++++ rtree-1.3.0.tar.gz -> rtree-1.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/CHANGES.rst new/rtree-1.4.0/CHANGES.rst --- old/rtree-1.3.0/CHANGES.rst 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/CHANGES.rst 2025-03-06 00:23:54.000000000 +0100 @@ -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 ================= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/DEPENDENCIES.txt new/rtree-1.4.0/DEPENDENCIES.txt --- old/rtree-1.3.0/DEPENDENCIES.txt 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/DEPENDENCIES.txt 2025-03-06 00:23:54.000000000 +0100 @@ -1,4 +1,4 @@ -- python 3.8+ +- python 3.9+ - setuptools - libspatialindex C library 1.8.5+: https://libspatialindex.org/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/PKG-INFO new/rtree-1.4.0/PKG-INFO --- old/rtree-1.3.0/PKG-INFO 2024-07-10 02:18:57.731500400 +0200 +++ new/rtree-1.4.0/PKG-INFO 2025-03-06 00:23:56.896201800 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 -Name: Rtree -Version: 1.3.0 +Metadata-Version: 2.2 +Name: rtree +Version: 1.4.0 Summary: R-Tree spatial index for Python GIS Author-email: Sean Gillies <sean.gill...@gmail.com> Maintainer-email: Howard Butler <how...@hobu.co>, Mike Taves <mwto...@gmail.com> @@ -14,21 +14,21 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Scientific/Engineering :: GIS Classifier: Topic :: Database -Requires-Python: >=3.8 +Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE.txt # 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/README.md new/rtree-1.4.0/README.md --- old/rtree-1.3.0/README.md 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/README.md 2025-03-06 00:23:54.000000000 +0100 @@ -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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/Rtree.egg-info/PKG-INFO new/rtree-1.4.0/Rtree.egg-info/PKG-INFO --- old/rtree-1.3.0/Rtree.egg-info/PKG-INFO 2024-07-10 02:18:57.000000000 +0200 +++ new/rtree-1.4.0/Rtree.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,54 +0,0 @@ -Metadata-Version: 2.1 -Name: Rtree -Version: 1.3.0 -Summary: R-Tree spatial index for Python GIS -Author-email: Sean Gillies <sean.gill...@gmail.com> -Maintainer-email: Howard Butler <how...@hobu.co>, Mike Taves <mwto...@gmail.com> -License: MIT -Project-URL: Documentation, https://rtree.readthedocs.io -Project-URL: Repository, https://github.com/Toblerity/rtree -Keywords: gis,spatial,index,r-tree -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Science/Research -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Topic :: Scientific/Engineering :: GIS -Classifier: Topic :: Database -Requires-Python: >=3.8 -Description-Content-Type: text/markdown -License-File: LICENSE.txt - -# Rtree: Spatial indexing for Python - - -[](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 -number of advanced spatial indexing features for the spatially curious Python -user. These features include: - -* Nearest neighbor search -* Intersection search -* Multi-dimensional indexes -* Clustered indexes (store Python pickles directly with index entries) -* Bulk loading -* Deletion -* Disk serialization -* Custom storage implementation (to implement spatial indexing in ZODB, for example) - - -Wheels are available for most major platforms, and `rtree` with bundled `libspatialindex` can be installed via pip: - -``` -pip install rtree -``` - -See [changes](https://rtree.readthedocs.io/en/latest/changes.html) for all versions. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/Rtree.egg-info/SOURCES.txt new/rtree-1.4.0/Rtree.egg-info/SOURCES.txt --- old/rtree-1.3.0/Rtree.egg-info/SOURCES.txt 2024-07-10 02:18:57.000000000 +0200 +++ new/rtree-1.4.0/Rtree.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,39 +0,0 @@ -CHANGES.rst -CREDITS.txt -DEPENDENCIES.txt -FAQ.txt -LICENSE.txt -MANIFEST.in -README.md -pyproject.toml -setup.py -Rtree.egg-info/PKG-INFO -Rtree.egg-info/SOURCES.txt -Rtree.egg-info/dependency_links.txt -Rtree.egg-info/not-zip-safe -Rtree.egg-info/top_level.txt -benchmarks/benchmarks.py -docs/Makefile -docs/requirements.txt -docs/source/changes.rst -docs/source/class.rst -docs/source/conf.py -docs/source/history.rst -docs/source/index.rst -docs/source/install.rst -docs/source/misc.rst -docs/source/performance.rst -docs/source/tutorial.rst -rtree/__init__.py -rtree/core.py -rtree/exceptions.py -rtree/finder.py -rtree/index.py -rtree/py.typed -tests/__init__.py -tests/boxes_15x15.data -tests/conftest.py -tests/rungrind.dist -tests/test_finder.py -tests/test_index.py -tests/test_tpr.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/Rtree.egg-info/dependency_links.txt new/rtree-1.4.0/Rtree.egg-info/dependency_links.txt --- old/rtree-1.3.0/Rtree.egg-info/dependency_links.txt 2024-07-10 02:18:57.000000000 +0200 +++ new/rtree-1.4.0/Rtree.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/Rtree.egg-info/not-zip-safe new/rtree-1.4.0/Rtree.egg-info/not-zip-safe --- old/rtree-1.3.0/Rtree.egg-info/not-zip-safe 2024-07-10 02:18:57.000000000 +0200 +++ new/rtree-1.4.0/Rtree.egg-info/not-zip-safe 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/Rtree.egg-info/top_level.txt new/rtree-1.4.0/Rtree.egg-info/top_level.txt --- old/rtree-1.3.0/Rtree.egg-info/top_level.txt 2024-07-10 02:18:57.000000000 +0200 +++ new/rtree-1.4.0/Rtree.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -rtree diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/benchmarks/benchmarks.py new/rtree-1.4.0/benchmarks/benchmarks.py --- old/rtree-1.3.0/benchmarks/benchmarks.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/benchmarks/benchmarks.py 2025-03-06 00:23:54.000000000 +0100 @@ -164,7 +164,7 @@ 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") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/docs/source/class.rst new/rtree-1.4.0/docs/source/class.rst --- old/rtree-1.3.0/docs/source/class.rst 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/docs/source/class.rst 2025-03-06 00:23:54.000000000 +0100 @@ -4,7 +4,7 @@ ------------------------------------------------------------------------------ .. 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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/docs/source/install.rst new/rtree-1.4.0/docs/source/install.rst --- old/rtree-1.3.0/docs/source/install.rst 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/docs/source/install.rst 2025-03-06 00:23:54.000000000 +0100 @@ -22,7 +22,7 @@ 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 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/docs/source/performance.rst new/rtree-1.4.0/docs/source/performance.rst --- old/rtree-1.3.0/docs/source/performance.rst 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/docs/source/performance.rst 2025-03-06 00:23:54.000000000 +0100 @@ -80,4 +80,5 @@ 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`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/pyproject.toml new/rtree-1.4.0/pyproject.toml --- old/rtree-1.3.0/pyproject.toml 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/pyproject.toml 2025-03-06 00:23:54.000000000 +0100 @@ -3,7 +3,7 @@ build-backend = "setuptools.build_meta" [project] -name = "Rtree" +name = "rtree" authors = [ {name = "Sean Gillies", email = "sean.gill...@gmail.com"}, ] @@ -13,7 +13,7 @@ ] 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 @@ "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 @@ 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 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree/__init__.py new/rtree-1.4.0/rtree/__init__.py --- old/rtree-1.3.0/rtree/__init__.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/rtree/__init__.py 2025-03-06 00:23:54.000000000 +0100 @@ -7,6 +7,6 @@ from __future__ import annotations -__version__ = "1.3.0" +__version__ = "1.4.0" from .index import Index, Rtree # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree/core.py new/rtree-1.4.0/rtree/core.py --- old/rtree-1.3.0/rtree/core.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/rtree/core.py 2025-03-06 00:23:54.000000000 +0100 @@ -125,6 +125,23 @@ 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.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), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree/finder.py new/rtree-1.4.0/rtree/finder.py --- old/rtree-1.3.0/rtree/finder.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/rtree/finder.py 2025-03-06 00:23:54.000000000 +0100 @@ -77,7 +77,7 @@ 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 ): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree/index.py new/rtree-1.4.0/rtree/index.py --- old/rtree-1.3.0/rtree/index.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/rtree/index.py 2025-03-06 00:23:54.000000000 +0100 @@ -6,7 +6,8 @@ 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 @@ 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] @@ -273,11 +280,25 @@ 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: self.handle = IndexHandle(self.properties.handle) 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 @@ 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 @@ 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 + arr = ctypes.c_double * dimension + mins = arr() - if not self.interleaved: - coordinates = Index.interleave(coordinates) - - # 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 @@ 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 @@ 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 @@ 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 @@ _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 @@ 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 @@ 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 @@ :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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree.egg-info/PKG-INFO new/rtree-1.4.0/rtree.egg-info/PKG-INFO --- old/rtree-1.3.0/rtree.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/rtree-1.4.0/rtree.egg-info/PKG-INFO 2025-03-06 00:23:56.000000000 +0100 @@ -0,0 +1,54 @@ +Metadata-Version: 2.2 +Name: rtree +Version: 1.4.0 +Summary: R-Tree spatial index for Python GIS +Author-email: Sean Gillies <sean.gill...@gmail.com> +Maintainer-email: Howard Butler <how...@hobu.co>, Mike Taves <mwto...@gmail.com> +License: MIT +Project-URL: Documentation, https://rtree.readthedocs.io +Project-URL: Repository, https://github.com/Toblerity/rtree +Keywords: gis,spatial,index,r-tree +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Scientific/Engineering :: GIS +Classifier: Topic :: Database +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE.txt + +# Rtree: Spatial indexing for Python + + +[](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 +number of advanced spatial indexing features for the spatially curious Python +user. These features include: + +* Nearest neighbor search +* Intersection search +* Multi-dimensional indexes +* Clustered indexes (store Python pickles directly with index entries) +* Bulk loading +* Deletion +* Disk serialization +* Custom storage implementation (to implement spatial indexing in ZODB, for example) + + +Wheels are available for most major platforms, and `rtree` with bundled `libspatialindex` can be installed via pip: + +``` +pip install rtree +``` + +See [changes](https://rtree.readthedocs.io/en/latest/changes.html) for all versions. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree.egg-info/SOURCES.txt new/rtree-1.4.0/rtree.egg-info/SOURCES.txt --- old/rtree-1.3.0/rtree.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/rtree-1.4.0/rtree.egg-info/SOURCES.txt 2025-03-06 00:23:56.000000000 +0100 @@ -0,0 +1,39 @@ +CHANGES.rst +CREDITS.txt +DEPENDENCIES.txt +FAQ.txt +LICENSE.txt +MANIFEST.in +README.md +pyproject.toml +setup.py +benchmarks/benchmarks.py +docs/Makefile +docs/requirements.txt +docs/source/changes.rst +docs/source/class.rst +docs/source/conf.py +docs/source/history.rst +docs/source/index.rst +docs/source/install.rst +docs/source/misc.rst +docs/source/performance.rst +docs/source/tutorial.rst +rtree/__init__.py +rtree/core.py +rtree/exceptions.py +rtree/finder.py +rtree/index.py +rtree/py.typed +rtree.egg-info/PKG-INFO +rtree.egg-info/SOURCES.txt +rtree.egg-info/dependency_links.txt +rtree.egg-info/not-zip-safe +rtree.egg-info/top_level.txt +tests/__init__.py +tests/boxes_15x15.data +tests/conftest.py +tests/rungrind.dist +tests/test_finder.py +tests/test_index.py +tests/test_tpr.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree.egg-info/dependency_links.txt new/rtree-1.4.0/rtree.egg-info/dependency_links.txt --- old/rtree-1.3.0/rtree.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/rtree-1.4.0/rtree.egg-info/dependency_links.txt 2025-03-06 00:23:56.000000000 +0100 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree.egg-info/not-zip-safe new/rtree-1.4.0/rtree.egg-info/not-zip-safe --- old/rtree-1.3.0/rtree.egg-info/not-zip-safe 1970-01-01 01:00:00.000000000 +0100 +++ new/rtree-1.4.0/rtree.egg-info/not-zip-safe 2025-03-06 00:23:56.000000000 +0100 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/rtree.egg-info/top_level.txt new/rtree-1.4.0/rtree.egg-info/top_level.txt --- old/rtree-1.3.0/rtree.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/rtree-1.4.0/rtree.egg-info/top_level.txt 2025-03-06 00:23:56.000000000 +0100 @@ -0,0 +1 @@ +rtree diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/setup.py new/rtree-1.4.0/setup.py --- old/rtree-1.3.0/setup.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/setup.py 2025-03-06 00:23:54.000000000 +0100 @@ -57,7 +57,7 @@ # See pyproject.toml for other project metadata setup( - name="Rtree", + name="rtree", distclass=BinaryDistribution, cmdclass={"bdist_wheel": bdist_wheel, "install": InstallPlatlib}, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/tests/conftest.py new/rtree-1.4.0/tests/conftest.py --- old/rtree-1.3.0/tests/conftest.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/tests/conftest.py 2025-03-06 00:23:54.000000000 +0100 @@ -2,11 +2,14 @@ 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 @@ 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/tests/test_index.py new/rtree-1.4.0/tests/test_index.py --- old/rtree-1.3.0/tests/test_index.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/tests/test_index.py 2025-03-06 00:23:54.000000000 +0100 @@ -5,7 +5,7 @@ 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 @@ 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 @@ 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 @@ 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 @@ ) 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 @@ """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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rtree-1.3.0/tests/test_tpr.py new/rtree-1.4.0/tests/test_tpr.py --- old/rtree-1.3.0/tests/test_tpr.py 2024-07-10 02:18:53.000000000 +0200 +++ new/rtree-1.4.0/tests/test_tpr.py 2025-03-06 00:23:54.000000000 +0100 @@ -3,8 +3,9 @@ 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