Repository: geopandas/geopandas

Issue:
Set operators are inconsistently handled
Since merging #68 to allow geometric operations on `GeoDataFrame` objects, set operators work with a dataframe as the second item in an operation, but not the first.

For example, 

```
a = GeoSeries(...)
b = GeoDataFrame(...)

a.intersection(b)  # Okay
b.intersection(a)  # Okay
a & b  # Okay
b & a # Error
```



Discussion:

Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -747,6 +747,26 @@ def astype(self, dtype, copy=True, errors="raise", **kwargs):
         # do not return a GeoDataFrame
         return pd.DataFrame(df)
 
+        #
+        # Implement standard operators for GeoSeries
+        #
+
+    def __xor__(self, other):
+        """Implement ^ operator as for builtin set type"""
+        return self.geometry.symmetric_difference(other)
+
+    def __or__(self, other):
+        """Implement | operator as for builtin set type"""
+        return self.geometry.union(other)
+
+    def __and__(self, other):
+        """Implement & operator as for builtin set type"""
+        return self.geometry.intersection(other)
+
+    def __sub__(self, other):
+        """Implement - operator as for builtin set type"""
+        return self.geometry.difference(other)
+
 
 def _dataframe_set_geometry(self, col, drop=False, inplace=False, crs=None):
     if inplace:


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
@@ -85,6 +85,9 @@ def setup_method(self):
         self.gdf2 = GeoDataFrame(
             {"geometry": self.g1, "col3": [4, 5], "col4": ["rand", "string"]}
         )
+        self.gdf3 = GeoDataFrame(
+            {"geometry": self.g3, "col3": [4, 5], "col4": ["rand", "string"]}
+        )
 
     def _test_unary_real(self, op, expected, a):
         """ Tests for 'area', 'length', 'is_valid', etc. """
@@ -125,6 +128,7 @@ def _test_binary_operator(self, op, expected, a, b):
         """
         The operators only have GeoSeries on the left, but can have
         GeoSeries or GeoDataFrame on the right.
+        If GeoDataFrame is on the left, geometry column is used.
 
         """
         if isinstance(expected, GeoPandasBase):
@@ -657,25 +661,29 @@ def test_explode_geodataframe(self, index_name):
 
     #
     # Test '&', '|', '^', and '-'
-    # The left can only be a GeoSeries. The right hand side can be a
-    # GeoSeries, GeoDataFrame or Shapely geometry
     #
     def test_intersection_operator(self):
         self._test_binary_operator("__and__", self.t1, self.g1, self.g2)
+        self._test_binary_operator("__and__", self.t1, self.gdf1, self.g2)
 
     def test_union_operator(self):
         self._test_binary_operator("__or__", self.sq, self.g1, self.g2)
+        self._test_binary_operator("__or__", self.sq, self.gdf1, self.g2)
 
     def test_union_operator_polygon(self):
         self._test_binary_operator("__or__", self.sq, self.g1, self.t2)
+        self._test_binary_operator("__or__", self.sq, self.gdf1, self.t2)
 
     def test_symmetric_difference_operator(self):
         self._test_binary_operator("__xor__", self.sq, self.g3, self.g4)
+        self._test_binary_operator("__xor__", self.sq, self.gdf3, self.g4)
 
     def test_difference_series2(self):
         expected = GeoSeries([GeometryCollection(), self.t2])
         self._test_binary_operator("__sub__", expected, self.g1, self.g2)
+        self._test_binary_operator("__sub__", expected, self.gdf1, self.g2)
 
     def test_difference_poly2(self):
         expected = GeoSeries([self.t1, self.t1])
         self._test_binary_operator("__sub__", expected, self.g1, self.t2)
+        self._test_binary_operator("__sub__", expected, self.gdf1, self.t2)