This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d4fb1bbba [GH-2060] Geopandas.GeoSeries: Implement crosses, overlaps, 
touches, within, covers, covered_by, contains, distance (#2061)
8d4fb1bbba is described below

commit 8d4fb1bbbafde8db01c9d845ba4d4261636c4681
Author: Peter Nguyen <[email protected]>
AuthorDate: Fri Jul 18 11:13:14 2025 -0700

    [GH-2060] Geopandas.GeoSeries: Implement crosses, overlaps, touches, 
within, covers, covered_by, contains, distance (#2061)
    
    * Implement functions
    
    * Skip covers and covered_by tests for old versions
    
    * Skip covered by for shapely < 2
    
    * Update python/tests/geopandas/test_match_geopandas_series.py
    
    Co-authored-by: Copilot <[email protected]>
    
    ---------
    
    Co-authored-by: Feng Zhang <[email protected]>
    Co-authored-by: Copilot <[email protected]>
---
 python/sedona/geopandas/geoseries.py               | 1021 ++++++++++++++++++--
 python/tests/geopandas/test_geopandas_base.py      |    6 +
 python/tests/geopandas/test_geoseries.py           |  269 +++++-
 .../tests/geopandas/test_match_geopandas_series.py |  172 +++-
 4 files changed, 1409 insertions(+), 59 deletions(-)

diff --git a/python/sedona/geopandas/geoseries.py 
b/python/sedona/geopandas/geoseries.py
index 1b0ebe66a3..9a42285a23 100644
--- a/python/sedona/geopandas/geoseries.py
+++ b/python/sedona/geopandas/geoseries.py
@@ -1052,6 +1052,8 @@ class GeoSeries(GeoFrame, pspd.Series):
             index,
             align=False,
             rename="get_geometry",
+            returns_geom=True,
+            default_val=None,
         )
 
     @property
@@ -1395,22 +1397,709 @@ class GeoSeries(GeoFrame, pspd.Series):
         geom = ps_series.iloc[0]
         return geom
 
+    def crosses(self, other, align=None) -> pspd.Series:
+        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
+        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.
+
+        Note: Unlike Geopandas, Sedona's implementation always return NULL 
when GeometryCollection is involved.
+
+        The operation works on a 1-to-1 row-wise manner.
+
+        Parameters
+        ----------
+        other : GeoSeries or geometric object
+            The GeoSeries (elementwise) or geometric object to test if is
+            crossed.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+        Examples
+        --------
+
+        >>> from sedona.geopandas import GeoSeries
+        >>> from shapely.geometry import Polygon, LineString, Point
+        >>> s = GeoSeries(
+        ...     [
+        ...         Polygon([(0, 0), (2, 2), (0, 2)]),
+        ...         LineString([(0, 0), (2, 2)]),
+        ...         LineString([(2, 0), (0, 2)]),
+        ...         Point(0, 1),
+        ...     ],
+        ... )
+        >>> s2 = 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 0, 2 2, 0 2, 0 0))
+        1             LINESTRING (0 0, 2 2)
+        2             LINESTRING (2 0, 0 2)
+        3                       POINT (0 1)
+        dtype: geometry
+        >>> s2
+        1    LINESTRING (1 0, 1 3)
+        2    LINESTRING (2 0, 0 2)
+        3              POINT (1 1)
+        4              POINT (0 1)
+        dtype: geometry
+
+        We can check if each geometry of GeoSeries crosses a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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
+
+        """
+        # Sedona does not support GeometryCollection (errors), so we return 
NULL for now to avoid error
+        select = """
+        CASE
+            WHEN GeometryType(`L`) == 'GEOMETRYCOLLECTION' OR 
GeometryType(`R`) == 'GEOMETRYCOLLECTION' THEN NULL
+            ELSE ST_Crosses(`L`, `R`)
+        END
+        """
+
+        result = self._row_wise_operation(
+            select, other, align, rename="crosses", default_val="FALSE"
+        )
+        return to_bool(result)
+
+    def disjoint(self, other, align=None):
+        # Implementation of the abstract method
+        raise NotImplementedError("This method is not implemented yet.")
+
     def intersects(
         self, other: Union["GeoSeries", BaseGeometry], align: Union[bool, 
None] = None
     ) -> pspd.Series:
         """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
         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.
