Repository: Toblerity/Shapely
Pull request #269 (2015-04-25 22:44:42)

Corrected restype on GEOSPrepared functions (c_int -> c_byte)
This fixes a bug with prepared geometries highlighted in issue #268. The `restype` was incorrectly set to `c_int` rather than `c_byte`. This caused unexpected results on Windows only, although I'm not sure why.


Patch:
diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py
index 8695229..3ee5141 100644
--- a/shapely/ctypes_declarations.py
+++ b/shapely/ctypes_declarations.py
@@ -249,6 +249,9 @@ def prototype(lgeos, geos_version):
     lgeos.GEOSOverlaps.restype = c_byte
     lgeos.GEOSOverlaps.argtypes = [c_void_p, c_void_p]
 
+    lgeos.GEOSCovers.restype = c_byte
+    lgeos.GEOSCovers.argtypes = [c_void_p, c_void_p]
+
     lgeos.GEOSEquals.restype = c_byte
     lgeos.GEOSEquals.argtypes = [c_void_p, c_void_p]
 
@@ -306,17 +309,33 @@ def prototype(lgeos, geos_version):
         lgeos.GEOSPreparedGeom_destroy.restype = None
         lgeos.GEOSPreparedGeom_destroy.argtypes = [c_void_p]
 
-        lgeos.GEOSPreparedContains.restype = c_int
+        lgeos.GEOSPreparedDisjoint.restype = c_byte
+        lgeos.GEOSPreparedDisjoint.argtypes = [c_void_p, c_void_p]
+
+        lgeos.GEOSPreparedTouches.restype = c_byte
+        lgeos.GEOSPreparedTouches.argtypes = [c_void_p, c_void_p]
+
+        lgeos.GEOSPreparedIntersects.restype = c_byte
+        lgeos.GEOSPreparedIntersects.argtypes = [c_void_p, c_void_p]
+
+        lgeos.GEOSPreparedCrosses.restype = c_byte
+        lgeos.GEOSPreparedCrosses.argtypes = [c_void_p, c_void_p]
+
+        lgeos.GEOSPreparedWithin.restype = c_byte
+        lgeos.GEOSPreparedWithin.argtypes = [c_void_p, c_void_p]
+
+        lgeos.GEOSPreparedContains.restype = c_byte
         lgeos.GEOSPreparedContains.argtypes = [c_void_p, c_void_p]
 
-        lgeos.GEOSPreparedContainsProperly.restype = c_int
+        lgeos.GEOSPreparedContainsProperly.restype = c_byte
         lgeos.GEOSPreparedContainsProperly.argtypes = [c_void_p, c_void_p]
 
-        lgeos.GEOSPreparedCovers.restype = c_int
+        lgeos.GEOSPreparedOverlaps.restype = c_byte
+        lgeos.GEOSPreparedOverlaps.argtypes = [c_void_p, c_void_p]
+
+        lgeos.GEOSPreparedCovers.restype = c_byte
         lgeos.GEOSPreparedCovers.argtypes = [c_void_p, c_void_p]
 
