Repository: geopandas/geopandas

Issue:
API: disable automatic alignment in spatial predicates/operations ?
A common mistake I see people do is something like:

```
df_points.within(df_polys['geometry'])
```

where they do not expect the default "alignment" behaviour (to align both calling and passed dataframe on the index, and then to perform the operation element-wise). 

In the above example, I think that the users often are trying to achieve something different: a) they are trying to pass a single geometry (but which still needs to unpacked from a single element GeoSeries) or b) they expect that it checks that the point is in any of the polygons, or checks for each point whether in which of the polygons it is  (in which case they rather need for example a spatial join operation).

Personally I think that needing this alignment is probably a more rare use case (compared to passing either a single geometry or an already aligned series). If this is correct, we could disable (first deprecate) this behaviour as the default, and actually require an already aligned series (in case of passing a series). 

We can still provide it as an option (eg with an `align=True` keyword), or otherwise expect that the user does it themselves (eg `df_points.within(df_polys['geometry'].reindex(df_points.index))`)

What do people think?

BUG: covers and covered_by try to return a GeoSeries instead of Series
- [x] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the latest version of geopandas.

- [ ] (optional) I have confirmed this bug exists on the master branch of geopandas.

---

**Note**: Please read [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) detailing how to provide the necessary information for us to reproduce your bug.

#### Code Sample, a copy-pastable example

```python
import geopandas as gpd
from shapely.geometry import Polygon

s = gpd.GeoSeries(
    [
        Polygon([(0, 0), (1, 1), (0, 1)]),
        Polygon([(10, 0), (10, 5), (0, 0)]),
        Polygon([(0, 0), (2, 2), (2, 0)])
    ]
)
p = Polygon([(0, 0), (1, 1), (0, 1)])
s.covered_by(p)
```

#### Problem description

Gives a:
```
FutureWarning:     You are passing non-geometry data to the GeoSeries constructor. Currently,
    it falls back to returning a pandas Series. But in the future, we will start
    to raise a TypeError instead.
  return GeoSeries(geoms.data, index=index, crs=this.crs)
0     True
1    False
2    False
dtype: bool
```

Exactly the same happens when calling `s.covers(p)`. They should probably both call `_binary_op` instead of `_binary_geo` on return, just like the `touches` and `within` methods, for example.

#### Expected Output

The same but without warning.

#### Output of ``geopandas.show_versions()``

<details>

SYSTEM INFO
-----------
python     : 3.8.6 | packaged by conda-forge | (default, Oct  7 2020, 18:22:52) [MSC v.1916 64 bit (AMD64)]
executable : C:\anaconda3\envs\my_env\python.exe
machine    : Windows-10-10.0.17763-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.1.4
GDAL data dir: None
PROJ       : 7.1.1
PROJ data dir: C:\anaconda3\envs\my_env\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.8.1
pandas     : 1.1.4
fiona      : 1.8.17
numpy      : 1.19.4
shapely    : 1.7.1
rtree      : 0.9.4
pyproj     : 2.6.1.post1
matplotlib : 3.3.2
mapclassify: None
geopy      : None
psycopg2   : None
geoalchemy2: None
pyarrow    : None

</details>



Discussion:
Having made, and observed, this mistake, and never having encountered a case where the alignment was desired, I think this is a good idea.
@jdmcbr +1 I have never had use of this alignment feature. IMO if it is supported it shall at least contain a check so that both arrays / series are of the same length. 


@leohardtke  
> and maybe have another keyword method with two possible values all or any, to check if df_points is within ANY or ALL of the polygons of df_polys.

I think this can be easily achieve by calling  `.any()` or `.all()` on boolean series result, no need for another `method` argument. 


Resolving patch:
diff --git a/geopandas/base.py b/geopandas/base.py
--- 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 @@ def unary_union(self):
     # 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 @@ def contains(self, other):
         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.
+        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), (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)
+        return _binary_op("contains", self, other, align)
 
