Repository: SciTools/cartopy

Issue:
404 error downloading ne_h_coastline.zip
I have recently been getting "HTTPError: HTTP Error 404: Not Found" errors when trying to use Cartopy. Even when I comment out the add features lines I am still getting this issue, so maybe it is caused by some of the other packages. I have clicked on the link and you do get the 404 message, however I am not sure where in my code I am still getting this issue if I comment out the add coastlines.

OS: CentOS 7

Some of the main packages in the environment:
Cartopy: 0.17.0          py36h0aa2c8f_1004    conda-forge
Python: 3.6.7             h381d211_1004    conda-forge
Satpy: 0.13.0             pyh326bf55_0    conda-forge
arm_pyart : 1.9.2                    py36_0    conda-forge
metpy : 0.10.0                py36_1001    conda-forge

 Conda info:
     active environment : sats
    active env location : /export/home/mbrewer/anaconda3/envs/sats
            shell level : 2
       user config file : /export/home/mbrewer/.condarc
 populated config files : /export/home/mbrewer/.condarc
          conda version : 4.6.11
    conda-build version : 3.17.6
         python version : 3.7.1.final.0
       base environment : /export/home/mbrewer/anaconda3  (writable)
           channel URLs : https://conda.anaconda.org/conda-forge/linux-64
                          https://conda.anaconda.org/conda-forge/noarch
                          https://repo.anaconda.com/pkgs/main/linux-64
                          https://repo.anaconda.com/pkgs/main/noarch
                          https://repo.anaconda.com/pkgs/free/linux-64
                          https://repo.anaconda.com/pkgs/free/noarch
                          https://repo.anaconda.com/pkgs/r/linux-64
                          https://repo.anaconda.com/pkgs/r/noarch
          package cache : /export/home/mbrewer/anaconda3/pkgs
                          /export/home/mbrewer/.conda/pkgs
       envs directories : /export/home/mbrewer/anaconda3/envs
                          /export/home/mbrewer/.conda/envs
               platform : linux-64
             user-agent : conda/4.6.11 requests/2.21.0 CPython/3.7.1 Linux/3.10.0-957.5.1.el7.x86_64 centos/7.6.1810 glibc/2.17
                UID:GID : 789585339:1341200513
             netrc file : None
           offline mode : False
<!--
If you are reporting a bug, attach the *entire* traceback from Python.

If you are proposing an enhancement/new feature, provide links to related articles, reference examples, etc.

If you are asking a question, please ask on StackOverflow and use the cartopy tag. All cartopy
questions on StackOverflow can be found at https://stackoverflow.com/questions/tagged/cartopy
-->

#### Code to reproduce