-        lgeos.GEOSPreparedIntersects.restype = c_int
-        lgeos.GEOSPreparedIntersects.argtypes = [c_void_p, c_void_p]
 
     '''
     Geometry info
diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py
index 7cefc2d..69d8405 100644
--- a/shapely/geometry/base.py
+++ b/shapely/geometry/base.py
@@ -616,6 +616,10 @@ class BaseGeometry(object):
         (string)"""
         return self.impl['relate'](self, other)
 
+    def covers(self, other):
+        """Returns True if the geometry covers the other, else False"""
+        return bool(self.impl['covers'](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 9bee9e1..15746b9 100644
--- a/shapely/geos.py
+++ b/shapely/geos.py
@@ -540,8 +540,18 @@ class LGEOS310(LGEOSBase):
                 self.GEOSWithin,
                 self.GEOSContains,
                 self.GEOSOverlaps,
+                self.GEOSCovers,
                 self.GEOSEquals,
                 self.GEOSEqualsExact,
+                self.GEOSPreparedDisjoint,
+                self.GEOSPreparedTouches,
+                self.GEOSPreparedCrosses,
+                self.GEOSPreparedWithin,
+                self.GEOSPreparedOverlaps,
+                self.GEOSPreparedContains,
+                self.GEOSPreparedContainsProperly,
+                self.GEOSPreparedCovers,
+                self.GEOSPreparedIntersects,
                 self.GEOSisEmpty,
                 self.GEOSisValid,
                 self.GEOSisSimple,
@@ -572,6 +582,7 @@ class LGEOS310(LGEOSBase):
         self.methods['within'] = self.GEOSWithin
         self.methods['contains'] = self.GEOSContains
         self.methods['overlaps'] = self.GEOSOverlaps
+        self.methods['covers'] = self.GEOSCovers
         self.methods['equals'] = self.GEOSEquals
         self.methods['equals_exact'] = self.GEOSEqualsExact
         self.methods['relate'] = self.GEOSRelate
@@ -579,10 +590,15 @@ class LGEOS310(LGEOSBase):
         self.methods['symmetric_difference'] = self.GEOSSymDifference
         self.methods['union'] = self.GEOSUnion
         self.methods['intersection'] = self.GEOSIntersection
+        self.methods['prepared_disjoint'] = self.GEOSPreparedDisjoint
+        self.methods['prepared_touches'] = self.GEOSPreparedTouches
         self.methods['prepared_intersects'] = self.GEOSPreparedIntersects
+        self.methods['prepared_crosses'] = self.GEOSPreparedCrosses
+        self.methods['prepared_within'] = self.GEOSPreparedWithin
         self.methods['prepared_contains'] = self.GEOSPreparedContains
         self.methods['prepared_contains_properly'] = \
             self.GEOSPreparedContainsProperly
+        self.methods['prepared_overlaps'] = self.GEOSPreparedOverlaps
         self.methods['prepared_covers'] = self.GEOSPreparedCovers
         self.methods['simplify'] = self.GEOSSimplify
         self.methods['topology_preserve_simplify'] = \
diff --git a/shapely/impl.py b/shapely/impl.py
index d4f386e..6d8c134 100644
--- a/shapely/impl.py
+++ b/shapely/impl.py
@@ -106,6 +106,7 @@ IMPL300 = {
     'overlaps': (BinaryPredicate, 'overlaps'),
     'touches': (BinaryPredicate, 'touches'),
     'within': (BinaryPredicate, 'within'),
+    'covers': (BinaryPredicate, 'covers'),
     'equals_exact': (BinaryPredicate, 'equals_exact'),
 
     # First pure Python implementation
@@ -116,6 +117,11 @@ IMPL310 = {
     'simplify': (UnaryTopologicalOp, 'simplify'),
     'topology_preserve_simplify':
         (UnaryTopologicalOp, 'topology_preserve_simplify'),
+    'prepared_disjoint': (BinaryPredicate, 'prepared_disjoint'),
+    'prepared_touches': (BinaryPredicate, 'prepared_touches'),
+    'prepared_crosses': (BinaryPredicate, 'prepared_crosses'),
+    'prepared_within': (BinaryPredicate, 'prepared_within'),
+    'prepared_overlaps': (BinaryPredicate, 'prepared_overlaps'),
     'prepared_intersects': (BinaryPredicate, 'prepared_intersects'),
     'prepared_contains': (BinaryPredicate, 'prepared_contains'),
     'prepared_contains_properly':
diff --git a/shapely/prepared.py b/shapely/prepared.py
index 5a3bf22..fb9080a 100644
--- a/shapely/prepared.py
+++ b/shapely/prepared.py
@@ -37,23 +37,51 @@ class PreparedGeometry(object):
     @property
     def _geom(self):
         return self.__geom__
-   
-    @delegated
-    def intersects(self, other):
-        return bool(self.impl['prepared_intersects'](self, other))
 
     @delegated
     def contains(self, other):
+        """Returns True if the geometry contains the other, else False"""
         return bool(self.impl['prepared_contains'](self, other))
 
     @delegated
     def contains_properly(self, other):
+        """Returns True if the geometry properly contains the other, else False"""
         return bool(self.impl['prepared_contains_properly'](self, other))
 
     @delegated
     def covers(self, other):
+        """Returns True if the geometry covers the other, else False"""
         return bool(self.impl['prepared_covers'](self, other))
 
+    @delegated
+    def crosses(self, other):
+        """Returns True if the geometries cross, else False"""
+        return bool(self.impl['prepared_crosses'](self, other))
+
+    @delegated
+    def disjoint(self, other):
+        """Returns True if geometries are disjoint, else False"""
+        return bool(self.impl['prepared_disjoint'](self, other))
+
+    @delegated
+    def intersects(self, other):
+        """Returns True if geometries intersect, else False"""
+        return bool(self.impl['prepared_intersects'](self, other))
+
+    @delegated
+    def overlaps(self, other):
+        """Returns True if geometries overlap, else False"""
+        return bool(self.impl['prepared_overlaps'](self, other))
+
+    @delegated
+    def touches(self, other):
+        """Returns True if geometries touch, else False"""
+        return bool(self.impl['prepared_touches'](self, other))
+
+    @delegated
+    def within(self, other):
+        """Returns True if geometry is within the other, else False"""
+        return bool(self.impl['prepared_within'](self, other))
 
 def prep(ob):
     """Creates and returns a prepared geometric object."""


Test patch:
diff --git a/tests/test_predicates.py b/tests/test_predicates.py
index 336a61b..3062eac 100644
--- a/tests/test_predicates.py
+++ b/tests/test_predicates.py
@@ -19,6 +19,8 @@ class PredicatesTestCase(unittest.TestCase):
         self.assertFalse(point.equals(Point(-1.0, -1.0)))
         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.assertFalse(point.covers(Point(-1.0, -1.0)))
 
     def test_unary_predicates(self):
 
diff --git a/tests/test_prepared.py b/tests/test_prepared.py
index fb3d4ed..4f43dc1 100644
--- a/tests/test_prepared.py
+++ b/tests/test_prepared.py
@@ -25,6 +25,26 @@ class PreparedGeometryTestCase(unittest.TestCase):
         p = prepared.PreparedGeometry(geometry.Point(0.0, 0.0).buffer(1.0))
         self.assertRaises(ValueError, geometry.Point(0.0, 0.0).contains, p)
 
+    @unittest.skipIf(geos_version < (3, 1, 0), 'GEOS 3.1.0 required')
+    def test_prepared_predicates(self):
+        # check prepared predicates give the same result as regular predicates
+        polygon1 = geometry.Polygon([
+            (0, 0), (0, 1), (1, 1), (1, 0), (0, 0)
+        ])
+        polygon2 = geometry.Polygon([
+            (0.5, 0.5), (1.5, 0.5), (1.0, 1.0), (0.5, 0.5)
+        ])
+        point2 = geometry.Point(0.5, 0.5)
+        polygon_empty = geometry.Polygon()
+        prepared_polygon1 = prepared.PreparedGeometry(polygon1)
+        for geom2 in (polygon2, point2, polygon_empty):
+            self.assertTrue(polygon1.disjoint(geom2) == prepared_polygon1.disjoint(geom2))
+            self.assertTrue(polygon1.touches(geom2) == prepared_polygon1.touches(geom2))
+            self.assertTrue(polygon1.intersects(geom2) == prepared_polygon1.intersects(geom2))
+            self.assertTrue(polygon1.crosses(geom2) == prepared_polygon1.crosses(geom2))
+            self.assertTrue(polygon1.within(geom2) == prepared_polygon1.within(geom2))
+            self.assertTrue(polygon1.contains(geom2) == prepared_polygon1.contains(geom2))
+            self.assertTrue(polygon1.overlaps(geom2) == prepared_polygon1.overlaps(geom2))
 
 def test_suite():
     loader = unittest.TestLoader()