+        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.
+
+        Parameters
+        ----------
+        other : GeoSeries or geometric object
+            The GeoSeries (elementwise) or geometric object to test if is
+            intersected.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+        Examples
+        --------
+        >>> from sedona.geopandas import GeoSeries
+        >>> from shapely.geometry import Polygon, LineString, Point
+        >>> s = GeoSeries(
+        ...     [
+        ...         Polygon([(0, 0), (2, 2), (0, 2)]),
+        ...         LineString([(0, 0), (2, 2)]),
+        ...         LineString([(2, 0), (0, 2)]),
+        ...         Point(0, 1),
+        ...     ],
+        ... )
+        >>> s2 = 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 0, 2 2, 0 2, 0 0))
+        1             LINESTRING (0 0, 2 2)
+        2             LINESTRING (2 0, 0 2)
+        3                       POINT (0 1)
+        dtype: geometry
+
+        >>> s2
+        1    LINESTRING (1 0, 1 3)
+        2    LINESTRING (2 0, 0 2)
+        3              POINT (1 1)
+        4              POINT (0 1)
+        dtype: geometry
+
+        We can check if each geometry of GeoSeries crosses a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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
+        """
+
+        result = self._row_wise_operation(
+            "ST_Intersects(`L`, `R`)",
+            other,
+            align,
+            rename="intersects",
+            default_val="FALSE",
+        )
+        return to_bool(result)
+
+    def overlaps(self, other, align=None) -> pspd.Series:
+        """Returns True for all aligned geometries that overlap other, else 
False.
+
+        In the original Geopandas, Geometries overlap 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.
+
+        However, in Sedona, we return True in the case where the geometries 
points match.
+
+        Note: Sedona's behavior may also differ from Geopandas for 
GeometryCollections.
+
+        The operation works on a 1-to-1 row-wise manner.
+
+        Parameters
+        ----------
+        other : GeoSeries or geometric object
+            The GeoSeries (elementwise) or geometric object to test if
+            overlaps.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+        Examples
+        --------
+        >>> from sedona.geopandas import GeoSeries
+        >>> from shapely.geometry import Polygon, LineString, MultiPoint, Point
+        >>> s = 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 = GeoSeries(
+        ...     [
+        ...         Polygon([(0, 0), (2, 0), (0, 2)]),
+        ...         LineString([(0, 1), (1, 1)]),
+        ...         LineString([(1, 1), (3, 3)]),
+        ...         Point(0, 1),
+        ...     ],
+        ... )
+
+        We can check if each geometry of GeoSeries overlaps a single
+        geometry:
+
+        >>> 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 align both GeoSeries
+        based on index values and compare elements with the same index.
+
+        >>> 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
+
+        """
+        # Note: We cannot efficiently match geopandas behavior because 
Sedona's ST_Overlaps returns True for equal geometries
+        # ST_Overlaps(`L`, `R`) AND ST_Equals(`L`, `R`) does not work because 
ST_Equals errors on invalid geometries
+
+        result = self._row_wise_operation(
+            "ST_Overlaps(`L`, `R`)",
+            other,
+            align,
+            rename="overlaps",
+            default_val="FALSE",
+        )
+        return to_bool(result)
+
+    def touches(self, other, align=None) -> pspd.Series:
+        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
+        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. Overlapping features therefore do not touch.
+
+        Note: Sedona's behavior may also differ from Geopandas for 
GeometryCollections.
+
+        The operation works on a 1-to-1 row-wise manner.
+
+        Parameters
+        ----------
+        other : GeoSeries or geometric object
+            The GeoSeries (elementwise) or geometric object to test if is
+            touched.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+        Examples
+        --------
+        >>> from shapely.geometry import Polygon, LineString, MultiPoint, Point
+        >>> s = 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 = 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 0, 2 2, 0 2, 0 0))
+        1    POLYGON ((0 0, 2 2, 0 2, 0 0))
+        2             LINESTRING (0 0, 2 2)
+        3         MULTIPOINT ((0 0), (0 1))
+        dtype: geometry
+
+        >>> s2
+        1    POLYGON ((0 0, -2 0, 0 -2, 0 0))
+        2               LINESTRING (0 1, 1 1)
+        3               LINESTRING (1 1, 3 0)
+        4                         POINT (0 1)
+        dtype: geometry
+
+        We can check if each geometry of GeoSeries touches a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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
+
+        """
+
+        result = self._row_wise_operation(
+            "ST_Touches(`L`, `R`)",
+            other,
+            align,
+            rename="touches",
+            default_val="FALSE",
+        )
+        return to_bool(result)
+
+    def within(self, other, align=None) -> pspd.Series:
+        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
+        each aligned geometry that is within `other`.
+
+        An object is said to be within `other` if at least one of its points 
is located
+        in the `interior` and no points are located in the `exterior` of the 
other.
+        If either object is empty, this operation returns ``False``.
+
+        This is the inverse of `contains` in the sense that the
+        expression ``a.within(b) == b.contains(a)`` always evaluates to
+        ``True``.
+
+        Note: Sedona's behavior may also differ from Geopandas for 
GeometryCollections and for geometries that are equal.
+
+        The operation works on a 1-to-1 row-wise manner.
+
+        Parameters
+        ----------
+        other : GeoSeries or geometric object
+            The GeoSeries (elementwise) or geometric object to test if each
+            geometry is within.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+
+        Examples
+        --------
+        >>> from shapely.geometry import Polygon, LineString, Point
+        >>> s = GeoSeries(
+        ...     [
+        ...         Polygon([(0, 0), (2, 2), (0, 2)]),
+        ...         Polygon([(0, 0), (1, 2), (0, 2)]),
+        ...         LineString([(0, 0), (0, 2)]),
+        ...         Point(0, 1),
+        ...     ],
+        ... )
+        >>> s2 = 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 0, 2 2, 0 2, 0 0))
+        1    POLYGON ((0 0, 1 2, 0 2, 0 0))
+        2             LINESTRING (0 0, 0 2)
+        3                       POINT (0 1)
+        dtype: geometry
+
+        >>> s2
+        1    POLYGON ((0 0, 1 1, 0 1, 0 0))
+        2             LINESTRING (0 0, 0 2)
+        3             LINESTRING (0 0, 0 1)
+        4                       POINT (0 1)
+        dtype: geometry
+
+        We can check if each geometry of GeoSeries is within a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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
+        """
+        result = self._row_wise_operation(
+            "ST_Within(`L`, `R`)",
+            other,
+            align,
+            rename="within",
+            default_val="FALSE",
+        )
+        return to_bool(result)
+
+    def covers(self, other, align=None) -> pspd.Series:
+        """
+        Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
+        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.
+        If either object is empty, this operation returns ``False``.
+
+        Note: Sedona's implementation instead returns False for identical 
geometries.
+        Sedona's behavior may also differ from Geopandas for 
GeometryCollections.
+
+        The operation works on a 1-to-1 row-wise manner.
+
+        See
+        
https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
+        for reference.
+
+        Parameters
+        ----------
+        other : Geoseries or geometric object
+            The Geoseries (elementwise) or geometric object to check is being 
covered.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+        Examples
+        --------
+        >>> from shapely.geometry import Polygon, LineString, Point
+        >>> s = 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 = 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 0, 2 0, 2 2, 0 2, 0 0))
+        1         POLYGON ((0 0, 2 2, 0 2, 0 0))
+        2                  LINESTRING (0 0, 2 2)
+        3                            POINT (0 0)
+        dtype: geometry
+
+        >>> s2
+        1    POLYGON ((0.5 0.5, 1.5 0.5, 1.5 1.5, 0.5 1.5, ...
+        2                  POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))
+        3                            LINESTRING (1 1, 1.5 1.5)
+        4                                          POINT (0 0)
+        dtype: geometry
+
+        We can check if each geometry of GeoSeries covers a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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
+        """
+        result = self._row_wise_operation(
+            "ST_Covers(`L`, `R`)",
+            other,
+            align,
+            rename="covers",
+            default_val="FALSE",
+        )
+        return to_bool(result)
+
+    def covered_by(self, other, align=None) -> pspd.Series:
+        """
+        Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
+        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.
+
+        Note: Sedona's implementation instead returns False for identical 
geometries.
+        Sedona's behavior may differ from Geopandas for GeometryCollections.
 
         The operation works on a 1-to-1 row-wise manner.
 
