'Cartopy can't download shapefiles because a certificate is not trusted

I need to use cartopy to plot a thing.

Running the example from their website

import cartopy.crs as ccrs
import matplotlib.pyplot as plt

ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()

plt.show()

I get a wonderfully long error related to a certificate that can't be verified when cartopy tries to download a shape file. (See the errors below and ignore the file paths. I removed the stuff specific to my hard drive. It's in a virtual environment).

If I go to the URL it is trying to download (https://naciscdn.org/naturalearth/110m/physical/ne_110m_coastline.zip), I can download the zip file. Inspecting the cert shows that it is distributed by Let's Encrypt and is valid for several more months. After I got the error, I even went and downloaded the certificate, added it to my system key chain and set the trust level to always trust.

I'm using Python 3.7.3 on macOS Catalina 10.15.4.

Anyone know how I could fix this?

/env/lib/python3.7/site-packages/cartopy/io/__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/110m/physical/ne_110m_coastline.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
Traceback (most recent call last):
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 1317, in do_open
    encode_chunked=req.has_header('Transfer-encoding'))
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/http/client.py", line 1229, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/http/client.py", line 1275, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/http/client.py", line 1224, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/http/client.py", line 1016, in _send_output
    self.send(msg)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/http/client.py", line 956, in send
    self.connect()
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/http/client.py", line 1392, in connect
    server_hostname=server_hostname)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/ssl.py", line 412, in wrap_socket
    session=session
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/ssl.py", line 853, in _create
    self.do_handshake()
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/ssl.py", line 1117, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/env/lib/python3.7/site-packages/matplotlib/backends/backend_macosx.py", line 74, in _draw
    self.figure.draw(renderer)
  File "/env/lib/python3.7/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/env/lib/python3.7/site-packages/matplotlib/figure.py", line 1736, in draw
    renderer, self, artists, self.suppressComposite)
  File "/env/lib/python3.7/site-packages/matplotlib/image.py", line 137, in _draw_list_compositing_images
    a.draw(renderer)
  File "/env/lib/python3.7/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/env/lib/python3.7/site-packages/cartopy/mpl/geoaxes.py", line 479, in draw
    return matplotlib.axes.Axes.draw(self, renderer=renderer, **kwargs)
  File "/env/lib/python3.7/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/env/lib/python3.7/site-packages/matplotlib/axes/_base.py", line 2630, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/env/lib/python3.7/site-packages/matplotlib/image.py", line 137, in _draw_list_compositing_images
    a.draw(renderer)
  File "/env/lib/python3.7/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/env/lib/python3.7/site-packages/cartopy/mpl/feature_artist.py", line 155, in draw
    geoms = self._feature.intersecting_geometries(extent)
  File "/env/lib/python3.7/site-packages/cartopy/feature/__init__.py", line 302, in intersecting_geometries
    return super(NaturalEarthFeature, self).intersecting_geometries(extent)
  File "/env/lib/python3.7/site-packages/cartopy/feature/__init__.py", line 110, in intersecting_geometries
    return (geom for geom in self.geometries() if
  File "/env/lib/python3.7/site-packages/cartopy/feature/__init__.py", line 286, in geometries
    name=self.name)
  File "/env/lib/python3.7/site-packages/cartopy/io/shapereader.py", line 295, in natural_earth
    return ne_downloader.path(format_dict)
  File "/env/lib/python3.7/site-packages/cartopy/io/__init__.py", line 222, in path
    result_path = self.acquire_resource(target_path, format_dict)
  File "/env/lib/python3.7/site-packages/cartopy/io/shapereader.py", line 350, in acquire_resource
    shapefile_online = self._urlopen(url)
  File "/env/lib/python3.7/site-packages/cartopy/io/__init__.py", line 261, in _urlopen
    return urlopen(url)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 525, in open
    response = self._open(req, data)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 543, in _open
    '_open', req)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 503, in _call_chain
    result = func(*args)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 1360, in https_open
    context=self._context, check_hostname=self._check_hostname)
  File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 1319, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)>


Solution 1:[1]

I had a very similar problem. I solved it following this post.
In my case, I did not need root privileges as I was using a conda environment.
So I got the directory with certificates:

$ dirname $(echo $REQUESTS_CA_BUNDLE)
/etc/ssl/certs

And the directory in which to create the link from openssl_capath from:

$ python -c 'import ssl; print(ssl.get_default_verify_paths())'
DefaultVerifyPaths(cafile='/home/<user>/miniconda3/envs/<env_name>/ssl/cert.pem', capath='/home/<user>/miniconda3/envs/<env_name>/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/home/<user>/miniconda3/envs/<env_name>/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/home/<user>/miniconda3/envs/<env_name>/ssl/certs')

So the solution was:

ln -s /etc/ssl/certs/ /home/<user>/miniconda3/envs/<env_name>/ssl/

(replace with your user and environment name).

(Ubuntu 20.04.3 LTS, Python 3.10.2, cartopy 0.20.2, Spyder IDE 5.2.2)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gavriel Cohen