```
from satpy import Scene
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from metpy.plots import USCOUNTIES
from satpy import find_files_and_readers
from glob import glob
import matplotlib.pyplot as plt
import numpy as np
from satpy.writers import cf_writer
from satpy.writers import get_enhanced_image
from metpy.plots import USCOUNTIES # Make sure metpy is updated to latest version.
import xarray as xr
from pathlib import Path
import pyart

el = Path("/export/home/mbrewer/Documents/GMTED2010_15n030_0125deg.nc")
rad = Path("/export/home/mbrewer/Documents/radar_files/KBBX20181108_213133_V06")

radar = pyart.io.read_nexrad_archive(rad)
#gf = pyart.filters.GateFilter(radar)
#gf.exclude_transition()
#gf.exclude_above('reflectivity', 100) #Mask out dBZ above 100
#gf.exclude_below('reflectivity', 5) #Mask out dBZ below 5
#despec = pyart.correct.despeckle_field(radar, 'reflectivity',gatefilter = gf, size = 20) #The despeckling mask routine that takes out small noisey reflectivity bits not near the main plume

elev = xr.open_dataset(el)
scn = Scene(
        filenames=glob("npp/*"),
        reader='viirs_l1b')
scn.load(['true_color', 'I02'])
new_scn = scn.resample('northamerica')
var = get_enhanced_image(new_scn['true_color']).data
var = var.transpose('y', 'x', 'bands')


st = str(scn.attrs['sensor'])[2:-2]


fig = plt.figure(figsize=(20, 10), dpi=200)
crs = new_scn['true_color'].attrs['area'].to_cartopy_crs()
ax = fig.add_subplot(1, 1, 1, projection=crs)

ax.imshow(var.data, extent=(var.x[0], var.x[-1], var.y[-1], var.y[0]), origin='upper')
ax.add_feature(cfeature.COASTLINE.with_scale('10m'), edgecolor='orange')
ax.add_feature(cfeature.STATES.with_scale('10m'), edgecolor='orange')
ax.add_feature(USCOUNTIES.with_scale('500k'), edgecolor='orange', alpha = .75)
ax.set_extent([-122.5, -120.5, 39., 40.5], crs=ccrs.PlateCarree())
display = pyart.graph.RadarMapDisplayCartopy(radar)    
display.plot_ppi_map('reflectivity', 0,  resolution = 'h', #The "0" is the lowest PPI scan, increasing this number increases the scanning elevation
             vmin=-10, vmax=64, colorbar_flag = False, fig = fig, ax=ax,alpha = .08, projection = crs)#, gatefilter = gf)
display.plot_colorbar(ax =ax)

plt.title('Satellite: %s'%(st), loc='left', fontweight='bold', fontsize = 18)
plt.title('Radar: KBBX', loc='center', fontsize = 15)
plt.title('Satellite Time:\n%s\nRadar Time:\n%s' % (scn.attrs['start_time'].strftime("%Y-%m-%d %H:%M:%S"),radar.time['units'][14:24] + ' ' + radar.time['units'][25:]), loc='right', fontsize = 12)
```

#### Traceback 

```
export/home/mbrewer/anaconda3/envs/sats/lib/python3.6/site-packages/dask/core.py:119: RuntimeWarning: invalid value encountered in log
  return func(*args2)
/export/home/mbrewer/anaconda3/envs/sats/lib/python3.6/site-packages/dask/core.py:119: RuntimeWarning: invalid value encountered in less
  return func(*args2)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

Text(1.0, 1.0, 'Satellite Time:\n2018-11-08 21:30:00\nRadar Time:\n2018-11-08 21:31:33Z')

/export/home/mbrewer/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/__init__.py:260: DownloadWarning: Downloading: http://naciscdn.org/naturalearth/h/physical/ne_h_coastline.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)

Error in callback <function install_repl_displayhook.<locals>.post_execute at 0x7fac46e28488> (for post_execute):

---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/pyplot.py in post_execute()
    107             def post_execute():
    108                 if matplotlib.is_interactive():
--> 109                     draw_all()
    110 
    111             # IPython >= 2

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/_pylab_helpers.py in draw_all(cls, force)
    130         for f_mgr in cls.get_all_fig_managers():
    131             if force or f_mgr.canvas.figure.stale:
--> 132                 f_mgr.canvas.draw_idle()
    133 
    134 atexit.register(Gcf.destroy_all)

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/backend_bases.py in draw_idle(self, *args, **kwargs)
   1897         if not self._is_idle_drawing:
   1898             with self._idle_draw_cntx():
-> 1899                 self.draw(*args, **kwargs)
   1900 
   1901     def draw_cursor(self, event):

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py in draw(self)
    400         toolbar = self.toolbar
    401         try:
--> 402             self.figure.draw(self.renderer)
    403             # A GUI class may be need to update a window using this draw, so
    404             # don't forget to call the superclass.

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/figure.py in draw(self, renderer)
   1647 
   1648             mimage._draw_list_compositing_images(
-> 1649                 renderer, self, artists, self.suppressComposite)
   1650 
   1651             renderer.close_group('figure')

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    136     if not_composite or not has_images:
    137         for a in artists:
--> 138             a.draw(renderer)
    139     else:
    140         # Composite any adjacent images together

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/mpl/geoaxes.py in draw(self, renderer, inframe)
    386 
    387         return matplotlib.axes.Axes.draw(self, renderer=renderer,
--> 388                                          inframe=inframe)
    389 
    390     def __str__(self):

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/axes/_base.py in draw(self, renderer, inframe)
   2626             renderer.stop_rasterizing()
   2627 
-> 2628         mimage._draw_list_compositing_images(renderer, self, artists)
   2629 
   2630         renderer.close_group('axes')

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    136     if not_composite or not has_images:
    137         for a in artists:
--> 138             a.draw(renderer)
    139     else:
    140         # Composite any adjacent images together

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/mpl/feature_artist.py in draw(self, renderer, *args, **kwargs)
    162         except ValueError:
    163             warnings.warn('Unable to determine extent. Defaulting to global.')
--> 164         geoms = self._feature.intersecting_geometries(extent)
    165 
    166         # Combine all the keyword args in priority order.

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/feature/__init__.py in intersecting_geometries(self, extent)
    301         """
    302         self.scaler.scale_from_extent(extent)
--> 303         return super(NaturalEarthFeature, self).intersecting_geometries(extent)
    304 
    305     def with_scale(self, new_scale):

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/feature/__init__.py in intersecting_geometries(self, extent)
    118             extent_geom = sgeom.box(extent[0], extent[2],
    119                                     extent[1], extent[3])
--> 120             return (geom for geom in self.geometries() if
    121                     geom is not None and extent_geom.intersects(geom))
    122         else:

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/feature/__init__.py in geometries(self)
    285             path = shapereader.natural_earth(resolution=self.scale,
    286                                              category=self.category,
--> 287                                              name=self.name)
    288             geometries = tuple(shapereader.Reader(path).geometries())
    289             _NATURAL_EARTH_GEOM_CACHE[key] = geometries

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/shapereader.py in natural_earth(resolution, category, name)
    357     format_dict = {'config': config, 'category': category,
    358                    'name': name, 'resolution': resolution}
--> 359     return ne_downloader.path(format_dict)
    360 
    361 

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/__init__.py in path(self, format_dict)
    220         else:
    221             # we need to download the file
--> 222             result_path = self.acquire_resource(target_path, format_dict)
    223 
    224         return result_path

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/shapereader.py in acquire_resource(self, target_path, format_dict)
    412         url = self.url(format_dict)
    413 
--> 414         shapefile_online = self._urlopen(url)
    415 
    416         zfh = ZipFile(six.BytesIO(shapefile_online.read()), 'r')

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/__init__.py in _urlopen(self, url)
    259         """
    260         warnings.warn('Downloading: {}'.format(url), DownloadWarning)
--> 261         return urlopen(url)
    262 
    263     @staticmethod

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    221     else:
    222         opener = _opener
--> 223     return opener.open(url, data, timeout)
    224 
    225 def install_opener(opener):

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in open(self, fullurl, data, timeout)
    530         for processor in self.process_response.get(protocol, []):
    531             meth = getattr(processor, meth_name)
--> 532             response = meth(req, response)
    533 
    534         return response

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in http_response(self, request, response)
    640         if not (200 <= code < 300):
    641             response = self.parent.error(
--> 642                 'http', request, response, code, msg, hdrs)
    643 
    644         return response

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in error(self, proto, *args)
    568         if http_err:
    569             args = (dict, 'default', 'http_error_default') + orig_args
--> 570             return self._call_chain(*args)
    571 
    572 # XXX probably also want an abstract factory that knows when it makes

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in _call_chain(self, chain, kind, meth_name, *args)
    502         for handler in handlers:
    503             func = getattr(handler, meth_name)
--> 504             result = func(*args)
    505             if result is not None:
    506                 return result

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in http_error_default(self, req, fp, code, msg, hdrs)
    648 class HTTPDefaultErrorHandler(BaseHandler):
    649     def http_error_default(self, req, fp, code, msg, hdrs):
--> 650         raise HTTPError(req.full_url, code, msg, hdrs, fp)
    651 
    652 class HTTPRedirectHandler(BaseHandler):

HTTPError: HTTP Error 404: Not Found

---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
~/anaconda3/envs/sats/lib/python3.6/site-packages/IPython/core/formatters.py in __call__(self, obj)
    339                 pass
    340             else:
--> 341                 return printer(obj)
    342             # Finally look for special method names
    343             method = get_real_method(obj, self.print_method)

~/anaconda3/envs/sats/lib/python3.6/site-packages/IPython/core/pylabtools.py in <lambda>(fig)
    242 
    243     if 'png' in formats:
--> 244         png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
    245     if 'retina' in formats or 'png2x' in formats:
    246         png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))

~/anaconda3/envs/sats/lib/python3.6/site-packages/IPython/core/pylabtools.py in print_figure(fig, fmt, bbox_inches, **kwargs)
    126 
    127     bytes_io = BytesIO()
--> 128     fig.canvas.print_figure(bytes_io, **kw)
    129     data = bytes_io.getvalue()
    130     if fmt == 'svg':

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, **kwargs)
   2047                         orientation=orientation,
   2048                         dryrun=True,
-> 2049                         **kwargs)
   2050                     renderer = self.figure._cachedRenderer
   2051                     bbox_artists = kwargs.pop("bbox_extra_artists", None)

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py in print_png(self, filename_or_obj, *args, **kwargs)
    508 
    509         """
--> 510         FigureCanvasAgg.draw(self)
    511         renderer = self.get_renderer()
    512 

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py in draw(self)
    400         toolbar = self.toolbar
    401         try:
--> 402             self.figure.draw(self.renderer)
    403             # A GUI class may be need to update a window using this draw, so
    404             # don't forget to call the superclass.

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/figure.py in draw(self, renderer)
   1647 
   1648             mimage._draw_list_compositing_images(
-> 1649                 renderer, self, artists, self.suppressComposite)
   1650 
   1651             renderer.close_group('figure')

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    136     if not_composite or not has_images:
    137         for a in artists:
--> 138             a.draw(renderer)
    139     else:
    140         # Composite any adjacent images together

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/mpl/geoaxes.py in draw(self, renderer, inframe)
    386 
    387         return matplotlib.axes.Axes.draw(self, renderer=renderer,
--> 388                                          inframe=inframe)
    389 
    390     def __str__(self):

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/axes/_base.py in draw(self, renderer, inframe)
   2626             renderer.stop_rasterizing()
   2627 
-> 2628         mimage._draw_list_compositing_images(renderer, self, artists)
   2629 
   2630         renderer.close_group('axes')

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    136     if not_composite or not has_images:
    137         for a in artists:
--> 138             a.draw(renderer)
    139     else:
    140         # Composite any adjacent images together

~/anaconda3/envs/sats/lib/python3.6/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer, *args, **kwargs)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/mpl/feature_artist.py in draw(self, renderer, *args, **kwargs)
    162         except ValueError:
    163             warnings.warn('Unable to determine extent. Defaulting to global.')
--> 164         geoms = self._feature.intersecting_geometries(extent)
    165 
    166         # Combine all the keyword args in priority order.

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/feature/__init__.py in intersecting_geometries(self, extent)
    301         """
    302         self.scaler.scale_from_extent(extent)
--> 303         return super(NaturalEarthFeature, self).intersecting_geometries(extent)
    304 
    305     def with_scale(self, new_scale):

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/feature/__init__.py in intersecting_geometries(self, extent)
    118             extent_geom = sgeom.box(extent[0], extent[2],
    119                                     extent[1], extent[3])
--> 120             return (geom for geom in self.geometries() if
    121                     geom is not None and extent_geom.intersects(geom))
    122         else:

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/feature/__init__.py in geometries(self)
    285             path = shapereader.natural_earth(resolution=self.scale,
    286                                              category=self.category,
--> 287                                              name=self.name)
    288             geometries = tuple(shapereader.Reader(path).geometries())
    289             _NATURAL_EARTH_GEOM_CACHE[key] = geometries

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/shapereader.py in natural_earth(resolution, category, name)
    357     format_dict = {'config': config, 'category': category,
    358                    'name': name, 'resolution': resolution}
--> 359     return ne_downloader.path(format_dict)
    360 
    361 

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/__init__.py in path(self, format_dict)
    220         else:
    221             # we need to download the file
--> 222             result_path = self.acquire_resource(target_path, format_dict)
    223 
    224         return result_path

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/shapereader.py in acquire_resource(self, target_path, format_dict)
    412         url = self.url(format_dict)
    413 
--> 414         shapefile_online = self._urlopen(url)
    415 
    416         zfh = ZipFile(six.BytesIO(shapefile_online.read()), 'r')

~/anaconda3/envs/sats/lib/python3.6/site-packages/cartopy/io/__init__.py in _urlopen(self, url)
    259         """
    260         warnings.warn('Downloading: {}'.format(url), DownloadWarning)
--> 261         return urlopen(url)
    262 
    263     @staticmethod

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    221     else:
    222         opener = _opener
--> 223     return opener.open(url, data, timeout)
    224 
    225 def install_opener(opener):

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in open(self, fullurl, data, timeout)
    530         for processor in self.process_response.get(protocol, []):
    531             meth = getattr(processor, meth_name)
--> 532             response = meth(req, response)
    533 
    534         return response

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in http_response(self, request, response)
    640         if not (200 <= code < 300):
    641             response = self.parent.error(
--> 642                 'http', request, response, code, msg, hdrs)
    643 
    644         return response

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in error(self, proto, *args)
    568         if http_err:
    569             args = (dict, 'default', 'http_error_default') + orig_args
--> 570             return self._call_chain(*args)
    571 
    572 # XXX probably also want an abstract factory that knows when it makes

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in _call_chain(self, chain, kind, meth_name, *args)
    502         for handler in handlers:
    503             func = getattr(handler, meth_name)
--> 504             result = func(*args)
    505             if result is not None:
    506                 return result

~/anaconda3/envs/sats/lib/python3.6/urllib/request.py in http_error_default(self, req, fp, code, msg, hdrs)
    648 class HTTPDefaultErrorHandler(BaseHandler):
    649     def http_error_default(self, req, fp, code, msg, hdrs):
--> 650         raise HTTPError(req.full_url, code, msg, hdrs, fp)
    651 
    652 class HTTPRedirectHandler(BaseHandler):

HTTPError: HTTP Error 404: Not Found


```

