Repository: geopandas/geopandas

Issue:
BUG: pd.concat([..], axis=1) fails
This "works":

```
In [86]: geopandas.__version__
Out[86]: '0.5.1'

In [87]: countries = geopandas.read_file(geopandas.datasets.get_path("naturalearth_lowres"))

In [89]: cities = geopandas.read_file(geopandas.datasets.get_path("naturalearth_cities"))

In [90]: pd.concat([cities, countries], axis=1) 
Out[90]: 
             name                                      geometry      pop_est      continent                      name iso_a3  gdp_md_est                                           geometry
0    Vatican City   POINT (12.45338654497177 41.90328217996012)     920938.0        Oceania                      Fiji    FJI      8374.0  (POLYGON ((180 -16.06713266364245, 180 -16.555...
1      San Marino     POINT (12.44177015780014 43.936095834768)   53950935.0         Africa                  Tanzania    TZA    150600.0  POLYGON ((33.90371119710453 -0.950000000000000...
2           Vaduz   POINT (9.516669472907267 47.13372377429357)     603253.0         Africa                 W. Sahara    ESH       906.5  POLYGON ((-8.665589565454809 27.65642588959236...
3      Luxembourg   POINT (6.130002806227083 49.61166037912108)   35623680.0  North America                    Canada    CAN   1674000.0  (POLYGON ((-122.84 49.00000000000011, -122.974...
4         Palikir   POINT (158.1499743237623 6.916643696007725)  326625791.0  North America  United States of America    USA  18560000.0  (POLYGON ((-122.84 49.00000000000011, -120 49....
..            ...                                           ...          ...            ...                       ...    ...         ...                                                ...
[202 rows x 8 columns]
```

But once you do something spatial with the resulting GeoDataFrame (anything that accesses the "geometry column"), things breaks (due to there being two columns with the "geometry" name).

In 0.6.0 this already started failing when doing the concat:

```
In [10]: pd.concat([cities, pd.DataFrame(countries)], axis=1)  
---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
<ipython-input-10-e059be43b4e0> in <module>
----> 1 pd.concat([cities, pd.DataFrame(countries)], axis=1)

~/scipy/pandas/pandas/core/reshape/concat.py in concat(objs, axis, join, join_axes, ignore_index, keys, levels, names, verify_integrity, sort, copy)
    253     )
    254 
--> 255     return op.get_result()
    256 
    257 

~/scipy/pandas/pandas/core/reshape/concat.py in get_result(self)
    474 
    475             cons = self.objs[0]._constructor
--> 476             return cons._from_axes(new_data, self.new_axes).__finalize__(
    477                 self, method="concat"
    478             )

~/scipy/pandas/pandas/core/generic.py in _from_axes(cls, data, axes, **kwargs)
    407         # for construction from BlockManager
    408         if isinstance(data, BlockManager):
--> 409             return cls(data, **kwargs)
    410         else:
    411             if cls._AXIS_REVERSED:

~/scipy/geopandas/geopandas/geodataframe.py in __init__(self, *args, **kwargs)
     74             index = self.index
     75             try:
---> 76                 self["geometry"] = _ensure_geometry(self["geometry"].values)
     77             except TypeError:
     78                 pass

~/scipy/geopandas/geopandas/geodataframe.py in __getitem__(self, key)
    554         GeoDataFrame.
    555         """
--> 556         result = super(GeoDataFrame, self).__getitem__(key)
    557         geo_col = self._geometry_column_name
    558         if isinstance(key, str) and key == geo_col:

~/scipy/pandas/pandas/core/frame.py in __getitem__(self, key)
   2693             indexer = np.where(indexer)[0]
   2694 
-> 2695         data = self.take(indexer, axis=1)
   2696 
   2697         if is_single_key:

~/scipy/pandas/pandas/core/generic.py in take(self, indices, axis, is_copy, **kwargs)
   3432             indices, axis=self._get_block_manager_axis(axis), verify=True
   3433         )
-> 3434         result = self._constructor(new_data).__finalize__(self)
   3435 
   3436         # Maybe set copy if we didn't actually change the index.

... last 4 frames repeated, from the frame below ...

~/scipy/geopandas/geopandas/geodataframe.py in __init__(self, *args, **kwargs)
     74             index = self.index
     75             try:
---> 76                 self["geometry"] = _ensure_geometry(self["geometry"].values)
     77             except TypeError:
     78                 pass

RecursionError: maximum recursion depth exceeded
```
```

Discussion:
m-richards: I dont' know what happened to the CI here - I ran this on my fork before opening the PR in geopandas and it was fine there: https://github.com/m-richards/geopandas/pull/2
jorisvandenbossche: > I dont' know what happened to the CI here 

One of the test builds seemed to hang. Not sure why or whether it is reproducible (in any case doesn't seem related to this PR). Restarted the tests.
m-richards: @jorisvandenbossche thanks for all the comments, they all make sense, so I'll go through and fix them up

Patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
index bca0d2ba..e513ee68 100644
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -118,6 +118,15 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
         # allowed in that case
         # TODO do we want to raise / return normal DataFrame in this case?
         if geometry is None and "geometry" in self.columns:
+            # Check for multiple columns with name "geometry". If there are,
+            # self["geometry"] is a gdf and constructor gets recursively recalled
+            # by pandas internals trying to access this
+            if (self.columns == "geometry").sum() > 1:
+                raise ValueError(
+                    "GeoDataFrame does not support multiple columns "
+                    "using the geometry column name 'geometry'."
+                )
+
             # only if we have actual geometry values -> call set_geometry
             index = self.index
             try:
@@ -271,11 +280,17 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
             raise ValueError("Must pass array with one dimension only.")
         else:
             try:
-                level = frame[col].values
+                level = frame[col]
             except KeyError:
                 raise ValueError("Unknown column %s" % col)
             except Exception:
                 raise
+            if isinstance(level, DataFrame):
+                raise ValueError(
+                    "GeoDataFrame does not support setting the geometry column where "
+                    "the column name is shared by multiple columns."
+                )
+
             if drop:
                 to_remove = col
                 geo_column_name = self._geometry_column_name
@@ -1395,6 +1410,13 @@ box': (2.0, 1.0, 2.0, 1.0)}], 'bbox': (1.0, 1.0, 2.0, 2.0)}
             for name in self._metadata:
                 object.__setattr__(self, name, getattr(other.objs[0], name, None))
 
+            if (self.columns == self._geometry_column_name).sum() > 1:
+                raise ValueError(
+                    "Concat operation has resulted in multiple columns using "
+                    f"the geometry column name '{self._geometry_column_name}'.\n"
+                    f"Please ensure this column from the first DataFrame is not "
+                    f"repeated."
+                )
         return self
 
     def dissolve(


Test patch:
diff --git a/geopandas/tests/test_geodataframe.py b/geopandas/tests/test_geodataframe.py
index 420274c1..0b9e247a 100644
--- a/geopandas/tests/test_geodataframe.py
+++ b/geopandas/tests/test_geodataframe.py
@@ -1008,6 +1008,24 @@ class TestConstructor:
         res = GeoDataFrame(data, geometry=geoms)
         assert_geoseries_equal(res.geometry, GeoSeries(geoms))
 
+    def test_repeat_geo_col(self):
+        df = pd.DataFrame(
+            [
+                {"geometry": Point(x, y), "geom": Point(x, y)}
+                for x, y in zip(range(3), range(3))
+            ],
+        )
+        # explicitly prevent construction of gdf with repeat geometry column names
+        # two columns called "geometry", geom col inferred
+        df2 = df.rename(columns={"geom": "geometry"})
+        with pytest.raises(ValueError):
+            GeoDataFrame(df2)
+        # ensure case is caught when custom geom column name is used
+        # two columns called "geom", geom col explicit
+        df3 = df.rename(columns={"geometry": "geom"})
+        with pytest.raises(ValueError):
+            GeoDataFrame(df3, geometry="geom")
+
 
 def test_geodataframe_crs():
     gdf = GeoDataFrame()
diff --git a/geopandas/tests/test_merge.py b/geopandas/tests/test_merge.py
index d003b11e..a0495ab5 100644
--- a/geopandas/tests/test_merge.py
+++ b/geopandas/tests/test_merge.py
@@ -1,4 +1,6 @@
 import pandas as pd
+import pytest
+from geopandas.testing import assert_geodataframe_equal
 
 from shapely.geometry import Point
 
@@ -48,6 +50,13 @@ class TestMerging:
         assert isinstance(res, GeoDataFrame)
         assert isinstance(res.geometry, GeoSeries)
         self._check_metadata(res)
+        exp = GeoDataFrame(pd.concat([pd.DataFrame(self.gdf), pd.DataFrame(self.gdf)]))
+        assert_geodataframe_equal(exp, res)
+        # check metadata comes from first gdf
+        res4 = pd.concat([self.gdf.set_crs("epsg:4326"), self.gdf], axis=0)
+        # Note: this behaviour potentially does not make sense. If geom cols are
+        # concatenated but have different CRS, then the CRS will be overridden.
+        self._check_metadata(res4, crs="epsg:4326")
 
         # series
         res = pd.concat([self.gdf.geometry, self.gdf.geometry])
@@ -63,3 +72,28 @@ class TestMerging:
         assert isinstance(res, GeoDataFrame)
         assert isinstance(res.geometry, GeoSeries)
         self._check_metadata(res)
+
+    def test_concat_axis1_multiple_geodataframes(self):
+        # https://github.com/geopandas/geopandas/issues/1230
+        # Expect that concat should fail gracefully if duplicate column names belonging
+        # to geometry columns are introduced.
+        expected_err = (
+            "GeoDataFrame does not support multiple columns using the geometry"
+            " column name 'geometry'"
+        )
+        with pytest.raises(ValueError, match=expected_err):
+            pd.concat([self.gdf, self.gdf], axis=1)
+
+        # Check case is handled if custom geometry column name is used
+        df2 = self.gdf.rename_geometry("geom")
+        expected_err2 = (
+            "Concat operation has resulted in multiple columns using the geometry "
+            "column name 'geom'."
+        )
+        with pytest.raises(ValueError, match=expected_err2):
+            pd.concat([df2, df2], axis=1)
+
+        # Check that two geometry columns is fine, if they have different names
+        res3 = pd.concat([df2.set_crs("epsg:4326"), self.gdf], axis=1)
+        # check metadata comes from first df
+        self._check_metadata(res3, geometry_column_name="geom", crs="epsg:4326")