Skip to content

Commit

Permalink
ENH: add contains_properly predicate function (#267)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisvandenbossche committed Jan 30, 2021
1 parent 0dfbd84 commit 4210414
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Version 0.10 (unreleased)

* ...

**Added GEOS functions**

* Addition of a ``contains_properly`` function (#267).

**Acknowledgments**

Thanks to everyone who contributed to this release!
Expand Down
52 changes: 52 additions & 0 deletions pygeos/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"is_valid_reason",
"crosses",
"contains",
"contains_properly",
"covered_by",
"covers",
"disjoint",
Expand Down Expand Up @@ -416,13 +417,18 @@ def contains(a, b, **kwargs):
A contains B if no points of B lie in the exterior of A and at least one
point of the interior of B lies in the interior of A.
Note: following this definition, a geometry does not contain its boundary,
but it does contain itself. See `contains_properly` for a version where
a geometry does not contain itself.
Parameters
----------
a, b : Geometry or array_like
See also
--------
within : ``contains(A, B) == within(B, A)``
contains_properly : contains with no common boundary points
prepare : improve performance by preparing ``a`` (the first argument)
Examples
Expand Down Expand Up @@ -454,6 +460,52 @@ def contains(a, b, **kwargs):
return lib.contains(a, b, **kwargs)


@multithreading_enabled
def contains_properly(a, b, **kwargs):
"""Returns True if geometry B is completely inside geometry A, with no
common boundary points.
A contains B properly if B intersects the interior of A but not the
boundary (or exterior). This means that a geometry A does not
"contain properly" itself, which contrasts with the `contains` function,
where common points on the boundary are allowed.
Note: this function will prepare the geometries under the hood if needed.
You can prepare the geometries in advance to avoid repeated preparation
when calling this function multiple times.
Parameters
----------
a, b : Geometry or array_like
See also
--------
contains : contains which allows common boundary points
prepare : improve performance by preparing ``a`` (the first argument)
Examples
--------
>>> area1 = Geometry("POLYGON((0 0, 3 0, 3 3, 0 3, 0 0))")
>>> area2 = Geometry("POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))")
>>> area3 = Geometry("POLYGON((1 1, 2 1, 2 2, 1 2, 1 1))")
``area1`` and ``area2`` have a common border:
>>> contains(area1, area2)
True
>>> contains_properly(area1, area2)
False
``area3`` is completely inside ``area1`` with no common border:
>>> contains(area1, area3)
True
>>> contains_properly(area1, area3)
True
"""
return lib.contains_properly(a, b, **kwargs)


@multithreading_enabled
def covered_by(a, b, **kwargs):
"""Returns True if no point in geometry A is outside geometry B.
Expand Down
8 changes: 8 additions & 0 deletions pygeos/test/test_predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
pygeos.crosses,
pygeos.within,
pygeos.contains,
pygeos.contains_properly,
pygeos.overlaps,
pygeos.equals,
pygeos.covers,
Expand Down Expand Up @@ -180,6 +181,7 @@ def test_binary_prepared(a, func):
result = func(_prepare_with_copy(a), point)
assert actual == result


@pytest.mark.parametrize("geometry", all_types + (empty,))
def test_is_prepared_true(geometry):
assert pygeos.is_prepared(_prepare_with_copy(geometry))
Expand All @@ -188,3 +190,9 @@ def test_is_prepared_true(geometry):
@pytest.mark.parametrize("geometry", all_types + (empty, None))
def test_is_prepared_false(geometry):
assert not pygeos.is_prepared(geometry)


def test_contains_properly():
# polygon contains itself, but does not properly contains itself
assert pygeos.contains(polygon, polygon).item() is True
assert pygeos.contains_properly(polygon, polygon).item() is False
17 changes: 15 additions & 2 deletions src/ufuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,20 @@ static PyUFuncGenericFunction YY_b_funcs[1] = {&YY_b_func};
/* Define the geom, geom -> bool functions (YY_b) prepared */
static void* contains_func_tuple[2] = {GEOSContains_r, GEOSPreparedContains_r};
static void* contains_data[1] = {contains_func_tuple};
// static void* contains_properly_func_tuple[2] = {..., GEOSPreparedContainsProperly_r};
// static void* contains_properly_data[1] = {contains_properly_func_tuple};
static char GEOSContainsProperly(void* context, void* g1, void* g2) {
const GEOSPreparedGeometry* prepared_geom_tmp = NULL;
char ret;

prepared_geom_tmp = GEOSPrepare_r(context, g1);
if (prepared_geom_tmp == NULL) {
return 2;
}
ret = GEOSPreparedContainsProperly_r(context, prepared_geom_tmp, g2);
GEOSPreparedGeom_destroy_r(context, prepared_geom_tmp);
return ret;
}
static void* contains_properly_func_tuple[2] = {GEOSContainsProperly, GEOSPreparedContainsProperly_r};
static void* contains_properly_data[1] = {contains_properly_func_tuple};
static void* covered_by_func_tuple[2] = {GEOSCoveredBy_r, GEOSPreparedCoveredBy_r};
static void* covered_by_data[1] = {covered_by_func_tuple};
static void* covers_func_tuple[2] = {GEOSCovers_r, GEOSPreparedCovers_r};
Expand Down Expand Up @@ -2618,6 +2630,7 @@ int init_ufuncs(PyObject* m, PyObject* d) {
DEFINE_YY_b_p(crosses);
DEFINE_YY_b_p(within);
DEFINE_YY_b_p(contains);
DEFINE_YY_b_p(contains_properly);
DEFINE_YY_b_p(overlaps);
DEFINE_YY_b(equals);
DEFINE_YY_b_p(covers);
Expand Down

0 comments on commit 4210414

Please sign in to comment.