Skip to content

Commit a228836

Browse files
authored
Optionally exclude input geom from results (#1166)
* Optionally exclude input geom from results Resolves #1106 * exclusive instead of exclude_geom
1 parent eec996f commit a228836

3 files changed

Lines changed: 48 additions & 7 deletions

File tree

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Shapely 1.8 will support only Python versions >= 3.6.
1010

1111
New features:
1212

13+
- The STRtree nearest*() methods now take an optional argument that
14+
specifies exclusion of the input geometry from results (#1115).
1315
- A GeometryTypeError has been added to shapely.errors and is consistently
1416
raised instead of TypeError or ValueError as in version 1.7. For backwards
1517
compatibility, the new exception will derive from TypeError and Value error

shapely/strtree.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import ctypes
2222
import logging
2323
from typing import Any, ItemsView, Iterable, Iterator, Sequence, Tuple, Union
24+
import sys
2425
from warnings import warn
2526

2627
from shapely.errors import ShapelyDeprecationWarning
@@ -249,7 +250,9 @@ def query(self, geom: BaseGeometry) -> Sequence[BaseGeometry]:
249250
"""
250251
return self.query_geoms(geom)
251252

252-
def nearest_item(self, geom: BaseGeometry) -> Union[Any, None]:
253+
def nearest_item(
254+
self, geom: BaseGeometry, exclusive: bool = False
255+
) -> Union[Any, None]:
253256
"""Query the tree for the node nearest to geom and get the item
254257
stored in the node.
255258
@@ -259,6 +262,9 @@ def nearest_item(self, geom: BaseGeometry) -> Union[Any, None]:
259262
----------
260263
geom : geometry object
261264
The query geometry.
265+
exclusive : bool, optional
266+
Whether to exclude the item corresponding to the given geom
267+
from results or not. Default: False.
262268
263269
Returns
264270
-------
@@ -289,10 +295,14 @@ def nearest_item(self, geom: BaseGeometry) -> Union[Any, None]:
289295

290296
def callback(item1, item2, distance, userdata):
291297
try:
298+
callback_userdata = ctypes.cast(userdata, ctypes.py_object).value
292299
idx = ctypes.cast(item1, ctypes.py_object).value
293300
geom2 = ctypes.cast(item2, ctypes.py_object).value
294301
dist = ctypes.cast(distance, ctypes.POINTER(ctypes.c_double))
295-
lgeos.GEOSDistance(self._rev[idx]._geom, geom2._geom, dist)
302+
if callback_userdata["exclusive"] and self._rev[idx].equals(geom2):
303+
dist[0] = sys.float_info.max
304+
else:
305+
lgeos.GEOSDistance(self._rev[idx]._geom, geom2._geom, dist)
296306
return 1
297307
except Exception:
298308
log.exception("Caught exception")
@@ -303,19 +313,24 @@ def callback(item1, item2, distance, userdata):
303313
ctypes.py_object(geom),
304314
envelope._geom,
305315
lgeos.GEOSDistanceCallback(callback),
306-
None,
316+
ctypes.py_object({"exclusive": exclusive}),
307317
)
308318
result = ctypes.cast(item, ctypes.py_object).value
309319
return result
310320

311-
def nearest_geom(self, geom: BaseGeometry) -> Union[BaseGeometry, None]:
321+
def nearest_geom(
322+
self, geom: BaseGeometry, exclusive: bool = False
323+
) -> Union[BaseGeometry, None]:
312324
"""Query the tree for the node nearest to geom and get the
313325
geometry corresponding to the item stored in the node.
314326
315327
Parameters
316328
----------
317329
geom : geometry object
318330
The query geometry.
331+
exclusive : bool, optional
332+
Whether to exclude the given geom from results or not.
333+
Default: False.
319334
320335
Returns
321336
-------
@@ -325,13 +340,15 @@ def nearest_geom(self, geom: BaseGeometry) -> Union[BaseGeometry, None]:
325340
version 2.0.
326341
327342
"""
328-
item = self.nearest_item(geom)
343+
item = self.nearest_item(geom, exclusive=exclusive)
329344
if item is None:
330345
return None
331346
else:
332347
return self._rev[item]
333348

334-
def nearest(self, geom: BaseGeometry) -> Union[BaseGeometry, None]:
349+
def nearest(
350+
self, geom: BaseGeometry, exclusive: bool = False
351+
) -> Union[BaseGeometry, None]:
335352
"""Query the tree for the node nearest to geom and get the
336353
geometry corresponding to the item stored in the node.
337354
@@ -342,6 +359,9 @@ def nearest(self, geom: BaseGeometry) -> Union[BaseGeometry, None]:
342359
----------
343360
geom : geometry object
344361
The query geometry.
362+
exclusive : bool, optional
363+
Whether to exclude the given geom from results or not.
364+
Default: False.
345365
346366
Returns
347367
-------
@@ -351,4 +371,4 @@ def nearest(self, geom: BaseGeometry) -> Union[BaseGeometry, None]:
351371
version 2.0.
352372
353373
"""
354-
return self.nearest_geom(geom)
374+
return self.nearest_geom(geom, exclusive=exclusive)

tests/test_strtree.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,22 @@ def test_nearest_items(geoms, items):
177177
with pytest.warns(ShapelyDeprecationWarning):
178178
tree = STRtree(geoms, items)
179179
assert tree.nearest_item(None) is None
180+
181+
182+
@pytest.mark.skipif(geos_version < (3, 6, 0), reason="GEOS 3.6.0 required")
183+
@pytest.mark.parametrize(
184+
"geoms",
185+
[
186+
[
187+
Point(0, 0.5),
188+
Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]),
189+
Polygon([(0, 2), (1, 2), (1, 3), (0, 3)]),
190+
]
191+
],
192+
)
193+
@pytest.mark.parametrize("items", [list(range(1, 4)), list("abc")])
194+
@pytest.mark.parametrize("query_geom", [Point(0, 0.5)])
195+
def test_nearest_item_exclusive(geoms, items, query_geom):
196+
with pytest.warns(ShapelyDeprecationWarning):
197+
tree = STRtree(geoms, items)
198+
assert tree.nearest_item(query_geom, exclusive=True) != items[0]

0 commit comments

Comments
 (0)