Skip to content

cartopy shapefile downloads don't work in JupyterLite #4142

@MMesch

Description

@MMesch

Cartopy fails to download shapefiles with a long exception from JupyterLite.

Workaround is to predownload the data files with python -m cartopy.feature.download cultural, (or physical, ...) distribute the files in the deployment and run import cartopy; cartopy.config['data_dir'] = '/path/to/cartopy/'data before plotting.

In the browser console I get:

xpython.js:9 Mixed Content: The page at 'https:// ...  /jupyterlite-obspy/extensions/@jupyterlite/xeus-extension/static/78.27c4816….js?v=27c4816…' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://naturalearth.s3.amazonaws.com:443/'. This request has been blocked; this endpoint must be available over WS

Not sure what the reason for this is and why ws:// is involved here. The file url seems to be https: https://naturalearth.s3.amazonaws.com/110m_physical/ne_110m_ocean.zip . Trace is in the details below.

Details
Matplotlib is building the font cache; this may take a moment.
/lib/python3.13/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/110m_physical/ne_110m_ocean.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
<Figure size 640x480 with 1 Axes>
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
File /lib/python3.13/urllib/request.py:1319, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1318 try:
-> 1319     h.request(req.get_method(), req.selector, req.data, headers,
   1320               encode_chunked=req.has_header('Transfer-encoding'))
   1321 except OSError as err: # timeout error

File /lib/python3.13/http/client.py:1336, in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
   1335 """Send a complete request to the server."""
-> 1336 self._send_request(method, url, body, headers, encode_chunked)

File /lib/python3.13/http/client.py:1382, in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
   1381     body = _encode(body, 'body')
-> 1382 self.endheaders(body, encode_chunked=encode_chunked)

File /lib/python3.13/http/client.py:1331, in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1330     raise CannotSendHeader()
-> 1331 self._send_output(message_body, encode_chunked=encode_chunked)

File /lib/python3.13/http/client.py:1091, in HTTPConnection._send_output(self, message_body, encode_chunked)
   1090 del self._buffer[:]
-> 1091 self.send(msg)
   1093 if message_body is not None:
   1094 
   1095     # create a consistent interface to message_body

File /lib/python3.13/http/client.py:1035, in HTTPConnection.send(self, data)
   1034 if self.auto_open:
-> 1035     self.connect()
   1036 else:

File /lib/python3.13/http/client.py:1470, in HTTPSConnection.connect(self)
   1468 "Connect to a host on a given (SSL) port."
-> 1470 super().connect()
   1472 if self._tunnel_host:

File /lib/python3.13/http/client.py:1001, in HTTPConnection.connect(self)
   1000 sys.audit("http.client.connect", self, self.host, self.port)
-> 1001 self.sock = self._create_connection(
   1002     (self.host,self.port), self.timeout, self.source_address)
   1003 # Might fail in OSs that don't implement TCP_NODELAY

File /lib/python3.13/socket.py:864, in create_connection(address, timeout, source_address, all_errors)
    863 if not all_errors:
--> 864     raise exceptions[0]
    865 raise ExceptionGroup("create_connection failed", exceptions)

File /lib/python3.13/socket.py:849, in create_connection(address, timeout, source_address, all_errors)
    848     sock.bind(source_address)
--> 849 sock.connect(sa)
    850 # Break explicitly a reference cycle

OSError: [Errno 23] Host is unreachable

During handling of the above exception, another exception occurred:

URLError                                  Traceback (most recent call last)
File /lib/python3.13/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
    400     pass
    401 else:
--> 402     return printer(obj)
    403 # Finally look for special method names
    404 method = get_real_method(obj, self.print_method)

File /lib/python3.13/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    167     from matplotlib.backend_bases import FigureCanvasBase
    168     FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
    171 data = bytes_io.getvalue()
    172 if fmt == 'svg':

File /lib/python3.13/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2154     # we do this instead of `self.figure.draw_without_rendering`
   2155     # so that we can inject the orientation
   2156     with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157         self.figure.draw(renderer)
   2158 if bbox_inches:
   2159     if bbox_inches == "tight":

File /lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     92 @wraps(draw)
     93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94     result = draw(artist, renderer, *args, **kwargs)
     95     if renderer._rasterizing:
     96         renderer.stop_rasterizing()

File /lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /lib/python3.13/site-packages/matplotlib/figure.py:3257, in Figure.draw(self, renderer)
   3254             # ValueError can occur when resizing a window.
   3256     self.patch.draw(renderer)
-> 3257     mimage._draw_list_compositing_images(
   3258         renderer, self, artists, self.suppressComposite)
   3260     renderer.close_group('figure')
   3261 finally:

File /lib/python3.13/site-packages/matplotlib/image.py:134, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    132 if not_composite or not has_images:
    133     for a in artists:
--> 134         a.draw(renderer)
    135 else:
    136     # Composite any adjacent images together
    137     image_group = []

File /lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /lib/python3.13/site-packages/cartopy/mpl/geoaxes.py:525, in GeoAxes.draw(self, renderer, **kwargs)
    520         self.imshow(img, extent=extent, origin=origin,
    521                     transform=factory.crs, *factory_args[1:],
    522                     **factory_kwargs)
    523 self._done_img_factory = True
--> 525 return super().draw(renderer=renderer, **kwargs)

File /lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /lib/python3.13/site-packages/matplotlib/axes/_base.py:3226, in _AxesBase.draw(self, renderer)
   3223 if artists_rasterized:
   3224     _draw_rasterized(self.get_figure(root=True), artists_rasterized, renderer)
-> 3226 mimage._draw_list_compositing_images(
   3227     renderer, self, artists, self.get_figure(root=True).suppressComposite)
   3229 renderer.close_group('axes')
   3230 self.stale = False

