From 3342177802c57c62791fde8cf39746e7d09cc7c4 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:50:10 -0500 Subject: [PATCH] Fix deepcopy bug in CRS --- .github/workflows/ci-testing.yml | 2 +- INSTALL | 2 +- environment.yml | 4 ++-- lib/cartopy/_epsg.py | 3 +++ lib/cartopy/crs.py | 18 ++++++++++++------ lib/cartopy/tests/test_crs.py | 10 +++++----- pyproject.toml | 2 +- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 0dd968c6c..b645913f5 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -33,7 +33,7 @@ jobs: if: matrix.python-version == '3.9' && matrix.os == 'ubuntu-latest' id: minimum-packages run: | - pip install cython==0.29.24 matplotlib==3.5.3 numpy==1.21 owslib==0.24.1 pyproj==3.1 scipy==1.6.3 shapely==1.7.1 pyshp==2.3.1 + pip install cython==0.29.24 matplotlib==3.5.3 numpy==1.21 owslib==0.24.1 pyproj==3.3.1 scipy==1.6.3 shapely==1.7.1 pyshp==2.3.1 - name: Coverage packages id: coverage diff --git a/INSTALL b/INSTALL index f19033e84..698192154 100644 --- a/INSTALL +++ b/INSTALL @@ -72,7 +72,7 @@ Further information about the required dependencies can be found here: **pyshp** 2.3 or later (https://pypi.python.org/pypi/pyshp) Pure Python read/write support for ESRI Shapefile format. -**pyproj** 3.1.0 or later (https://github.com/pyproj4/pyproj/) +**pyproj** 3.3.1 or later (https://github.com/pyproj4/pyproj/) Python interface to PROJ (cartographic projections and coordinate transformations library). Optional Dependencies diff --git a/environment.yml b/environment.yml index 48e394cd3..851a80502 100644 --- a/environment.yml +++ b/environment.yml @@ -12,9 +12,9 @@ dependencies: - numpy>=1.21 - shapely>=1.7.1 - pyshp>=2.3 - - pyproj>=3.1.0 + - pyproj>=3.3.1 # The testing label has the proper version of freetype included - - conda-forge/label/testing::matplotlib-base>=3.4 + - conda-forge/label/testing::matplotlib-base>=3.5 # OWS - owslib>=0.24.1 diff --git a/lib/cartopy/_epsg.py b/lib/cartopy/_epsg.py index 481af089b..d966ca7ce 100644 --- a/lib/cartopy/_epsg.py +++ b/lib/cartopy/_epsg.py @@ -24,3 +24,6 @@ def __init__(self, code): def __repr__(self): return f'_EPSGProjection({self.epsg_code})' + + def __reduce__(self): + return self.__class__, (self.epsg_code, ) diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py index 2c707bf74..6ff9f63fb 100644 --- a/lib/cartopy/crs.py +++ b/lib/cartopy/crs.py @@ -147,6 +147,8 @@ def __init__(self, proj4_params, globe=None): See :class:`~cartopy.crs.Globe` for details. """ + self.input = (proj4_params, globe) + # for compatibility with pyproj.CRS and rasterio.crs.CRS try: proj4_params = proj4_params.to_wkt() @@ -209,13 +211,17 @@ def __hash__(self): def __reduce__(self): """ - Implement the __reduce__ API so that unpickling produces a stateless - instance of this class (e.g. an empty tuple). The state will then be - added via __getstate__ and __setstate__. - We are forced to this approach because a CRS does not store - the constructor keyword arguments in its state. + Implement the __reduce__ method used when pickling or performing deepcopy. """ - return self.__class__, (), self.__getstate__() + if type(self) is CRS: + # State can be reproduced by the proj4_params and globe inputs. + return self.__class__, self.input + else: + # Produces a stateless instance of this class (e.g. an empty tuple). + # The state will then be added via __getstate__ and __setstate__. + # We are forced to this approach because a CRS does not store + # the constructor keyword arguments in its state. + return self.__class__, (), self.__getstate__() def __getstate__(self): """Return the full state of this instance for reconstruction diff --git a/lib/cartopy/tests/test_crs.py b/lib/cartopy/tests/test_crs.py index de25374f7..41f4ff1a3 100644 --- a/lib/cartopy/tests/test_crs.py +++ b/lib/cartopy/tests/test_crs.py @@ -254,11 +254,11 @@ def test_utm(self): @pytest.fixture(params=[ [ccrs.PlateCarree, {}], - [ccrs.PlateCarree, dict( - central_longitude=1.23)], - [ccrs.NorthPolarStereo, dict( - central_longitude=42.5, - globe=ccrs.Globe(ellipse="helmert"))], + [ccrs.PlateCarree, dict(central_longitude=1.23)], + [ccrs.NorthPolarStereo, dict(central_longitude=42.5, + globe=ccrs.Globe(ellipse="helmert"))], + [ccrs.CRS, dict(proj4_params="3088")], + [ccrs.epsg, dict(code="3088")] ]) def proj_to_copy(request): cls, kwargs = request.param diff --git a/pyproject.toml b/pyproject.toml index 3385443a0..5b974a242 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ dependencies = [ "shapely>=1.7", "packaging>=20", "pyshp>=2.3", - "pyproj>=3.1.0", + "pyproj>=3.3.1", ] dynamic = ["version"]