+        See
+        
https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
+        for reference.
+
         Parameters
         ----------
-        other : GeoSeries or geometric object
-            The GeoSeries (elementwise) or geometric object to test if is
-            intersected.
+        other : Geoseries or geometric object
+            The Geoseries (elementwise) or geometric object to check is being 
covered.
         align : bool | None (default None)
             If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
             If False, the order of elements is preserved.
@@ -1421,45 +2110,45 @@ class GeoSeries(GeoFrame, pspd.Series):
 
         Examples
         --------
-        >>> from sedona.geopandas import GeoSeries
         >>> from shapely.geometry import Polygon, LineString, Point
         >>> s = GeoSeries(
         ...     [
-        ...         Polygon([(0, 0), (2, 2), (0, 2)]),
-        ...         LineString([(0, 0), (2, 2)]),
-        ...         LineString([(2, 0), (0, 2)]),
-        ...         Point(0, 1),
+        ...         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 = GeoSeries(
         ...     [
-        ...         LineString([(1, 0), (1, 3)]),
-        ...         LineString([(2, 0), (0, 2)]),
-        ...         Point(1, 1),
-        ...         Point(0, 1),
+        ...         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 0, 2 2, 0 2, 0 0))
-        1             LINESTRING (0 0, 2 2)
-        2             LINESTRING (2 0, 0 2)
-        3                       POINT (0 1)
+        0    POLYGON ((0.5 0.5, 1.5 0.5, 1.5 1.5, 0.5 1.5, ...
+        1                  POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))
+        2                            LINESTRING (1 1, 1.5 1.5)
+        3                                          POINT (0 0)
         dtype: geometry
+        >>>
 
         >>> s2
-        1    LINESTRING (1 0, 1 3)
-        2    LINESTRING (2 0, 0 2)
-        3              POINT (1 1)
-        4              POINT (0 1)
+        1    POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))
+        2         POLYGON ((0 0, 2 2, 0 2, 0 0))
+        3                  LINESTRING (0 0, 2 2)
+        4                            POINT (0 0)
         dtype: geometry
 
