Repository: geopandas/geopandas

Issue:
BUG: duplicate column names causes wrong geojson output
- [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 gp

gdf = gp.GeoDataFrame(
    {"a": [1], "b": [2], "c": [3]},
    geometry=gp.points_from_xy(x=[1], y=[1])
)
gdf = gdf[["a", "b", "a", "geometry"]]  # Select a column twice

print("\nLooks good!")
print(gdf)

gdf.to_file("./geo.geojson", driver="GeoJSON")

print(f"\nLooks bad!")
with open("./geo.geojson") as f:
    print(f.read())

```

Output:
```text
Looks good!
   a  b  a                 geometry
0  1  2  1  POINT (1.00000 1.00000)

Looks bad!
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "properties": { "a": 1, "b": 1 }, "geometry": { "type": "Point", "coordinates": [ 1.0, 1.0 ] } }
]
}
```

#### Problem description

The saved geojson file has the wrong attribute data when the source geodataframe has duplicated column names.
Should be ``{ "a": 1, "b": 2 }`` (or perhaps it should fail to save?).
What you get is the incorrect result ``{ "a": 1, "b": 1 }``

#### Output of ``geopandas.show_versions()``

<details>

SYSTEM INFO
-----------
python     : 3.9.2 (default, Mar  3 2021, 11:58:52)  [Clang 10.0.0 ]
executable : /opt/miniconda3/envs/xxx/bin/python
machine    : macOS-10.15.7-x86_64-i386-64bit
GEOS, GDAL, PROJ INFO
---------------------
GEOS       : 3.9.1
GEOS lib   : /opt/miniconda3/envs/xxx/lib/libgeos_c.dylib
GDAL       : 3.2.2
GDAL data dir: /opt/miniconda3/envs/xxx/share/gdal
PROJ       : 8.0.0
PROJ data dir: /opt/miniconda3/envs/xxx/lib/python3.9/site-packages/pyproj/proj_dir/share/proj
PYTHON DEPENDENCIES
-------------------
geopandas  : 0.9.0
pandas     : 1.2.3
fiona      : 1.8.18
numpy      : 1.20.1
shapely    : 1.7.1
rtree      : 0.9.7
pyproj     : 3.0.1
matplotlib : 3.3.4
mapclassify: None
geopy      : None
psycopg2   : None
geoalchemy2: None
pyarrow    : None
pygeos     : None
</details>



Discussion:
Thanks for the report! It is indeed a bug on our side, affecting both `to_file` and `to_json`, because it happens in `__geo_interface__`, most likely starting on this line https://github.com/geopandas/geopandas/blob/2a5c42f8fcf7093caf5e59c012ed05817b68569f/geopandas/geodataframe.py#L813

Since JSON keys [should be unique](https://tools.ietf.org/html/rfc8259#section-4) I am not sure what the correct output should be. I would probably add a check for duplicated column names and raise an error in this case. Because what would be the output for the following df?

```
   a  b  a                 geometry
0  1  2  3   POINT (1.00000 1.00000)
```

Let's raise an error (unless someone has a better idea).
Yeah I would say an error is the way go. good stuff
@martinfleis Should this also raise an error for `to_file`?
@mesejo If we raise an error in `iterfeatures` it will be automatically raised in `to_file`. `iterfeatures` is used there.

Resolving patch:
diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py
--- a/geopandas/geodataframe.py
+++ b/geopandas/geodataframe.py
@@ -810,6 +810,9 @@ def iterfeatures(self, na="null", show_bbox=False, drop_id=False):
         ids = np.array(self.index, copy=False)
         geometries = np.array(self[self._geometry_column_name], copy=False)
 
+        if not self.columns.is_unique:
+            raise ValueError("GeoDataFrame cannot contain duplicated column names.")
+
         properties_cols = self.columns.difference([self._geometry_column_name])
 
         if len(properties_cols) > 0:


Test patch:
diff --git a/geopandas/tests/test_geodataframe.py b/geopandas/tests/test_geodataframe.py
--- a/geopandas/tests/test_geodataframe.py
+++ b/geopandas/tests/test_geodataframe.py
@@ -457,6 +457,15 @@ def test_to_json_drop_id_only_geom_column(self):
         for f in data["features"]:
             assert "id" not in f.keys()
 
+    def test_to_json_with_duplicate_columns(self):
+        df = GeoDataFrame(
+            data=[[1, 2, 3]], columns=["a", "b", "a"], geometry=[Point(1, 1)]
+        )
+        with pytest.raises(
+            ValueError, match="GeoDataFrame cannot contain duplicated column names."
+        ):
+            df.to_json()
+
     def test_copy(self):
         df2 = self.df.copy()
         assert type(df2) is GeoDataFrame
@@ -485,6 +494,16 @@ def test_to_file_crs(self):
         df = GeoDataFrame.from_file(tempfilename)
         assert df.crs == "epsg:2263"
 
+    def test_to_file_with_duplicate_columns(self):
+        df = GeoDataFrame(
+            data=[[1, 2, 3]], columns=["a", "b", "a"], geometry=[Point(1, 1)]
+        )
+        with pytest.raises(
+            ValueError, match="GeoDataFrame cannot contain duplicated column names."
+        ):
+            tempfilename = os.path.join(self.tempdir, "crs.shp")
+            df.to_file(tempfilename)
+
     def test_bool_index(self):
         # Find boros with 'B' in their name
         df = self.df[self.df["BoroName"].str.contains("B")]
@@ -666,6 +685,14 @@ def test_geodataframe_iterfeatures(self):
         result = list(df_only_numerical_cols.iterfeatures(na="keep"))[0]
         assert type(result["properties"]["Shape_Leng"]) is float
 
+        with pytest.raises(
+            ValueError, match="GeoDataFrame cannot contain duplicated column names."
+        ):
+            df_with_duplicate_columns = df[
+                ["Shape_Leng", "Shape_Leng", "Shape_Area", "geometry"]
+            ]
+            list(df_with_duplicate_columns.iterfeatures())
+
         # geometry not set
         df = GeoDataFrame({"values": [0, 1], "geom": [Point(0, 1), Point(1, 0)]})
         with pytest.raises(AttributeError):