You can run this notebook in a live session or view it on Github.

# GRIB Data Example

GRIB format is commonly used to disseminate atmospheric model data. With xarray and the cfgrib engine, GRIB data can easily be analyzed and visualized.

    [1]:

    import xarray as xr
    import matplotlib.pyplot as plt

    Fontconfig error: No writable cache directories

To read GRIB data, you can use `xarray.load_dataset`. The only extra code you need is to specify the engine as `cfgrib`.

    [2]:

    ds = xr.tutorial.load_dataset("era5-2mt-2019-03-uk.grib", engine="cfgrib")

    ---------------------------------------------------------------------------
    PermissionError                           Traceback (most recent call last)
    File /usr/lib/python3/dist-packages/pooch/utils.py:262, in make_local_storage(path, env)
        258 if action == "create":
        259     # When running in parallel, it's possible that multiple jobs will
        260     # try to create the path at the same time. Use exist_ok to avoid
        261     # raising an error.
    --> 262     os.makedirs(path, exist_ok=True)
        263 else:

    File /usr/lib/python3.13/os.py:217, in makedirs(name, mode, exist_ok)
        216 try:
    --> 217     makedirs(head, exist_ok=exist_ok)
        218 except FileExistsError:
        219     # Defeats race condition when another thread created the path

    File /usr/lib/python3.13/os.py:217, in makedirs(name, mode, exist_ok)
        216 try:
    --> 217     makedirs(head, exist_ok=exist_ok)
        218 except FileExistsError:
        219     # Defeats race condition when another thread created the path

    File /usr/lib/python3.13/os.py:227, in makedirs(name, mode, exist_ok)
        226 try:
    --> 227     mkdir(name, mode)
        228 except OSError:
        229     # Cannot rely on checking for EEXIST, since the operating system
        230     # could give priority to other errors like EACCES or EROFS

    PermissionError: [Errno 13] Permission denied: '/sbuild-nonexistent'

    The above exception was the direct cause of the following exception:

    PermissionError                           Traceback (most recent call last)
    Cell In[2], line 1
    ----> 1 ds = xr.tutorial.load_dataset("era5-2mt-2019-03-uk.grib", engine="cfgrib")

    File /usr/lib/python3/dist-packages/xarray/tutorial.py:213, in load_dataset(*args, **kwargs)
        176 def load_dataset(*args, **kwargs) -> Dataset:
        177     """
        178     Open, load into memory, and close a dataset from the online repository
        179     (requires internet).
       (...)
        211     load_dataset
        212     """
    --> 213     with open_dataset(*args, **kwargs) as ds:
        214         return ds.load()

    File /usr/lib/python3/dist-packages/xarray/tutorial.py:165, in open_dataset(name, cache, cache_dir, engine, **kws)
        162 downloader = pooch.HTTPDownloader(headers=headers)
        164 # retrieve the file
    --> 165 filepath = pooch.retrieve(
        166     url=url, known_hash=None, path=cache_dir, downloader=downloader
        167 )
        168 ds = _open_dataset(filepath, engine=engine, **kws)
        169 if not cache:

    File /usr/lib/python3/dist-packages/pooch/core.py:227, in retrieve(url, known_hash, fname, path, processor, downloader, progressbar)
        222 action, verb = download_action(full_path, known_hash)
        224 if action in ("download", "update"):
        225     # We need to write data, so create the local data directory if it
        226     # doesn't already exist.
    --> 227     make_local_storage(path)
        229     get_logger().info(
        230         "%s data from '%s' to file '%s'.",
        231         verb,
        232         url,
        233         str(full_path),
        234     )
        236     if downloader is None:

    File /usr/lib/python3/dist-packages/pooch/utils.py:276, in make_local_storage(path, env)
        272 if env is not None:
        273     message.append(
        274         f"Use environment variable '{env}' to specify a different location."
        275     )
    --> 276 raise PermissionError(" ".join(message)) from error

    PermissionError: [Errno 13] Permission denied: '/sbuild-nonexistent' | Pooch could not create data cache folder '/sbuild-nonexistent/.cache/xarray_tutorial_data'. Will not be able to download data files.

Let’s create a simple plot of 2-m air temperature in degrees Celsius:

    [3]:

    ds = ds - 273.15
    ds.t2m[0].plot(cmap=plt.cm.coolwarm)

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    Cell In[3], line 1
    ----> 1 ds = ds - 273.15
          2 ds.t2m[0].plot(cmap=plt.cm.coolwarm)

    NameError: name 'ds' is not defined

