From d3e8f7e8353a6e45b1d6bd35f922cd1380163214 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:30:20 -0500 Subject: [PATCH] Allow version specific NE --- lib/cartopy/feature/__init__.py | 16 +++++++++++----- lib/cartopy/io/shapereader.py | 17 ++++++++++------- lib/cartopy/tests/io/test_downloaders.py | 3 ++- lib/cartopy/tests/mpl/test_features.py | 2 +- lib/cartopy/tests/test_shapereader.py | 3 ++- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/cartopy/feature/__init__.py b/lib/cartopy/feature/__init__.py index 9804f5c7b..d039a6d81 100644 --- a/lib/cartopy/feature/__init__.py +++ b/lib/cartopy/feature/__init__.py @@ -235,11 +235,13 @@ class NaturalEarthFeature(Feature): """ A simple interface to Natural Earth shapefiles. - See https://www.naturalearthdata.com/ + See https://www.naturalearthdata.com/ for an overview of the data + and https://github.com/nvkelso/natural-earth-vector/releases for recent + version information. """ - def __init__(self, category, name, scale, **kwargs): + def __init__(self, category, name, scale, version=None, **kwargs): """ Parameters ---------- @@ -251,6 +253,8 @@ def __init__(self, category, name, scale, **kwargs): The dataset scale, i.e. one of '10m', '50m', or '110m', or Scaler object. Dataset scales correspond to 1:10,000,000, 1:50,000,000, and 1:110,000,000 respectively. + version: optional + The specific dataset version to use, e.g. '5.1.0'. Other Parameters ---------------- @@ -261,6 +265,7 @@ def __init__(self, category, name, scale, **kwargs): super().__init__(cartopy.crs.PlateCarree(), **kwargs) self.category = category self.name = name + self.version = version # Cast the given scale to a (constant) Scaler if a string is passed. if isinstance(scale, str): @@ -286,11 +291,12 @@ def geometries(self): Returns an iterator of (shapely) geometries for this feature. """ - key = (self.name, self.category, self.scale) + key = (self.name, self.category, self.scale, self.version) if key not in _NATURAL_EARTH_GEOM_CACHE: path = shapereader.natural_earth(resolution=self.scale, category=self.category, - name=self.name) + name=self.name, + version=self.version) geometries = tuple(shapereader.Reader(path).geometries()) _NATURAL_EARTH_GEOM_CACHE[key] = geometries else: @@ -321,7 +327,7 @@ def with_scale(self, new_scale): """ return NaturalEarthFeature(self.category, self.name, new_scale, - **self.kwargs) + self.version, **self.kwargs) class GSHHSFeature(Feature): diff --git a/lib/cartopy/io/shapereader.py b/lib/cartopy/io/shapereader.py index 344465b26..e3f665cd0 100644 --- a/lib/cartopy/io/shapereader.py +++ b/lib/cartopy/io/shapereader.py @@ -275,10 +275,12 @@ def records(self): """ -def natural_earth(resolution='110m', category='physical', name='coastline'): +def natural_earth(resolution='110m', category='physical', + name='coastline', version=None): """ Return the path to the requested natural earth shapefile, - downloading and unzipping if necessary. + downloading and unzipping if necessary. If version is not specified, + the latest available version will be downloaded. To identify valid components for this function, either browse NaturalEarthData.com, or if you know what you are looking for, go to @@ -299,10 +301,11 @@ def natural_earth(resolution='110m', category='physical', name='coastline'): # get hold of the Downloader (typically a NEShpDownloader instance) # which we can then simply call its path method to get the appropriate # shapefile (it will download if necessary) + _version_string = "" if version is None else f"{version}/" ne_downloader = Downloader.from_config(('shapefiles', 'natural_earth', - resolution, category, name)) - format_dict = {'config': config, 'category': category, - 'name': name, 'resolution': resolution} + resolution, category, name)) + format_dict = {'config': config, 'category': category, 'name': name, + 'resolution': resolution, 'version': _version_string} return ne_downloader.path(format_dict) @@ -321,7 +324,7 @@ class NEShpDownloader(Downloader): # Define the NaturalEarth URL template. Shapefiles are hosted on AWS since # 2021: https://github.com/nvkelso/natural-earth-vector/issues/445 _NE_URL_TEMPLATE = ('https://naturalearth.s3.amazonaws.com/' - '{resolution}_{category}/ne_{resolution}_{name}.zip') + '{version}{resolution}_{category}/ne_{resolution}_{name}.zip') def __init__(self, url_template=_NE_URL_TEMPLATE, @@ -386,7 +389,7 @@ def default_downloader(): ne_{resolution}_{name}.shp """ - default_spec = ('shapefiles', 'natural_earth', '{category}', + default_spec = ('shapefiles', 'natural_earth', '{category}', '{version}', 'ne_{resolution}_{name}.shp') ne_path_template = str( Path('{config[data_dir]}').joinpath(*default_spec)) diff --git a/lib/cartopy/tests/io/test_downloaders.py b/lib/cartopy/tests/io/test_downloaders.py index 475f066d1..03f155afe 100644 --- a/lib/cartopy/tests/io/test_downloaders.py +++ b/lib/cartopy/tests/io/test_downloaders.py @@ -139,7 +139,8 @@ def test_natural_earth_downloader(tmp_path): # isn't important - it is the download mechanism that is. format_dict = {'category': 'physical', 'name': 'rivers_lake_centerlines', - 'resolution': '110m'} + 'resolution': '110m', + 'version': ''} dnld_item = NEShpDownloader(target_path_template=shp_path_template) diff --git a/lib/cartopy/tests/mpl/test_features.py b/lib/cartopy/tests/mpl/test_features.py index 8f34ac403..4c4608201 100644 --- a/lib/cartopy/tests/mpl/test_features.py +++ b/lib/cartopy/tests/mpl/test_features.py @@ -37,7 +37,7 @@ def test_natural_earth(): @pytest.mark.mpl_image_compare(filename='natural_earth_custom.png') def test_natural_earth_custom(): ax = plt.axes(projection=ccrs.PlateCarree()) - feature = cfeature.NaturalEarthFeature('physical', 'coastline', '50m', + feature = cfeature.NaturalEarthFeature('physical', 'coastline', '50m', '5.1.0', edgecolor='black', facecolor='none') ax.add_feature(feature) diff --git a/lib/cartopy/tests/test_shapereader.py b/lib/cartopy/tests/test_shapereader.py index 66c4e4d6e..0c6590ffb 100644 --- a/lib/cartopy/tests/test_shapereader.py +++ b/lib/cartopy/tests/test_shapereader.py @@ -83,7 +83,8 @@ class TestRivers: def setup_class(self): RIVERS_PATH = shp.natural_earth(resolution='110m', category='physical', - name='rivers_lake_centerlines') + name='rivers_lake_centerlines', + version='5.0.0') self.reader = shp.Reader(RIVERS_PATH) names = [record.attributes['name'] for record in self.reader.records()] # Choose a nice small river