diff --git a/CHANGELOG.md b/CHANGELOG.md index b1af58c..611c067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,8 @@ Deprecations and compatibility notes: Bug fixes: - Accessing `gdf.geometry` where the active geometry column is missing, and a column named `"geometry"` is present will now raise an `AttributeError`, rather than returning `gdf["geometry"]` (#2575) - +- Combining GeoSeries/GeoDataFrames with ``pandas.concat`` will no longer silently + override CRS information if not all inputs have the same CRS (#2056). Version 0.11.1 (July 24, 2022) ------------------------------ diff --git a/geopandas/array.py b/geopandas/array.py index c206556..ee56019 100644 --- a/geopandas/array.py +++ b/geopandas/array.py @@ -1317,7 +1317,7 @@ class GeometryArray(ExtensionArray): ExtensionArray """ data = np.concatenate([ga.data for ga in to_concat]) - return GeometryArray(data, crs=to_concat[0].crs) + return GeometryArray(data, crs=_get_common_crs(to_concat)) def _reduce(self, name, skipna=True, **kwargs): # including the base class version here (that raises by default) @@ -1388,3 +1388,26 @@ class GeometryArray(ExtensionArray): else: return False return (self == item).any() + + +def _get_common_crs(arr_seq): + + crs_set = {arr.crs for arr in arr_seq} + crs_not_none = [crs for crs in crs_set if crs is not None] + names = [crs.name for crs in crs_not_none] + + if len(crs_not_none) == 0: + return None + if len(crs_not_none) == 1: + if len(crs_set) != 1: + warnings.warn( + "CRS not set for some of the concatenation inputs. " + f"Setting output's CRS as {names[0]} " + "(the single non-null crs provided)." + ) + return crs_not_none[0] + + raise ValueError( + f"Cannot determine common CRS for concatenation inputs, got {names}. " + "Use `to_crs()` to transform geometries to the same CRS before merging." + ) diff --git a/geopandas/tests/test_merge.py b/geopandas/tests/test_merge.py index 5794b6c..3f65346 100644 --- a/geopandas/tests/test_merge.py +++ b/geopandas/tests/test_merge.py @@ -54,11 +54,6 @@ class TestMerging: 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]) @@ -66,6 +61,48 @@ class TestMerging: assert isinstance(res, GeoSeries) assert isinstance(res.geometry, GeoSeries) + def test_concat_axis0_crs(self): + + # CRS not set for both GeoDataFrame + res = pd.concat([self.gdf, self.gdf]) + self._check_metadata(res) + + # CRS set for both GeoDataFrame, same CRS + res1 = pd.concat([self.gdf.set_crs("epsg:4326"), self.gdf.set_crs("epsg:4326")]) + self._check_metadata(res1, crs="epsg:4326") + + # CRS not set for one GeoDataFrame, but set for the other GeoDataFrame + with pytest.warns( + UserWarning, match=r"CRS not set for some of the concatenation inputs.*" + ): + res2 = pd.concat([self.gdf, self.gdf.set_crs("epsg:4326")]) + self._check_metadata(res2, crs="epsg:4326") + + # CRS set for both GeoDataFrame, different CRS + with pytest.raises( + ValueError, match=r"Cannot determine common CRS for concatenation inputs.*" + ): + pd.concat([self.gdf.set_crs("epsg:4326"), self.gdf.set_crs("epsg:4327")]) + + # CRS not set for one GeoDataFrame, but set for the other GeoDataFrames, + # same CRS + with pytest.warns( + UserWarning, match=r"CRS not set for some of the concatenation inputs.*" + ): + res3 = pd.concat( + [self.gdf, self.gdf.set_crs("epsg:4326"), self.gdf.set_crs("epsg:4326")] + ) + self._check_metadata(res3, crs="epsg:4326") + + # CRS not set for one GeoDataFrame, but set for the other GeoDataFrames, + # different CRS + with pytest.raises( + ValueError, match=r"Cannot determine common CRS for concatenation inputs.*" + ): + pd.concat( + [self.gdf, self.gdf.set_crs("epsg:4326"), self.gdf.set_crs("epsg:4327")] + ) + def test_concat_axis1(self): res = pd.concat([self.gdf, self.df], axis=1)