-        We can check if each geometry of GeoSeries crosses a single
+        We can check if each geometry of GeoSeries is covered by a single
         geometry:
 
-        >>> line = LineString([(-1, 1), (3, 1)])
-        >>> s.intersects(line)
+        >>> poly = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
+        >>> s.covered_by(poly)
         0    True
         1    True
         2    True
@@ -1472,49 +2161,131 @@ class GeoSeries(GeoFrame, pspd.Series):
         ``align=True`` or ignore index and compare elements based on their 
matching
         order using ``align=False``:
 
-        >>> s.intersects(s2, align=True)
+        >>> s.covered_by(s2, align=True)
         0    False
         1     True
         2     True
-        3    False
+        3     True
         4    False
         dtype: bool
 
-        >>> s.intersects(s2, align=False)
-        0    True
-        1    True
-        2    True
-        3    True
+        >>> 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 ``crosses`` *any* element of the other one.
+        of one GeoSeries is ``covered_by`` any element of the other one.
 
         See also
         --------
-        GeoSeries.disjoint
-        GeoSeries.crosses
-        GeoSeries.touches
-        GeoSeries.intersection
+        GeoSeries.covers
+        GeoSeries.overlaps
         """
+        result = self._row_wise_operation(
+            "ST_CoveredBy(`L`, `R`)",
+            other,
+            align,
+            rename="covered_by",
+            default_val="FALSE",
+        )
+        return to_bool(result)
 
-        select = "ST_Intersects(`L`, `R`)"
+    def distance(self, other, align=None) -> pspd.Series:
+        """Returns a ``Series`` containing the distance to aligned `other`.
 
-        # ps.Series.fillna() call in to_bool, doesn't work for the output for
-        # intersects here for some reason. So we manually handle the nulls 
here.
-        select = f"""
-            CASE
-                WHEN `L` IS NULL OR `R` IS NULL THEN FALSE
-                ELSE {select}
-            END
+        The operation works on a 1-to-1 row-wise manner:
+
+        Parameters
+        ----------
+        other : Geoseries or geometric object
+            The Geoseries (elementwise) or geometric object to find the
+            distance to.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (float)
+
+        Examples
+        --------
+        >>> from sedona.geopandas import GeoSeries
+        >>> from shapely.geometry import Polygon, LineString, Point
+        >>> s = GeoSeries(
+        ...     [
+        ...         Polygon([(0, 0), (1, 0), (1, 1)]),
+        ...         Polygon([(0, 0), (-1, 0), (-1, 1)]),
+        ...         LineString([(1, 1), (0, 0)]),
+        ...         Point(0, 0),
+        ...     ],
+        ... )
+        >>> s2 = 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 0, 1 0, 1 1, 0 0))
+        1    POLYGON ((0 0, -1 0, -1 1, 0 0))
+        2               LINESTRING (1 1, 0 0)
+        3                         POINT (0 0)
+        dtype: geometry
+
+        >>> s2
+        1    POLYGON ((0.5 0.5, 1.5 0.5, 1.5 1.5, 0.5 1.5, ...
+        2                                          POINT (3 1)
+        3                                LINESTRING (1 0, 2 0)
+        4                                          POINT (0 1)
+        dtype: geometry
+
+        We can check the distance of each geometry of GeoSeries to a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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
         """
 
         result = self._row_wise_operation(
-            select, other, align, rename="intersects", returns_geom=False
+            "ST_Distance(`L`, `R`)", other, align, rename="distance", 
default_val="NULL"
         )
