Repository: Toblerity/Shapely
Pull request #927 (2020-06-06 19:21:44)

Add support for GEOSCoveredBy 
Adding support for GEOSCoveredBy. See geopandas - https://github.com/geopandas/geopandas/issues/1452 

coveralls: 
[![Coverage Status](https://coveralls.io/builds/31285445/badge)](https://coveralls.io/builds/31285445)

Coverage increased (+0.04%) to 81.847% when pulling **16990189d11850b9bb6bd0d056d16bcab1d26bce on bhavika:covered-by** into **ea25f8cbaa177213aabfda55a8ab2f272f263b2b on Toblerity:master**.

jorisvandenbossche: Looking a bit in the segfault (https://github.com/geopandas/geopandas/issues/1452#issuecomment-640108490), I see two things:

- I think you still need to declare the argument/return types of the ctypes function here (the equivalent for covers): https://github.com/Toblerity/Shapely/blob/ea25f8cbaa177213aabfda55a8ab2f272f263b2b/shapely/ctypes_declarations.py#L288-L289

- it seems that one difference between Covers and CoveredBy is that for CoveredBy there is only the `_r` version exposed. So it might be that the automatic setting of the argtypes/restype based on the non-`_r` version migth not work here: https://github.com/Toblerity/Shapely/blob/ea25f8cbaa177213aabfda55a8ab2f272f263b2b/shapely/geos.py#L224
mwtoews: The suggested  `ctypes_declarations.py` modification should clear up the Windows AppVeyor issue.

@jorisvandenbossche there are both [`GEOSCoveredBy`](https://git.osgeo.org/gitea/geos/geos/src/commit/099e74b3127348e1f8544ab279b609e2fdc6cc74/capi/geos_c.h.in#L1810) and [`GEOSCoveredBy_r`](https://git.osgeo.org/gitea/geos/geos/src/commit/099e74b3127348e1f8544ab279b609e2fdc6cc74/capi/geos_c.h.in#L814-L816) functions, so no special considerations are required. It's pretty much the same as `GEOSCovers`.
bhavika: > Looking a bit in the segfault ([geopandas/geopandas#1452 (comment)](https://github.com/geopandas/geopandas/issues/1452#issuecomment-640108490)), I see two things:
> 
> * I think you still need to declare the argument/return types of the ctypes function here (the equivalent for covers): https://github.com/Toblerity/Shapely/blob/ea25f8cbaa177213aabfda55a8ab2f272f263b2b/shapely/ctypes_declarations.py#L288-L289
> * it seems that one difference between Covers and CoveredBy is that for CoveredBy there is only the `_r` version exposed. So it might be that the automatic setting of the argtypes/restype based on the non-`_r` version migth not work here: https://github.com/Toblerity/Shapely/blob/ea25f8cbaa177213aabfda55a8ab2f272f263b2b/shapely/geos.py#L224

@jorisvandenbossche this is what I was missing. I didn't realize I'd have to define those ctypes as well. Additionally, both `GEOSCoveredBy` and `GEOSCoveredBy_r` methods are exposed (see the "details" in my previous comment). 

Patch:
diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py
index f9d199a..adcd90d 100644
--- a/shapely/ctypes_declarations.py
+++ b/shapely/ctypes_declarations.py
@@ -288,6 +288,9 @@ def prototype(lgeos, geos_version):
     lgeos.GEOSCovers.restype = c_byte
     lgeos.GEOSCovers.argtypes = [c_void_p, c_void_p]
 
+    lgeos.GEOSCoveredBy.restype = c_byte
+    lgeos.GEOSCoveredBy.argtypes = [c_void_p, c_void_p]
+
     lgeos.GEOSEquals.restype = c_byte
     lgeos.GEOSEquals.argtypes = [c_void_p, c_void_p]
 
@@ -546,7 +549,7 @@ def prototype(lgeos, geos_version):
         lgeos.GEOSMinimumClearance.argtypes = [c_void_p]
         lgeos.GEOSMinimumClearance.restype = c_double
 
-    if geos_version > (3, 8, 0):
+    if geos_version >= (3, 8, 0):
         lgeos.GEOSMakeValid.restype = c_void_p
         lgeos.GEOSMakeValid.argtypes = [c_void_p]
 
diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py
index a4e0d6a..a5c85d7 100644
--- a/shapely/geometry/base.py
+++ b/shapely/geometry/base.py
@@ -732,6 +732,10 @@ class BaseGeometry(object):
         """Returns True if the geometry covers the other, else False"""
         return bool(self.impl['covers'](self, other))
 
+    def covered_by(self, other):
+        """Returns True if the geometry is covered by the other, else False"""
+        return bool(self.impl['covered_by'](self, other))
+
     def contains(self, other):
         """Returns True if the geometry contains the other, else False"""
         return bool(self.impl['contains'](self, other))
diff --git a/shapely/geos.py b/shapely/geos.py
index 791fda8..936c2fa 100644
--- a/shapely/geos.py
+++ b/shapely/geos.py
@@ -817,7 +817,7 @@ class LGEOS330(LGEOS320):
         attr.__name__ = func.__name__
         setattr(self, key, attr)
 
-        for pred in (self.GEOSisClosed,):
+        for pred in (self.GEOSisClosed, self.GEOSCoveredBy):
             pred.func.errcheck = errcheck_predicate
 
         def parallel_offset(geom, distance, resolution=16, join_style=1,
@@ -834,6 +834,7 @@ class LGEOS330(LGEOS320):
         self.methods['snap'] = self.GEOSSnap
         self.methods['shared_paths'] = self.GEOSSharedPaths
         self.methods['buffer_with_params'] = self.GEOSBufferWithParams
+        self.methods['covered_by'] = self.GEOSCoveredBy
 
 
 class LGEOS340(LGEOS330):
diff --git a/shapely/impl.py b/shapely/impl.py
index dbc9bc4..1a7481b 100644
--- a/shapely/impl.py
+++ b/shapely/impl.py
@@ -146,6 +146,7 @@ IMPL320 = {
 IMPL330 = {
     'is_closed': (UnaryPredicate, 'is_closed'),
     'buffer_with_params': (UnaryTopologicalOp, 'buffer_with_params'),
+    'covered_by': (BinaryPredicate, 'covered_by')
 }
 
 IMPL360 = {


Test patch:
diff --git a/tests/test_predicates.py b/tests/test_predicates.py
index c3c1cb4..62c4793 100644
--- a/tests/test_predicates.py
+++ b/tests/test_predicates.py
@@ -10,6 +10,7 @@ class PredicatesTestCase(unittest.TestCase):
     def test_binary_predicates(self):
 
         point = Point(0.0, 0.0)
+        point2 = Point(2.0, 2.0)
 
         self.assertTrue(point.disjoint(Point(-1.0, -1.0)))
         self.assertFalse(point.touches(Point(-1.0, -1.0)))
@@ -20,6 +21,9 @@ class PredicatesTestCase(unittest.TestCase):
         self.assertFalse(point.touches(Point(-1.0, -1.0)))
         self.assertTrue(point.equals(Point(0.0, 0.0)))
         self.assertTrue(point.covers(Point(0.0, 0.0)))
+        self.assertTrue(point.covered_by(Point(0.0, 0.0)))
+        self.assertFalse(point.covered_by(point2))
+        self.assertFalse(point2.covered_by(point))
         self.assertFalse(point.covers(Point(-1.0, -1.0)))
 
     def test_unary_predicates(self):