With CartoPy, we can create a more detailed plot, using built-in shapefiles to help provide geographic context:

    [4]:

    import cartopy.crs as ccrs
    import cartopy

    fig = plt.figure(figsize=(10, 10))
    ax = plt.axes(projection=ccrs.Robinson())
    ax.coastlines(resolution="10m")
    plot = ds.t2m[0].plot(
        cmap=plt.cm.coolwarm, transform=ccrs.PlateCarree(), cbar_kwargs={"shrink": 0.6}
    )
    plt.title("ERA5 - 2m temperature British Isles March 2019")

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    Cell In[4], line 7
          5 ax = plt.axes(projection=ccrs.Robinson())
          6 ax.coastlines(resolution="10m")
    ----> 7 plot = ds.t2m[0].plot(
          8     cmap=plt.cm.coolwarm, transform=ccrs.PlateCarree(), cbar_kwargs={"shrink": 0.6}
          9 )
         10 plt.title("ERA5 - 2m temperature British Isles March 2019")

    NameError: name 'ds' is not defined

    Error in callback <function _draw_all_if_interactive at 0x7038884e4c20> (for post_execute), with arguments args (),kwargs {}:

    ---------------------------------------------------------------------------
    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy/shapefiles/natural_earth/physical'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy/shapefiles/natural_earth'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy/shapefiles'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local'

    During handling of the above exception, another exception occurred:

    PermissionError                           Traceback (most recent call last)
    File /usr/lib/python3/dist-packages/matplotlib/pyplot.py:197, in _draw_all_if_interactive()
        195 def _draw_all_if_interactive() -> None:
        196     if matplotlib.is_interactive():
    --> 197         draw_all()

    File /usr/lib/python3/dist-packages/matplotlib/_pylab_helpers.py:132, in Gcf.draw_all(cls, force)
        130 for manager in cls.get_all_fig_managers():
        131     if force or manager.canvas.figure.stale:
    --> 132         manager.canvas.draw_idle()

    File /usr/lib/python3/dist-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
       1891 if not self._is_idle_drawing:
       1892     with self._idle_draw_cntx():
    -> 1893         self.draw(*args, **kwargs)

    File /usr/lib/python3/dist-packages/matplotlib/backends/backend_agg.py:388, in FigureCanvasAgg.draw(self)
        385 # Acquire a lock on the shared font cache.
        386 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
        387       else nullcontext()):
    --> 388     self.figure.draw(self.renderer)
        389     # A GUI class may be need to update a window using this draw, so
        390     # don't forget to call the superclass.
        391     super().draw()

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

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

    File /usr/lib/python3/dist-packages/matplotlib/figure.py:3154, in Figure.draw(self, renderer)
       3151         # ValueError can occur when resizing a window.
       3153 self.patch.draw(renderer)
    -> 3154 mimage._draw_list_compositing_images(
       3155     renderer, self, artists, self.suppressComposite)
       3157 for sfig in self.subfigs:
       3158     sfig.draw(renderer)

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

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

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

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

    File /usr/lib/python3/dist-packages/matplotlib/axes/_base.py:3070, in _AxesBase.draw(self, renderer)
       3067 if artists_rasterized:
       3068     _draw_rasterized(self.figure, artists_rasterized, renderer)
    -> 3070 mimage._draw_list_compositing_images(
       3071     renderer, self, artists, self.figure.suppressComposite)
       3073 renderer.close_group('axes')
       3074 self.stale = False

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

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

    File /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-packages/cartopy/io/shapereader.py:355, in NEShpDownloader.acquire_resource(self, target_path, format_dict)
        352 from zipfile import ZipFile
        354 target_dir = Path(target_path).parent
    --> 355 target_dir.mkdir(parents=True, exist_ok=True)
        357 url = self.url(format_dict)
        359 shapefile_online = self._urlopen(url)

    File /usr/lib/python3.13/pathlib/_local.py:728, in Path.mkdir(self, mode, parents, exist_ok)
        726     if not parents or self.parent == self:
        727         raise
    --> 728     self.parent.mkdir(parents=True, exist_ok=True)
        729     self.mkdir(mode, parents=False, exist_ok=exist_ok)
        730 except OSError:
        731     # Cannot rely on checking for EEXIST, since the operating system
        732     # could give priority to other errors like EACCES or EROFS

    File /usr/lib/python3.13/pathlib/_local.py:728, in Path.mkdir(self, mode, parents, exist_ok)
        726     if not parents or self.parent == self:
        727         raise
    --> 728     self.parent.mkdir(parents=True, exist_ok=True)
        729     self.mkdir(mode, parents=False, exist_ok=exist_ok)
        730 except OSError:
        731     # Cannot rely on checking for EEXIST, since the operating system
        732     # could give priority to other errors like EACCES or EROFS

        [... skipping similar frames: Path.mkdir at line 728 (3 times)]

    File /usr/lib/python3.13/pathlib/_local.py:728, in Path.mkdir(self, mode, parents, exist_ok)
        726     if not parents or self.parent == self:
        727         raise
    --> 728     self.parent.mkdir(parents=True, exist_ok=True)
        729     self.mkdir(mode, parents=False, exist_ok=exist_ok)
        730 except OSError:
        731     # Cannot rely on checking for EEXIST, since the operating system
        732     # could give priority to other errors like EACCES or EROFS

    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        720 """
        721 Create a new directory at this given path.
        722 """
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:
        726     if not parents or self.parent == self:

    PermissionError: [Errno 13] Permission denied: '/sbuild-nonexistent'

    ---------------------------------------------------------------------------
    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy/shapefiles/natural_earth/physical'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy/shapefiles/natural_earth'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy/shapefiles'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share/cartopy'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local/share'

    During handling of the above exception, another exception occurred:

    FileNotFoundError                         Traceback (most recent call last)
    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:

    FileNotFoundError: [Errno 2] No such file or directory: '/sbuild-nonexistent/.local'

    During handling of the above exception, another exception occurred:

    PermissionError                           Traceback (most recent call last)
    File /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-packages/matplotlib/backend_bases.py:2164, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
       2161     # we do this instead of `self.figure.draw_without_rendering`
       2162     # so that we can inject the orientation
       2163     with getattr(renderer, "_draw_disabled", nullcontext)():
    -> 2164         self.figure.draw(renderer)
       2165 if bbox_inches:
       2166     if bbox_inches == "tight":

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

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

    File /usr/lib/python3/dist-packages/matplotlib/figure.py:3154, in Figure.draw(self, renderer)
       3151         # ValueError can occur when resizing a window.
       3153 self.patch.draw(renderer)
    -> 3154 mimage._draw_list_compositing_images(
       3155     renderer, self, artists, self.suppressComposite)
       3157 for sfig in self.subfigs:
       3158     sfig.draw(renderer)

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

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

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

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

    File /usr/lib/python3/dist-packages/matplotlib/axes/_base.py:3070, in _AxesBase.draw(self, renderer)
       3067 if artists_rasterized:
       3068     _draw_rasterized(self.figure, artists_rasterized, renderer)
    -> 3070 mimage._draw_list_compositing_images(
       3071     renderer, self, artists, self.figure.suppressComposite)
       3073 renderer.close_group('axes')
       3074 self.stale = False

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

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

    File /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-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 /usr/lib/python3/dist-packages/cartopy/io/shapereader.py:355, in NEShpDownloader.acquire_resource(self, target_path, format_dict)
        352 from zipfile import ZipFile
        354 target_dir = Path(target_path).parent
    --> 355 target_dir.mkdir(parents=True, exist_ok=True)
        357 url = self.url(format_dict)
        359 shapefile_online = self._urlopen(url)

    File /usr/lib/python3.13/pathlib/_local.py:728, in Path.mkdir(self, mode, parents, exist_ok)
        726     if not parents or self.parent == self:
        727         raise
    --> 728     self.parent.mkdir(parents=True, exist_ok=True)
        729     self.mkdir(mode, parents=False, exist_ok=exist_ok)
        730 except OSError:
        731     # Cannot rely on checking for EEXIST, since the operating system
        732     # could give priority to other errors like EACCES or EROFS

    File /usr/lib/python3.13/pathlib/_local.py:728, in Path.mkdir(self, mode, parents, exist_ok)
        726     if not parents or self.parent == self:
        727         raise
    --> 728     self.parent.mkdir(parents=True, exist_ok=True)
        729     self.mkdir(mode, parents=False, exist_ok=exist_ok)
        730 except OSError:
        731     # Cannot rely on checking for EEXIST, since the operating system
        732     # could give priority to other errors like EACCES or EROFS

        [... skipping similar frames: Path.mkdir at line 728 (3 times)]

    File /usr/lib/python3.13/pathlib/_local.py:728, in Path.mkdir(self, mode, parents, exist_ok)
        726     if not parents or self.parent == self:
        727         raise
    --> 728     self.parent.mkdir(parents=True, exist_ok=True)
        729     self.mkdir(mode, parents=False, exist_ok=exist_ok)
        730 except OSError:
        731     # Cannot rely on checking for EEXIST, since the operating system
        732     # could give priority to other errors like EACCES or EROFS

    File /usr/lib/python3.13/pathlib/_local.py:724, in Path.mkdir(self, mode, parents, exist_ok)
        720 """
        721 Create a new directory at this given path.
        722 """
        723 try:
    --> 724     os.mkdir(self, mode)
        725 except FileNotFoundError:
        726     if not parents or self.parent == self:

    PermissionError: [Errno 13] Permission denied: '/sbuild-nonexistent'

    <Figure size 1000x1000 with 1 Axes>

Finally, we can also pull out a time series for a given location easily:

    [5]:

    ds.t2m.sel(longitude=0, latitude=51.5).plot()
    plt.title("ERA5 - London 2m temperature March 2019")

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    Cell In[5], line 1
    ----> 1 ds.t2m.sel(longitude=0, latitude=51.5).plot()
          2 plt.title("ERA5 - London 2m temperature March 2019")

    NameError: name 'ds' is not defined