-    def geom_equals(self, other):
+    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.
+        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), (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)
+        return _binary_op("geom_equals", self, other, align)
 
-    def geom_almost_equals(self, other, decimal=6):
+    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,211 +969,1565 @@ def geom_almost_equals(self, other, decimal=6):
             The GeoSeries (elementwise) or geometric object to compare to.
         decimal : int
             Decimal place presion used when testing for approximate equality.
-        """
-        return _binary_op("geom_almost_equals", self, other, decimal=decimal)
+        align : bool (default True)
+            If True, automatically aligns GeoSeries based on their indices.
+            If False, the order of elements is preserved.
 
-    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)
+        Returns
+        -------
+        Series (bool)
 
-    def crosses(self, other):
-        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
-        each geometry that cross `other`.
+        Examples
+        --------
+        >>> from shapely.geometry import Point
+        >>> s = geopandas.GeoSeries(
+        ...     [
+        ...         Point(0, 1.1),
+        ...         Point(0, 1.01),
+        ...         Point(0, 1.001),
+        ...     ],
+        ... )
 
-        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.
+        >>> s
+        0    POINT (0.00000 1.10000)
+        1    POINT (0.00000 1.01000)
+        2    POINT (0.00000 1.00100)
+        dtype: geometry
 
-        Parameters
-        ----------
-        other : GeoSeries or geometric object
-            The GeoSeries (elementwise) or geometric object to test if is
-            crossed.
-        """
-        return _binary_op("crosses", self, other)
 
-    def disjoint(self, other):
-        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
-        each geometry disjoint to `other`.
+        >>> s.geom_almost_equals(Point(0, 1), decimal=2)
+        0    False
+        1    False
+        2     True
+        dtype: bool
 
-        An object is said to be disjoint to `other` if its `boundary` and
-        `interior` does not intersect at all with those of the other.
+        >>> 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
 
-        Parameters
-        ----------
-        other : GeoSeries or geometric object
-            The GeoSeries (elementwise) or geometric object to test if is
-            disjoint.
         """
-        return _binary_op("disjoint", self, other)
+        return _binary_op(
+            "geom_almost_equals", self, other, decimal=decimal, align=align
+        )
 
-    def intersects(self, other):
-        """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
-        each geometry that intersects `other`.
+    def geom_equals_exact(self, other, tolerance, align=True):
+        """Return True for all geometries that equal aligned *other* to a given
+        tolerance, else False.
 
-        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)
+            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.
 
-    def overlaps(self, other):
-        """Returns True for all geometries that overlap *other*, else False.
+        Returns
+        -------
+        Series (bool)
 
-        Parameters
-        ----------
-        other : GeoSeries or geometric object
-            The GeoSeries (elementwise) or geometric object to test if
-            overlaps.
+        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("overlaps", self, other)
+        return _binary_op(
+            "geom_equals_exact", self, other, tolerance=tolerance, align=align
+        )
 
-    def touches(self, other):
+    def crosses(self, other, align=True):
         """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
-        each geometry that touches `other`.
+        each aligned geometry that cross `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.
+        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
-            touched.
+            crossed.
+        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)]),
+        ...         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("touches", self, other)
+        return _binary_op("crosses", self, other, align)
 
-    def within(self, other):
+    def disjoint(self, other, align=True):
         """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