-        return to_bool(result)
+        return result
 
     def intersection(
         self, other: Union["GeoSeries", BaseGeometry], align: Union[bool, 
None] = None
@@ -1619,7 +2390,12 @@ class GeoSeries(GeoFrame, pspd.Series):
         GeoSeries.union
         """
         return self._row_wise_operation(
-            "ST_Intersection(`L`, `R`)", other, align, rename="intersection"
+            "ST_Intersection(`L`, `R`)",
+            other,
+            align,
+            rename="intersection",
+            returns_geom=True,
+            default_val="NULL",
         )
 
     def _row_wise_operation(
@@ -1628,11 +2404,15 @@ class GeoSeries(GeoFrame, pspd.Series):
         other: Any,
         align: Union[bool, None],
         rename: str,
-        returns_geom: bool = True,
+        returns_geom: bool = False,
+        default_val: Union[str, None] = None,
     ):
         """
         Helper function to perform a row-wise operation on two GeoSeries.
         The self column and other column are aliased to `L` and `R`, 
respectively.
+
+        default_val : str or None (default "FALSE")
+            The value to use if either L or R is null. If None, nulls are not 
handled.
         """
         from pyspark.sql.functions import col
 
@@ -1666,6 +2446,17 @@ class GeoSeries(GeoFrame, pspd.Series):
             col(index_col),
         )
         joined_df = df.join(other_df, on=index_col, how="outer")
+
+        if default_val is not None:
+            # ps.Series.fillna() doesn't always work for the output for some 
reason
+            # so we manually handle the nulls here.
+            select = f"""
+                CASE
+                    WHEN `L` IS NULL OR `R` IS NULL THEN {default_val}
+                    ELSE {select}
+                END
+            """
+
         return self._query_geometry_column(
             select,
             cols=["L", "R"],
@@ -1678,9 +2469,124 @@ class GeoSeries(GeoFrame, pspd.Series):
         # Implementation of the abstract method
         raise NotImplementedError("This method is not implemented yet.")
 
-    def contains(self, other, align=None):
-        # Implementation of the abstract method
-        raise NotImplementedError("This method is not implemented yet.")
+    def contains(self, other, align=None) -> pspd.Series:
+        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
+        each aligned geometry that contains `other`.
+
+        An object is said to contain `other` if at least one point of `other` 
lies in
+        the interior and no points of `other` lie in the exterior of the 
object.
+        (Therefore, any given polygon does not contain its own boundary - 
there is not
+        any point that lies in the interior.)
+        If either object is empty, this operation returns ``False``.
+
+        This is the inverse of `within` in the sense that the expression
+        ``a.contains(b) == b.within(a)`` always evaluates to ``True``.
+
+        Note: Sedona's implementation instead returns False for identical 
geometries.
+
+        The operation works on a 1-to-1 row-wise manner.
+
+        Parameters
+        ----------
+        other : GeoSeries or geometric object
+            The GeoSeries (elementwise) or geometric object to test if it
+            is contained.
+        align : bool | None (default None)
+            If True, automatically aligns GeoSeries based on their indices. 
None defaults to True.
+            If False, the order of elements is preserved.
+
+        Returns
+        -------
+        Series (bool)
+
+        Examples
+        --------
+
+        >>> from sedona.geopandas import GeoSeries
+        >>> from shapely.geometry import Polygon, LineString, Point
+        >>> s = 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 = 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 0, 1 1, 0 1, 0 0))
+        1             LINESTRING (0 0, 0 2)
+        2             LINESTRING (0 0, 0 1)
+        3                       POINT (0 1)
+        dtype: geometry
+
+        >>> s2
+        1    POLYGON ((0 0, 2 2, 0 2, 0 0))
+        2    POLYGON ((0 0, 1 2, 0 2, 0 0))
+        3             LINESTRING (0 0, 0 2)
+        4                       POINT (0 1)
+        dtype: geometry
+
+        We can check if each geometry of GeoSeries contains a single
+        geometry:
+
+        >>> 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``:
+
+        >>> 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.contains_properly
+        GeoSeries.within
+        """
+        result = self._row_wise_operation(
+            "ST_Contains(`L`, `R`)",
+            other,
+            align,
+            rename="contains",
+            default_val="FALSE",
+        )
+        return to_bool(result)
 
     def contains_properly(self, other, align=None):
         # Implementation of the abstract method
@@ -2398,16 +3304,21 @@ class GeoSeries(GeoFrame, pspd.Series):
             result = self._query_geometry_column(select, col, "")
         elif isinstance(value, (GeoSeries, GeometryArray, gpd.GeoSeries)):
 
-            if isinstance(value, (gpd.GeoSeries, GeometryArray)):
+            if not isinstance(value, GeoSeries):
                 value = GeoSeries(value)
 
-            # Replace all None's with empty geometries
+            # Replace all None's with empty geometries (this is a recursive 
call)
             value = value.fillna(None)
 
             # Coalesce: If the value in L is null, use the corresponding value 
