Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Allow Natural Earth version to be specified #2351

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions lib/cartopy/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also update the above cases to follow numpydoc formatting by adding : str to all of them.

Suggested change
version: optional
version : str, optional

The specific dataset version to use, e.g. '5.1.0'.

Other Parameters
----------------
Expand All @@ -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):
Expand All @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
17 changes: 10 additions & 7 deletions lib/cartopy/io/shapereader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be private if it is just used within the method.

Suggested change
_version_string = "" if version is None else f"{version}/"
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)


Expand All @@ -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,
Expand Down Expand Up @@ -386,7 +389,7 @@ def default_downloader():
ne_{resolution}_{name}.shp

"""
default_spec = ('shapefiles', 'natural_earth', '{category}',
default_spec = ('shapefiles', 'natural_earth', '{category}', '{version}',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the doctest failure is related to the docstring right above this. Where it got a /{version}/ added in now. I actually don't think that should be included there now because it is path joining the version, but we don't want that if version is None, we want to leave off the /. I guess the question is does this need to be udpated since it is the default case (no version) or can we just leave it alone for now?

'ne_{resolution}_{name}.shp')
ne_path_template = str(
Path('{config[data_dir]}').joinpath(*default_spec))
Expand Down
3 changes: 2 additions & 1 deletion lib/cartopy/tests/io/test_downloaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion lib/cartopy/tests/mpl/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you find that version 5.1.0 successfully passed this test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Yes, no diff between the baseline image here of Iceland and when using 5.1.0

edgecolor='black',
facecolor='none')
ax.add_feature(feature)
Expand Down
3 changes: 2 additions & 1 deletion lib/cartopy/tests/test_shapereader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, did this specific version (5.0.0) of rivers pass the image comparison tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was passing. Note there is no image comparison here, the test just looks at a few very specific features and there was no change.

self.reader = shp.Reader(RIVERS_PATH)
names = [record.attributes['name'] for record in self.reader.records()]
# Choose a nice small river
Expand Down
Loading