File /lib/python3.13/site-packages/matplotlib/image.py:134, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    132 if not_composite or not has_images:
    133     for a in artists:
--> 134         a.draw(renderer)
    135 else:
    136     # Composite any adjacent images together
    137     image_group = []

File /lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     68     if artist.get_agg_filter() is not None:
     69         renderer.start_filter()
---> 71     return draw(artist, renderer)
     72 finally:
     73     if artist.get_agg_filter() is not None:

File /lib/python3.13/site-packages/cartopy/mpl/feature_artist.py:185, in FeatureArtist.draw(self, renderer)
    180     geoms = self._feature.geometries()
    181 else:
    182     # For efficiency on local maps with high resolution features (e.g
    183     # from Natural Earth), only create paths for geometries that are
    184     # in view.
--> 185     geoms = self._feature.intersecting_geometries(extent)
    187 stylised_paths = {}
    188 # Make an empty placeholder style dictionary for when styler is not
    189 # used.  Freeze it so that we can use it as a dict key.  We will need
    190 # to unfreeze all style dicts with dict(frozen) before passing to mpl.

File /lib/python3.13/site-packages/cartopy/feature/__init__.py:309, in NaturalEarthFeature.intersecting_geometries(self, extent)
    302 """
    303 Returns an iterator of shapely geometries that intersect with
    304 the given extent.
    305 The extent is assumed to be in the CRS of the feature.
    306 If extent is None, the method returns all geometries for this dataset.
    307 """
    308 self.scaler.scale_from_extent(extent)
--> 309 return super().intersecting_geometries(extent)

File /lib/python3.13/site-packages/cartopy/feature/__init__.py:112, in Feature.intersecting_geometries(self, extent)
    109 if extent is not None and not np.isnan(extent[0]):
    110     extent_geom = sgeom.box(extent[0], extent[2],
    111                             extent[1], extent[3])
--> 112     return (geom for geom in self.geometries() if
    113             geom is not None and extent_geom.intersects(geom))
    114 else:
    115     return self.geometries()

File /lib/python3.13/site-packages/cartopy/feature/__init__.py:291, in NaturalEarthFeature.geometries(self)
    289 key = (self.name, self.category, self.scale)
    290 if key not in _NATURAL_EARTH_GEOM_CACHE:
--> 291     path = shapereader.natural_earth(resolution=self.scale,
    292                                      category=self.category,
    293                                      name=self.name)
    294     geometries = tuple(shapereader.Reader(path).geometries())
    295     _NATURAL_EARTH_GEOM_CACHE[key] = geometries

File /lib/python3.13/site-packages/cartopy/io/shapereader.py:306, in natural_earth(resolution, category, name)
    302 ne_downloader = Downloader.from_config(('shapefiles', 'natural_earth',
    303                                         resolution, category, name))
    304 format_dict = {'config': config, 'category': category,
    305                'name': name, 'resolution': resolution}
--> 306 return ne_downloader.path(format_dict)

File /lib/python3.13/site-packages/cartopy/io/__init__.py:203, in Downloader.path(self, format_dict)
    200     result_path = target_path
    201 else:
    202     # we need to download the file
--> 203     result_path = self.acquire_resource(target_path, format_dict)
    205 return result_path

File /lib/python3.13/site-packages/cartopy/io/shapereader.py:359, in NEShpDownloader.acquire_resource(self, target_path, format_dict)
    355 target_dir.mkdir(parents=True, exist_ok=True)
    357 url = self.url(format_dict)
--> 359 shapefile_online = self._urlopen(url)
    361 zfh = ZipFile(io.BytesIO(shapefile_online.read()), 'r')
    363 for member_path in self.zip_file_contents(format_dict):

File /lib/python3.13/site-packages/cartopy/io/__init__.py:242, in Downloader._urlopen(self, url)
    235 """
    236 Returns a file handle to the given HTTP resource URL.
    237 
    238 Caller should close the file handle when finished with it.
    239 
    240 """
    241 warnings.warn(f'Downloading: {url}', DownloadWarning)
--> 242 return urlopen(url)

File /lib/python3.13/urllib/request.py:189, in urlopen(url, data, timeout, context)
    187 else:
    188     opener = _opener
--> 189 return opener.open(url, data, timeout)

File /lib/python3.13/urllib/request.py:489, in OpenerDirector.open(self, fullurl, data, timeout)
    486     req = meth(req)
    488 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method())
--> 489 response = self._open(req, data)
    491 # post-process response
    492 meth_name = protocol+"_response"

File /lib/python3.13/urllib/request.py:506, in OpenerDirector._open(self, req, data)
    503     return result
    505 protocol = req.type
--> 506 result = self._call_chain(self.handle_open, protocol, protocol +
    507                           '_open', req)
    508 if result:
    509     return result

File /lib/python3.13/urllib/request.py:466, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    464 for handler in handlers:
    465     func = getattr(handler, meth_name)
--> 466     result = func(*args)
    467     if result is not None:
    468         return result

File /lib/python3.13/urllib/request.py:1367, in HTTPSHandler.https_open(self, req)
   1366 def https_open(self, req):
-> 1367     return self.do_open(http.client.HTTPSConnection, req,
   1368                         context=self._context)

File /lib/python3.13/urllib/request.py:1322, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1319         h.request(req.get_method(), req.selector, req.data, headers,
   1320                   encode_chunked=req.has_header('Transfer-encoding'))
   1321     except OSError as err: # timeout error
-> 1322         raise URLError(err)
   1323     r = h.getresponse()
   1324 except:

URLError: <urlopen error [Errno 23] Host is unreachable>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions