diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index d0d0d9f008..c0808f2e59 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -294,6 +294,14 @@ class GEOSGeometry(GEOSBase, ListMixin): "Returns true if other.within(this) returns true." return capi.geos_contains(self.ptr, other.ptr) + def covers(self, other): + """ + Return True if the DE-9IM Intersection Matrix for the two geometries is + T*****FF*, *T****FF*, ***T**FF*, or ****T*FF*. If either geometry is + empty, return False. + """ + return capi.geos_covers(self.ptr, other.ptr) + def crosses(self, other): """ Returns true if the DE-9IM intersection matrix for the two Geometries diff --git a/django/contrib/gis/geos/prototypes/__init__.py b/django/contrib/gis/geos/prototypes/__init__.py index d2950d0d97..595555897d 100644 --- a/django/contrib/gis/geos/prototypes/__init__.py +++ b/django/contrib/gis/geos/prototypes/__init__.py @@ -17,8 +17,9 @@ from django.contrib.gis.geos.prototypes.geom import ( # NOQA ) from django.contrib.gis.geos.prototypes.misc import * # NOQA from django.contrib.gis.geos.prototypes.predicates import ( # NOQA - geos_contains, geos_crosses, geos_disjoint, geos_equals, geos_equalsexact, - geos_hasz, geos_intersects, geos_isempty, geos_isring, geos_issimple, - geos_isvalid, geos_overlaps, geos_relatepattern, geos_touches, geos_within, + geos_contains, geos_covers, geos_crosses, geos_disjoint, geos_equals, + geos_equalsexact, geos_hasz, geos_intersects, geos_isempty, geos_isring, + geos_issimple, geos_isvalid, geos_overlaps, geos_relatepattern, + geos_touches, geos_within, ) from django.contrib.gis.geos.prototypes.topology import * # NOQA diff --git a/django/contrib/gis/geos/prototypes/predicates.py b/django/contrib/gis/geos/prototypes/predicates.py index e1b85f4e35..8d335fa187 100644 --- a/django/contrib/gis/geos/prototypes/predicates.py +++ b/django/contrib/gis/geos/prototypes/predicates.py @@ -30,6 +30,7 @@ geos_isvalid = UnaryPredicate('GEOSisValid') # ## Binary Predicates ## geos_contains = BinaryPredicate('GEOSContains') +geos_covers = BinaryPredicate('GEOSCovers') geos_crosses = BinaryPredicate('GEOSCrosses') geos_disjoint = BinaryPredicate('GEOSDisjoint') geos_equals = BinaryPredicate('GEOSEquals') diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index fffaf61fe7..43727a1ed7 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -349,6 +349,28 @@ return a boolean. Returns ``True`` if :meth:`other.within(this) ` returns ``True``. +.. method:: GEOSGeometry.covers(other) + +.. versionadded:: 1.10 + +Returns ``True`` if this geometry covers the specified geometry. + +The ``covers`` predicate has the following equivalent definitions: + +* Every point of the other geometry is a point of this geometry. +* The DE-9IM Intersection Matrix for the two geometries is + ``T*****FF*``, ``*T****FF*``, ``***T**FF*``, or ``****T*FF*``. + +If either geometry is empty, returns ``False``. + +This predicate is similar to :meth:`GEOSGeometry.contains`, but is more +inclusive (i.e. returns ``True`` for more cases). In particular, unlike +:meth:`~GEOSGeometry.contains` it does not distinguish between points in the +boundary and in the interior of geometries. For most situations, ``covers()`` +should be preferred to :meth:`~GEOSGeometry.contains`. As an added benefit, +``covers()`` is more amenable to optimization and hence should outperform +:meth:`~GEOSGeometry.contains`. + .. method:: GEOSGeometry.crosses(other) Returns ``True`` if the DE-9IM intersection matrix for the two Geometries diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 9eb6e1d93a..26e8849f3f 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -71,6 +71,9 @@ Minor features ` property computes the union of all the elements of this geometry. +* Added the :meth:`GEOSGeometry.covers() + ` binary predicate. + :mod:`django.contrib.messages` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py index b68720c89e..301d406317 100644 --- a/tests/gis_tests/geos_tests/test_geos.py +++ b/tests/gis_tests/geos_tests/test_geos.py @@ -643,6 +643,11 @@ class GEOSTest(SimpleTestCase, TestDataMixin): self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9) self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9) + def test_covers(self): + poly = Polygon(((0, 0), (0, 10), (10, 10), (10, 0), (0, 0))) + self.assertTrue(poly.covers(Point(5, 5))) + self.assertFalse(poly.covers(Point(100, 100))) + def test_srid(self): "Testing the SRID property and keyword." # Testing SRID keyword on Point @@ -1084,12 +1089,11 @@ class GEOSTest(SimpleTestCase, TestDataMixin): # A set of test points. pnts = [Point(5, 5), Point(7.5, 7.5), Point(2.5, 7.5)] - covers = [True, True, False] # No `covers` op for regular GEOS geoms. - for pnt, c in zip(pnts, covers): + for pnt in pnts: # Results should be the same (but faster) self.assertEqual(mpoly.contains(pnt), prep.contains(pnt)) self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt)) - self.assertEqual(c, prep.covers(pnt)) + self.assertEqual(mpoly.covers(pnt), prep.covers(pnt)) self.assertTrue(prep.crosses(fromstr('LINESTRING(1 1, 15 15)'))) self.assertTrue(prep.disjoint(Point(-5, -5)))