-        each geometry that is within `other`.
+        each aligned geometry disjoint to `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
-        `exterior`).
+        An object is said to be disjoint to `other` if its `boundary` and
+        `interior` does not intersect at all with those of the other.
 
-        This is the inverse of :meth:`contains` in the sense that the
-        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 is
+            disjoint.
+        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)]),
+        ...         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 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.
+        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)]),
+        ...         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.
+        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, 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 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.
+
+        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.
+        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, 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 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
+        `exterior`).
+
+        This is the inverse of :meth:`contains` in the sense that the
+        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.
+
+        Parameters
+        ----------
+        other : Geoseries or geometric object
+            The Geoseries (elementwise) or geometric object to check is being covered.
+        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, 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 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.
+
+        Parameters
+        ----------
+        other : Geoseries or geometric object
+            The Geoseries (elementwise) or geometric object to check is being covered.
+        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.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),
+        ... )
 
-        An object A is said to cover another object B if no points of B lie
-        in the exterior of A.
+        >>> 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
 
-        See
-        https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
-        for reference.
+        >>> 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
 
-        Parameters
-        ----------
-        other : Geoseries or geometric object
-            The Geoseries (elementwise) or geometric object to check is being covered.
-        """
-        return _binary_geo("covers", self, other)
+        We can check the distance of each geometry of GeoSeries to a single
+        geometry:
 
-    def covered_by(self, other):
-        """
-        Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
-        each geometry that is entirely covered by `other`.
+        .. image:: ../../../_static/binary_op-03.svg
+           :align: center
 
-        An object A is said to cover another object B if no points of B lie
-        in the exterior of A.
+        >>> point = Point(-1, 0)
+        >>> s.distance(point)
+        0    1.0
+        1    0.0
+        2    1.0
+        3    1.0
+        dtype: float64
 
-        See
-        https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html
-        for reference.
+        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``:
 
-        Parameters
-        ----------
-        other : Geoseries or geometric object
-            The Geoseries (elementwise) or geometric object to check is being covered.
-        """
-        return _binary_geo("covered_by", self, other)
+        .. image:: ../../../_static/binary_op-02.svg
 
-    def distance(self, other):
-        """Returns a ``Series`` containing the distance to `other`.
+        >>> s.distance(s2, align=True)
+        0         NaN
+        1    0.707107
+        2    2.000000
+        3    1.000000
+        4         NaN
+        dtype: float64
 
-        Parameters
-        ----------
-        other : Geoseries or geometric object
-            The Geoseries (elementwise) or geometric object to find the
-            distance to.
+        >>> 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.
+        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 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)
+        return _binary_geo("difference", self, other, align)
 
-    def symmetric_difference(self, other):
+    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.
+        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 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)
+        return _binary_geo("symmetric_difference", self, other, align)
 
-    def union(self, other):
-        """Returns a ``GeoSeries`` of the union of points in each geometry with
+    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.
+        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 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)
+        return _binary_geo("union", self, other, align)
 
-    def intersection(self, other):
+    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 @@ def simplify(self, *args, **kwargs):
         """
         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.
+
+        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)
+        return _binary_op("relate", self, other, align)
 
-    def project(self, other, normalized=False):
+    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 @@ def project(self, other, normalized=False):
         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):
         """


Test patch:
diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py
--- a/geopandas/tests/test_geom_methods.py
+++ b/geopandas/tests/test_geom_methods.py
@@ -75,6 +75,7 @@ def setup_method(self):
         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)])
@@ -235,15 +236,24 @@ def test_intersection(self):
                 "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(
@@ -254,6 +264,9 @@ def test_difference_series(self):
         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)
@@ -332,6 +345,12 @@ def test_contains(self):
         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)
@@ -350,10 +369,22 @@ def test_crosses(self):
         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(
             [
@@ -372,6 +403,36 @@ def test_relate(self):
         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
@@ -381,6 +442,13 @@ def test_distance(self):
         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)
@@ -401,6 +469,12 @@ def test_intersects(self):
         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))
@@ -408,10 +482,22 @@ def test_overlaps(self):
         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))
@@ -419,6 +505,12 @@ def test_within(self):
         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)
@@ -430,6 +522,12 @@ def test_covers(self):
         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])
@@ -444,6 +542,12 @@ def test_covered_by(self):
         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)
@@ -554,6 +658,13 @@ def test_project(self):
         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
--- a/geopandas/tests/test_geoseries.py
+++ b/geopandas/tests/test_geoseries.py
@@ -136,20 +136,38 @@ def test_geom_equals(self):
 
     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)