Skip to content

Commit

Permalink
Allow version specific NE
Browse files Browse the repository at this point in the history
  • Loading branch information
lgolston committed Mar 17, 2024
1 parent 01d7aff commit 1654f0a
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 13 deletions.
16 changes: 11 additions & 5 deletions lib/cartopy/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,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 @@ -246,6 +248,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
----------------
Expand All @@ -256,6 +260,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 @@ -281,11 +286,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 @@ -316,7 +322,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
43 changes: 37 additions & 6 deletions lib/cartopy/io/shapereader.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,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 @@ -298,10 +300,14 @@ 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)
ne_downloader = Downloader.from_config(('shapefiles', 'natural_earth',
resolution, category, name))
format_dict = {'config': config, 'category': category,
'name': name, 'resolution': resolution}
if version is None:
ne_downloader = Downloader.from_config(('shapefiles', 'natural_earth',
resolution, category, name))
else:
ne_downloader = Downloader.from_config(('shapefiles', 'natural_earth_versioned',
resolution, category, name))
format_dict = {'config': config, 'category': category, 'name': name,
'resolution': resolution, 'version': version}
return ne_downloader.path(format_dict)


Expand Down Expand Up @@ -394,13 +400,38 @@ def default_downloader():
return NEShpDownloader(target_path_template=ne_path_template,
pre_downloaded_path_template=pre_path_template)

@staticmethod
def versioned_downloader():
"""
Return a NEShpDownloader instance, which extends the default downloader
with support for specific file versions.
"""
_NE_URL_TEMPLATE = ('https://naturalearth.s3.amazonaws.com/'
'{version}/{resolution}_{category}/'
'ne_{resolution}_{name}.zip')

default_spec = ('shapefiles', 'natural_earth', '{category}', '{version}',
'ne_{resolution}_{name}.shp')
ne_path_template = str(
Path('{config[data_dir]}').joinpath(*default_spec))
pre_path_template = str(
Path('{config[pre_existing_data_dir]}').joinpath(*default_spec))
return NEShpDownloader(url_template=_NE_URL_TEMPLATE,
target_path_template=ne_path_template,
pre_downloaded_path_template=pre_path_template)


# add a generic Natural Earth shapefile downloader to the config dictionary's
# 'downloaders' section.
_ne_key = ('shapefiles', 'natural_earth')
config['downloaders'].setdefault(_ne_key,
NEShpDownloader.default_downloader())

_ne_key = ('shapefiles', 'natural_earth_versioned')
config['downloaders'].setdefault(_ne_key,
NEShpDownloader.versioned_downloader())


def gshhs(scale='c', level=1):
"""
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',
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')
self.reader = shp.Reader(RIVERS_PATH)
names = [record.attributes['name'] for record in self.reader.records()]
# Choose a nice small river
Expand Down

0 comments on commit 1654f0a

Please sign in to comment.