From 53e06302cb8d7526e02ba7f3c84eba58d31376de Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Thu, 18 Feb 2021 21:51:48 +0000 Subject: [PATCH] ENH: add a keyword to control alignment in binary operations and predicates (#1668) * align keyword * tests * first set of docstrings * docstrings * docs * shapely test * fix condition * fix project test * docstring changes per review --- .gitignore | 2 +- doc/source/_static/binary_geo-difference.svg | 1 + .../_static/binary_geo-intersection.svg | 1 + doc/source/_static/binary_geo-symm_diff.svg | 1 + doc/source/_static/binary_geo-union.svg | 1 + doc/source/_static/binary_op-01.svg | 1 + doc/source/_static/binary_op-02.svg | 1 + doc/source/_static/binary_op-03.svg | 1 + doc/source/docs/reference/geoseries.rst | 3 +- geopandas/base.py | 1872 ++++++++++++++++- geopandas/tests/test_geom_methods.py | 111 + geopandas/tests/test_geoseries.py | 20 +- 12 files changed, 1934 insertions(+), 81 deletions(-) create mode 100644 doc/source/_static/binary_geo-difference.svg create mode 100644 doc/source/_static/binary_geo-intersection.svg create mode 100644 doc/source/_static/binary_geo-symm_diff.svg create mode 100644 doc/source/_static/binary_geo-union.svg create mode 100644 doc/source/_static/binary_op-01.svg create mode 100644 doc/source/_static/binary_op-02.svg create mode 100644 doc/source/_static/binary_op-03.svg diff --git a/.gitignore b/.gitignore index 1cfe9fe..8eefebd 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,7 @@ examples/nybb_*.zip doc/source/savefig doc/source/reference -doc/source/docs/reference/api +doc/source/docs/reference/api/*.rst geopandas.egg-info geopandas/version.py diff --git a/doc/source/_static/binary_geo-difference.svg b/doc/source/_static/binary_geo-difference.svg new file mode 100644 index 0000000..d430d25 --- /dev/null +++ b/doc/source/_static/binary_geo-difference.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/binary_geo-intersection.svg b/doc/source/_static/binary_geo-intersection.svg new file mode 100644 index 0000000..2a06f5b --- /dev/null +++ b/doc/source/_static/binary_geo-intersection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/binary_geo-symm_diff.svg b/doc/source/_static/binary_geo-symm_diff.svg new file mode 100644 index 0000000..f33736b --- /dev/null +++ b/doc/source/_static/binary_geo-symm_diff.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/binary_geo-union.svg b/doc/source/_static/binary_geo-union.svg new file mode 100644 index 0000000..1c6901a --- /dev/null +++ b/doc/source/_static/binary_geo-union.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/binary_op-01.svg b/doc/source/_static/binary_op-01.svg new file mode 100644 index 0000000..3bedcb6 --- /dev/null +++ b/doc/source/_static/binary_op-01.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/binary_op-02.svg b/doc/source/_static/binary_op-02.svg new file mode 100644 index 0000000..a8d4087 --- /dev/null +++ b/doc/source/_static/binary_op-02.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/_static/binary_op-03.svg b/doc/source/_static/binary_op-03.svg new file mode 100644 index 0000000..37b1fa6 --- /dev/null +++ b/doc/source/_static/binary_op-03.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/docs/reference/geoseries.rst b/doc/source/docs/reference/geoseries.rst index 4f6bfd2..82726ac 100644 --- a/doc/source/docs/reference/geoseries.rst +++ b/doc/source/docs/reference/geoseries.rst @@ -49,11 +49,12 @@ Binary Predicates .. autosummary:: :toctree: api/ - GeoSeries.geom_almost_equals GeoSeries.contains GeoSeries.crosses GeoSeries.disjoint GeoSeries.geom_equals + GeoSeries.geom_almost_equals + GeoSeries.geom_equals_exact GeoSeries.intersects GeoSeries.overlaps GeoSeries.touches diff --git a/geopandas/base.py b/geopandas/base.py index b2e2802..a4edd73 100644 --- a/geopandas/base.py +++ b/geopandas/base.py @@ -24,11 +24,11 @@ def is_geometry_type(data): return False -def _delegate_binary_method(op, this, other, *args, **kwargs): +def _delegate_binary_method(op, this, other, align, *args, **kwargs): # type: (str, GeoSeries, GeoSeries) -> GeoSeries/Series this = this.geometry if isinstance(other, GeoPandasBase): - if not this.index.equals(other.index): + if align and not this.index.equals(other.index): warn("The indices of the two GeoSeries are different.") this, other = this.align(other.geometry) else: @@ -45,19 +45,19 @@ def _delegate_binary_method(op, this, other, *args, **kwargs): return data, this.index -def _binary_geo(op, this, other): +def _binary_geo(op, this, other, align): # type: (str, GeoSeries, GeoSeries) -> GeoSeries """Binary operation on GeoSeries objects that returns a GeoSeries""" from .geoseries import GeoSeries - geoms, index = _delegate_binary_method(op, this, other) + geoms, index = _delegate_binary_method(op, this, other, align) return GeoSeries(geoms.data, index=index, crs=this.crs) -def _binary_op(op, this, other, *args, **kwargs): +def _binary_op(op, this, other, align, *args, **kwargs): # type: (str, GeoSeries, GeoSeries, args/kwargs) -> Series[bool/float] """Binary operation on GeoSeries objects that returns a Series""" - data, index = _delegate_binary_method(op, this, other, *args, **kwargs) + data, index = _delegate_binary_method(op, this, other, align, *args, **kwargs) return Series(data, index=index) @@ -727,9 +727,9 @@ GeometryCollection # Binary operations that return a pandas Series # - def contains(self, other): + def contains(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that contains `other`. + each aligned geometry that contains `other`. An object is said to contain `other` if its `interior` contains the `boundary` and `interior` of the other object and their boundaries do @@ -738,36 +738,230 @@ GeometryCollection This is the inverse of :meth:`within` in the sense that the expression ``a.contains(b) == b.within(a)`` always evaluates to ``True``. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if is contained. - """ - return _binary_op("contains", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def geom_equals(self, other): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(0, 0), (0, 2)]), + ... LineString([(0, 0), (0, 1)]), + ... Point(0, 1), + ... ], + ... index=range(0, 4), + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (1, 2), (0, 2)]), + ... LineString([(0, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 1 LINESTRING (0.00000 0.00000, 0.00000 2.00000) + 2 LINESTRING (0.00000 0.00000, 0.00000 1.00000) + 3 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 POLYGON ((0.00000 0.00000, 1.00000 2.00000, 0.... + 3 LINESTRING (0.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check if each geometry of GeoSeries contains a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> point = Point(0, 1) + >>> s.contains(point) + 0 False + 1 True + 2 False + 3 True + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s2.contains(s, align=True) + 0 False + 1 False + 2 False + 3 True + 4 False + dtype: bool + + >>> s2.contains(s, align=False) + 1 True + 2 False + 3 True + 4 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries ``contains`` *any* element of the other one. + + See also + -------- + GeoSeries.within + """ + return _binary_op("contains", self, other, align) + + def geom_equals(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry equal to `other`. + each aligned geometry equal to `other`. An object is said to be equal to `other` if its set-theoretic `boundary`, `interior`, and `exterior` coincides with those of the other. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test for equality. - """ - return _binary_op("geom_equals", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def geom_almost_equals(self, other, decimal=6): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (1, 2), (0, 2)]), + ... LineString([(0, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (1, 2), (0, 2)]), + ... Point(0, 1), + ... LineString([(0, 0), (0, 2)]), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 1.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 0.00000 2.00000) + 3 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 POLYGON ((0.00000 0.00000, 1.00000 2.00000, 0.... + 3 POINT (0.00000 1.00000) + 4 LINESTRING (0.00000 0.00000, 0.00000 2.00000) + dtype: geometry + + We can check if each geometry of GeoSeries contains a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> polygon = Polygon([(0, 0), (2, 2), (0, 2)]) + >>> s.geom_equals(polygon) + 0 True + 1 False + 2 False + 3 False + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.geom_equals(s2) + 0 False + 1 False + 2 False + 3 True + 4 False + dtype: bool + + >>> s.geom_equals(s2, align=False) + 0 True + 1 True + 2 False + 3 False + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries is equal to *any* element of the other one. + + See also + -------- + GeoSeries.geom_almost_equals + GeoSeries.geom_equals_exact + + """ + return _binary_op("geom_equals", self, other, align) + + def geom_almost_equals(self, other, decimal=6, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` if - each geometry is approximately equal to `other`. + each aligned geometry is approximately equal to `other`. Approximate equality is tested at all points to the specified `decimal` - place precision. See also :meth:`geom_equals`. + place precision. + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center Parameters ---------- @@ -775,90 +969,677 @@ GeometryCollection The GeoSeries (elementwise) or geometric object to compare to. decimal : int Decimal place presion used when testing for approximate equality. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. + + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Point + >>> s = geopandas.GeoSeries( + ... [ + ... Point(0, 1.1), + ... Point(0, 1.01), + ... Point(0, 1.001), + ... ], + ... ) + + >>> s + 0 POINT (0.00000 1.10000) + 1 POINT (0.00000 1.01000) + 2 POINT (0.00000 1.00100) + dtype: geometry + + + >>> s.geom_almost_equals(Point(0, 1), decimal=2) + 0 False + 1 False + 2 True + dtype: bool + + >>> s.geom_almost_equals(Point(0, 1), decimal=1) + 0 False + 1 True + 2 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries is equal to *any* element of the other one. + + See also + -------- + GeoSeries.geom_equals + GeoSeries.geom_equals_exact + """ - return _binary_op("geom_almost_equals", self, other, decimal=decimal) + return _binary_op( + "geom_almost_equals", self, other, decimal=decimal, align=align + ) - def geom_equals_exact(self, other, tolerance): - """Return True for all geometries that equal *other* to a given - tolerance, else False""" - return _binary_op("geom_equals_exact", self, other, tolerance=tolerance) + def geom_equals_exact(self, other, tolerance, align=True): + """Return True for all geometries that equal aligned *other* to a given + tolerance, else False. - def crosses(self, other): + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + + Parameters + ---------- + other : GeoSeries or geometric object + The GeoSeries (elementwise) or geometric object to compare to. + tolerance : float + Decimal place presion used when testing for approximate equality. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. + + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Point + >>> s = geopandas.GeoSeries( + ... [ + ... Point(0, 1.1), + ... Point(0, 1.0), + ... Point(0, 1.2), + ... ] + ... ) + + >>> s + 0 POINT (0.00000 1.10000) + 1 POINT (0.00000 1.00000) + 2 POINT (0.00000 1.20000) + dtype: geometry + + + >>> s.geom_equals_exact(Point(0, 1), tolerance=0.1) + 0 False + 1 True + 2 False + dtype: bool + + >>> s.geom_equals_exact(Point(0, 1), tolerance=0.15) + 0 True + 1 True + 2 False + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries is equal to *any* element of the other one. + + See also + -------- + GeoSeries.geom_equals + GeoSeries.geom_almost_equals + """ + return _binary_op( + "geom_equals_exact", self, other, tolerance=tolerance, align=align + ) + + def crosses(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that cross `other`. + each aligned geometry that cross `other`. An object is said to cross `other` if its `interior` intersects the `interior` of the other but does not contain it, and the dimension of the intersection is less than the dimension of the one or the other. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if is crossed. - """ - return _binary_op("crosses", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def disjoint(self, other): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 2 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 3 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 2 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 3 POINT (1.00000 1.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check if each geometry of GeoSeries crosses a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> line = LineString([(-1, 1), (3, 1)]) + >>> s.crosses(line) + 0 True + 1 True + 2 True + 3 False + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.crosses(s2, align=True) + 0 False + 1 True + 2 False + 3 False + 4 False + dtype: bool + + >>> s.crosses(s2, align=False) + 0 True + 1 True + 2 False + 3 False + dtype: bool + + Notice that a line does not cross a point that it contains. + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries ``crosses`` *any* element of the other one. + + See also + -------- + GeoSeries.disjoint + GeoSeries.intersects + + """ + return _binary_op("crosses", self, other, align) + + def disjoint(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry disjoint to `other`. + each aligned geometry disjoint to `other`. An object is said to be disjoint to `other` if its `boundary` and `interior` does not intersect at all with those of the other. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if is disjoint. - """ - return _binary_op("disjoint", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def intersects(self, other): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(-1, 0), (-1, 2), (0, -2)]), + ... LineString([(0, 0), (0, 1)]), + ... Point(1, 1), + ... Point(0, 0), + ... ], + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 2 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 3 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 0 POLYGON ((-1.00000 0.00000, -1.00000 2.00000, ... + 1 LINESTRING (0.00000 0.00000, 0.00000 1.00000) + 2 POINT (1.00000 1.00000) + 3 POINT (0.00000 0.00000) + dtype: geometry + + We can check each geometry of GeoSeries to a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> line = LineString([(0, 0), (2, 0)]) + >>> s.disjoint(line) + 0 False + 1 False + 2 False + 3 True + dtype: bool + + We can also check two GeoSeries against each other, row by row. + We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.disjoint(s2) + 0 True + 1 False + 2 False + 3 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries is equal to *any* element of the other one. + + See also + -------- + GeoSeries.intersects + GeoSeries.touches + + """ + return _binary_op("disjoint", self, other, align) + + def intersects(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that intersects `other`. + each aligned geometry that intersects `other`. An object is said to intersect `other` if its `boundary` and `interior` intersects in any way with those of the other. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if is intersected. - """ - return _binary_op("intersects", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def overlaps(self, other): - """Returns True for all geometries that overlap *other*, else False. + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 2 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 3 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 2 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 3 POINT (1.00000 1.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check if each geometry of GeoSeries crosses a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> line = LineString([(-1, 1), (3, 1)]) + >>> s.intersects(line) + 0 True + 1 True + 2 True + 3 True + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.intersects(s2, align=True) + 0 False + 1 True + 2 True + 3 False + 4 False + dtype: bool + + >>> s.intersects(s2, align=False) + 0 True + 1 True + 2 True + 3 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries ``crosses`` *any* element of the other one. + + See also + -------- + GeoSeries.disjoint + GeoSeries.crosses + GeoSeries.touches + GeoSeries.intersection + """ + return _binary_op("intersects", self, other, align) + + def overlaps(self, other, align=True): + """Returns True for all aligned geometries that overlap *other*, else False. + + Geometries overlaps if they have more than one but not all + points in common, have the same dimension, and the intersection of the + interiors of the geometries has the same dimension as the geometries + themselves. + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if overlaps. - """ - return _binary_op("overlaps", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def touches(self, other): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, MultiPoint, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... MultiPoint([(0, 0), (0, 1)]), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 0), (0, 2)]), + ... LineString([(0, 1), (1, 1)]), + ... LineString([(1, 1), (3, 3)]), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 MULTIPOINT (0.00000 0.00000, 0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 2.00000 0.00000, 0.... + 2 LINESTRING (0.00000 1.00000, 1.00000 1.00000) + 3 LINESTRING (1.00000 1.00000, 3.00000 3.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check if each geometry of GeoSeries overlaps a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + >>> s.overlaps(polygon) + 0 True + 1 True + 2 False + 3 False + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.overlaps(s2) + 0 False + 1 True + 2 False + 3 False + 4 False + dtype: bool + + >>> s.overlaps(s2, align=False) + 0 True + 1 False + 2 True + 3 False + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries ``overlaps`` *any* element of the other one. + + See also + -------- + GeoSeries.crosses + GeoSeries.intersects + + """ + return _binary_op("overlaps", self, other, align) + + def touches(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that touches `other`. + each aligned geometry that touches `other`. An object is said to touch `other` if it has at least one point in common with `other` and its interior does not intersect with any part - of the other. + of the other. Overlapping features therefore do not touch. + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if is touched. - """ - return _binary_op("touches", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def within(self, other): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, MultiPoint, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... MultiPoint([(0, 0), (0, 1)]), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (-2, 0), (0, -2)]), + ... LineString([(0, 1), (1, 1)]), + ... LineString([(1, 1), (3, 0)]), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 MULTIPOINT (0.00000 0.00000, 0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, -2.00000 0.00000, 0... + 2 LINESTRING (0.00000 1.00000, 1.00000 1.00000) + 3 LINESTRING (1.00000 1.00000, 3.00000 0.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check if each geometry of GeoSeries touches a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + + >>> line = LineString([(0, 0), (-1, -2)]) + >>> s.touches(line) + 0 True + 1 True + 2 True + 3 True + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.touches(s2, align=True) + 0 False + 1 True + 2 True + 3 False + 4 False + dtype: bool + + >>> s.touches(s2, align=False) + 0 True + 1 False + 2 True + 3 False + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries ``touches`` *any* element of the other one. + + See also + -------- + GeoSeries.overlaps + GeoSeries.intersects + + """ + return _binary_op("touches", self, other, align) + + def within(self, other, align=True): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that is within `other`. + each aligned geometry that is within `other`. An object is said to be within `other` if its `boundary` and `interior` intersects only with the `interior` of the other (not its `boundary` or @@ -868,23 +1649,121 @@ GeometryCollection expression ``a.within(b) == b.contains(a)`` always evaluates to ``True``. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : GeoSeries or geometric object The GeoSeries (elementwise) or geometric object to test if each geometry is within. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. + Returns + ------- + Series (bool) + + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (1, 2), (0, 2)]), + ... LineString([(0, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(0, 0), (0, 2)]), + ... LineString([(0, 0), (0, 1)]), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 1.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 0.00000 2.00000) + 3 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 0.00000 2.00000) + 3 LINESTRING (0.00000 0.00000, 0.00000 1.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check if each geometry of GeoSeries is within a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> polygon = Polygon([(0, 0), (2, 2), (0, 2)]) + >>> s.within(polygon) + 0 True + 1 True + 2 False + 3 False + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s2.within(s) + 0 False + 1 False + 2 True + 3 False + 4 False + dtype: bool + + >>> s2.within(s, align=False) + 1 True + 2 False + 3 True + 4 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries is ``within`` *any* element of the other one. + + See also + -------- + GeoSeries.contains """ - return _binary_op("within", self, other) + return _binary_op("within", self, other, align) - def covers(self, other): + def covers(self, other, align=True): """ Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that is entirely covering `other`. + each aligned geometry that is entirely covering `other`. An object A is said to cover another object B if no points of B lie in the exterior of A. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + See https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html for reference. @@ -893,17 +1772,111 @@ GeometryCollection ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to check is being covered. - """ - return _binary_geo("covers", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def covered_by(self, other): + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... Point(0, 0), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]), + ... Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]), + ... LineString([(1, 1), (1.5, 1.5)]), + ... Point(0, 0), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 0.00000, 2.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 POINT (0.00000 0.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.50000 0.50000, 1.50000 0.50000, 1.... + 2 POLYGON ((0.00000 0.00000, 2.00000 0.00000, 2.... + 3 LINESTRING (1.00000 1.00000, 1.50000 1.50000) + 4 POINT (0.00000 0.00000) + dtype: geometry + + We can check if each geometry of GeoSeries covers a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> poly = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]) + >>> s.covers(poly) + 0 True + 1 False + 2 False + 3 False + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.covers(s2, align=True) + 0 False + 1 False + 2 False + 3 False + 4 False + dtype: bool + + >>> s.covers(s2, align=False) + 0 True + 1 False + 2 True + 3 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries ``covers`` *any* element of the other one. + + See also + -------- + GeoSeries.covered_by + GeoSeries.overlaps + """ + return _binary_op("covers", self, other, align) + + def covered_by(self, other, align=True): """ Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - each geometry that is entirely covered by `other`. + each aligned geometry that is entirely covered by `other`. An object A is said to cover another object B if no points of B lie in the exterior of A. + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + See https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html for reference. @@ -912,74 +1885,649 @@ GeometryCollection ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to check is being covered. - """ - return _binary_geo("covered_by", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def distance(self, other): - """Returns a ``Series`` containing the distance to `other`. + Returns + ------- + Series (bool) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]), + ... Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]), + ... LineString([(1, 1), (1.5, 1.5)]), + ... Point(0, 0), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... Point(0, 0), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.50000 0.50000, 1.50000 0.50000, 1.... + 1 POLYGON ((0.00000 0.00000, 2.00000 0.00000, 2.... + 2 LINESTRING (1.00000 1.00000, 1.50000 1.50000) + 3 POINT (0.00000 0.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 2.00000 0.00000, 2.... + 2 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 3 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 4 POINT (0.00000 0.00000) + dtype: geometry + + We can check if each geometry of GeoSeries is covered by a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> poly = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]) + >>> s.covered_by(poly) + 0 True + 1 True + 2 True + 3 True + dtype: bool + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.covered_by(s2, align=True) + 0 False + 1 True + 2 True + 3 True + 4 False + dtype: bool + + >>> s.covered_by(s2, align=False) + 0 True + 1 False + 2 True + 3 True + dtype: bool + + Notes + ----- + This method works in a row-wise manner. It does not check if an element + of one GeoSeries is ``covered_by`` *any* element of the other one. + + See also + -------- + GeoSeries.covers + GeoSeries.overlaps + """ + return _binary_op("covered_by", self, other, align) + + def distance(self, other, align=True): + """Returns a ``Series`` containing the distance to aligned `other`. + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center Parameters ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to find the distance to. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. + + + Returns + ------- + Series (float) + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 0), (1, 1)]), + ... Polygon([(0, 0), (-1, 0), (-1, 1)]), + ... LineString([(1, 1), (0, 0)]), + ... Point(0, 0), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]), + ... Point(3, 1), + ... LineString([(1, 0), (2, 0)]), + ... Point(0, 1), + ... ], + ... index=range(1, 5), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 1.00000 0.00000, 1.... + 1 POLYGON ((0.00000 0.00000, -1.00000 0.00000, -... + 2 LINESTRING (1.00000 1.00000, 0.00000 0.00000) + 3 POINT (0.00000 0.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.50000 0.50000, 1.50000 0.50000, 1.... + 2 POINT (3.00000 1.00000) + 3 LINESTRING (1.00000 0.00000, 2.00000 0.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can check the distance of each geometry of GeoSeries to a single + geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> point = Point(-1, 0) + >>> s.distance(point) + 0 1.0 + 1 0.0 + 2 1.0 + 3 1.0 + dtype: float64 + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and use elements with the same index using + ``align=True`` or ignore index and use elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.distance(s2, align=True) + 0 NaN + 1 0.707107 + 2 2.000000 + 3 1.000000 + 4 NaN + dtype: float64 + + >>> s.distance(s2, align=False) + 0 0.000000 + 1 3.162278 + 2 0.707107 + 3 1.000000 + dtype: float64 """ - return _binary_op("distance", self, other) + return _binary_op("distance", self, other, align) # # Binary operations that return a GeoSeries # - def difference(self, other): - """Returns a ``GeoSeries`` of the points in each geometry that + def difference(self, other, align=True): + """Returns a ``GeoSeries`` of the points in each aligned geometry that are not in `other`. + .. image:: ../../../_static/binary_geo-difference.svg + :align: center + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to find the difference to. - """ - return _binary_geo("difference", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def symmetric_difference(self, other): + Returns + ------- + GeoSeries + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 6), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 2 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (1.00000 1.00000) + 5 POINT (0.00000 1.00000) + dtype: geometry + + We can do difference of each geometry and a single + shapely geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> s.difference(Polygon([(0, 0), (1, 1), (0, 1)])) + 0 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 1 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 2 LINESTRING (1.00000 1.00000, 2.00000 2.00000) + 3 MULTILINESTRING ((2.00000 0.00000, 1.00000 1.0... + 4 POINT EMPTY + dtype: geometry + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.difference(s2, align=True) + 0 None + 1 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 2 MULTILINESTRING ((0.00000 0.00000, 1.00000 1.0... + 3 LINESTRING EMPTY + 4 POINT (0.00000 1.00000) + 5 None + dtype: geometry + + >>> s.difference(s2, align=False) + 0 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 1 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 2 MULTILINESTRING ((0.00000 0.00000, 1.00000 1.0... + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT EMPTY + dtype: geometry + + See Also + -------- + GeoSeries.symmetric_difference + GeoSeries.union + GeoSeries.intersection + """ + return _binary_geo("difference", self, other, align) + + def symmetric_difference(self, other, align=True): """Returns a ``GeoSeries`` of the symmetric difference of points in - each geometry with `other`. + each aligned geometry with `other`. For each geometry, the symmetric difference consists of points in the geometry not in `other`, and points in `other` not in the geometry. + .. image:: ../../../_static/binary_geo-symm_diff.svg + :align: center + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + + Parameters ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to find the symmetric difference to. - """ - return _binary_geo("symmetric_difference", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def union(self, other): - """Returns a ``GeoSeries`` of the union of points in each geometry with + Returns + ------- + GeoSeries + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 6), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 2 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (1.00000 1.00000) + 5 POINT (0.00000 1.00000) + dtype: geometry + + We can do symmetric difference of each geometry and a single + shapely geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> s.symmetric_difference(Polygon([(0, 0), (1, 1), (0, 1)])) + 0 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 1 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 2 GEOMETRYCOLLECTION (LINESTRING (1.00000 1.0000... + 3 GEOMETRYCOLLECTION (LINESTRING (2.00000 0.0000... + 4 POLYGON ((0.00000 0.00000, 0.00000 1.00000, 1.... + dtype: geometry + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.symmetric_difference(s2, align=True) + 0 None + 1 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 2 MULTILINESTRING ((0.00000 0.00000, 1.00000 1.0... + 3 LINESTRING EMPTY + 4 MULTIPOINT (0.00000 1.00000, 1.00000 1.00000) + 5 None + dtype: geometry + + >>> s.symmetric_difference(s2, align=False) + 0 POLYGON ((0.00000 1.00000, 0.00000 2.00000, 2.... + 1 GEOMETRYCOLLECTION (LINESTRING (1.00000 0.0000... + 2 MULTILINESTRING ((0.00000 0.00000, 1.00000 1.0... + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT EMPTY + dtype: geometry + + See Also + -------- + GeoSeries.difference + GeoSeries.union + GeoSeries.intersection + """ + return _binary_geo("symmetric_difference", self, other, align) + + def union(self, other, align=True): + """Returns a ``GeoSeries`` of the union of points in each aligned geometry with `other`. + .. image:: ../../../_static/binary_geo-union.svg + :align: center + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + + Parameters ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to find the union with. - """ - return _binary_geo("union", self, other) + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - def intersection(self, other): + Returns + ------- + GeoSeries + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 6), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 2 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (1.00000 1.00000) + 5 POINT (0.00000 1.00000) + dtype: geometry + + We can do union of each geometry and a single + shapely geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> s.union(Polygon([(0, 0), (1, 1), (0, 1)])) + 0 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 1 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 2 GEOMETRYCOLLECTION (LINESTRING (1.00000 1.0000... + 3 GEOMETRYCOLLECTION (LINESTRING (2.00000 0.0000... + 4 POLYGON ((0.00000 0.00000, 0.00000 1.00000, 1.... + dtype: geometry + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.union(s2, align=True) + 0 None + 1 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 2 MULTILINESTRING ((0.00000 0.00000, 1.00000 1.0... + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 MULTIPOINT (0.00000 1.00000, 1.00000 1.00000) + 5 None + dtype: geometry + + >>> s.union(s2, align=False) + 0 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 1 GEOMETRYCOLLECTION (LINESTRING (1.00000 0.0000... + 2 MULTILINESTRING ((0.00000 0.00000, 1.00000 1.0... + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + + See Also + -------- + GeoSeries.symmetric_difference + GeoSeries.difference + GeoSeries.intersection + """ + return _binary_geo("union", self, other, align) + + def intersection(self, other, align=True): """Returns a ``GeoSeries`` of the intersection of points in each - geometry with `other`. + aligned geometry with `other`. + + .. image:: ../../../_static/binary_geo-intersection.svg + :align: center + + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : Geoseries or geometric object The Geoseries (elementwise) or geometric object to find the intersection with. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. + + Returns + ------- + GeoSeries + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 6), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 2 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (1.00000 1.00000) + 5 POINT (0.00000 1.00000) + dtype: geometry + + We can also do intersection of each geometry and a single + shapely geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> s.intersection(Polygon([(0, 0), (1, 1), (0, 1)])) + 0 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 1 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 1.00000 1.00000) + 3 POINT (1.00000 1.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.intersection(s2, align=True) + 0 None + 1 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 2 POINT (1.00000 1.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT EMPTY + 5 None + dtype: geometry + + >>> s.intersection(s2, align=False) + 0 POLYGON ((1.00000 1.00000, 0.00000 0.00000, 0.... + 1 LINESTRING (1.00000 1.00000, 1.00000 2.00000) + 2 POINT (1.00000 1.00000) + 3 POINT (1.00000 1.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + + See Also + -------- + GeoSeries.difference + GeoSeries.symmetric_difference + GeoSeries.union """ - return _binary_geo("intersection", self, other) + return _binary_geo("intersection", self, other, align) # # Other operations @@ -1161,28 +2709,123 @@ GeometryCollection """ return _delegate_geo_method("simplify", self, *args, **kwargs) - def relate(self, other): + def relate(self, other, align=True): """ Returns the DE-9IM intersection matrices for the geometries + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + Parameters ---------- other : BaseGeometry or GeoSeries The other geometry to computed the DE-9IM intersection matrices from. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. Returns ---------- spatial_relations: Series of strings The DE-9IM intersection matrices which describe the spatial relations of the other geometry. - """ - return _binary_op("relate", self, other) - def project(self, other, normalized=False): + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(0, 1), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (1, 1), (0, 1)]), + ... LineString([(1, 0), (1, 3)]), + ... LineString([(2, 0), (0, 2)]), + ... Point(1, 1), + ... Point(0, 1), + ... ], + ... index=range(1, 6), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 2 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (0.00000 1.00000) + dtype: geometry + + >>> s2 + 1 POLYGON ((0.00000 0.00000, 1.00000 1.00000, 0.... + 2 LINESTRING (1.00000 0.00000, 1.00000 3.00000) + 3 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + 4 POINT (1.00000 1.00000) + 5 POINT (0.00000 1.00000) + dtype: geometry + + We can relate each geometry and a single + shapely geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> s.relate(Polygon([(0, 0), (1, 1), (0, 1)])) + 0 212F11FF2 + 1 212F11FF2 + 2 F11F00212 + 3 F01FF0212 + 4 F0FFFF212 + dtype: object + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and compare elements with the same index using + ``align=True`` or ignore index and compare elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.relate(s2, align=True) + 0 None + 1 212F11FF2 + 2 0F1FF0102 + 3 1FFF0FFF2 + 4 FF0FFF0F2 + 5 None + dtype: object + + >>> s.relate(s2, align=False) + 0 212F11FF2 + 1 1F20F1102 + 2 0F1FF0102 + 3 0F1FF0FF2 + 4 0FFFFFFF2 + dtype: object + + """ + return _binary_op("relate", self, other, align) + + def project(self, other, normalized=False, align=True): """ Return the distance along each geometry nearest to *other* + The operation works on a 1-to-1 row-wise manner: + + .. image:: ../../../_static/binary_op-01.svg + :align: center + + The project method is the inverse of interpolate. + + Parameters ---------- other : BaseGeometry or GeoSeries @@ -1190,10 +2833,83 @@ GeometryCollection normalized : boolean If normalized is True, return the distance normalized to the length of the object. + align : bool (default True) + If True, automatically aligns GeoSeries based on their indices. + If False, the order of elements is preserved. - The project method is the inverse of interpolate. + Returns + ------- + Series + + Examples + -------- + >>> from shapely.geometry import Polygon, LineString, Point + >>> s = geopandas.GeoSeries( + ... [ + ... Polygon([(0, 0), (2, 2), (0, 2)]), + ... LineString([(0, 0), (2, 2)]), + ... LineString([(2, 0), (0, 2)]), + ... ], + ... ) + >>> s2 = geopandas.GeoSeries( + ... [ + ... Point(1, 0), + ... Point(1, 0), + ... Point(2, 1), + ... ], + ... index=range(1, 4), + ... ) + + >>> s + 0 POLYGON ((0.00000 0.00000, 2.00000 2.00000, 0.... + 1 LINESTRING (0.00000 0.00000, 2.00000 2.00000) + 2 LINESTRING (2.00000 0.00000, 0.00000 2.00000) + dtype: geometry + + >>> s2 + 1 POINT (1.00000 0.00000) + 2 POINT (1.00000 0.00000) + 3 POINT (2.00000 1.00000) + dtype: geometry + + We can project each geometry on a single + shapely geometry: + + .. image:: ../../../_static/binary_op-03.svg + :align: center + + >>> s.project(Point(1, 0)) + 0 -1.000000 + 1 0.707107 + 2 0.707107 + dtype: float64 + + We can also check two GeoSeries against each other, row by row. + The GeoSeries above have different indices. We can either align both GeoSeries + based on index values and project elements with the same index using + ``align=True`` or ignore index and project elements based on their matching + order using ``align=False``: + + .. image:: ../../../_static/binary_op-02.svg + + >>> s.project(s2, align=True) + 0 NaN + 1 0.707107 + 2 0.707107 + 3 NaN + dtype: float64 + + >>> s.project(s2, align=False) + 0 -1.000000 + 1 0.707107 + 2 0.707107 + dtype: float64 + + See also + -------- + GeoSeries.interpolate """ - return _binary_op("project", self, other, normalized=normalized) + return _binary_op("project", self, other, normalized=normalized, align=align) def interpolate(self, distance, normalized=False): """ diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py index 1764a2f..d4a86a6 100644 --- a/geopandas/tests/test_geom_methods.py +++ b/geopandas/tests/test_geom_methods.py @@ -81,6 +81,7 @@ class TestGeomMethods: self.empty = GeoSeries([]) self.all_none = GeoSeries([None, None]) self.empty_poly = Polygon() + self.g9 = GeoSeries(self.g0, index=range(1, 8)) # Crossed lines self.l3 = LineString([(0, 0), (1, 1)]) @@ -244,15 +245,24 @@ class TestGeomMethods: "intersection", self.all_none, self.g1, self.empty ) + assert len(self.g0.intersection(self.g9, align=True) == 8) + assert len(self.g0.intersection(self.g9, align=False) == 7) + def test_union_series(self): self._test_binary_topological("union", self.sq, self.g1, self.g2) + assert len(self.g0.union(self.g9, align=True) == 8) + assert len(self.g0.union(self.g9, align=False) == 7) + def test_union_polygon(self): self._test_binary_topological("union", self.sq, self.g1, self.t2) def test_symmetric_difference_series(self): self._test_binary_topological("symmetric_difference", self.sq, self.g3, self.g4) + assert len(self.g0.symmetric_difference(self.g9, align=True) == 8) + assert len(self.g0.symmetric_difference(self.g9, align=False) == 7) + def test_symmetric_difference_poly(self): expected = GeoSeries([GeometryCollection(), self.sq], crs=self.g3.crs) self._test_binary_topological( @@ -263,6 +273,9 @@ class TestGeomMethods: expected = GeoSeries([GeometryCollection(), self.t2]) self._test_binary_topological("difference", expected, self.g1, self.g2) + assert len(self.g0.difference(self.g9, align=True) == 8) + assert len(self.g0.difference(self.g9, align=False) == 7) + def test_difference_poly(self): expected = GeoSeries([self.t1, self.t1]) self._test_binary_topological("difference", expected, self.g1, self.t2) @@ -341,6 +354,12 @@ class TestGeomMethods: expected = [True, False, True, False, False, False, False] assert_array_dtype_equal(expected, self.g0.contains(self.t1)) + expected = [False, True, True, True, True, True, False, False] + assert_array_dtype_equal(expected, self.g0.contains(self.g9, align=True)) + + expected = [False, False, True, False, False, False, False] + assert_array_dtype_equal(expected, self.g0.contains(self.g9, align=False)) + def test_length(self): expected = Series(np.array([2 + np.sqrt(2), 4]), index=self.g1.index) self._test_unary_real("length", expected, self.g1) @@ -359,10 +378,22 @@ class TestGeomMethods: expected = [False, True] assert_array_dtype_equal(expected, self.crossed_lines.crosses(self.l3)) + expected = [False] * 8 + assert_array_dtype_equal(expected, self.g0.crosses(self.g9, align=True)) + + expected = [False] * 7 + assert_array_dtype_equal(expected, self.g0.crosses(self.g9, align=False)) + def test_disjoint(self): expected = [False, False, False, False, False, True, False] assert_array_dtype_equal(expected, self.g0.disjoint(self.t1)) + expected = [False] * 8 + assert_array_dtype_equal(expected, self.g0.disjoint(self.g9, align=True)) + + expected = [False, False, False, False, True, False, False] + assert_array_dtype_equal(expected, self.g0.disjoint(self.g9, align=False)) + def test_relate(self): expected = Series( [ @@ -381,6 +412,36 @@ class TestGeomMethods: expected = Series(["FF0FFF212", None], index=self.g6.index) assert_array_dtype_equal(expected, self.g6.relate(self.na_none)) + expected = Series( + [ + None, + "2FFF1FFF2", + "2FFF1FFF2", + "2FFF1FFF2", + "2FFF1FFF2", + "0FFFFFFF2", + None, + None, + ], + index=range(8), + ) + + assert_array_dtype_equal(expected, self.g0.relate(self.g9, align=True)) + + expected = Series( + [ + "FF2F11212", + "2FF11F212", + "212FF1FF2", + "FF2F1F212", + "FF2FF10F2", + None, + None, + ], + index=self.g0.index, + ) + assert_array_dtype_equal(expected, self.g0.relate(self.g9, align=False)) + def test_distance(self): expected = Series( np.array([np.sqrt((5 - 1) ** 2 + (5 - 1) ** 2), np.nan]), self.na_none.index @@ -390,6 +451,13 @@ class TestGeomMethods: expected = Series(np.array([np.sqrt(4 ** 2 + 4 ** 2), np.nan]), self.g6.index) assert_array_dtype_equal(expected, self.g6.distance(self.na_none)) + expected = Series(np.array([np.nan, 0, 0, 0, 0, 0, np.nan, np.nan]), range(8)) + assert_array_dtype_equal(expected, self.g0.distance(self.g9, align=True)) + + val = self.g0.iloc[4].distance(self.g9.iloc[4]) + expected = Series(np.array([0, 0, 0, 0, val, np.nan, np.nan]), self.g0.index) + assert_array_dtype_equal(expected, self.g0.distance(self.g9, align=False)) + def test_distance_crs_warning(self): with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): self.g4.distance(self.p0) @@ -410,6 +478,12 @@ class TestGeomMethods: expected = [False] * 7 assert_array_dtype_equal(expected, self.g0.intersects(self.empty_poly)) + expected = [False, True, True, True, True, True, False, False] + assert_array_dtype_equal(expected, self.g0.intersects(self.g9, align=True)) + + expected = [True, True, True, True, False, False, False] + assert_array_dtype_equal(expected, self.g0.intersects(self.g9, align=False)) + def test_overlaps(self): expected = [True, True, False, False, False, False, False] assert_array_dtype_equal(expected, self.g0.overlaps(self.inner_sq)) @@ -417,10 +491,22 @@ class TestGeomMethods: expected = [False, False] assert_array_dtype_equal(expected, self.g4.overlaps(self.t1)) + expected = [False] * 8 + assert_array_dtype_equal(expected, self.g0.overlaps(self.g9, align=True)) + + expected = [False] * 7 + assert_array_dtype_equal(expected, self.g0.overlaps(self.g9, align=False)) + def test_touches(self): expected = [False, True, False, False, False, False, False] assert_array_dtype_equal(expected, self.g0.touches(self.t1)) + expected = [False] * 8 + assert_array_dtype_equal(expected, self.g0.touches(self.g9, align=True)) + + expected = [True, False, False, True, False, False, False] + assert_array_dtype_equal(expected, self.g0.touches(self.g9, align=False)) + def test_within(self): expected = [True, False, False, False, False, False, False] assert_array_dtype_equal(expected, self.g0.within(self.t1)) @@ -428,6 +514,12 @@ class TestGeomMethods: expected = [True, True, True, True, True, False, False] assert_array_dtype_equal(expected, self.g0.within(self.sq)) + expected = [False, True, True, True, True, True, False, False] + assert_array_dtype_equal(expected, self.g0.within(self.g9, align=True)) + + expected = [False, True, False, False, False, False, False] + assert_array_dtype_equal(expected, self.g0.within(self.g9, align=False)) + def test_covers_itself(self): # Each polygon in a Series covers itself res = self.g1.covers(self.g1) @@ -439,6 +531,12 @@ class TestGeomMethods: exp = Series([True, False]) assert_series_equal(res, exp) + expected = [False, True, True, True, True, True, False, False] + assert_array_dtype_equal(expected, self.g0.covers(self.g9, align=True)) + + expected = [False, False, True, False, False, False, False] + assert_array_dtype_equal(expected, self.g0.covers(self.g9, align=False)) + def test_covers_inverse(self): res = self.g8.covers(self.g7) exp = Series([False, False]) @@ -453,6 +551,12 @@ class TestGeomMethods: exp = Series([True, True]) assert_series_equal(res, exp) + expected = [False, True, True, True, True, True, False, False] + assert_array_dtype_equal(expected, self.g0.covered_by(self.g9, align=True)) + + expected = [False, True, False, False, False, False, False] + assert_array_dtype_equal(expected, self.g0.covered_by(self.g9, align=False)) + def test_is_valid(self): expected = Series(np.array([True] * len(self.g1)), self.g1.index) self._test_unary_real("is_valid", expected, self.g1) @@ -572,6 +676,13 @@ class TestGeomMethods: expected = Series([1.0, 0.5], index=self.g5.index) self._test_binary_real("project", expected, self.g5, p, normalized=True) + s = GeoSeries([Point(2, 2), Point(0.5, 0.5)], index=[1, 2]) + expected = Series([np.nan, 2.0, np.nan]) + assert_series_equal(self.g5.project(s), expected) + + expected = Series([2.0, 0.5], index=self.g5.index) + assert_series_equal(self.g5.project(s, align=False), expected) + def test_affine_transform(self): # 45 degree reflection matrix matrix = [0, 1, 1, 0, 0, 0] diff --git a/geopandas/tests/test_geoseries.py b/geopandas/tests/test_geoseries.py index 9bcc763..1ffb649 100644 --- a/geopandas/tests/test_geoseries.py +++ b/geopandas/tests/test_geoseries.py @@ -136,20 +136,38 @@ class TestSeries: def test_geom_equals_align(self): with pytest.warns(UserWarning, match="The indices .+ different"): - a = self.a1.geom_equals(self.a2) + a = self.a1.geom_equals(self.a2, align=True) exp = pd.Series([False, True, False], index=["A", "B", "C"]) assert_series_equal(a, exp) + a = self.a1.geom_equals(self.a2, align=False) + exp = pd.Series([False, False], index=["A", "B"]) + assert_series_equal(a, exp) + def test_geom_almost_equals(self): # TODO: test decimal parameter assert np.all(self.g1.geom_almost_equals(self.g1)) assert_array_equal(self.g1.geom_almost_equals(self.sq), [False, True]) + assert_array_equal( + self.a1.geom_almost_equals(self.a2, align=True), [False, True, False] + ) + assert_array_equal( + self.a1.geom_almost_equals(self.a2, align=False), [False, False] + ) + def test_geom_equals_exact(self): # TODO: test tolerance parameter assert np.all(self.g1.geom_equals_exact(self.g1, 0.001)) assert_array_equal(self.g1.geom_equals_exact(self.sq, 0.001), [False, True]) + assert_array_equal( + self.a1.geom_equals_exact(self.a2, 0.001, align=True), [False, True, False] + ) + assert_array_equal( + self.a1.geom_equals_exact(self.a2, 0.001, align=False), [False, False] + ) + def test_equal_comp_op(self): s = GeoSeries([Point(x, x) for x in range(3)]) res = s == Point(1, 1)