mirror of
https://github.com/wassname/geopandas.git
synced 2026-09-09 11:22:50 +08:00
REF: deprecate GeoDataFrame._crs (#2373)
Co-authored-by: Joris Van den Bossche <jorisvandenbossche@gmail.com>
This commit is contained in:
co-authored by
Joris Van den Bossche
parent
d1d8b5319a
commit
8bac66c94f
+57
-21
@@ -49,7 +49,9 @@ def _ensure_geometry(data, crs=None):
|
||||
if is_geometry_type(data):
|
||||
if isinstance(data, Series):
|
||||
data = GeoSeries(data)
|
||||
if data.crs is None:
|
||||
if data.crs is None and crs is not None:
|
||||
# Avoids caching issues/crs sharing issues
|
||||
data = data.copy()
|
||||
data.crs = crs
|
||||
return data
|
||||
else:
|
||||
@@ -121,6 +123,7 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
GeoSeries : Series object designed to store shapely geometry objects
|
||||
"""
|
||||
|
||||
# TODO: remove "_crs" in 0.12
|
||||
_metadata = ["_crs", "_geometry_column_name"]
|
||||
|
||||
_geometry_column_name = DEFAULT_GEO_COLUMN_NAME
|
||||
@@ -129,9 +132,8 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
with compat.ignore_shapely2_warnings():
|
||||
super().__init__(data, *args, **kwargs)
|
||||
|
||||
# need to set this before calling self['geometry'], because
|
||||
# getitem accesses crs
|
||||
self._crs = CRS.from_user_input(crs) if crs else None
|
||||
# TODO: to be removed in 0.12
|
||||
self._crs = None
|
||||
|
||||
# set_geometry ensures the geometry data have the proper dtype,
|
||||
# but is not called if `geometry=None` ('geometry' column present
|
||||
@@ -180,7 +182,7 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
):
|
||||
raise ValueError(crs_mismatch_error)
|
||||
|
||||
self.set_geometry(geometry, inplace=True)
|
||||
self.set_geometry(geometry, inplace=True, crs=crs)
|
||||
|
||||
if geometry is None and crs:
|
||||
raise ValueError(
|
||||
@@ -330,8 +332,7 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
del frame[to_remove]
|
||||
|
||||
if not crs:
|
||||
level_crs = getattr(level, "crs", None)
|
||||
crs = level_crs if level_crs is not None else self._crs
|
||||
crs = getattr(level, "crs", None)
|
||||
|
||||
if isinstance(level, (GeoSeries, GeometryArray)) and level.crs != crs:
|
||||
# Avoids caching issues/crs sharing issues
|
||||
@@ -342,7 +343,9 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
level = _ensure_geometry(level, crs=crs)
|
||||
frame[geo_column_name] = level
|
||||
frame._geometry_column_name = geo_column_name
|
||||
frame.crs = crs
|
||||
|
||||
# TODO: to be removed in 0.12
|
||||
frame._crs = level.crs
|
||||
if not inplace:
|
||||
return frame
|
||||
|
||||
@@ -424,7 +427,19 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
GeoDataFrame.to_crs : re-project to another CRS
|
||||
|
||||
"""
|
||||
return self._crs
|
||||
# TODO: remove try/except in 0.12
|
||||
try:
|
||||
return self.geometry.crs
|
||||
except AttributeError:
|
||||
# the active geometry column might not be set
|
||||
warnings.warn(
|
||||
"Accessing CRS of a GeoDataFrame without a geometry column is "
|
||||
"deprecated and will be removed in GeoPandas 0.12. "
|
||||
"Use GeoDataFrame.set_geometry to set the active geometry column.",
|
||||
FutureWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._crs
|
||||
|
||||
@crs.setter
|
||||
def crs(self, value):
|
||||
@@ -438,20 +453,27 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
else:
|
||||
if hasattr(self.geometry.values, "crs"):
|
||||
self.geometry.values.crs = value
|
||||
self._crs = self.geometry.values.crs
|
||||
else:
|
||||
# column called 'geometry' without geometry
|
||||
self._crs = None if not value else CRS.from_user_input(value)
|
||||
|
||||
# TODO: raise this error in 0.12. This already raises a FutureWarning
|
||||
# TODO: defined in the crs property above
|
||||
# raise ValueError(
|
||||
# "Assigning CRS to a GeoDataFrame without an active geometry "
|
||||
# "column is not supported. Use GeoDataFrame.set_geometry to set "
|
||||
# "the active geometry column.",
|
||||
# )
|
||||
|
||||
def __setstate__(self, state):
|
||||
# overriding DataFrame method for compat with older pickles (CRS handling)
|
||||
crs = None
|
||||
if isinstance(state, dict):
|
||||
if "_metadata" in state and "crs" in state["_metadata"]:
|
||||
metadata = state["_metadata"]
|
||||
metadata[metadata.index("crs")] = "_crs"
|
||||
if "crs" in state and "_crs" not in state:
|
||||
crs = state.pop("crs")
|
||||
state["_crs"] = CRS.from_user_input(crs) if crs is not None else crs
|
||||
crs = state.pop("crs", None)
|
||||
else:
|
||||
crs = state.pop("_crs", None)
|
||||
crs = CRS.from_user_input(crs) if crs is not None else crs
|
||||
|
||||
super().__setstate__(state)
|
||||
|
||||
@@ -459,9 +481,9 @@ class GeoDataFrame(GeoPandasBase, DataFrame):
|
||||
# at GeoDataFrame level with '_crs' (and not 'crs'), so without propagating
|
||||
# to the GeoSeries/GeometryArray
|
||||
try:
|
||||
if self.crs is not None:
|
||||
if crs is not None:
|
||||
if self.geometry.values.crs is None:
|
||||
self.crs = self.crs
|
||||
self.crs = crs
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -1316,7 +1338,6 @@ individually so that features may have different properties
|
||||
df = self.copy()
|
||||
geom = df.geometry.to_crs(crs=crs, epsg=epsg)
|
||||
df.geometry = geom
|
||||
df.crs = geom.crs
|
||||
if not inplace:
|
||||
return df
|
||||
|
||||
@@ -1377,7 +1398,6 @@ individually so that features may have different properties
|
||||
result._geometry_column_name = geo_col
|
||||
else:
|
||||
result._geometry_column_name = None
|
||||
result._crs = None
|
||||
else:
|
||||
result.__class__ = DataFrame
|
||||
return result
|
||||
@@ -1391,8 +1411,24 @@ individually so that features may have different properties
|
||||
if pd.api.types.is_scalar(value) or isinstance(value, BaseGeometry):
|
||||
value = [value] * self.shape[0]
|
||||
try:
|
||||
value = _ensure_geometry(value, crs=self.crs)
|
||||
self._crs = value.crs
|
||||
# TODO: remove this use of _crs in 0.12
|
||||
warn = False
|
||||
if not (hasattr(self, "geometry") and hasattr(self.geometry, "crs")):
|
||||
crs = self._crs
|
||||
warn = True
|
||||
else:
|
||||
crs = getattr(self, "crs", None)
|
||||
value = _ensure_geometry(value, crs=crs)
|
||||
if warn and crs is not None:
|
||||
warnings.warn(
|
||||
"Setting geometries to a GeoDataFrame without a geometry "
|
||||
"column will currently preserve the CRS, if present. "
|
||||
"This is deprecated, and in the future the CRS will be lost "
|
||||
"in this case. You can use set_crs(..) on the result to "
|
||||
"set the CRS manually.",
|
||||
FutureWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
except TypeError:
|
||||
warnings.warn("Geometry column does not contain geometry.")
|
||||
super().__setitem__(key, value)
|
||||
|
||||
Binary file not shown.
@@ -209,7 +209,6 @@ class TestGeometryArrayCRS:
|
||||
s = GeoSeries(arr, crs=4326)
|
||||
assert s.crs == self.osgb
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Assigning CRS")
|
||||
def test_dataframe(self):
|
||||
arr = from_shapely(self.geoms, crs=27700)
|
||||
df = GeoDataFrame(geometry=arr)
|
||||
@@ -298,8 +297,23 @@ class TestGeometryArrayCRS:
|
||||
|
||||
# geometry column without geometry
|
||||
df = GeoDataFrame({"geometry": [0, 1]})
|
||||
df.crs = 27700
|
||||
assert df.crs == self.osgb
|
||||
with pytest.warns(
|
||||
FutureWarning, match="Accessing CRS of a GeoDataFrame without a geometry"
|
||||
):
|
||||
df.crs = 27700
|
||||
with pytest.warns(
|
||||
FutureWarning, match="Accessing CRS of a GeoDataFrame without a geometry"
|
||||
):
|
||||
assert df.crs == self.osgb
|
||||
|
||||
def test_dataframe_getitem_without_geometry_column(self):
|
||||
df = GeoDataFrame({"col": range(10)}, geometry=self.arr)
|
||||
df["geom2"] = df.geometry.centroid
|
||||
subset = df[["col", "geom2"]]
|
||||
with pytest.warns(
|
||||
FutureWarning, match="Accessing CRS of a GeoDataFrame without a geometry"
|
||||
):
|
||||
assert subset.crs == self.osgb
|
||||
|
||||
def test_dataframe_setitem(self):
|
||||
# new geometry CRS has priority over GDF CRS
|
||||
@@ -337,6 +351,22 @@ class TestGeometryArrayCRS:
|
||||
assert df["geometry"].crs == self.wgs
|
||||
assert df["other_geom"].crs == self.osgb
|
||||
|
||||
def test_dataframe_setitem_without_geometry_column(self):
|
||||
arr = from_shapely(self.geoms)
|
||||
df = GeoDataFrame({"col1": [1, 2], "geometry": arr}, crs=4326)
|
||||
|
||||
# create a dataframe without geometry column, but currently has cached _crs
|
||||
with pytest.warns(UserWarning):
|
||||
df["geometry"] = 1
|
||||
|
||||
# assigning a list of geometry object will currently use _crs
|
||||
with pytest.warns(
|
||||
FutureWarning,
|
||||
match="Setting geometries to a GeoDataFrame without a geometry",
|
||||
):
|
||||
df["geometry"] = self.geoms
|
||||
assert df.crs == self.wgs
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"scalar", [None, Point(0, 0), LineString([(0, 0), (1, 1)])]
|
||||
)
|
||||
@@ -348,6 +378,7 @@ class TestGeometryArrayCRS:
|
||||
assert df.geometry.crs == self.wgs
|
||||
assert df.geometry.values.crs == self.wgs
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Accessing CRS")
|
||||
def test_crs_with_no_geom_fails(self):
|
||||
with pytest.raises(ValueError, match="Assigning CRS to a GeoDataFrame without"):
|
||||
df = GeoDataFrame()
|
||||
|
||||
@@ -105,6 +105,7 @@ class TestMerging:
|
||||
reason="pandas <=1.0 hard codes concat([GeoSeries, GeoSeries]) -> "
|
||||
"DataFrame or Union[DataFrame, SparseDataFrame] in 0.25",
|
||||
)
|
||||
@pytest.mark.filterwarnings("ignore:Accessing CRS")
|
||||
def test_concat_axis1_geoseries(self):
|
||||
gseries2 = GeoSeries([Point(i, i) for i in range(3, 6)], crs="epsg:4326")
|
||||
result = pd.concat([gseries2, self.gseries], axis=1)
|
||||
|
||||
Reference in New Issue
Block a user