<details>
 <summary>Full environment definition</summary>

<!-- fill in the following information as appropriate -->

### Operating system


### Cartopy version


### conda list

```
```

### pip list

```
```

</details>



Discussion:
So the error occurs because it's trying to download this link, which doesn't work:

http://naciscdn.org/naturalearth/h/physical/ne_h_coastline.zip

The question is...why?

Aha! I think it's this line:
```python
display.plot_ppi_map('reflectivity', 0,  resolution = 'h', #The "0" is the lowest PPI scan, increasing this number increases the scanning elevation
             vmin=-10, vmax=64, colorbar_flag = False, fig = fig, ax=ax,alpha = .08, projection = crs)
```
Notice the use of `'h'` for `resolution`--which corresponds to the bad part of the URL. I'm guess this is left over from some Basemap code? Try changing to `'10m'` or `'110m'`.
Changing the 'h' to '10m' fixed the issue, should I open an issue with pyart? Thank you for the help though.
![image](https://user-images.githubusercontent.com/29383396/55761109-1681e200-5a13-11e9-96e2-0e1e55740bf6.png)


It'd probably be nice of them to try to check and issue a more useful error, but that's up to them. Wouldn't hurt to open an issue there to let them know.

I wonder if we should be whitelisting the resolutions here, at least in `NaturalEarthFeature` class, since we know the currently supported resolutions.
So, the question is where to check:
* `NaturalEarthFeature`
* `shapereader.natural_earth`
* `NEShpDownloader`

Thoughts anyone? @pelson?
Unfortunately something seems to be wrong at NaturalEarth: `https://naciscdn.org/naturalearth/110m/physical/ne_110m_coastline.zip` gives a 404 as well.  
Just coming to write that same thing. It appears that naciscdn.org is offline right now: https://downforeveryoneorjustme.com/naciscdn.org
Is there a mirror where one can grab these images?
I couldn't find one.  I've just switched to GSHHS:
`ax.add_feature(cartopy.feature.GSHHSFeature(edgecolor='k'))`

You can grab it from [their GitHub](https://github.com/nvkelso/natural-earth-vector), though unfortunately due to https://github.com/nvkelso/natural-earth-vector/issues/279 it's not entirely consistent with what's on the site. On Fedora, you can also install one of the [natural-earth-map-data](https://apps.fedoraproject.org/packages/natural-earth-map-data) packages.
It appears that naciscdn.org has come back up, which should resolve this issue.
Back down for me...

EDIT:
> You can grab it from [their GitHub](https://github.com/nvkelso/natural-earth-vector), though unfortunately due to [nvkelso/natural-earth-vector#279](https://github.com/nvkelso/natural-earth-vector/issues/279) it's not entirely consistent with what's on the site. On Fedora, you can also install one of the [natural-earth-map-data](https://apps.fedoraproject.org/packages/natural-earth-map-data) packages.

 Is there a way to download the data from GitHub from within Cartopy?

Is there an action item for Cartopy here? Better error/failure messages?

Otherwise, there isn't a whole lot we can do when external websites go down.
External websites aside, the actual bug here is that we need to be more stringent about what's accepted for scale (because `h` is not valid for NaturalEarth). That's why this is still open.

Resolving patch:
diff --git a/lib/cartopy/feature/__init__.py b/lib/cartopy/feature/__init__.py
--- a/lib/cartopy/feature/__init__.py
+++ b/lib/cartopy/feature/__init__.py
@@ -260,11 +260,20 @@ def __init__(self, category, name, scale, **kwargs):
             scale = Scaler(scale)
 
         self.scaler = scale
+        # Make sure this is a valid resolution
+        self._validate_scale()
 
     @property
     def scale(self):
         return self.scaler.scale
 
+    def _validate_scale(self):
+        if self.scale not in ('110m', '50m', '10m'):
+            raise ValueError(
+                '{} is not a valid Natural Earth scale. '.format(self.scale) +
+                'Valid scales are "110m", "50m", and "10m".'
+            )
+
     def geometries(self):
         """
         Returns an iterator of (shapely) geometries for this feature.


Test patch:
diff --git a/lib/cartopy/tests/test_features.py b/lib/cartopy/tests/test_features.py
--- a/lib/cartopy/tests/test_features.py
+++ b/lib/cartopy/tests/test_features.py
@@ -1,23 +1,13 @@
-# (C) British Crown Copyright 2017 - 2018, Met Office
+# Copyright Cartopy Contributors
 #
-# This file is part of cartopy.
-#
-# cartopy is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the
-# Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# cartopy is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with cartopy.  If not, see <https://www.gnu.org/licenses/>.
+# This file is part of Cartopy and is released under the LGPL license.
+# See COPYING and COPYING.LESSER in the root of the repository for full
+# licensing details.
 
 from __future__ import (absolute_import, division, print_function)
 
 import cartopy.feature as cfeature
+import pytest
 
 small_extent = (-6, -8, 56, 59)
 medium_extent = (-20, 20, 20, 60)
@@ -71,3 +61,8 @@ def test_intersecting_geometries_large(self, monkeypatch):
         # '110m' when the extent is large and autoscale is True.
         auto_land.intersecting_geometries(large_extent)
         assert auto_land.scale == '110m'
+
+
+def test_bad_ne_scale():
+    with pytest.raises(ValueError, match='not a valid Natural Earth scale'):
+        cfeature.NaturalEarthFeature('physical', 'land', '30m')