in R for that row
             select = f"COALESCE(`L`, `R`)"
             result = self._row_wise_operation(
-                select, value, align=None, rename="fillna"
+                select,
+                value,
+                align=None,
+                rename="fillna",
+                returns_geom=True,
+                default_val=None,
             )
         else:
             raise ValueError(f"Invalid value type: {type(value)}")
diff --git a/python/tests/geopandas/test_geopandas_base.py 
b/python/tests/geopandas/test_geopandas_base.py
index c67af0dd5f..a772b273a6 100644
--- a/python/tests/geopandas/test_geopandas_base.py
+++ b/python/tests/geopandas/test_geopandas_base.py
@@ -23,6 +23,7 @@ import pandas as pd
 import pyspark.pandas as ps
 from pandas.testing import assert_series_equal
 from contextlib import contextmanager
+from shapely.geometry import GeometryCollection
 from shapely.geometry.base import BaseGeometry
 
 
@@ -98,6 +99,11 @@ class TestGeopandasBase(TestBase):
         finally:
             ps.reset_option("compute.ops_on_diff_frames")
 
+    def contains_any_geom_collection(self, geoms1, geoms2) -> bool:
+        return any(isinstance(g, GeometryCollection) for g in geoms1) or any(
+            isinstance(g, GeometryCollection) for g in geoms2
+        )
+
     @classmethod
     def check_geom_equals(cls, actual: BaseGeometry, expected: BaseGeometry):
         assert isinstance(actual, BaseGeometry)
diff --git a/python/tests/geopandas/test_geoseries.py 
b/python/tests/geopandas/test_geoseries.py
index 625d1b9ffa..48d1e11c11 100644
--- a/python/tests/geopandas/test_geoseries.py
+++ b/python/tests/geopandas/test_geoseries.py
@@ -793,6 +793,41 @@ class TestGeoSeries(TestGeopandasBase):
         expected = GeometryCollection()
         self.check_geom_equals(result, expected)
 
