Skip to content
Commits on Source (5)
......@@ -19,7 +19,9 @@ addons:
- libspatialindex-c3
install:
- pip install flake8
- pip install -e .
script:
- flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
- python -m pytest --doctest-modules rtree tests/test_*
......@@ -2,6 +2,7 @@ Rtree
=====
[![Build Status](https://travis-ci.org/Toblerity/rtree.svg)](https://travis-ci.org/Toblerity/rtree)
[![PyPI version](https://badge.fury.io/py/Rtree.svg)](https://badge.fury.io/py/Rtree)
Python bindings for libspatialindex 1.8.3.
......@@ -27,11 +27,12 @@ jobs:
- bash: |
source activate rtree
conda install --yes --quiet --name rtree python=$PYTHON_VERSION libspatialindex=$SIDX_VERSION
conda install --yes --quiet --name rtree -c conda-forge python=$PYTHON_VERSION libspatialindex=$SIDX_VERSION
displayName: Install Anaconda packages
- bash: |
source activate rtree
pip install pytest numpy
pip install flake8 pytest numpy
flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
python -m pytest --doctest-modules rtree tests/test_*
displayName: pytest
displayName: Lint with Flake8 and run unit tests
......@@ -36,6 +36,7 @@ jobs:
sudo update-locale LANG=en_US.UTF-8
export LANG="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
pip install pytest numpy
pip install flake8 pytest numpy
flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
python -m pytest --doctest-modules rtree tests/test_*
displayName: 'Run pytest'
displayName: Lint with Flake8 and run unit tests
......@@ -14,6 +14,7 @@ jobs:
displayName: pip install
- bash: |
pip3 install pytest numpy
pip3 install flake8 pytest numpy
flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
python3 -m pytest --doctest-modules rtree tests/test_*
displayName: pytest
displayName: Lint with Flake8 and run unit tests
......@@ -14,6 +14,7 @@ jobs:
displayName: pip install
- bash: |
pip3 install pytest numpy
pip3 install flake8 pytest numpy
flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
python3 -m pytest --doctest-modules rtree tests/test_*
displayName: pytest
displayName: Lint with Flake8 and run unit tests
......@@ -40,11 +40,12 @@ jobs:
- bash: |
source activate rtree
conda install --yes --quiet --name rtree python=$PYTHON_VERSION libspatialindex=$SIDX_VERSION
conda install --yes --quiet --name rtree -c conda-forge python=$PYTHON_VERSION libspatialindex=$SIDX_VERSION
displayName: Install Anaconda packages
- bash: |
source activate rtree
pip install pytest numpy
pip install flake8 pytest numpy
flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
python -m pytest --doctest-modules rtree tests/test_*
displayName: pytest
displayName: Lint with Flake8 and run unit tests
......@@ -29,11 +29,12 @@ jobs:
- script: |
call activate rtree
conda install --yes --quiet --name rtree python=%PYTHON_VERSION% libspatialindex=%SIDX_VERSION%
conda install --yes --quiet --name rtree -c conda-forge python=%PYTHON_VERSION% libspatialindex=%SIDX_VERSION%
displayName: Install Anaconda packages
- script: |
call activate rtree
pip install pytest numpy
pip install flake8 pytest numpy
flake8 --ignore=E501 --exclude=rtree/__init__.py rtree/
python -m pytest --doctest-modules rtree tests
displayName: pytest
displayName: Lint with Flake8 and run unit tests
python-rtree (0.9.3-2) UNRELEASED; urgency=medium
python-rtree (0.9.4-1) unstable; urgency=medium
* New upstream release.
* Bump Standards-Version to 4.5.0, no changes.
* Update Source, Comment & Files-Excluded for GitHub releases.
-- Bas Couwenberg <sebastic@debian.org> Sat, 25 Jan 2020 11:09:37 +0100
-- Bas Couwenberg <sebastic@debian.org> Wed, 12 Feb 2020 05:52:09 +0100
python-rtree (0.9.3-1) unstable; urgency=medium
......
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Rtree
Upstream-Contact: Howard Butler <hobu.inc@gmail.com>
Source: http://pypi.debian.net/rtree/
Comment: The upstream sources are repacked to excluded the Rtree.egg-info
directory that is automatically removed by dh_clean.
Files-Excluded: Rtree.egg-info/*
docs/build/*
tests/*.pyc
Source: https://github.com/Toblerity/rtree
Files: *
Copyright: 2011, Howard Butler, Brent Pedersen, Sean Gilles, and others.
......
......@@ -2,4 +2,4 @@ from .index import Rtree
from .core import rt
__version__ = '0.9.3'
__version__ = '0.9.4'
......@@ -107,8 +107,6 @@ if os.name == 'nt':
os.environ['PATH'] = oldenv
return None
base_name = 'spatialindex_c'
if '64' in platform.architecture()[0]:
arch = '64'
......@@ -328,23 +326,25 @@ try:
rt.Index_Flush.restype = None
rt.Index_Flush.errcheck = check_void_done
rt.Index_Contains_obj.argtypes = [ctypes.c_void_p,
rt.Index_Contains_obj.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.c_uint32,
ctypes.POINTER(
ctypes.POINTER(ctypes.c_void_p)),
ctypes.POINTER(ctypes.c_uint64)]
ctypes.POINTER(ctypes.POINTER(ctypes.c_void_p)),
ctypes.POINTER(ctypes.c_uint64)
]
rt.Index_Contains_obj.restype = ctypes.c_int
rt.Index_Contains_obj.errcheck = check_return
rt.Index_Contains_id.argtypes = [ctypes.c_void_p,
rt.Index_Contains_id.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_double),
ctypes.POINTER(ctypes.c_double),
ctypes.c_uint32,
ctypes.POINTER(
ctypes.POINTER(ctypes.c_int64)),
ctypes.POINTER(ctypes.c_uint64)]
ctypes.POINTER(ctypes.POINTER(ctypes.c_int64)),
ctypes.POINTER(ctypes.c_uint64)
]
rt.Index_Contains_id.restype = ctypes.c_int
rt.Index_Contains_id.errcheck = check_return
......
......@@ -5,24 +5,8 @@ import pprint
from . import core
try:
import cPickle as pickle
except ImportError:
import pickle
import sys
if sys.version_info[0] == 2:
range = xrange
string_types = basestring
elif sys.version_info[0] == 3:
string_types = str
def string_output(s):
if sys.version_info[0] == 2:
return s
elif sys.version_info[0] == 3:
return s.decode('UTF-8')
RT_Memory = 0
RT_Disk = 1
......@@ -61,10 +45,12 @@ def _get_bounds(handle, bounds_fn, interleaved):
if (dimension.value == 0):
return None
mins = ctypes.cast(pp_mins, ctypes.POINTER(ctypes.c_double
* dimension.value))
maxs = ctypes.cast(pp_maxs, ctypes.POINTER(ctypes.c_double
* dimension.value))
mins = ctypes.cast(
pp_mins, ctypes.POINTER(ctypes.c_double * dimension.value)
)
maxs = ctypes.cast(
pp_maxs, ctypes.POINTER(ctypes.c_double * dimension.value)
)
results = [mins.contents[i] for i in range(dimension.value)]
results += [maxs.contents[i] for i in range(dimension.value)]
......@@ -161,7 +147,7 @@ class Index(object):
>>> idx = index.Index(properties=p)
>>> idx # doctest: +ELLIPSIS
<rtree.index.Index object at 0x...>
rtree.index.Index(bounds=[1.7976931348623157e+308, 1.7976931348623157e+308, -1.7976931348623157e+308, -1.7976931348623157e+308], size=0)
Insert an item into the index::
......@@ -220,7 +206,7 @@ class Index(object):
basename = None
storage = None
if args:
if isinstance(args[0], string_types) or isinstance(args[0], bytes):
if isinstance(args[0], str) or isinstance(args[0], bytes):
# they sent in a filename
basename = args[0]
# they sent in a filename, stream
......@@ -283,7 +269,6 @@ class Index(object):
else:
self.properties.storage = RT_Memory
ps = kwargs.get('pagesize', None)
if ps:
self.properties.pagesize = int(ps)
......@@ -299,6 +284,15 @@ class Index(object):
for item in stream:
self.insert(*item)
def get_size(self):
try:
return self.count(self.bounds)
except core.RTreeError:
return 0
def __repr__(self):
return 'rtree.index.Index(bounds={}, size={})'.format(self.bounds, self.get_size())
def __getstate__(self):
state = self.__dict__.copy()
del state["handle"]
......@@ -386,7 +380,6 @@ class Index(object):
# return serialized to keep it alive for the pointer.
return size, ctypes.cast(p, ctypes.POINTER(ctypes.c_uint8)), serialized
def set_result_limit(self, value):
return core.rt.Index_SetResultSetOffset(self.handle, value)
......@@ -529,7 +522,6 @@ class Index(object):
return p_num_results.value
def _countTP(self, coordinates, velocities, times):
p_mins, p_maxs = self.get_coordinate_pointers(coordinates)
pv_mins, pv_maxs = self.get_coordinate_pointers(velocities)
......@@ -777,7 +769,7 @@ class Index(object):
yield self.loads(data)
core.rt.Index_DestroyObjResults(its, num_results)
except: # need to catch all exceptions, not just rtree.
except Exception: # need to catch all exceptions, not just rtree.
core.rt.Index_DestroyObjResults(its, num_results)
raise
......@@ -790,7 +782,7 @@ class Index(object):
for i in range(num_results):
yield items.contents[i]
core.rt.Index_Free(its)
except:
except Exception:
core.rt.Index_Free(its)
raise
......@@ -854,6 +846,12 @@ class Index(object):
return self._nearest_obj(coordinates, num_results, objects)
p_mins, p_maxs = self.get_coordinate_pointers(coordinates)
# p_num_results is an input and output for C++ lib
# as an input it says "get n closest neighbors"
# but if multiple neighbors are at the same distance, both will be returned
# so the number of returned neighbors may be > p_num_results
# thus p_num_results.contents.value gets set as an output by the C++ lib
# to indicate the actual number of results for _get_ids to use
p_num_results = ctypes.pointer(ctypes.c_uint64(num_results))
it = ctypes.pointer(ctypes.c_int64())
......@@ -865,10 +863,9 @@ class Index(object):
ctypes.byref(it),
p_num_results)
return self._get_ids(it, min(num_results,p_num_results.contents.value))
return self._get_ids(it, p_num_results.contents.value)
def _nearestTP(self, coordinates, velocities, times, num_results=1,
objects=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)
t_start, t_end = self._get_time_doubles(times)
......@@ -907,13 +904,17 @@ class Index(object):
bounds = property(get_bounds)
def delete(self, id, coordinates):
"""Deletes items from the index with the given ``'id'`` within the
specified coordinates.
"""Deletes an item from the index with the given ``'id'`` and
coordinates given by the ``coordinates`` sequence. As the index can
contain multiple items with the same ID and coordinates, deletion
is not guaranteed to delete all items in the index with the given ID
and coordinates.
:param id: long integer
A long integer that is the identifier for this index entry. IDs
need not be unique to be inserted into the index, and it is up
to the user to ensure they are unique if this is a requirement.
A long integer ID for the entry, which need not be unique. The
index can contain multiple entries with identical IDs and
coordinates. Uniqueness of items should be enforced at the
application level by the user.
:param coordinates: sequence or array
Dimension * 2 coordinate pairs, representing the min
......@@ -1140,6 +1141,7 @@ class Index(object):
return output
# An alias to preserve backward compatibility
Rtree = Index
......@@ -1239,6 +1241,7 @@ class IndexHandle(Handle):
except AttributeError:
pass
class IndexStreamHandle(IndexHandle):
_create = core.rt.Index_CreateWithStream
......@@ -1515,11 +1518,10 @@ class Property(object):
"""Reinsert factor"""
def get_filename(self):
s = core.rt.IndexProperty_GetFileName(self.handle)
return string_output(s)
return core.rt.IndexProperty_GetFileName(self.handle).decode()
def set_filename(self, value):
if isinstance(value, string_types):
if isinstance(value, str):
value = value.encode('utf-8')
return core.rt.IndexProperty_SetFileName(self.handle, value)
......@@ -1527,11 +1529,10 @@ class Property(object):
"""Index filename for disk storage"""
def get_dat_extension(self):
s = core.rt.IndexProperty_GetFileNameExtensionDat(self.handle)
return string_output(s)
return core.rt.IndexProperty_GetFileNameExtensionDat(self.handle).decode()
def set_dat_extension(self, value):
if isinstance(value, string_types):
if isinstance(value, str):
value = value.encode('utf-8')
return core.rt.IndexProperty_SetFileNameExtensionDat(
self.handle, value)
......@@ -1540,11 +1541,10 @@ class Property(object):
"""Extension for .dat file"""
def get_idx_extension(self):
s = core.rt.IndexProperty_GetFileNameExtensionIdx(self.handle)
return string_output(s)
return core.rt.IndexProperty_GetFileNameExtensionIdx(self.handle).decode()
def set_idx_extension(self, value):
if isinstance(value, string_types):
if isinstance(value, str):
value = value.encode('utf-8')
return core.rt.IndexProperty_SetFileNameExtensionIdx(
self.handle, value)
......@@ -1836,7 +1836,7 @@ class RtreeContainer(Rtree):
>>> idx = index.RtreeContainer(properties=p)
>>> idx # doctest: +ELLIPSIS
<rtree.index.RtreeContainer object at 0x...>
rtree.index.RtreeContainer(bounds=[1.7976931348623157e+308, 1.7976931348623157e+308, -1.7976931348623157e+308, -1.7976931348623157e+308], size=0)
Insert an item into the index::
......@@ -1854,7 +1854,7 @@ class RtreeContainer(Rtree):
[34.37768294..., 26.73758537..., 49.37768294..., 41.73758537...]
"""
if args:
if isinstance(args[0], string_types) \
if isinstance(args[0], str) \
or isinstance(args[0], bytes) \
or isinstance(args[0], ICustomStorage):
raise ValueError('%s supports only in-memory indexes'
......@@ -1862,6 +1862,15 @@ class RtreeContainer(Rtree):
self._objects = {}
return super(RtreeContainer, self).__init__(*args, **kwargs)
def get_size(self):
try:
return self.count(self.bounds)
except core.RTreeError:
return 0
def __repr__(self):
return 'rtree.index.RtreeContainer(bounds={}, size={})'.format(self.bounds, self.get_size())
def __contains__(self, obj):
return id(obj) in self._objects
......@@ -1974,11 +1983,11 @@ class RtreeContainer(Rtree):
49.3776829412, 41.7375853734])]
"""
if bbox == False:
if bbox is False:
for id in super(RtreeContainer,
self).intersection(coordinates, bbox):
yield self._objects[id][1]
elif bbox == True:
elif bbox is True:
for value in super(RtreeContainer,
self).intersection(coordinates, bbox):
value.object = self._objects[value.id][1]
......@@ -2022,11 +2031,11 @@ class RtreeContainer(Rtree):
>>> idx.insert(object(), (34.37, 26.73, 49.37, 41.73))
>>> hits = idx.nearest((0, 0, 10, 10), 3, bbox=True)
"""
if bbox == False:
if bbox is False:
for id in super(RtreeContainer,
self).nearest(coordinates, num_results, bbox):
yield self._objects[id][1]
elif bbox == True:
elif bbox is True:
for value in super(RtreeContainer,
self).nearest(coordinates, num_results, bbox):
value.object = self._objects[value.id][1]
......
......@@ -341,6 +341,38 @@ class IndexNearest(IndexTestCase):
hits = sorted(idx.nearest((13, 0, 20, 2), 3))
self.assertEqual(hits, [3, 4, 5])
def test_nearest_equidistant(self):
"""Test that if records are equidistant, both are returned."""
point = (0, 0)
small_box = (-10, -10, 10, 10)
large_box = (-50, -50, 50, 50)
idx = index.Index()
idx.insert(0, small_box)
idx.insert(1, large_box)
self.assertEqual(list(idx.nearest(point, 2)), [0, 1])
self.assertEqual(list(idx.nearest(point, 1)), [0, 1])
idx.insert(2, (0, 0))
self.assertEqual(list(idx.nearest(point, 2)), [0, 1, 2])
self.assertEqual(list(idx.nearest(point, 1)), [0, 1, 2])
idx = index.Index()
idx.insert(0, small_box)
idx.insert(1, large_box)
idx.insert(2, (50, 50)) # point on top right vertex of large_box
point = (51, 51) # right outside of large_box
self.assertEqual(list(idx.nearest(point, 2)), [1, 2])
self.assertEqual(list(idx.nearest(point, 1)), [1, 2])
idx = index.Index()
idx.insert(0, small_box)
idx.insert(1, large_box)
idx.insert(2, (51, 51)) # point right outside on top right vertex of large_box
point = (51, 52) # shifted 1 unit up from the point above
self.assertEqual(list(idx.nearest(point, 2)), [2, 1])
self.assertEqual(list(idx.nearest(point, 1)), [2])
def test_nearest_object(self):
"""Test nearest object selection of records"""
......