+    def test_crosses(self):
+        s = GeoSeries(
+            [
+                Polygon([(0, 0), (2, 2), (0, 2)]),
+                LineString([(0, 0), (2, 2)]),
+                LineString([(2, 0), (0, 2)]),
+                Point(0, 1),
+            ],
+        )
+        s2 = GeoSeries(
+            [
+                LineString([(1, 0), (1, 3)]),
+                LineString([(2, 0), (0, 2)]),
+                Point(1, 1),
+                Point(0, 1),
+            ],
+            index=range(1, 5),
+        )
+
+        line = LineString([(-1, 1), (3, 1)])
+        result = s.crosses(line)
+        expected = pd.Series([True, True, True, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.crosses(s2, align=True)
+        expected = pd.Series([False, True, False, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.crosses(s2, align=False)
+        expected = pd.Series([True, True, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+    def test_disjoint(self):
+        pass
+
     def test_intersects(self):
         s = sgpd.GeoSeries(
             [
@@ -837,6 +872,208 @@ class TestGeoSeries(TestGeopandasBase):
         result = s.intersects(s2, align=False)
         expected = pd.Series([True, True, True, True])
 
+    def test_overlaps(self):
+        s = 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 = 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),
+        )
+
+        polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
+        result = s.overlaps(polygon)
+        expected = pd.Series([True, True, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.overlaps(s2, align=True)
+        expected = pd.Series([False, True, False, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.overlaps(s2, align=False)
+        expected = pd.Series([True, False, True, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+    def test_touches(self):
+        s = 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 = 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),
+        )
+        line = LineString([(0, 0), (-1, -2)])
+        result = s.touches(line)
+        expected = pd.Series([True, True, True, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.touches(s2, align=True)
+        expected = pd.Series([False, True, True, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.touches(s2, align=False)
+        expected = pd.Series([True, False, True, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+    def test_within(self):
+        s = GeoSeries(
+            [
+                Polygon([(0, 0), (2, 2), (0, 2)]),
+                Polygon([(0, 0), (1, 2), (0, 2)]),
+                LineString([(0, 0), (0, 2)]),
+                Point(0, 1),
+            ],
+        )
+        s2 = 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),
+        )
+
+        polygon = Polygon([(0, 0), (2, 2), (0, 2)])
+        result = s.within(polygon)
+        expected = pd.Series([True, True, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s2.within(s, align=True)
+        expected = pd.Series([False, False, True, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s2.within(s, align=False)
+        expected = pd.Series([True, False, True, True], index=range(1, 5))
+        assert_series_equal(result.to_pandas(), expected)
+
+        # Ensure we return False if either geometries are empty
+        s = GeoSeries([Point(), Point(), Polygon(), Point(0, 1)])
+        result = s.within(s2, align=False)
+        expected = pd.Series([False, False, False, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+    def test_covers(self):
+        s = 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 = 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),
+        )
+
+        poly = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
+        result = s.covers(poly)
+        expected = pd.Series([True, False, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.covers(s2, align=True)
+        expected = pd.Series([False, False, False, False, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.covers(s2, align=False)
+        expected = pd.Series([True, False, True, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+        # Ensure we return False if either geometries are empty
+        s = GeoSeries([Point(), Point(), Polygon(), Point(0, 0)])
+        result = s.covers(s2, align=False)
+        expected = pd.Series([False, False, False, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+    def test_covered_by(self):
+        s = 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 = 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),
+        )
+
+        poly = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
+        result = s.covered_by(poly)
+        expected = pd.Series([True, True, True, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.covered_by(s2, align=True)
+        expected = pd.Series([False, True, True, True, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.covered_by(s2, align=False)
+        expected = pd.Series([True, False, True, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+    def test_distance(self):
+        s = GeoSeries(
+            [
+                Polygon([(0, 0), (1, 0), (1, 1)]),
+                Polygon([(0, 0), (-1, 0), (-1, 1)]),
+                LineString([(1, 1), (0, 0)]),
+                Point(0, 0),
+            ],
+        )
+        s2 = 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),
+        )
+        point = Point(-1, 0)
+        result = s.distance(point)
+        expected = pd.Series([1.0, 0.0, 1.0, 1.0])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.distance(s2, align=True)
+        expected = pd.Series([np.nan, 0.707107, 2.000000, 1.000000, np.nan])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s.distance(s2, align=False)
+        expected = pd.Series([0.000000, 3.162278, 0.707107, 1.000000])
+        assert_series_equal(result.to_pandas(), expected)
+
     def test_intersection(self):
         import pyspark.pandas as ps
 
@@ -946,7 +1183,37 @@ class TestGeoSeries(TestGeopandasBase):
         pass
 
     def test_contains(self):
-        pass
+        s = 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 = 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),
+        )
+
+        point = Point(0, 1)
+        result = s.contains(point)
+        expected = pd.Series([False, True, False, True])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s2.contains(s, align=True)
+        expected = pd.Series([False, False, False, True, False])
+        assert_series_equal(result.to_pandas(), expected)
+
+        result = s2.contains(s, align=False)
+        expected = pd.Series([True, False, True, True], index=range(1, 5))
+        assert_series_equal(result.to_pandas(), expected)
 
     def test_contains_properly(self):
         pass
diff --git a/python/tests/geopandas/test_match_geopandas_series.py 
b/python/tests/geopandas/test_match_geopandas_series.py
index 16bf86ad33..e71ec4ce9a 100644
--- a/python/tests/geopandas/test_match_geopandas_series.py
+++ b/python/tests/geopandas/test_match_geopandas_series.py
@@ -657,11 +657,36 @@ class TestMatchGeopandasSeries(TestGeopandasBase):
         gpd_result = gpd.GeoSeries([]).union_all()
         self.check_geom_equals(sgpd_result, gpd_result)
 
+    def test_crosses(self):
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                if self.contains_any_geom_collection(geom, geom2):
+                    continue
+
+                # We explicitly specify align=True to quite warnings in 
geopandas, despite it being the default
+                gpd_result = gpd.GeoSeries(geom).crosses(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                sgpd_result = GeoSeries(geom).crosses(GeoSeries(geom2), 
align=True)
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).crosses(GeoSeries(geom2), 
align=False)
+                    gpd_result = gpd.GeoSeries(geom).crosses(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
+    def test_disjoint(self):
+        pass
+
     def test_intersects(self):
         for _, geom in self.geoms:
             for _, geom2 in self.geoms:
-                sgpd_result = GeoSeries(geom).intersects(GeoSeries(geom2))
-                gpd_result = 
gpd.GeoSeries(geom).intersects(gpd.GeoSeries(geom2))
+                sgpd_result = GeoSeries(geom).intersects(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).intersects(
+                    gpd.GeoSeries(geom2), align=True
+                )
                 self.check_pd_series_equal(sgpd_result, gpd_result)
 
                 if len(geom) == len(geom2):
@@ -721,8 +746,149 @@ class TestMatchGeopandasSeries(TestGeopandasBase):
     def test_intersection_all(self):
         pass
 
+    def test_overlaps(self):
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                # Sedona's results differ from geopandas for these cases
+                if geom == geom2 or self.contains_any_geom_collection(geom, 
geom2):
+                    continue
+
+                sgpd_result = GeoSeries(geom).overlaps(GeoSeries(geom2, 
align=True))
+                gpd_result = gpd.GeoSeries(geom).overlaps(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).overlaps(
+                        GeoSeries(geom2), align=False
+                    )
+                    gpd_result = gpd.GeoSeries(geom).overlaps(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
+    def test_touches(self):
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                if self.contains_any_geom_collection(geom, geom2):
+                    continue
+                sgpd_result = GeoSeries(geom).touches(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).touches(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).touches(GeoSeries(geom2), 
align=False)
+                    gpd_result = gpd.GeoSeries(geom).touches(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
+    def test_within(self):
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                if geom == geom2 or self.contains_any_geom_collection(geom, 
geom2):
+                    continue
+
+                sgpd_result = GeoSeries(geom).within(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).within(
+                    gpd.GeoSeries(geom2), align=True
+                )
+
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).within(GeoSeries(geom2), 
align=False)
+                    gpd_result = gpd.GeoSeries(geom).within(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
+    def test_covers(self):
+        if parse_version(gpd.__version__) < parse_version("0.8.0"):
+            pytest.skip("geopandas < 0.8.0 does not support covered_by")
+
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                if geom == geom2 or self.contains_any_geom_collection(geom, 
geom2):
+                    continue
+
+                sgpd_result = GeoSeries(geom).covers(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).covers(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).covers(GeoSeries(geom2), 
align=False)
+                    gpd_result = gpd.GeoSeries(geom).covers(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
+    def test_covered_by(self):
+        if parse_version(shapely.__version__) < parse_version("2.0.0"):
+            pytest.skip("shapely < 2.0.0 does not support covered_by")
+
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                if geom == geom2 or self.contains_any_geom_collection(geom, 
geom2):
+                    continue
+
+                sgpd_result = GeoSeries(geom).covered_by(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).covered_by(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).covered_by(
+                        GeoSeries(geom2), align=False
+                    )
+                    gpd_result = gpd.GeoSeries(geom).covered_by(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
+    def test_distance(self):
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                sgpd_result = GeoSeries(geom).distance(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).distance(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).distance(
+                        GeoSeries(geom2), align=False
+                    )
+                    gpd_result = gpd.GeoSeries(geom).distance(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
+
     def test_contains(self):
-        pass
+        for _, geom in self.geoms:
+            for _, geom2 in self.geoms:
+                if geom == geom2 or self.contains_any_geom_collection(geom, 
geom2):
+                    continue
+                sgpd_result = GeoSeries(geom).contains(GeoSeries(geom2), 
align=True)
+                gpd_result = gpd.GeoSeries(geom).contains(
+                    gpd.GeoSeries(geom2), align=True
+                )
+                self.check_pd_series_equal(sgpd_result, gpd_result)
+
+                if len(geom) == len(geom2):
+                    sgpd_result = GeoSeries(geom).contains(
+                        GeoSeries(geom2), align=False
+                    )
+                    gpd_result = gpd.GeoSeries(geom).contains(
+                        gpd.GeoSeries(geom2), align=False
+                    )
+                    self.check_pd_series_equal(sgpd_result, gpd_result)
 
     def test_contains_properly(self):
         pass

Reply via email to