Repository: SciTools/iris (language: python)

Issue:
Merge-back `v3.4.x` to `main`
# Merge needs to be a merge commit


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index b197b58e80..9ae3534c76 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -6,6 +6,12 @@ on:
   schedule:
     # Runs every day at 23:00.
     - cron: "0 23 * * *"
+  workflow_dispatch:
+    inputs:
+      first_commit:
+        description: "Argument to be passed to the overnight benchmark script."
+        required: false
+        type: string
 
 jobs:
   benchmark:
@@ -15,7 +21,7 @@ jobs:
     env:
       IRIS_TEST_DATA_LOC_PATH: benchmarks
       IRIS_TEST_DATA_PATH: benchmarks/iris-test-data
-      IRIS_TEST_DATA_VERSION: "2.15"
+      IRIS_TEST_DATA_VERSION: "2.18"
       # Lets us manually bump the cache to rebuild
       ENV_CACHE_BUILD: "0"
       TEST_DATA_CACHE_BUILD: "2"
@@ -64,7 +70,12 @@ jobs:
 
       - name: Run overnight benchmarks
         run: |
-          first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1)
+          first_commit=${{ inputs.first_commit }}
+          if [ "$first_commit" == "" ]
+          then
+            first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1)
+          fi
+          
           if [ "$first_commit" != "" ]
           then
             nox --session="benchmarks(overnight)" -- $first_commit
diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml
index a45cccfeab..94c20aedb9 100644
--- a/.github/workflows/refresh-lockfiles.yml
+++ b/.github/workflows/refresh-lockfiles.yml
@@ -1,13 +1,5 @@
-# This workflow periodically creates new environment lock files based on the newest
-# available packages and dependencies.
-#
-# Environment specifications are given as conda environment.yml files found in
-# `requirements/ci/py**.yml`.  These state the packages required, the conda channels
-# that the packages will be pulled from, and any versions of packages that need to be
-# pinned at specific versions.
-#
-# For environments that have changed, a pull request will be made and submitted
-# to the main branch
+# Updates the environment lock files. See the called workflow in the
+# scitools/workflows repo for more details.
 
 name: Refresh Lockfiles
 
@@ -20,98 +12,7 @@ on:
     # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule
     - cron: "1 0 * * 6"
 
-
 jobs:
-
-  get_python_matrix:
-    # Determines which Python versions should be included in the matrix used in
-    # the gen_lockfiles job.
-    if: "github.repository == 'SciTools/iris'"
-    runs-on: ubuntu-latest
-    outputs:
-      matrix: ${{ steps.get_py.outputs.matrix }}
-    steps:
-      - uses: actions/checkout@v3
-      - id: get_py
-        run: echo "::set-output name=matrix::$(ls -1 requirements/ci/py*.yml | xargs -n1 basename | sed 's/....$//' | jq -cnR '[inputs]')"
-
-  gen_lockfiles:
-    # this is a matrix job: it splits to create new lockfiles for each
-    # of the CI test python versions.
-    if: "github.repository == 'SciTools/iris'"
-    runs-on: ubuntu-latest
-    needs: get_python_matrix
-
-    strategy:
-      matrix:
-        python: ${{ fromJSON(needs.get_python_matrix.outputs.matrix) }}
-
-    steps:
-      - uses: actions/checkout@v3
-      - name: install requirements
-        run: |
-          source $CONDA/bin/activate base
-          conda install -y -c conda-forge conda-libmamba-solver conda-lock
-      - name: generate lockfile
-        env:
-          CONDA_EXPERIMENTAL_SOLVER: libmamba
-        run: |
-          $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/${{matrix.python}}.yml
-          mv conda-linux-64.lock ${{matrix.python}}-linux-64.lock
-      - name: output lockfile
-        uses: actions/upload-artifact@v3
-        with:
-          path: ${{matrix.python}}-linux-64.lock
-
-  create_pr:
-    # once the matrix job has completed all the lock files will have been uploaded as artifacts.
-    # Download the artifacts, add them to the repo, and create a PR.
-    if: "github.repository == 'SciTools/iris'"
-    runs-on: ubuntu-latest
-    needs: gen_lockfiles
-
-    steps:
-      - uses: actions/checkout@v3
-      - name: get artifacts
-        uses: actions/download-artifact@v3
-        with:
-          path: artifacts
-
-      - name: Update lock files in repo
-        run: |
-          cp artifacts/artifact/*.lock requirements/ci/nox.lock
-          rm -r artifacts
-
-      - name: "Generate token"
-        uses: tibdex/github-app-token@v1
-        id: generate-token
-        with:
-          app_id: ${{ secrets.AUTH_APP_ID }}
-          private_key: ${{ secrets.AUTH_APP_PRIVATE_KEY }}
-
-      - name: Create Pull Request
-        id: cpr
-        uses: peter-evans/create-pull-request@671dc9c9e0c2d73f07fa45a3eb0220e1622f0c5f
-        with:
-          token: ${{ steps.generate-token.outputs.token }}
-          commit-message: Updated environment lockfiles
-          committer: "Lockfile bot <noreply@github.com>"
-          author: "Lockfile bot <noreply@github.com>"
-          delete-branch: true
-          branch: auto-update-lockfiles
-          title: "[iris.ci] environment lockfiles auto-update"
-          body: |
-            Lockfiles updated to the latest resolvable environment.
-
-            ### If the CI tasks fail, create a new branch based on this PR and add the required fixes to that branch.
-          labels: |
-            New: Pull Request
-            Bot
-
-      - name: Check Pull Request
-        if: steps.cpr.outputs.pull-request-number != ''
-        run: |
-          echo "pull-request #${{ steps.cpr.outputs.pull-request-number }}"
-          echo "pull-request URL ${{ steps.cpr.outputs.pull-request-url }}"
-          echo "pull-request operation [${{ steps.cpr.outputs.pull-request-operation }}]"
-          echo "pull-request head SHA ${{ steps.cpr.outputs.pull-request-head-sha }}"
+  refresh_lockfiles:
+    uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@main
+    secrets: inherit
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index c65f37284f..44b77e5c7d 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -14,7 +14,7 @@ jobs:
     if: "github.repository == 'SciTools/iris'"
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v6
+      - uses: actions/stale@v7
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 22746cb0ee..7c95eeaca3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,7 +13,7 @@ minimum_pre_commit_version: 1.21.0
 
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.3.0
+    rev: v4.4.0
     hooks:
         # Prevent giant files from being committed.
     -   id: check-added-large-files
@@ -29,32 +29,31 @@ repos:
     -   id: no-commit-to-branch
 
 -   repo: https://github.com/psf/black
-    rev: 22.8.0
+    rev: 23.1.0
     hooks:
     -   id: black
         pass_filenames: false
         args: [--config=./pyproject.toml, .]
 
 -   repo: https://github.com/PyCQA/flake8
-    rev: 5.0.4
+    rev: 6.0.0
     hooks:
     -   id: flake8
         types: [file, python]
         args: [--config=./setup.cfg]
 
 -   repo: https://github.com/pycqa/isort
-    rev: 5.10.1
+    rev: 5.12.0
     hooks:
     -   id: isort
         types: [file, python]
         args: [--filter-files]
 
 -   repo: https://github.com/asottile/blacken-docs
-    rev: v1.12.1
+    rev: 1.13.0
     hooks:
     -   id: blacken-docs
         types: [file, rst]
-        additional_dependencies: [black==21.6b0]
 
 -   repo: https://github.com/aio-libs/sort-all
     rev: v1.2.0
diff --git a/README.md b/README.md
index ac2781f469..cdf4b2b043 100644
--- a/README.md
+++ b/README.md
@@ -54,3 +54,24 @@ For documentation see the
 developer version or the most recent released
 <a href="https://scitools-iris.readthedocs.io/en/stable/">stable</a> version.
 </p>
+
+## [#ShowYourStripes](https://showyourstripes.info/s/globe)
+
+<h4 align="center">
+  <a href="https://showyourstripes.info/s/globe">
+    <img src="https://raw.githubusercontent.com/ed-hawkins/show-your-stripes/master/2021/GLOBE---1850-2021-MO.png"
+         height="50" width="800"
+         alt="#showyourstripes Global 1850-2021"></a>
+</h4>
+
+**Graphics and Lead Scientist**: [Ed Hawkins](http://www.met.reading.ac.uk/~ed/home/index.php), National Centre for Atmospheric Science, University of Reading.
+
+**Data**: Berkeley Earth, NOAA, UK Met Office, MeteoSwiss, DWD, SMHI, UoR, Meteo France & ZAMG.
+
+<p>
+<a href="https://showyourstripes.info/s/globe">#ShowYourStripes</a> is distributed under a
+<a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>
+<a href="https://creativecommons.org/licenses/by/4.0/">
+  <img src="https://i.creativecommons.org/l/by/4.0/80x15.png" alt="creative-commons-by" style="border-width:0"></a>
+</p>
+
diff --git a/benchmarks/benchmarks/experimental/ugrid/__init__.py b/benchmarks/benchmarks/experimental/ugrid/__init__.py
index 2f9bb04e35..2e40c525a6 100644
--- a/benchmarks/benchmarks/experimental/ugrid/__init__.py
+++ b/benchmarks/benchmarks/experimental/ugrid/__init__.py
@@ -50,7 +50,7 @@ def time_create(self, *params):
 
 class Connectivity(UGridCommon):
     def setup(self, n_faces):
-        self.array = np.zeros([n_faces, 3], dtype=np.int)
+        self.array = np.zeros([n_faces, 3], dtype=int)
         super().setup(n_faces)
 
     def create(self):
diff --git a/benchmarks/benchmarks/generate_data/__init__.py b/benchmarks/benchmarks/generate_data/__init__.py
index 78b971d9de..52a5aceca8 100644
--- a/benchmarks/benchmarks/generate_data/__init__.py
+++ b/benchmarks/benchmarks/generate_data/__init__.py
@@ -113,7 +113,7 @@ def load_realised():
     file loading, but some benchmarks are only meaningful if starting with real
     arrays.
     """
-    from iris.fileformats.netcdf import _get_cf_var_data as pre_patched
+    from iris.fileformats.netcdf.loader import _get_cf_var_data as pre_patched
 
     def patched(cf_var, filename):
         return as_concrete_data(pre_patched(cf_var, filename))
diff --git a/benchmarks/benchmarks/import_iris.py b/benchmarks/benchmarks/import_iris.py
index ad54c23122..fc32ac289b 100644
--- a/benchmarks/benchmarks/import_iris.py
+++ b/benchmarks/benchmarks/import_iris.py
@@ -5,10 +5,30 @@
 # licensing details.
 from importlib import import_module, reload
 
+################
+# Prepare info for reset_colormaps:
+
+# Import and capture colormaps.
+from matplotlib import colormaps  # isort:skip
+
+_COLORMAPS_ORIG = set(colormaps)
+
+# Import iris.palette, which modifies colormaps.
+import iris.palette
+
+# Derive which colormaps have been added by iris.palette.
+_COLORMAPS_MOD = set(colormaps)
+COLORMAPS_EXTRA = _COLORMAPS_MOD - _COLORMAPS_ORIG
+
+# Touch iris.palette to prevent linters complaining.
+_ = iris.palette
+
+################
+
 
 class Iris:
     @staticmethod
-    def _import(module_name):
+    def _import(module_name, reset_colormaps=False):
         """
         Have experimented with adding sleep() commands into the imported
         modules. The results reveal:
@@ -25,6 +45,13 @@ def _import(module_name):
         and the repetitions are therefore no faster than the first run.
         """
         mod = import_module(module_name)
+
+        if reset_colormaps:
+            # Needed because reload() will attempt to register new colormaps a
+            #  second time, which errors by default.
+            for cm_name in COLORMAPS_EXTRA:
+                colormaps.unregister(cm_name)
+
         reload(mod)
 
     def time_iris(self):
@@ -205,7 +232,7 @@ def time_iterate(self):
         self._import("iris.iterate")
 
     def time_palette(self):
-        self._import("iris.palette")
+        self._import("iris.palette", reset_colormaps=True)
 
     def time_plot(self):
         self._import("iris.plot")
diff --git a/benchmarks/benchmarks/regridding.py b/benchmarks/benchmarks/regridding.py
index c315119c11..44bd1b6c95 100644
--- a/benchmarks/benchmarks/regridding.py
+++ b/benchmarks/benchmarks/regridding.py
@@ -12,8 +12,11 @@
 # importing anything else
 from iris import tests  # isort:skip
 
+import numpy as np
+
 import iris
-from iris.analysis import AreaWeighted
+from iris.analysis import AreaWeighted, PointInCell
+from iris.coords import AuxCoord
 
 
 class HorizontalChunkedRegridding:
@@ -53,3 +56,48 @@ def time_regrid_area_w_new_grid(self) -> None:
         out = self.chunked_cube.regrid(self.template_cube, self.scheme_area_w)
         # Realise data
         out.data
+
+
+class CurvilinearRegridding:
+    def setup(self) -> None:
+        # Prepare a cube and a template
+
+        cube_file_path = tests.get_data_path(
+            ["NetCDF", "regrid", "regrid_xyt.nc"]
+        )
+        self.cube = iris.load_cube(cube_file_path)
+
+        # Make the source cube curvilinear
+        x_coord = self.cube.coord("longitude")
+        y_coord = self.cube.coord("latitude")
+        xx, yy = np.meshgrid(x_coord.points, y_coord.points)
+        self.cube.remove_coord(x_coord)
+        self.cube.remove_coord(y_coord)
+        x_coord_2d = AuxCoord(
+            xx,
+            standard_name=x_coord.standard_name,
+            units=x_coord.units,
+            coord_system=x_coord.coord_system,
+        )
+        y_coord_2d = AuxCoord(
+            yy,
+            standard_name=y_coord.standard_name,
+            units=y_coord.units,
+            coord_system=y_coord.coord_system,
+        )
+        self.cube.add_aux_coord(x_coord_2d, (1, 2))
+        self.cube.add_aux_coord(y_coord_2d, (1, 2))
+
+        template_file_path = tests.get_data_path(
+            ["NetCDF", "regrid", "regrid_template_global_latlon.nc"]
+        )
+        self.template_cube = iris.load_cube(template_file_path)
+
+        # Prepare a regridding scheme
+        self.scheme_pic = PointInCell()
+
+    def time_regrid_pic(self) -> None:
+        # Regrid the cube onto the template.
+        out = self.cube.regrid(self.template_cube, self.scheme_pic)
+        # Realise the data
+        out.data
diff --git a/docs/Makefile b/docs/Makefile
index f4c8d0b7f4..fcb0ec0116 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -20,11 +20,6 @@ html-quick:
         echo "make html-quick in $$i..."; \
         (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) html-quick); done
 
-spelling:
-	@for i in $(SUBDIRS); do \
-        echo "make spelling in $$i..."; \
-        (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) spelling); done
-
 all:
 	@for i in $(SUBDIRS); do \
 		echo "make all in $$i..."; \
@@ -55,3 +50,8 @@ linkcheck:
 		echo "Running linkcheck in $$i..."; \
 		(cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) linkcheck); done
 
+show:
+	@for i in $(SUBDIRS); do \
+		echo "Running show in $$i..."; \
+		(cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) show); done
+
diff --git a/docs/gallery_code/general/plot_lineplot_with_legend.py b/docs/gallery_code/general/plot_lineplot_with_legend.py
index 78401817ba..aad7906acd 100644
--- a/docs/gallery_code/general/plot_lineplot_with_legend.py
+++ b/docs/gallery_code/general/plot_lineplot_with_legend.py
@@ -24,7 +24,6 @@ def main():
     )
 
     for cube in temperature.slices("longitude"):
-
         # Create a string label to identify this cube (i.e. latitude: value).
         cube_label = "latitude: %s" % cube.coord("latitude").points[0]
 
diff --git a/docs/gallery_code/general/plot_projections_and_annotations.py b/docs/gallery_code/general/plot_projections_and_annotations.py
index 75122591b9..2cf42e66e0 100644
--- a/docs/gallery_code/general/plot_projections_and_annotations.py
+++ b/docs/gallery_code/general/plot_projections_and_annotations.py
@@ -26,7 +26,6 @@
 
 
 def make_plot(projection_name, projection_crs):
-
     # Create a matplotlib Figure.
     plt.figure()
 
diff --git a/docs/gallery_code/general/plot_zonal_means.py b/docs/gallery_code/general/plot_zonal_means.py
index 08a9578e63..195f8b4bb0 100644
--- a/docs/gallery_code/general/plot_zonal_means.py
+++ b/docs/gallery_code/general/plot_zonal_means.py
@@ -16,7 +16,6 @@
 
 
 def main():
-
     # Loads air_temp.pp and "collapses" longitude into a single, average value.
     fname = iris.sample_data_path("air_temp.pp")
     temperature = iris.load_cube(fname)
diff --git a/docs/gallery_code/meteorology/plot_lagged_ensemble.py b/docs/gallery_code/meteorology/plot_lagged_ensemble.py
index 5cd2752f39..e15aa0e6ef 100644
--- a/docs/gallery_code/meteorology/plot_lagged_ensemble.py
+++ b/docs/gallery_code/meteorology/plot_lagged_ensemble.py
@@ -86,7 +86,6 @@ def main():
 
     # Iterate over all possible latitude longitude slices.
     for cube in last_timestep.slices(["latitude", "longitude"]):
-
         # Get the ensemble member number from the ensemble coordinate.
         ens_member = cube.coord("realization").points[0]
 
diff --git a/docs/gallery_code/oceanography/plot_atlantic_profiles.py b/docs/gallery_code/oceanography/plot_atlantic_profiles.py
index dc038ecffe..6604b61ec3 100644
--- a/docs/gallery_code/oceanography/plot_atlantic_profiles.py
+++ b/docs/gallery_code/oceanography/plot_atlantic_profiles.py
@@ -34,7 +34,7 @@ def main():
     # the southern portion of the domain, and limit the depth of the profile
     # to 1000m.
     lon_cons = iris.Constraint(longitude=330.5)
-    lat_cons = iris.Constraint(latitude=lambda l: -10 < l < -9)
+    lat_cons = iris.Constraint(latitude=lambda lat: -10 < lat < -9)
     depth_cons = iris.Constraint(depth=lambda d: d <= 1000)
     theta_1000m = theta.extract(depth_cons & lon_cons & lat_cons)
     salinity_1000m = salinity.extract(depth_cons & lon_cons & lat_cons)
diff --git a/docs/src/Makefile b/docs/src/Makefile
index c693a2c900..a75da5371b 100644
--- a/docs/src/Makefile
+++ b/docs/src/Makefile
@@ -16,7 +16,7 @@ PAPEROPT_a4     = -D latex_paper_size=a4
 PAPEROPT_letter = -D latex_paper_size=letter
 ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 
-.PHONY: help clean html html-noplot dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+.PHONY: help clean html html-noplot dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest show
 
 help:
 	@echo "Please use \`make <target>' where <target> is one of"
@@ -36,6 +36,7 @@ help:
 	@echo "  changes    to make an overview of all changed/added/deprecated items"
 	@echo "  linkcheck  to check all external links for integrity"
 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+	@echo "  show       to open the built documentation in the default browser"
 
 clean:
 	-rm -rf $(BUILDDIR)
@@ -61,11 +62,6 @@ html-quick:
 	@echo
 	@echo "Build finished. The HTML (no gallery or api docs) pages are in $(BUILDDIR)/html"
 
-spelling:
-	$(SPHINXBUILD) -b spelling $(SRCDIR) $(BUILDDIR)
-	@echo
-	@echo "Build finished. The HTML (no gallery) pages are in $(BUILDDIR)/html"
-
 dirhtml:
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 	@echo
@@ -153,3 +149,7 @@ doctest:
 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
 	@echo "Testing of doctests in the sources finished, look at the " \
 	      "results in $(BUILDDIR)/doctest/output.txt."
+
+show:
+	@python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/$(BUILDDIR)/html/index.html')"
+
diff --git a/docs/src/_templates/layout.html b/docs/src/_templates/layout.html
index 7377e866b7..974bd12753 100644
--- a/docs/src/_templates/layout.html
+++ b/docs/src/_templates/layout.html
@@ -10,9 +10,8 @@
     {% if on_rtd and rtd_version == 'latest' %}
         <div id="slim-red-box-banner">
             You are viewing the <b>latest</b> unreleased documentation
-            <b>v{{ version }}</b>.  You may prefer a
-            <a href="https://scitools-iris.readthedocs.io/en/stable/">stable</a>
-            version.
+            <b>v{{ version }}</b>. You can switch to a <b>stable</b> version
+            via the flyout menu in the bottom corner of the screen.
         </div>
         <p></p>
     {%- endif %}
diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc
index ec7e1efd6d..17278460dd 100644
--- a/docs/src/common_links.inc
+++ b/docs/src/common_links.inc
@@ -9,7 +9,7 @@
 .. _conda: https://docs.conda.io/en/latest/
 .. _contributor: https://github.com/SciTools/scitools.org.uk/blob/master/contributors.json
 .. _core developers: https://github.com/SciTools/scitools.org.uk/blob/master/contributors.json
-.. _generating sss keys for GitHub: https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account
+.. _generating ssh keys for GitHub: https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account
 .. _GitHub Actions: https://docs.github.com/en/actions
 .. _GitHub Help Documentation: https://docs.github.com/en/github
 .. _GitHub Discussions: https://github.com/SciTools/iris/discussions
@@ -21,7 +21,7 @@
 .. _isort: https://pycqa.github.io/isort/
 .. _issue: https://github.com/SciTools/iris/issues
 .. _issues: https://github.com/SciTools/iris/issues
-.. _legacy documentation: https://scitools.org.uk/iris/docs/v2.4.0/
+.. _legacy documentation: https://github.com/SciTools/scitools.org.uk/tree/master/iris/docs/archive
 .. _matplotlib: https://matplotlib.org/stable/
 .. _napolean: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/sphinxcontrib.napoleon.html
 .. _nox: https://nox.thea.codes/en/stable/
@@ -39,22 +39,26 @@
 .. _requirements/ci/: https://github.com/SciTools/iris/tree/main/requirements/ci
 .. _CF-UGRID: https://ugrid-conventions.github.io/ugrid-conventions/
 .. _issues on GitHub: https://github.com/SciTools/iris/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc
+.. _python-stratify: https://github.com/SciTools/python-stratify
+.. _iris-esmf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid
 
 
 .. comment
-    Core developers (@github names) in alphabetical order:
+    Core developers and prolific contributors (@github names) in alphabetical order:
 
 .. _@abooton: https://github.com/abooton
 .. _@alastair-gemmell: https://github.com/alastair-gemmell
 .. _@ajdawson: https://github.com/ajdawson
 .. _@bjlittle: https://github.com/bjlittle
 .. _@bouweandela: https://github.com/bouweandela
+.. _@bsherratt: https://github.com/bsherratt
 .. _@corinnebosley: https://github.com/corinnebosley
 .. _@cpelley: https://github.com/cpelley
 .. _@djkirkham: https://github.com/djkirkham
 .. _@DPeterK: https://github.com/DPeterK
 .. _@ESadek-MO: https://github.com/ESadek-MO
 .. _@esc24: https://github.com/esc24
+.. _@HGWright: https://github.com/HGWright
 .. _@jamesp: https://github.com/jamesp
 .. _@jonseddon: https://github.com/jonseddon
 .. _@jvegasbsc: https://github.com/jvegasbsc
diff --git a/docs/src/community/index.rst b/docs/src/community/index.rst
new file mode 100644
index 0000000000..114cb96fe9
--- /dev/null
+++ b/docs/src/community/index.rst
@@ -0,0 +1,58 @@
+.. include:: ../common_links.inc
+
+.. todo:
+    consider scientific-python.org
+    consider scientific-python.org/specs/
+
+Iris in the Community
+=====================
+
+Iris aims to be a valuable member of the open source scientific Python
+community.
+
+We listen out for developments in our dependencies and neighbouring projects,
+and we reach out to them when we can solve problems together; please feel free
+to reach out to us!
+
+We are aware of our place in the user's wider 'toolbox' - offering unique
+functionality and interoperating smoothly with other packages.
+
+We welcome contributions from all; whether that's an opinion, a 1-line
+clarification, or a whole new feature 🙂
+
+Quick Links
+-----------
+
+* `GitHub Discussions`_
+* :ref:`Getting involved<development_where_to_start>`
+* `Twitter <https://twitter.com/scitools_iris>`_
+
+Interoperability
+----------------
+
+There's a big choice of Python tools out there! Each one has strengths and
+weaknesses in different areas, so we don't want to force a single choice for your
+whole workflow - we'd much rather make it easy for you to choose the right tool
+for the moment, switching whenever you need. Below are our ongoing efforts at
+smoother interoperability:
+
+.. not using toctree due to combination of child pages and cross-references.
+
+* The :mod:`iris.pandas` module
+* :doc:`iris_xarray`
+
+.. toctree::
+   :maxdepth: 1
+   :hidden:
+
+   iris_xarray
+
+Plugins
+-------
+
+Iris can be extended with **plugins**!  See below for further information:
+
+.. toctree::
+   :maxdepth: 2
+
+   plugins
diff --git a/docs/src/community/iris_xarray.rst b/docs/src/community/iris_xarray.rst
new file mode 100644
index 0000000000..859597da78
--- /dev/null
+++ b/docs/src/community/iris_xarray.rst
@@ -0,0 +1,154 @@
+.. include:: ../common_links.inc
+
+======================
+Iris ❤️ :term:`Xarray`
+======================
+
+There is a lot of overlap between Iris and :term:`Xarray`, but some important
+differences too. Below is a summary of the most important differences, so that
+you can be prepared, and to help you choose the best package for your use case.
+
+Overall Experience
+------------------
+
+Iris is the more specialised package, focussed on making it as easy
+as possible to work with meteorological and climatological data. Iris
+is built to natively handle many key concepts, such as the CF conventions,
+coordinate systems and bounded coordinates. Iris offers a smaller toolkit of
+operations compared to Xarray, particularly around API for sophisticated
+computation such as array manipulation and multi-processing.
+
+Xarray's more generic data model and community-driven development give it a
+richer range of operations and broader possible uses. Using Xarray
+specifically for meteorology/climatology may require deeper knowledge
+compared to using Iris, and you may prefer to add Xarray plugins
+such as :ref:`cfxarray` to get the best experience. Advanced users can likely
+achieve better performance with Xarray than with Iris.
+
+Conversion
+----------
+There are multiple ways to convert between Iris and Xarray objects.
+
+* Xarray includes the :meth:`~xarray.DataArray.to_iris` and
+  :meth:`~xarray.DataArray.from_iris` methods - detailed in the
+  `Xarray IO notes on Iris`_. Since Iris evolves independently of Xarray, be
+  vigilant for concepts that may be lost during the conversion.
+* Because both packages are closely linked to the :term:`NetCDF Format`, it is
+  feasible to save a NetCDF file using one package then load that file using
+  the other package. This will be lossy in places, as both Iris and Xarray
+  are opinionated on how certain NetCDF concepts relate to their data models.
+* The Iris development team are exploring an improved 'bridge' between the two
+  packages. Follow the conversation on GitHub: `iris#4994`_. This project is
+  expressly intended to be as lossless as possible.
+
+Regridding
+----------
+Iris and Xarray offer a range of regridding methods - both natively and via
+additional packages such as `iris-esmf-regrid`_ and `xESMF`_ - which overlap
+in places
+but tend to cover a different set of use cases (e.g. Iris handles unstructured
+meshes but offers access to fewer ESMF methods). The behaviour of these
+regridders also differs slightly (even between different regridders attached to
+the same package) so the appropriate package to use depends highly on the
+particulars of the use case.
+
+Plotting
+--------
+Xarray and Iris have a large overlap of functionality when creating
+:term:`Matplotlib` plots and both support the plotting of multidimensional
+coordinates. This means the experience is largely similar using either package.
+
+Xarray supports further plotting backends through external packages (e.g. Bokeh through `hvPlot`_)
+and, if a user is already familiar with `pandas`_, the interface should be
+familiar. It also supports some different plot types to Iris, and therefore can
+be used for a wider variety of plots. It also has benefits regarding "out of
+the box", quick customisations to plots. However, if further customisation is
+required, knowledge of matplotlib is still required.
+
+In both cases, :term:`Cartopy` is/can be used. Iris does more work
+automatically for the user here, creating Cartopy
+:class:`~cartopy.mpl.geoaxes.GeoAxes` for latitude and longitude coordinates,
+whereas the user has to do this manually in Xarray.
+
+Statistics
+----------
+Both libraries are quite comparable with generally similar capabilities,
+performance and laziness. Iris offers more specificity in some cases, such as
+some more specific unique functions and masked tolerance in most statistics.
+Xarray seems more approachable however, with some less unique but more
+convenient solutions (these tend to be wrappers to :term:`Dask` functions).
+
+Laziness and Multi-Processing with :term:`Dask`
+-----------------------------------------------
+Iris and Xarray both support lazy data and out-of-core processing through
+utilisation of Dask.
+
+While both Iris and Xarray expose :term:`NumPy` conveniences at the API level
+(e.g. the `ndim()` method), only Xarray exposes Dask conveniences. For example
+:attr:`xarray.DataArray.chunks`, which gives the user direct control
+over the underlying Dask array chunks. The Iris API instead takes control of
+such concepts and user control is only possible by manipulating the underlying
+Dask array directly (accessed via :meth:`iris.cube.Cube.core_data`).
+
+:class:`xarray.DataArray`\ s comply with `NEP-18`_, allowing NumPy arrays to be
+based on them, and they also include the necessary extra members for Dask
+arrays to be based on them too. Neither of these is currently possible with
+Iris :class:`~iris.cube.Cube`\ s, although an ambition for the future.
+
+NetCDF File Control
+-------------------
+(More info: :term:`NetCDF Format`)
+
+Unlike Iris, Xarray generally provides full control of major file structures,
+i.e. dimensions + variables, including their order in the file.  It mostly
+respects these in a file input, and can reproduce them on output.
+However, attribute handling is not so complete: like Iris, it interprets and
+modifies some recognised aspects, and can add some extra attributes not in the
+input.
+
+.. todo:
+    More detail on dates and fill values (@pp-mo suggestion).
+
+Handling of dates and fill values have some special problems here.
+
+Ultimately, nearly everything wanted in a particular desired result file can
+be achieved in Xarray, via provided override mechanisms (`loading keywords`_
+and the '`encoding`_' dictionaries).
+
+Missing Data
+------------
+Xarray uses :data:`numpy.nan` to represent missing values and this will support
+many simple use cases assuming the data are floats. Iris enables more
+sophisticated missing data handling by representing missing values as masks
+(:class:`numpy.ma.MaskedArray` for real data and :class:`dask.array.Array`
+for lazy data) which allows data to be any data type and to include either/both
+a mask and :data:`~numpy.nan`\ s.
+
+.. _cfxarray:
+
+`cf-xarray`_
+-------------
+Iris has a data model entirely based on :term:`CF Conventions`. Xarray has a
+data model based on :term:`NetCDF Format` with cf-xarray acting as translation
+into CF. Xarray/cf-xarray methods can be
+called and data accessed with CF like arguments (e.g. axis, standard name) and
+there are some CF specific utilities (similar
+to Iris utilities). Iris tends to cover more of and be stricter about CF.
+
+
+.. seealso::
+
+    * `Xarray IO notes on Iris`_
+    * `Xarray notes on other NetCDF libraries`_
+
+.. _Xarray IO notes on Iris: https://docs.xarray.dev/en/stable/user-guide/io.html#iris
+.. _Xarray notes on other NetCDF libraries: https://docs.xarray.dev/en/stable/getting-started-guide/faq.html#what-other-netcdf-related-python-libraries-should-i-know-about
+.. _loading keywords: https://docs.xarray.dev/en/stable/generated/xarray.open_dataset.html#xarray.open_dataset
+.. _encoding: https://docs.xarray.dev/en/stable/user-guide/io.html#writing-encoded-data
+.. _xESMF: https://github.com/pangeo-data/xESMF/
+.. _seaborn: https://seaborn.pydata.org/
+.. _hvPlot: https://hvplot.holoviz.org/
+.. _pandas: https://pandas.pydata.org/
+.. _NEP-18: https://numpy.org/neps/nep-0018-array-function-protocol.html
+.. _cf-xarray: https://github.com/xarray-contrib/cf-xarray
+.. _iris#4994: https://github.com/SciTools/iris/issues/4994
diff --git a/docs/src/community/plugins.rst b/docs/src/community/plugins.rst
new file mode 100644
index 0000000000..0d79d64623
--- /dev/null
+++ b/docs/src/community/plugins.rst
@@ -0,0 +1,68 @@
+.. _namespace package: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/
+
+.. _community_plugins:
+
+Plugins
+=======
+
+Iris supports **plugins** under the ``iris.plugins`` `namespace package`_.
+This allows packages that extend Iris' functionality to be developed and
+maintained independently, while still being installed into ``iris.plugins``
+instead of a separate package.  For example, a plugin may provide loaders or
+savers for additional file formats, or alternative visualisation methods.
+
+
+Using plugins
+-------------
+
+Once a plugin is installed, it can be used either via the
+:func:`iris.use_plugin` function, or by importing it directly:
+
+.. code-block:: python
+
+    import iris
+
+    iris.use_plugin("my_plugin")
+    # OR
+    import iris.plugins.my_plugin
+
+
+Creating plugins
+----------------
+
+The choice of a `namespace package`_ makes writing a plugin relatively
+straightforward: it simply needs to appear as a folder within ``iris/plugins``,
+then can be distributed in the same way as any other package.  An example
+repository layout:
+
+.. code-block:: text
+
+    + lib
+      + iris
+        + plugins
+          + my_plugin
+            - __init__.py
+            - (more code...)
+    - README.md
+    - pyproject.toml
+    - setup.cfg
+    - (other project files...)
+
+In particular, note that there must **not** be any ``__init__.py`` files at
+higher levels than the plugin itself.
+
+The package name - how it is referred to by PyPI/conda, specified by
+``metadata.name`` in ``setup.cfg`` - is recommended to include both "iris" and
+the plugin name.  Continuing this example, its ``setup.cfg`` should include, at
+minimum:
+
+.. code-block:: ini
+
+    [metadata]
+    name = iris-my-plugin
+
+    [options]
+    packages = find_namespace:
+
+    [options.packages.find]
+    where = lib
diff --git a/docs/src/conf.py b/docs/src/conf.py
index 33864c4658..576a099b90 100644
--- a/docs/src/conf.py
+++ b/docs/src/conf.py
@@ -158,8 +158,6 @@ def _dotv(version):
     "sphinx_copybutton",
     "sphinx.ext.napoleon",
     "sphinx_panels",
-    # TODO: Spelling extension disabled until the dependencies can be included
-    # "sphinxcontrib.spelling",
     "sphinx_gallery.gen_gallery",
     "matplotlib.sphinxext.mathmpl",
     "matplotlib.sphinxext.plot_directive",
@@ -193,16 +191,6 @@ def _dotv(version):
 napoleon_use_keyword = True
 napoleon_custom_sections = None
 
-# -- spellingextension --------------------------------------------------------
-# See https://sphinxcontrib-spelling.readthedocs.io/en/latest/customize.html
-spelling_lang = "en_GB"
-# The lines in this file must only use line feeds (no carriage returns).
-spelling_word_list_filename = ["spelling_allow.txt"]
-spelling_show_suggestions = False
-spelling_show_whole_line = False
-spelling_ignore_importable_modules = True
-spelling_ignore_python_builtins = True
-
 # -- copybutton extension -----------------------------------------------------
 # See https://sphinx-copybutton.readthedocs.io/en/latest/
 copybutton_prompt_text = r">>> |\.\.\. "
@@ -235,6 +223,7 @@ def _dotv(version):
     "python": ("https://docs.python.org/3/", None),
     "scipy": ("https://docs.scipy.org/doc/scipy/", None),
     "pandas": ("https://pandas.pydata.org/docs/", None),
+    "dask": ("https://docs.dask.org/en/stable/", None),
 }
 
 # The name of the Pygments (syntax highlighting) style to use.
@@ -310,6 +299,9 @@ def _dotv(version):
     ],
     "use_edit_page_button": True,
     "show_toc_level": 1,
+    # Omitted `theme-switcher` below to disable it
+    # Info: https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/light-dark.html#configure-default-theme-mode
+    "navbar_end": ["navbar-icon-links"],
 }
 
 rev_parse = run(["git", "rev-parse", "--short", "HEAD"], capture_output=True)
@@ -321,6 +313,9 @@ def _dotv(version):
     "github_user": "scitools",
     "github_version": "main",
     "doc_path": "docs/src",
+    # default theme.  Also disabled the button in the html_theme_options.
+    # Info: https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/light-dark.html#configure-default-theme-mode
+    "default_mode": "light",
     # custom
     "on_rtd": on_rtd,
     "rtd_version": rtd_version,
diff --git a/docs/src/developers_guide/contributing_documentation_full.rst b/docs/src/developers_guide/contributing_documentation_full.rst
index ac62a67373..a470def683 100755
--- a/docs/src/developers_guide/contributing_documentation_full.rst
+++ b/docs/src/developers_guide/contributing_documentation_full.rst
@@ -61,7 +61,10 @@ If you wish to run a full clean build you can run::
     make clean
     make html
 
-This is useful for a final test before committing your changes.
+This is useful for a final test before committing your changes. Having built
+the documentation, you can view them in your default browser via::
+
+    make show
 
 .. note:: In order to preserve a clean build for the html, all **warnings**
           have been promoted to be **errors** to ensure they are addressed.
@@ -84,6 +87,8 @@ pattern matching, e.g.::
 
     pytest -v -k plot_coriolis docs/gallery_tests/test_gallery_examples.py
 
+If a gallery test fails, follow the instructions in :ref:`testing.graphics`.
+
 The ``make`` commands shown below can be run in the ``docs`` or ``docs/src``
 directory.
 
@@ -108,18 +113,6 @@ adding it to the ``linkcheck_ignore`` array that is defined in the
 If this fails check the output for the text **broken** and then correct
 or ignore the url.
 
-.. comment
-    Finally, the spelling in the documentation can be checked automatically via the
-    command::
-
-        make spelling
-
-    The spelling check may pull up many technical abbreviations and acronyms.  This
-    can be managed by using an **allow** list in the form of a file.  This file,
-    or list of files is set in the `conf.py`_ using the string list
-    ``spelling_word_list_filename``.
-
-
 .. note:: In addition to the automated `Iris GitHub Actions`_ build of all the
           documentation build options above, the
           https://readthedocs.org/ service is also used.  The configuration
@@ -162,13 +155,13 @@ The code for the gallery entries are in ``docs/gallery_code``.
 Each sub directory in this directory is a sub section of the gallery.  The
 respective ``README.rst`` in each folder is included in the gallery output.
 
-For each gallery entry there must be a corresponding test script located in
-``docs/gallery_tests``.
-
 To add an entry to the gallery simple place your python code into the
 appropriate sub directory and name it with a prefix of ``plot_``.  If your
 gallery entry does not fit into any existing sub directories then create a new
-directory and place it in there.
+directory and place it in there.  A test for the gallery entry will be
+automatically generated (see Testing_ for how to run it).  To add a new
+reference image for this test, follow the instructions in
+:ref:`testing.graphics`.
 
 The reStructuredText (rst) output of the gallery is located in
 ``docs/src/generated/gallery``.
diff --git a/docs/src/developers_guide/gitwash/forking.rst b/docs/src/developers_guide/gitwash/forking.rst
index 247e3cf678..baeb243c86 100644
--- a/docs/src/developers_guide/gitwash/forking.rst
+++ b/docs/src/developers_guide/gitwash/forking.rst
@@ -18,7 +18,7 @@ Set up and Configure a Github Account
 If you don't have a github account, go to the github page, and make one.
 
 You then need to configure your account to allow write access, see
-the `generating sss keys for GitHub`_ help on `github help`_.
+the `generating ssh keys for GitHub`_ help on `github help`_.
 
 
 Create Your own Forked Copy of Iris
diff --git a/docs/src/developers_guide/gitwash/set_up_fork.rst b/docs/src/developers_guide/gitwash/set_up_fork.rst
index d5c5bc5c44..5318825488 100644
--- a/docs/src/developers_guide/gitwash/set_up_fork.rst
+++ b/docs/src/developers_guide/gitwash/set_up_fork.rst
@@ -15,7 +15,7 @@ Overview
 
    git clone git@github.com:your-user-name/iris.git
    cd iris
-   git remote add upstream git://github.com/SciTools/iris.git
+   git remote add upstream git@github.com/SciTools/iris.git
 
 In Detail
 =========
diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst
index de7aa6c719..bae77a7d21 100644
--- a/docs/src/developers_guide/release.rst
+++ b/docs/src/developers_guide/release.rst
@@ -277,6 +277,11 @@ Post Release Steps
 #. On main, make a new ``latest.rst`` from ``latest.rst.template`` and update
    the include statement and the toctree in ``index.rst`` to point at the new
    ``latest.rst``.
+#. Consider updating ``docs/src/userguide/citation.rst`` on ``main`` to include 
+   the version number, date and `Zenodo DOI <https://doi.org/10.5281/zenodo.595182>`_ 
+   of the new release. Ideally this would be updated before the release, but 
+   the DOI for the new version is only available once the release has been 
+   created in GitHub.
 
 
 .. _SciTools/iris: https://github.com/SciTools/iris
diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst
index de1afb15af..4c55047d4c 100644
--- a/docs/src/further_topics/metadata.rst
+++ b/docs/src/further_topics/metadata.rst
@@ -389,10 +389,10 @@ instances. Normally, this would cause issues. For example,
 
 .. doctest:: richer-metadata
 
-    >>> simply = {"one": np.int(1), "two": np.array([1.0, 2.0])}
+    >>> simply = {"one": np.int32(1), "two": np.array([1.0, 2.0])}
     >>> simply
     {'one': 1, 'two': array([1., 2.])}
-    >>> fruity = {"one": np.int(1), "two": np.array([1.0, 2.0])}
+    >>> fruity = {"one": np.int32(1), "two": np.array([1.0, 2.0])}
     >>> fruity
     {'one': 1, 'two': array([1., 2.])}
     >>> simply == fruity
@@ -419,7 +419,7 @@ However, metadata class equality is rich enough to handle this eventuality,
 
     >>> metadata1
     CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'one': 1, 'two': array([1., 2.])}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))
-    >>> metadata2 = cube.metadata._replace(attributes={"one": np.int(1), "two": np.array([1000.0, 2000.0])})
+    >>> metadata2 = cube.metadata._replace(attributes={"one": np.int32(1), "two": np.array([1000.0, 2000.0])})
     >>> metadata2
     CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'one': 1, 'two': array([1000., 2000.])}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),))
     >>> metadata1 == metadata2
diff --git a/docs/src/further_topics/ugrid/images/orca_grid.png b/docs/src/further_topics/ugrid/images/orca_grid.png
new file mode 100644
index 0000000000..6676e84fbb
Binary files /dev/null and b/docs/src/further_topics/ugrid/images/orca_grid.png differ
diff --git a/docs/src/further_topics/ugrid/other_meshes.rst b/docs/src/further_topics/ugrid/other_meshes.rst
index e6f477624e..38abeeca03 100644
--- a/docs/src/further_topics/ugrid/other_meshes.rst
+++ b/docs/src/further_topics/ugrid/other_meshes.rst
@@ -221,5 +221,140 @@ as the **nodes** when creating the Iris
                     <AuxCoord: longitude / (degrees)  [...]  shape(666328,)>
                     <AuxCoord: latitude / (degrees)  [...]  shape(666328,)>
 
+
+.. _ORCA_example:
+
+`NEMO`_ data on ORCA tripolar grid
+----------------------------------
+..  figure:: images/orca_grid.png
+    :width: 300
+    :alt: Plot of ORCA-gridded data from NEMO.
+
+NEMO can use various grids, but is frequently used with ORCA type grids.
+ORCA grids store global data in 2-dimensional ny * nx arrays.  All cells are
+four-sided.  The grids are based on tri-polar layouts, but X and Y spacings are
+irregular and not given by any defined functional forms.
+
+* arrays (ny, nx) of face-located data variables
+* arrays (ny, nx) of X+Y face centre coordinates
+* arrays (ny, nx, 4) of X+Y face corner coordinates
+  (all faces are quadrilaterals)
+
+For simplicity, we treat each face corner as an independent node, and use a face-node
+connectivity which simply lists the nodes in sequence,
+i.e. [[0, 1, 2, 3], [4, 5, 6, 7], ...].
+
+.. Note::
+    This is the simplest solution, but produces approx 4x more nodes than
+    necessary, since the coordinate bounds contain many duplicate locations.
+    Removing the duplicates is quite easy, but often not necessary.
+
+To make an unstructured cube, the data must be 'flattened' to convert the given X and Y
+dimensions into a single mesh dimension.  Since Iris cubes don't support a "reshape" or
+"flatten" operations, we create a new cube from the flattened data.
+
+.. dropdown:: :opticon:`code`
+
+    .. code-block:: python
+
+        >>> import numpy as np
+        >>> import iris
+        >>> from iris.coords import AuxCoord, CellMeasure
+        >>> from iris.cube import Cube
+        >>> from iris.experimental.ugrid.mesh import Mesh, Connectivity
+
+
+        >>> filepath = iris.sample_data_path('orca2_votemper.nc')
+        >>> cube = iris.load_cube(filepath)
+        >>> print(cube)
+        sea_water_potential_temperature / (degC) (-- : 148; -- : 180)
+            Auxiliary coordinates:
+                latitude                             x         x
+                longitude                            x         x
+            Scalar coordinates:
+                depth                            4.999938 m, bound=(0.0, 10.0) m
+                time                             0001-01-01 12:00:00
+            Cell methods:
+                mean                             time
+            Attributes:
+                Conventions                      'CF-1.5'
+
+
+        >>> co_x =  cube.coord("longitude")
+        >>> co_y = cube.coord("latitude")
+        >>> ny, nx = co_x.shape
+        >>> n_faces = ny * nx
+
+        >>> # Create face coords from flattened face-points
+        >>> face_x_co = AuxCoord(co_x.points.flatten())
+        >>> face_y_co = AuxCoord(co_y.points.flatten())
+        >>> assert face_x_co.shape == (n_faces,)
+        >>> face_x_co.metadata = co_x.metadata
+        >>> face_y_co.metadata = co_y.metadata
+
+        >>> # Create node coordinates from bound points.
+        >>> n_nodes = n_faces * 4
+        >>> node_x_co = AuxCoord(co_x.bounds.flatten())
+        >>> node_y_co = AuxCoord(co_y.bounds.flatten())
+        >>> assert node_x_co.shape == (n_nodes,)
+        >>> node_x_co.metadata = co_x.metadata
+        >>> node_y_co.metadata = co_y.metadata
+
+        >>> # Create a face-node Connectivity matching the order of nodes in the bounds array
+        >>> face_node_inds = np.arange(n_nodes).reshape((n_faces, 4))
+        >>> face_nodes_conn = Connectivity(
+        ...     indices=face_node_inds,
+        ...     cf_role='face_node_connectivity',
+        ...     long_name='face_inds', units='1',
+        ... )
+
+        >>> # Create a mesh object.
+        >>> mesh = Mesh(
+        ...     topology_dimension=2,
+        ...     node_coords_and_axes=[(node_x_co, 'x'), (node_y_co, 'y')],
+        ...     connectivities=face_nodes_conn,
+        ...     face_coords_and_axes=[(face_x_co, 'x'), (face_y_co, 'y')]
+        ... )
+        >>> print(mesh)
+        Mesh : 'unknown'
+            topology_dimension: 2
+            node
+                node_dimension: 'Mesh2d_node'
+                node coordinates
+                    <AuxCoord: longitude / (degrees)  [...]  shape(106560,)>
+                    <AuxCoord: latitude / (degrees)  [...]  shape(106560,)>
+            face
+                face_dimension: 'Mesh2d_face'
+                face_node_connectivity: <Connectivity: face_inds / (1)  [...]  shape(26640, 4)>
+                face coordinates
+                    <AuxCoord: longitude / (degrees)  [...]  shape(26640,)>
+                    <AuxCoord: latitude / (degrees)  [...]  shape(26640,)>
+
+
+        >>> # Create an unstructured version of the input with flattened data
+        >>> meshcube = Cube(cube.core_data().flatten())
+        >>> meshcube.metadata = cube.metadata
+
+        >>> # Attach the mesh by adding the mesh 'face' MeshCoords into the cube
+        >>> mesh_dim = meshcube.ndim - 1
+        >>> for co in mesh.to_MeshCoords('face'):
+        ...     meshcube.add_aux_coord(co, mesh_dim)
+        ...
+
+        >>> print(meshcube)
+        sea_water_potential_temperature / (degC) (-- : 26640)
+            Mesh coordinates:
+                latitude                             x
+                longitude                            x
+            Mesh:
+                name                             unknown
+                location                         face
+            Cell methods:
+                mean                             time
+            Attributes:
+                Conventions                      'CF-1.5'
+
+
 .. _WAVEWATCH III: https://github.com/NOAA-EMC/WW3
 .. _FESOM 1.4: https://fesom.de/models/fesom14/
+.. _NEMO: https://www.nemo-ocean.eu/
\ No newline at end of file
diff --git a/docs/src/further_topics/ugrid/partner_packages.rst b/docs/src/further_topics/ugrid/partner_packages.rst
index 8e36f4ffc2..75b54b037f 100644
--- a/docs/src/further_topics/ugrid/partner_packages.rst
+++ b/docs/src/further_topics/ugrid/partner_packages.rst
@@ -1,3 +1,5 @@
+.. include:: ../../common_links.inc
+
 .. _ugrid partners:
 
 Iris' Mesh Partner Packages
@@ -97,4 +99,3 @@ Applications
 
 .. _GeoVista: https://github.com/bjlittle/geovista
 .. _PyVista: https://docs.pyvista.org/index.html
-.. _iris-esmf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid
diff --git a/docs/src/index.rst b/docs/src/index.rst
index b9f7faaa03..531c0e0b26 100644
--- a/docs/src/index.rst
+++ b/docs/src/index.rst
@@ -88,6 +88,8 @@ Icons made by `FreePik <https://www.freepik.com>`_ from
 `Flaticon <https://www.flaticon.com/>`_
 
 
+.. _iris_support:
+
 Support
 ~~~~~~~
 
@@ -101,7 +103,11 @@ The legacy support resources:
 
 * `Users Google Group <https://groups.google.com/forum/#!forum/scitools-iris>`_
 * `Developers Google Group <https://groups.google.com/forum/#!forum/scitools-iris-dev>`_
-* `Legacy Documentation`_ (Iris 2.4 or earlier)
+* `Legacy Documentation`_ (Iris 2.4 or earlier).  This is an archive of zip
+  files of past documentation.  You can download, unzip and view the
+  documentation locally (index.html).  There may be some incorrect rendering
+  and older javascvript (.js) files may show a warning when uncompressing, in
+  which case we suggest you use a different unzip tool.
 
 
 .. toctree::
@@ -130,6 +136,15 @@ The legacy support resources:
    developers_guide/contributing_getting_involved
 
 
+.. toctree::
+   :caption: Community
+   :maxdepth: 1
+   :name: community_index
+   :hidden:
+
+   Community <community/index>
+
+
 .. toctree::
    :caption: Iris API
    :maxdepth: 1
diff --git a/docs/src/installing.rst b/docs/src/installing.rst
index 6a2d2f6131..b2481973c0 100644
--- a/docs/src/installing.rst
+++ b/docs/src/installing.rst
@@ -14,7 +14,7 @@ Subsystem for Linux).  This is a great option to get started with Iris
 for users and developers.  Be aware that we do not currently test against
 any WSL_ distributions.
 
-.. _WSL: https://docs.microsoft.com/en-us/windows/wsl/install-win10
+.. _WSL: https://learn.microsoft.com/en-us/windows/wsl/install
 
 .. note:: Iris is currently supported and tested against |python_support|
           running on Linux.  We do not currently actively test on other
diff --git a/docs/src/spelling_allow.txt b/docs/src/spelling_allow.txt
deleted file mode 100644
index ed883ac3bf..0000000000
--- a/docs/src/spelling_allow.txt
+++ /dev/null
@@ -1,361 +0,0 @@
-Admin
-Albers
-Arakawa
-Arg
-Args
-Autoscale
-Biggus
-CF
-CI
-Cartopy
-Checklist
-Color
-Conda
-Constraining
-DAP
-Dask
-Debian
-Duchon
-EO
-Eos
-Exner
-Fieldsfile
-Fieldsfiles
-FillValue
-Gb
-GeogCS
-Hovmoller
-Jul
-Jun
-Jupyter
-Lanczos
-Mappables
-Matplotlib
-Mb
-Modeling
-Mollweide
-NetCDF
-Nino
-PPfield
-PPfields
-Perez
-Proj
-Quickplot
-Regrids
-Royer
-Scitools
-Scitools
-Sep
-Stehfest
-Steroegraphic
-Subsetting
-TestCodeFormat
-TestLicenseHeaders
-Torvalds
-Trans
-Trenberth
-Tri
-URIs
-URLs
-Ubuntu
-Ugrid
-Unidata
-Vol
-Vuuren
-Workflow
-Yury
-Zaytsev
-Zorder
-abf
-abl
-advection
-aggregator
-aggregators
-alphap
-ancils
-antimeridian
-ap
-arg
-args
-arithmetic
-arraylike
-atol
-auditable
-aux
-basemap
-behaviour
-betap
-bhulev
-biggus
-blev
-boolean
-boundpoints
-branchname
-broadcastable
-bugfix
-bugfixes
-builtin
-bulev
-carrée
-cartesian
-celsius
-center
-centrepoints
-cf
-cftime
-chunksizes
-ci
-clabel
-cmap
-cmpt
-codebase
-color
-colorbar
-colorbars
-complevel
-conda
-config
-constraining
-convertor
-coord
-coords
-cs
-datafiles
-datatype
-datetime
-datetimes
-ddof
-deepcopy
-deprecations
-der
-dewpoint
-dict
-dicts
-diff
-discontiguities
-discontiguous
-djf
-docstring
-docstrings
-doi
-dom
-dropdown
-dtype
-dtypes
-dx
-dy
-edgecolor
-endian
-endianness
-equirectangular
-eta
-etc
-fh
-fieldsfile
-fieldsfiles
-fileformat
-fileformats
-filename
-filenames
-filepath
-filespec
-fullname
-func
-geolocations
-github
-gregorian
-grib
-gribapi
-gridcell
-griddata
-gridlines
-hPa
-hashable
-hindcast
-hyperlink
-hyperlinks
-idiff
-ieee
-ifunc
-imagehash
-inc
-init
-inline
-inplace
-int
-interable
-interpolator
-ints
-io
-isosurfaces
-iterable
-jja
-jupyter
-kwarg
-kwargs
-landsea
-lat
-latlon
-latlons
-lats
-lbcode
-lbegin
-lbext
-lbfc
-lbft
-lblrec
-lbmon
-lbmond
-lbnrec
-lbrsvd
-lbtim
-lbuser
-lbvc
-lbyr
-lbyrd
-lh
-lhs
-linewidth
-linted
-linting
-lon
-lons
-lt
-mam
-markup
-matplotlib
-matplotlibrc
-max
-mdtol
-meaned
-mercator
-metadata
-min
-mpl
-nanmask
-nc
-ndarray
-neighbor
-ness
-netCDF
-netcdf
-netcdftime
-nimrod
-np
-nsigma
-numpy
-nx
-ny
-online
-orog
-paramId
-params
-parsable
-pcolormesh
-pdf
-placeholders
-plugin
-png
-proj
-ps
-pseudocolor
-pseudocolour
-pseudocoloured
-py
-pyplot
-quickplot
-rST
-rc
-rd
-reST
-reStructuredText
-rebase
-rebases
-rebasing
-regrid
-regridded
-regridder
-regridders
-regridding
-regrids
-rel
-repo
-repos
-reprojecting
-rh
-rhs
-rst
-rtol
-scipy
-scitools
-seekable
-setup
-sines
-sinh
-spec
-specs
-src
-ssh
-st
-stashcode
-stashcodes
-stats
-std
-stdout
-str
-subcube
-subcubes
-submodule
-submodules
-subsetting
-sys
-tanh
-tb
-testcases
-tgt
-th
-timepoint
-timestamp
-timesteps
-todo
-tol
-tos
-traceback
-travis
-tripolar
-tuple
-tuples
-txt
-udunits
-ufunc
-ugrid
-ukmo
-un
-unhandled
-unicode
-unittest
-unrotate
-unrotated
-uris
-url
-urls
-util
-var
-versioning
-vmax
-vmin
-waypoint
-waypoints
-whitespace
-wildcard
-wildcards
-windspeeds
-withnans
-workflow
-workflows
-xN
-xx
-xxx
-zeroth
-zlev
-zonal
diff --git a/docs/src/userguide/citation.rst b/docs/src/userguide/citation.rst
index 0a3a85fb89..1498b9dfe1 100644
--- a/docs/src/userguide/citation.rst
+++ b/docs/src/userguide/citation.rst
@@ -15,11 +15,12 @@ For example::
 
  @manual{Iris,
  author = {{Met Office}},
- title = {Iris: A Python package for analysing and visualising meteorological and oceanographic data sets},
- edition = {v1.2},
- year = {2010 - 2013},
+ title = {Iris: A powerful, format-agnostic, and community-driven Python package for analysing and visualising Earth science data },
+ edition = {v3.4},
+ year = {2010 - 2022},
  address = {Exeter, Devon },
- url = {http://scitools.org.uk/}
+ url = {http://scitools.org.uk/},
+ doi = {10.5281/zenodo.7386117}
  }
 
 
@@ -33,7 +34,7 @@ Suggested format::
 
 For example::
 
- Iris. v1.2. 28-Feb-2013. Met Office. UK. https://github.com/SciTools/iris/archive/v1.2.0.tar.gz 01-03-2013
+ Iris. v3.4. 1-Dec-2022. Met Office. UK. https://doi.org/10.5281/zenodo.7386117 22-12-2022
 
 
 ********************
@@ -46,7 +47,7 @@ Suggested format::
 
 For example::
 
- Iris. Met Office. git@github.com:SciTools/iris.git 06-03-2013
+ Iris. Met Office. git@github.com:SciTools/iris.git 22-12-2022
 
 .. _How to cite and describe software: https://software.ac.uk/how-cite-software
 
diff --git a/docs/src/userguide/cube_maths.rst b/docs/src/userguide/cube_maths.rst
index fe9a5d63d2..56a2041bd3 100644
--- a/docs/src/userguide/cube_maths.rst
+++ b/docs/src/userguide/cube_maths.rst
@@ -5,8 +5,8 @@ Cube Maths
 ==========
 
 
-The section :doc:`navigating_a_cube` highlighted that 
-every cube has a data attribute; 
+The section :doc:`navigating_a_cube` highlighted that
+every cube has a data attribute;
 this attribute can then be manipulated directly::
 
    cube.data -= 273.15
@@ -37,7 +37,7 @@ Let's load some air temperature which runs from 1860 to 2100::
     filename = iris.sample_data_path('E1_north_america.nc')
     air_temp = iris.load_cube(filename, 'air_temperature')
 
-We can now get the first and last time slices using indexing 
+We can now get the first and last time slices using indexing
 (see :ref:`cube_indexing` for a reminder)::
 
     t_first = air_temp[0, :, :]
@@ -50,8 +50,8 @@ We can now get the first and last time slices using indexing
     t_first = air_temp[0, :, :]
     t_last = air_temp[-1, :, :]
 
-And finally we can subtract the two. 
-The result is a cube of the same size as the original two time slices, 
+And finally we can subtract the two.
+The result is a cube of the same size as the original two time slices,
 but with the data representing their difference:
 
     >>> print(t_last - t_first)
@@ -70,8 +70,8 @@ but with the data representing their difference:
 
 .. note::
 
-    Notice that the coordinates "time" and "forecast_period" have been removed 
-    from the resultant cube; 
+    Notice that the coordinates "time" and "forecast_period" have been removed
+    from the resultant cube;
     this is because these coordinates differed between the two input cubes.
 
 
@@ -165,18 +165,24 @@ broadcasting behaviour::
     >>> print(result.summary(True))
     unknown / (K)                       (time: 240; latitude: 37; longitude: 49)
 
+
+.. seealso::
+
+    Relevant gallery example:
+    :ref:`sphx_glr_generated_gallery_general_plot_anomaly_log_colouring.py` (Anomaly)
+
 Combining Multiple Phenomena to Form a New One
 ----------------------------------------------
 
-Combining cubes of potential-temperature and pressure we can calculate 
+Combining cubes of potential-temperature and pressure we can calculate
 the associated temperature using the equation:
 
 .. math::
-   
+
     T = \theta (\frac{p}{p_0}) ^ {(287.05 / 1005)}
 
-Where :math:`p` is pressure, :math:`\theta` is potential temperature, 
-:math:`p_0` is the potential temperature reference pressure 
+Where :math:`p` is pressure, :math:`\theta` is potential temperature,
+:math:`p_0` is the potential temperature reference pressure
 and :math:`T` is temperature.
 
 First, let's load pressure and potential temperature cubes::
@@ -185,7 +191,7 @@ First, let's load pressure and potential temperature cubes::
     phenomenon_names = ['air_potential_temperature', 'air_pressure']
     pot_temperature, pressure = iris.load_cubes(filename, phenomenon_names)
 
-In order to calculate :math:`\frac{p}{p_0}` we can define a coordinate which 
+In order to calculate :math:`\frac{p}{p_0}` we can define a coordinate which
 represents the standard reference pressure of 1000 hPa::
 
     import iris.coords
@@ -199,7 +205,7 @@ the :meth:`iris.coords.Coord.convert_units` method::
 
     p0.convert_units(pressure.units)
 
-Now we can combine all of this information to calculate the air temperature 
+Now we can combine all of this information to calculate the air temperature
 using the equation above::
 
     temperature = pot_temperature * ( (pressure / p0) ** (287.05 / 1005) )
@@ -213,12 +219,12 @@ The result could now be plotted using the guidance provided in the
 
 .. only:: html
 
-    A very similar example to this can be found in 
+    A very similar example to this can be found in
     :ref:`sphx_glr_generated_gallery_meteorology_plot_deriving_phenomena.py`.
 
 .. only:: latex
 
-    A very similar example to this can be found in the examples section, 
+    A very similar example to this can be found in the examples section,
     with the title "Deriving Exner Pressure and Air Temperature".
 
 .. _cube_maths_combining_units:
@@ -243,7 +249,7 @@ unit (if ``a`` had units ``'m2'`` then ``a ** 0.5`` would result in a cube
 with units ``'m'``).
 
 Iris inherits units from `cf_units <https://scitools.org.uk/cf-units/docs/latest/>`_
-which in turn inherits from `UDUNITS <https://www.unidata.ucar.edu/software/udunits/udunits-current/udunits2.html>`_.
+which in turn inherits from `UDUNITS <https://docs.unidata.ucar.edu/udunits/current/>`_.
 As well as the units UDUNITS provides, cf units also provides the units
 ``'no-unit'`` and ``'unknown'``. A unit of ``'no-unit'`` means that the
 associated data is not suitable for describing with a unit, cf units
diff --git a/docs/src/userguide/cube_statistics.rst b/docs/src/userguide/cube_statistics.rst
index 980f1e132f..08297c2a51 100644
--- a/docs/src/userguide/cube_statistics.rst
+++ b/docs/src/userguide/cube_statistics.rst
@@ -4,6 +4,11 @@
 Cube Statistics
 ===============
 
+.. seealso::
+
+    Relevant gallery example:
+    :ref:`sphx_glr_generated_gallery_general_plot_zonal_means.py` (Collapsing)
+
 .. _cube-statistics-collapsing:
 
 Collapsing Entire Data Dimensions
diff --git a/docs/src/userguide/glossary.rst b/docs/src/userguide/glossary.rst
new file mode 100644
index 0000000000..818ef0c7ad
--- /dev/null
+++ b/docs/src/userguide/glossary.rst
@@ -0,0 +1,210 @@
+.. _glossary:
+
+Glossary
+=============
+
+.. glossary::
+
+   Cartopy
+        A python package for producing maps, and other geospatial data.
+        Allows plotting on these maps, over a range of projections.
+
+        | **Related:** :term:`Matplotlib`
+        | **More information:** `CartoPy Site <https://scitools.org.uk/cartopy/docs/latest/>`_
+        |
+
+   CF Conventions
+        Rules for storing meteorological Climate and Forecast data in
+        :term:`NetCDF Format` files, defining a standard metadata format to
+        describe what the data is.
+        This also forms the data model which iris is based on.
+
+        | **Related:** :term:`NetCDF Format`
+        | **More information:** `CF Conventions <http://cfconventions.org/>`_
+        |
+
+   Coordinate
+        A container for data points, comes in three main flavours.
+
+        - Dimensional Coordinate -
+            A coordinate that describes a single data dimension of a cube.
+            They can only contain numerical values, in a sorted order (ascending
+            or descending).
+        - Auxiliary Coordinate -
+            A coordinate that can map to multiple data dimensions. Can
+            contain any type of data.
+        - Scalar Coordinate -
+                A coordinate that is not mapped to any data dimension, instead
+                representing the cube as a whole.
+
+        | **Related:** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Cube
+        Cubes are the main method of storing data in Iris. A cube can consist of:
+
+        - Array of :term:`Phenomenon` Data (Required)
+        - :term:`Coordinates <Coordinate>`
+        - :term:`Standard Name`
+        - :term:`Long Name`
+        - :term:`Unit`
+        - :term:`Cell Methods <Cell Method>`
+        - :term:`Coordinate Factories <Coordinate Factory>`
+
+        | **Related:** :term:`NumPy`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Cell Method
+        A cell method represents that a cube's data has been derived from
+        a past statistical operation, such as a
+        MEAN or SUM operation.
+
+        | **Related:** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Coordinate Factory
+        A coordinate factory derives coordinates (sometimes referred to as
+        derived coordinates) from the values of existing coordinates.
+        E.g. A hybrid height factory might use "height above sea level"
+        and "height at ground level" coordinate data to calculate a
+        "height above ground level" coordinate.
+
+        | **Related:** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+
+   Dask
+        A data analytics python library. Iris predominantly uses Dask Arrays;
+        a collection of NumPy-esque arrays. The data is operated in batches,
+        so that not all data is in RAM at once.
+
+        | **Related:** :term:`Lazy Data` **|** :term:`NumPy`
+        | **More information:** :doc:`real_and_lazy_data`
+        |
+
+   Fields File (FF) Format
+        A meteorological file format, the output of the Unified Model.
+
+        | **Related:**  :term:`GRIB Format`
+         **|** :term:`Post Processing (PP) Format` **|** :term:`NetCDF Format`
+        | **More information:** `Unified Model <https://www.metoffice.gov.uk/research/approach/modelling-systems/unified-model/index>`_
+        |
+
+   GRIB Format
+        A WMO-standard meteorological file format.
+
+        | **Related:** :term:`Fields File (FF) Format`
+         **|** :term:`Post Processing (PP) Format` **|** :term:`NetCDF Format`
+        | **More information:** `GRIB 1 User Guide <https://old.wmo.int/extranet/pages/prog/www/WMOCodes/Guides/GRIB/GRIB1-Contents.html>`_
+         **|** `GRIB 2 User Guide.pdf <https://old.wmo.int/extranet/pages/prog/www/WMOCodes/Guides/GRIB/GRIB2_062006.pdf>`_
+        |
+
+   Lazy Data
+        Data stored in hard drive, and then temporarily loaded into RAM in
+        batches when needed. Allows of less memory usage and faster performance,
+        thanks to parallel processing.
+
+        | **Related:** :term:`Dask` **|** :term:`Real Data`
+        | **More information:** :doc:`real_and_lazy_data`
+        |
+
+   Long Name
+        A name describing a :term:`phenomenon`, not limited to the
+        the same restraints as :term:`standard name`.
+
+        | **Related:** :term:`Standard Name` **|** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Matplotlib
+        A python package for plotting and projecting data in a wide variety
+        of formats.
+
+        | **Related:** :term:`CartoPy` **|** :term:`NumPy`
+        | **More information:** `Matplotlib <https://scitools.org.uk/cartopy/docs/latest/>`_
+        |
+
+   Metadata
+        The information which describes a phenomenon.
+        Within Iris specifically, all information which
+        distinguishes one phenomenon from another,
+        e.g. :term:`units <Unit>` or :term:`Cell Methods <Cell Method>`
+
+        | **Related:** :term:`Phenomenon` **|** :term:`Cube`
+        | **More information:** :doc:`../further_topics/metadata`
+        |
+
+   NetCDF Format
+        A flexible file format for storing multi-dimensional array-like data.
+        When Iris loads this format, it also especially recognises and interprets data
+        encoded according to the :term:`CF Conventions`.
+
+        | **Related:** :term:`Fields File (FF) Format`
+         **|** :term:`GRIB Format` **|** :term:`Post Processing (PP) Format`
+        | **More information:** `NetCDF-4 Python Git <https://github.com/Unidata/netcdf4-python>`_
+        |
+
+   NumPy
+        A mathematical Python library, predominantly based around
+        multi-dimensional arrays.
+
+        | **Related:** :term:`Dask`  **|** :term:`Cube`
+         **|** :term:`Xarray`
+        | **More information:** `NumPy.org <https://numpy.org/>`_
+        |
+
+   Phenomenon
+        The primary data which is measured, usually within a cube, e.g.
+        air temperature.
+
+        | **Related:** :term:`Metadata`
+         **|** :term:`Standard Name` **|** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Post Processing (PP) Format
+        A meteorological file format, created from a post processed
+        :term:`Fields File (FF) Format`.
+
+        | **Related:** :term:`GRIB Format` **|** :term:`NetCDF Format`
+        | **More information:** `PP Wikipedia Page <https://en.wikipedia.org/wiki/PP-format>`_
+        |
+
+   Real Data
+        Data that has been loaded into RAM, as opposed to sitting
+        on the hard drive.
+
+        | **Related:** :term:`Lazy Data` **|** :term:`NumPy`
+        | **More information:** :doc:`real_and_lazy_data`
+        |
+
+   Standard Name
+        A name describing a :term:`phenomenon`,  one from a fixed list
+        defined at `CF Standard Names <http://cfconventions.org/standard-names.html>`_.
+
+        | **Related:** :term:`Long Name` **|** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Unit
+        The unit with which the :term:`phenomenon` is measured e.g. m / sec.
+
+        | **Related:** :term:`Cube`
+        | **More information:** :doc:`iris_cubes`
+        |
+
+   Xarray
+        A python library for sophisticated labelled multi-dimensional operations.
+        Has a broader scope than Iris - it is not focused on meteorological data.
+
+        | **Related:** :term:`NumPy`
+        | **More information:** `Xarray Documentation <https://docs.xarray.dev/en/stable/index.html>`_
+        |
+
+----
+
+`To top <glossary_>`_
diff --git a/docs/src/userguide/index.rst b/docs/src/userguide/index.rst
index 08923e7662..fdd0c4d03e 100644
--- a/docs/src/userguide/index.rst
+++ b/docs/src/userguide/index.rst
@@ -35,6 +35,7 @@ they may serve as a useful reference for future exploration.
    cube_maths
    citation
    code_maintenance
+   glossary
 
 
 .. toctree::
diff --git a/docs/src/userguide/merge_and_concat.rst b/docs/src/userguide/merge_and_concat.rst
index 08c3ce9711..b521d49a59 100644
--- a/docs/src/userguide/merge_and_concat.rst
+++ b/docs/src/userguide/merge_and_concat.rst
@@ -253,6 +253,11 @@ which are described below.
 Using CubeList.concatenate
 ==========================
 
+.. seealso::
+
+    Relevant gallery example:
+    :ref:`sphx_glr_generated_gallery_general_plot_projections_and_annotations.py` (Brief concatenating examples)
+
 The :meth:`CubeList.concatenate <iris.cube.CubeList.concatenate>` method operates on a list
 of cubes and returns a new :class:`~iris.cube.CubeList` containing the cubes
 that have been concatenated.
diff --git a/docs/src/userguide/navigating_a_cube.rst b/docs/src/userguide/navigating_a_cube.rst
index c5924a61c6..b4c16b094b 100644
--- a/docs/src/userguide/navigating_a_cube.rst
+++ b/docs/src/userguide/navigating_a_cube.rst
@@ -110,6 +110,10 @@ cube with the :attr:`Cube.cell_methods <iris.cube.Cube.cell_methods>` attribute:
 
     print(cube.cell_methods)
 
+.. seealso::
+
+    Relevant gallery example:
+    :ref:`sphx_glr_generated_gallery_meteorology_plot_wind_barbs.py`
 
 Accessing Coordinates on the Cube
 ---------------------------------
@@ -176,6 +180,10 @@ We can add and remove coordinates via :func:`Cube.add_dim_coord<iris.cube.Cube.a
 
 The coordinate ``my_custom_coordinate`` now exists on the cube and is listed under the non-dimensioned single valued scalar coordinates.
 
+.. seealso::
+
+    Relevant gallery example:
+    :ref:`sphx_glr_generated_gallery_general_plot_custom_file_loading.py` (Adding Metadata)
 
 Adding and Removing Metadata to the Cube at Load Time
 -----------------------------------------------------
diff --git a/docs/src/userguide/plotting_a_cube.rst b/docs/src/userguide/plotting_a_cube.rst
index a2334367c5..70a1fbba91 100644
--- a/docs/src/userguide/plotting_a_cube.rst
+++ b/docs/src/userguide/plotting_a_cube.rst
@@ -10,6 +10,10 @@ high quality, production ready 1D and 2D plots.
 The functionality of the Matplotlib :py:mod:`~matplotlib.pyplot` module has 
 been extended within Iris to facilitate easy visualisation of a cube's data.
 
+.. seealso::
+
+    Relevant gallery example:
+    :ref:`sphx_glr_generated_gallery_general_plot_zonal_means.py` (Plotting with multiple axes)
 
 ***************************
 Matplotlib's Pyplot Basics
@@ -82,9 +86,9 @@ so ensure that interactive mode is turned off with::
 
         plt.interactive(False)
 
-
-Saving a Plot
-=============
+===============
+ Saving a Plot
+===============
 
 The :py:func:`matplotlib.pyplot.savefig` function is similar to **plt.show()** 
 in that they are both *non-interactive* visualisation modes. 
diff --git a/docs/src/userguide/plotting_examples/1d_with_legend.py b/docs/src/userguide/plotting_examples/1d_with_legend.py
index 9b9fd8a49d..626335af45 100644
--- a/docs/src/userguide/plotting_examples/1d_with_legend.py
+++ b/docs/src/userguide/plotting_examples/1d_with_legend.py
@@ -13,7 +13,6 @@
 temperature = temperature[5:9, :]
 
 for cube in temperature.slices("longitude"):
-
     # Create a string label to identify this cube (i.e. latitude: value)
     cube_label = "latitude: %s" % cube.coord("latitude").points[0]
 
diff --git a/docs/src/userguide/subsetting_a_cube.rst b/docs/src/userguide/subsetting_a_cube.rst
index c4f55490af..019982ad6d 100644
--- a/docs/src/userguide/subsetting_a_cube.rst
+++ b/docs/src/userguide/subsetting_a_cube.rst
@@ -10,6 +10,13 @@ However it is often necessary to reduce the dimensionality of a cube down to som
 Iris provides several ways of reducing both the amount of data and/or the number of dimensions in your cube depending on the circumstance.
 In all cases **the subset of a valid cube is itself a valid cube**.
 
+
+.. seealso::
+
+    Relevant gallery examples:
+        - :ref:`sphx_glr_generated_gallery_general_plot_polynomial_fit.py` (Slices)
+        - :ref:`sphx_glr_generated_gallery_general_plot_anomaly_log_colouring.py` (Extraction)
+
 .. _cube_extraction:
 
 Cube Extraction
diff --git a/docs/src/whatsnew/1.0.rst b/docs/src/whatsnew/1.0.rst
index b226dc609b..c256c33566 100644
--- a/docs/src/whatsnew/1.0.rst
+++ b/docs/src/whatsnew/1.0.rst
@@ -147,8 +147,7 @@ the surface pressure. In return, it provides a virtual "pressure"
 coordinate whose values are derived from the given components.
 
 This facility is utilised by the GRIB2 loader to automatically provide
-the derived "pressure" coordinate for certain data [#f1]_ from the
-`ECMWF <http://www.ecmwf.int/>`_.
+the derived "pressure" coordinate for certain data [#f1]_ from the ECMWF.
 
 .. [#f1] Where the level type is either 105 or 119, and where the
          surface pressure has an ECMWF paramId of
diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst
index 5812b79860..c2e47f298a 100644
--- a/docs/src/whatsnew/3.3.rst
+++ b/docs/src/whatsnew/3.3.rst
@@ -31,6 +31,36 @@ This document explains the changes made to Iris for this release
    any issues or feature requests for improving Iris. Enjoy!
 
 
+v3.3.1 (29 Sep 2022)
+====================
+
+.. dropdown:: :opticon:`alert` v3.3.1 Patches
+   :container: + shadow
+   :title: text-primary text-center font-weight-bold
+   :body: bg-light
+   :animate: fade-in
+
+   The patches in this release of Iris include:
+
+    #. `@pp-mo`_ fixed the Jupyter notebook display of :class:`~iris.cube.CubeList`.
+       (:issue:`4973`, :pull:`4976`)
+
+    #. `@pp-mo`_ fixed a bug in NAME loaders where data with no associated statistic would
+       load as a cube with invalid cell-methods, which cannot be printed or saved to netcdf.
+       (:issue:`3288`, :pull:`4933`)
+
+    #. `@pp-mo`_ ensured that :data:`iris.cube.Cube.cell_methods` must always be an iterable
+       of :class:`iris.coords.CellMethod` objects (:pull:`4933`).
+
+    #. `@trexfeathers`_ advanced the Cartopy pin to ``>=0.21``, as Cartopy's
+       change to default Transverse Mercator projection affects an Iris test.
+       See `SciTools/cartopy@fcb784d`_ and `SciTools/cartopy@8860a81`_ for more
+       details. (:pull:`4992`)
+
+    #. `@trexfeathers`_ introduced the ``netcdf4!=1.6.1`` pin to avoid a
+       problem with segfaults. (:pull:`4992`)
+
+
 📢 Announcements
 ================
 
@@ -339,3 +369,5 @@ This document explains the changes made to Iris for this release
 .. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html
 .. _pytest: https://docs.pytest.org
 .. _setuptools-scm: https://github.com/pypa/setuptools_scm
+.. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108
+.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e
diff --git a/docs/src/whatsnew/3.4.rst b/docs/src/whatsnew/3.4.rst
new file mode 100644
index 0000000000..1ad676c049
--- /dev/null
+++ b/docs/src/whatsnew/3.4.rst
@@ -0,0 +1,288 @@
+.. include:: ../common_links.inc
+
+v3.4 (01 Dec 2022)
+******************
+
+This document explains the changes made to Iris for this release
+(:doc:`View all changes <index>`.)
+
+
+.. dropdown:: :opticon:`report` v3.4.0 Release Highlights
+   :container: + shadow
+   :title: text-primary text-center font-weight-bold
+   :body: bg-light
+   :animate: fade-in
+   :open:
+
+   The highlights for this minor release of Iris include:
+
+   * We have **archived older Iris documentation** - everything before
+     ``v3.0.0`` - so older versions will soon no longer appear in search
+     engines. If you need this older documentation: please
+     see :ref:`iris_support`.
+   * We have added a :ref:`glossary` to the Iris documentation.
+   * We have completed work to make **Pandas interoperability** handle
+     n-dimensional :class:`~iris.cube.Cube`\s.
+   * We have **begun refactoring Iris' regridding**, which has already improved
+     performance and functionality, with more potential in future!
+   * We have made several other significant `🚀 Performance Enhancements`_.
+   * Please note that **Iris cannot currently work with the latest NetCDF4
+     releases**. The pin is set to ``<v1.6.1``, due to incompatibility with
+     Iris' lack of thread safety. We're working hard to make Iris NetCDF
+     loading thread safe as soon as possible.
+
+   And finally, get in touch with us on :issue:`GitHub<new/choose>` if you have
+   any issues or feature requests for improving Iris. Enjoy!
+
+
+📢 Announcements
+================
+
+#. Welcome to `@ESadek-MO`_, `@TTV-Intrepid`_ and `@hsteptoe`_,  who made their
+   first contributions to Iris 🎉
+
+   .. _try_experimental_stratify:
+
+#. Do you enjoy `python-stratify`_? Did you know that Iris includes a
+   convenience for using `python-stratify`_ with :class:`~iris.cube.Cube`\s?
+   It has been 'experimental' for several years now, without receiving much
+   feedback, so it's **use it or lose it** time: please try out
+   :mod:`iris.experimental.stratify` and let us know what you think!
+
+
+✨ Features
+===========
+
+#. `@ESadek-MO`_ edited :func:`~iris.io.expand_filespecs` to allow expansion of
+   non-existing paths, and added expansion functionality to :func:`~iris.io.save`.
+   (:issue:`4772`, :pull:`4913`)
+
+#. `@trexfeathers`_ and `Julian Heming`_ added new mappings between CF
+   standard names and UK Met Office LBFC codes. (:pull:`4859`)
+
+#. `@pp-mo`_ changed the metadata of a face/edge-type
+   :class:`~iris.experimental.ugrid.mesh.MeshCoord`, to be same as the face/edge
+   coordinate in the mesh from which it takes its ``.points``.  Previously, all MeshCoords
+   took their metadata from the node coord, but only a node-type MeshCoord now does
+   that.  Also, the MeshCoord ``.var_name`` is now that of the underlying coord, whereas
+   previously this was always None.  These changes make MeshCoord more like an ordinary
+   :class:`~iris.coords.AuxCoord`, which avoids some specific known usage problems.
+   (:issue:`4860`, :pull:`5020`)
+
+#. `@Esadek-MO`_ and `@trexfeathers`_ added dim coord
+   prioritisation to ``_get_lon_lat_coords()`` in :mod:`iris.analysis.cartography`.
+   This allows :func:`iris.analysis.cartography.area_weights` and
+   :func:`~iris.analysis.cartography.project` to handle cubes which contain
+   both dim and aux coords of the same type e.g. ``longitude`` and ``grid_longitude``.
+   (:issue:`3916`, :pull:`5029`).
+
+#. `@stephenworsley`_ added the ability to regrid derived coordinates with the
+   :obj:`~iris.analysis.PointInCell` regridding scheme. (:pull:`4807`)
+
+#. `@trexfeathers`_ made NetCDF loading more tolerant by enabling skipping of
+   :class:`~iris.coords.DimCoord`\s, :class:`~iris.coords.AuxCoord`\s,
+   :class:`~iris.coords.CellMeasure`\s and
+   :class:`~iris.coords.AncillaryVariable`\s if they cannot be added to a
+   :class:`~iris.cube.Cube` (e.g. due to CF non-compliance). This is done via
+   a new error class: :class:`~iris.exceptions.CannotAddError` (subclass of
+   :class:`ValueError`). (:pull:`5054`)
+
+#. `@pp-mo`_ implemented == and != comparisons for :class:`~iris.Constraint` s.
+   A simple constraint is now == to another one constructed in the same way.
+   However, equality is limited for more complex cases :  value-matching functions must
+   be the same identical function, and for &-combinations order is significant,
+   i.e.  ``(c1 & c2) != (c2 & c1)``.
+   (:issue:`3616`, :pull:`3749`).
+
+#. `@hsteptoe`_ and `@trexfeathers`_ improved
+   :func:`iris.pandas.as_data_frame`\'s conversion of :class:`~iris.cube.Cube`\s to
+   :class:`~pandas.DataFrame`\s. This includes better handling of multiple
+   :class:`~iris.cube.Cube` dimensions, auxiliary coordinates and attribute
+   information. **Note:** the improvements are opt-in, by setting the
+   :obj:`iris.FUTURE.pandas_ndim` flag (see :class:`iris.Future` for more).
+   (:issue:`4526`, :pull:`4909`, :pull:`4669`, :pull:`5059`, :pull:`5074`)
+
+
+🐛 Bugs Fixed
+=============
+
+#. `@rcomer`_ and `@pp-mo`_ (reviewer) factored masking into the returned
+   sum-of-weights calculation from :obj:`~iris.analysis.SUM`. (:pull:`4905`)
+
+#. `@schlunma`_ fixed a bug which prevented using
+   :meth:`iris.cube.Cube.collapsed` on coordinates whose number of bounds
+   differs from 0 or 2. This enables the use of this method on mesh
+   coordinates. (:issue:`4672`, :pull:`4870`)
+
+#. `@bjlittle`_ and `@lbdreyer`_ (reviewer) fixed the building of the CF
+   Standard Names module ``iris.std_names`` for the ``setup.py`` commands
+   ``develop`` and ``std_names``. (:issue:`4951`, :pull:`4952`)
+
+#. `@lbdreyer`_ and `@pp-mo`_ (reviewer) fixed the cube print out such that
+   scalar ancillary variables are displayed in a dedicated section rather than
+   being added to the vector ancillary variables section. Further, ancillary
+   variables and cell measures that map to a cube dimension of length 1 are now
+   included in the respective vector sections. (:pull:`4945`)
+
+#. `@rcomer`_ removed some old redundant code that prevented determining the
+   order of time cells. (:issue:`4697`, :pull:`4729`)
+
+#. `@stephenworsley`_ improved the accuracy of the error messages for
+   :meth:`~iris.cube.Cube.coord` when failing to find coordinates in the case where
+   a coordinate is given as the argument. Similarly, improved the error messages for
+   :meth:`~iris.cube.Cube.cell_measure` and :meth:`~iris.cube.Cube.ancillary_variable`.
+   (:issue:`4898`, :pull:`4928`)
+
+#. `@stephenworsley`_ fixed a bug which caused derived coordinates to be realised
+   after calling :meth:`iris.cube.Cube.aggregated_by`. (:issue:`3637`, :pull:`4947`)
+
+#. `@rcomer`_ corrected the ``standard_name`` mapping from UM stash code ``m01s30i311``
+   to indicate that this is the upward, rather than northward part of the flow.
+   (:pull:`5060`)
+
+#. `@bjlittle`_ and `@trexfeathers`_ (reviewer) fixed an issue which prevented
+   uncompressed PP fields with additional trailing padded words in the field
+   data to be loaded and saved. (:pull:`5058`)
+
+#. `@lbdreyer`_ and `@trexfeathers`_ (reviewer) fixed the handling of data when
+   regridding with :class:`~iris.analysis.UnstructuredNearest` or calling
+   :func:`~iris.analysis.trajectory.interpolate` such that the data type and mask is
+   preserved. (:issue:`4463`, :pull:`5062`)
+
+
+💣 Incompatible Changes
+=======================
+
+#. `@trexfeathers`_ altered testing to accept new Dask copying behaviour from
+   `dask/dask#9555`_ - copies of a Dask array created using ``da.from_array()``
+   will all ``compute()`` to a shared identical array. So creating a
+   :class:`~iris.cube.Cube` using ``Cube(data=da.from_array(...``, then
+   using :class:`~iris.cube.Cube` :meth:`~iris.cube.Cube.copy`,
+   will produce two :class:`~iris.cube.Cube`\s that both return an identical
+   array when requesting :class:`~iris.cube.Cube` :attr:`~iris.cube.Cube.data`.
+   We do not expect this to affect typical user workflows but please get in
+   touch if you need help. (:pull:`5041`)
+
+#. `@trexfeathers`_ moved ``iris.experimental.animate.animate()`` to
+   :func:`iris.plot.animate`, in recognition of its successful use over several
+   years since introduction. (:pull:`5056`)
+
+
+🚀 Performance Enhancements
+===========================
+
+#. `@rcomer`_ and `@pp-mo`_ (reviewer) increased aggregation speed for
+   :obj:`~iris.analysis.SUM`, :obj:`~iris.analysis.COUNT` and
+   :obj:`~iris.analysis.PROPORTION` on real data. (:pull:`4905`)
+
+#. `@bouweandela`_ made :meth:`iris.coords.Coord.cells` faster for time
+   coordinates. This also affects :meth:`iris.cube.Cube.extract`,
+   :meth:`iris.cube.Cube.subset`, and :meth:`iris.coords.Coord.intersect`.
+   (:pull:`4969`)
+
+#. `@bouweandela`_ improved the speed of :meth:`iris.cube.Cube.subset` /
+   :meth:`iris.coords.Coord.intersect`.
+   (:pull:`4955`)
+
+#. `@stephenworsley`_ improved the speed of the :obj:`~iris.analysis.PointInCell`
+   regridding scheme. (:pull:`4807`)
+
+
+🔥 Deprecations
+===============
+
+#. `@hsteptoe`_ and `@trexfeathers`_ (reviewer) deprecated
+   :func:`iris.pandas.as_series` in favour of the new
+   :func:`iris.pandas.as_data_frame` - see `✨ Features`_ for more details.
+   (:pull:`4669`)
+
+
+🔗 Dependencies
+===============
+
+#. `@rcomer`_ introduced the ``dask >=2.26`` minimum pin, so that Iris can benefit
+   from Dask's support for `NEP13`_ and `NEP18`_. (:pull:`4905`)
+
+#. `@trexfeathers`_ advanced the Cartopy pin to ``>=0.21``, as Cartopy's
+   change to default Transverse Mercator projection affects an Iris test.
+   See `SciTools/cartopy@fcb784d`_ and `SciTools/cartopy@8860a81`_ for more
+   details.
+   (:pull:`4968`)
+
+#. `@trexfeathers`_ introduced the ``netcdf4<1.6.1`` pin to avoid a problem
+   with segfaults. (:pull:`4968`, :pull:`5075`, :issue:`5016`)
+
+#. `@trexfeathers`_ updated the Matplotlib colormap registration in
+   :mod:`iris.palette` in response to a deprecation warning. Using the new
+   Matplotlib API also means a ``matplotlib>=3.5`` pin. (:pull:`4998`)
+
+#. See `💣 Incompatible Changes`_ for notes about `dask/dask#9555`_.
+
+
+📚 Documentation
+================
+
+#. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal
+   means plotted parallel to a cartographic plot. (:pull:`4871`)
+
+#. `@Esadek-MO`_ added a key-terms :ref:`glossary` page into the user guide. (:pull:`4902`)
+
+#. `@pp-mo`_ added a :ref:`code example <ORCA_example>`
+   for converting ORCA-gridded data to an unstructured cube. (:pull:`5013`)
+#. `@Esadek-MO`_ added links to relevant Gallery examples within the User Guide
+   to improve understanding. (:pull:`5009`)
+
+#. `@trexfeathers`_ changed the warning header for the **latest** documentation
+   to reference Read the Docs' built-in version switcher, instead of generating
+   its own independent links. (:pull:`5055`)
+
+#. `@tkknight`_ updated the links for the Iris documentation to v2.4 and
+   earlier to point to the archive of zip files instead. (:pull:`5064`)
+
+#. `@Esadek-MO`_ began adding notes at the bottom of functions to
+   to clarify if the function preserves laziness or not. See :issue:`3292` for
+   the ongoing checklist. (:pull:`5066`)
+
+💼 Internal
+===========
+
+#. `@rcomer`_ removed the obsolete ``setUpClass`` method from Iris testing.
+   (:pull:`4927`)
+
+#. `@bjlittle`_ and `@lbdreyer`_ (reviewer) removed support for
+   ``python setup.py test``, which is a deprecated approach to executing
+   package tests, see `pypa/setuptools#1684`_.  Also performed assorted
+   ``setup.py`` script hygiene. (:pull:`4948`, :pull:`4949`, :pull:`4950`)
+
+#. `@pp-mo`_ split the module :mod:`iris.fileformats.netcdf` into separate
+   :mod:`~iris.fileformats.netcdf.loader` and :mod:`~iris.fileformats.netcdf.saver`
+   submodules, just to make the code easier to handle.
+
+#. `@trexfeathers`_ adapted the benchmark for importing :mod:`iris.palette` to
+   cope with new colormap behaviour in Matplotlib `v3.6`. (:pull:`4998`)
+
+#. `@rcomer`_ removed a now redundant workaround for an old matplotlib bug,
+   highlighted by :issue:`4090`.  (:pull:`4999`)
+
+#. `@rcomer`_ added the ``show`` option to the documentation Makefiles, as a
+   convenient way for contributors to view their built documentation.
+   (:pull:`5000`)
+
+.. comment
+    Whatsnew author names (@github name) in alphabetical order. Note that,
+    core dev names are automatically included by the common_links.inc:
+
+.. _@TTV-Intrepid: https://github.com/TTV-Intrepid
+.. _Julian Heming: https://www.metoffice.gov.uk/research/people/julian-heming
+.. _@hsteptoe: https://github.com/hsteptoe
+
+
+.. comment
+    Whatsnew resources in alphabetical order:
+
+.. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html
+.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html
+.. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684
+.. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108
+.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e
+.. _dask/dask#9555: https://github.com/dask/dask/pull/9555
diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst
index 8cff21f32f..005fac70c4 100644
--- a/docs/src/whatsnew/index.rst
+++ b/docs/src/whatsnew/index.rst
@@ -12,6 +12,7 @@ What's New in Iris
    :hidden:
 
    latest.rst
+   3.4.rst
    3.3.rst
    3.2.rst
    3.1.rst
diff --git a/docs/src/why_iris.rst b/docs/src/why_iris.rst
index 63a515f68e..82b791b4bd 100644
--- a/docs/src/why_iris.rst
+++ b/docs/src/why_iris.rst
@@ -40,5 +40,4 @@ Interoperability with packages from the wider scientific Python ecosystem comes
 from Iris' use of standard NumPy/dask arrays as its underlying data storage.
 
 Iris is part of SciTools, for more information see https://scitools.org.uk/.
-For **Iris 2.4** and earlier documentation please see the
-:link-badge:`https://scitools.org.uk/iris/docs/v2.4.0/,"legacy documentation",cls=badge-info text-white`.
+For **Iris 2.4** and earlier documentation please see :ref:`iris_support`.
\ No newline at end of file
diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py
index b944f9b22f..38465472ee 100644
--- a/lib/iris/__init__.py
+++ b/lib/iris/__init__.py
@@ -91,6 +91,7 @@ def callback(cube, field, filename):
 
 import contextlib
 import glob
+import importlib
 import itertools
 import os.path
 import pathlib
@@ -129,6 +130,7 @@ def callback(cube, field, filename):
     "sample_data_path",
     "save",
     "site_configuration",
+    "use_plugin",
 ]
 
 
@@ -140,22 +142,28 @@ def callback(cube, field, filename):
 class Future(threading.local):
     """Run-time configuration controller."""
 
-    def __init__(self, datum_support=False):
+    def __init__(self, datum_support=False, pandas_ndim=False):
         """
         A container for run-time options controls.
 
         To adjust the values simply update the relevant attribute from
         within your code. For example::
 
+            # example_future_flag is a fictional example.
             iris.FUTURE.example_future_flag = False
 
         If Iris code is executed with multiple threads, note the values of
         these options are thread-specific.
 
-        .. note::
-
-            iris.FUTURE.example_future_flag does not exist. It is provided
-            as an example.
+        Parameters
+        ----------
+        datum_support : bool, default=False
+            Opts in to loading coordinate system datum information from NetCDF
+            files into :class:`~iris.coord_systems.CoordSystem`\\ s, wherever
+            this information is present.
+        pandas_ndim : bool, default=False
+            See :func:`iris.pandas.as_data_frame` for details - opts in to the
+            newer n-dimensional behaviour.
 
         """
         # The flag 'example_future_flag' is provided as a reference for the
@@ -166,13 +174,13 @@ def __init__(self, datum_support=False):
         #
         # self.__dict__['example_future_flag'] = example_future_flag
         self.__dict__["datum_support"] = datum_support
+        self.__dict__["pandas_ndim"] = pandas_ndim
 
     def __repr__(self):
-
         # msg = ('Future(example_future_flag={})')
         # return msg.format(self.example_future_flag)
-        msg = "Future(datum_support={})"
-        return msg.format(self.datum_support)
+        msg = "Future(datum_support={}, pandas_ndim={})"
+        return msg.format(self.datum_support, self.pandas_ndim)
 
     # deprecated_options = {'example_future_flag': 'warning',}
     deprecated_options = {}
@@ -211,14 +219,11 @@ def context(self, **kwargs):
         statement, the previous state is restored.
 
         For example::
+
+            # example_future_flag is a fictional example.
             with iris.FUTURE.context(example_future_flag=False):
                 # ... code that expects some past behaviour
 
-        .. note::
-
-            iris.FUTURE.example_future_flag does not exist and is
-            provided only as an example.
-
         """
         # Save the current context
         current_state = self.__dict__.copy()
@@ -467,3 +472,22 @@ def sample_data_path(*path_to_join):
             "appropriate for general file access.".format(target)
         )
     return target
+
+
+def use_plugin(plugin_name):
+    """
+    Convenience function to import a plugin
+
+    For example::
+
+        use_plugin("my_plugin")
+
+    is equivalent to::
+
+        import iris.plugins.my_plugin
+
+    This is useful for plugins that are not used directly, but instead do all
+    their setup on import.  In this case, style checkers would not know the
+    significance of the import statement and warn that it is an unused import.
+    """
+    importlib.import_module(f"iris.plugins.{plugin_name}")
diff --git a/lib/iris/_constraints.py b/lib/iris/_constraints.py
index 4e23793e1d..bfd4865f56 100644
--- a/lib/iris/_constraints.py
+++ b/lib/iris/_constraints.py
@@ -131,6 +131,30 @@ def latitude_bands(cell):
                 _CoordConstraint(coord_name, coord_thing)
             )
 
+    def __eq__(self, other):
+        # Equivalence is defined, but is naturally limited for any Constraints
+        # based on callables, i.e. "cube_func", or value functions for
+        # attributes/names/coords :  These can only be == if they contain the
+        # *same* callable object (i.e. same object identity).
+        eq = (
+            type(other) == Constraint
+            and self._name == other._name
+            and self._cube_func == other._cube_func
+            and self._coord_constraints == other._coord_constraints
+        )
+        # NOTE: theoretically, you could compare coord constraints as a *set*,
+        # as order should not affect matching.
+        # Not totally sure, so for now let's not.
+        return eq
+
+    def __hash__(self):
+        # We want constraints to have hashes, so they can act as e.g.
+        # dictionary keys or tuple elements.
+        # So, we *must* provide this, as overloading '__eq__' automatically
+        # disables it.
+        # Just use basic object identity.
+        return id(self)
+
     def __repr__(self):
         args = []
         if self._name:
@@ -218,6 +242,19 @@ def __init__(self, lhs, rhs, operator):
         self.rhs = rhs_constraint
         self.operator = operator
 
+    def __eq__(self, other):
+        eq = (
+            type(other) == ConstraintCombination
+            and self.lhs == other.lhs
+            and self.rhs == other.rhs
+            and self.operator == other.operator
+        )
+        return eq
+
+    def __hash__(self):
+        # Must re-define if you overload __eq__ : Use object identity.
+        return id(self)
+
     def _coordless_match(self, cube):
         return self.operator(
             self.lhs._coordless_match(cube), self.rhs._coordless_match(cube)
@@ -261,6 +298,18 @@ def __repr__(self):
             self._coord_thing,
         )
 
+    def __eq__(self, other):
+        eq = (
+            type(other) == _CoordConstraint
+            and self.coord_name == other.coord_name
+            and self._coord_thing == other._coord_thing
+        )
+        return eq
+
+    def __hash__(self):
+        # Must re-define if you overload __eq__ : Use object identity.
+        return id(self)
+
     def extract(self, cube):
         """
         Returns the the column based indices of the given cube which
@@ -493,6 +542,17 @@ def __init__(self, **attributes):
         self._attributes = attributes
         super().__init__(cube_func=self._cube_func)
 
+    def __eq__(self, other):
+        eq = (
+            type(other) == AttributeConstraint
+            and self._attributes == other._attributes
+        )
+        return eq
+
+    def __hash__(self):
+        # Must re-define if you overload __eq__ : Use object identity.
+        return id(self)
+
     def _cube_func(self, cube):
         match = True
         for name, value in self._attributes.items():
@@ -577,6 +637,17 @@ def __init__(
         self._names = ("standard_name", "long_name", "var_name", "STASH")
         super().__init__(cube_func=self._cube_func)
 
+    def __eq__(self, other):
+        eq = type(other) == NameConstraint and all(
+            getattr(self, attname) == getattr(other, attname)
+            for attname in self._names
+        )
+        return eq
+
+    def __hash__(self):
+        # Must re-define if you overload __eq__ : Use object identity.
+        return id(self)
+
     def _cube_func(self, cube):
         def matcher(target, value):
             if callable(value):
diff --git a/lib/iris/_lazy_data.py b/lib/iris/_lazy_data.py
index ac7ae34511..e0566fc8f2 100644
--- a/lib/iris/_lazy_data.py
+++ b/lib/iris/_lazy_data.py
@@ -39,7 +39,7 @@ def is_lazy_data(data):
     """
     Return whether the argument is an Iris 'lazy' data array.
 
-    At present, this means simply a Dask array.
+    At present, this means simply a :class:`dask.array.Array`.
     We determine this by checking for a "compute" property.
 
     """
@@ -67,7 +67,8 @@ def _optimum_chunksize_internals(
     * shape (tuple of int):
         The full array shape of the target data.
     * limit (int):
-        The 'ideal' target chunk size, in bytes.  Default from dask.config.
+        The 'ideal' target chunk size, in bytes.  Default from
+        :mod:`dask.config`.
     * dtype (np.dtype):
         Numpy dtype of target data.
 
@@ -77,7 +78,7 @@ def _optimum_chunksize_internals(
 
     .. note::
         The purpose of this is very similar to
-        `dask.array.core.normalize_chunks`, when called as
+        :func:`dask.array.core.normalize_chunks`, when called as
         `(chunks='auto', shape, dtype=dtype, previous_chunks=chunks, ...)`.
         Except, the operation here is optimised specifically for a 'c-like'
         dimension order, i.e. outer dimensions first, as for netcdf variables.
@@ -174,13 +175,13 @@ def _optimum_chunksize(
 
 def as_lazy_data(data, chunks=None, asarray=False):
     """
-    Convert the input array `data` to a dask array.
+    Convert the input array `data` to a :class:`dask.array.Array`.
 
     Args:
 
     * data (array-like):
         An indexable object with 'shape', 'dtype' and 'ndim' properties.
-        This will be converted to a dask array.
+        This will be converted to a :class:`dask.array.Array`.
 
     Kwargs:
 
@@ -192,7 +193,7 @@ def as_lazy_data(data, chunks=None, asarray=False):
         Set to False (default) to pass passed chunks through unchanged.
 
     Returns:
-        The input array converted to a dask array.
+        The input array converted to a :class:`dask.array.Array`.
 
     .. note::
         The result chunk size is a multiple of 'chunks', if given, up to the
@@ -284,15 +285,16 @@ def multidim_lazy_stack(stack):
     """
     Recursively build a multidimensional stacked dask array.
 
-    This is needed because dask.array.stack only accepts a 1-dimensional list.
+    This is needed because :meth:`dask.array.Array.stack` only accepts a
+    1-dimensional list.
 
     Args:
 
     * stack:
-        An ndarray of dask arrays.
+        An ndarray of :class:`dask.array.Array`.
 
     Returns:
-        The input array converted to a lazy dask array.
+        The input array converted to a lazy :class:`dask.array.Array`.
 
     """
     if stack.ndim == 0:
diff --git a/lib/iris/_merge.py b/lib/iris/_merge.py
index bc12080523..5ca5f31a8e 100644
--- a/lib/iris/_merge.py
+++ b/lib/iris/_merge.py
@@ -1418,6 +1418,7 @@ def _define_space(self, space, positions, indexes, function_matrix):
             participates in a functional relationship.
 
         """
+
         # Heuristic reordering of coordinate defintion indexes into
         # preferred dimension order.
         def axis_and_name(name):
diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py
index 11810f2901..f34cda1402 100644
--- a/lib/iris/analysis/__init__.py
+++ b/lib/iris/analysis/__init__.py
@@ -296,7 +296,6 @@ def _dimensional_metadata_comparison(*cubes, object_get=None):
     # for coordinate groups
     for cube, coords in zip(cubes, all_coords):
         for coord in coords:
-
             # if this coordinate has already been processed, then continue on
             # to the next one
             if id(coord) in processed_coords:
@@ -1778,7 +1777,7 @@ def interp_order(length):
 
 .. seealso:: The :func:`~iris.analysis.PROPORTION` aggregator.
 
-This aggregator handles masked data.
+This aggregator handles masked data and lazy data.
 
 """
 
@@ -1808,7 +1807,7 @@ def interp_order(length):
     result = precip_cube.collapsed('time', iris.analysis.MAX_RUN,
                                    function=lambda values: values > 10)
 
-This aggregator handles masked data, which it treats as interrupting a run.
+This aggregator handles masked data, which it treats as interrupting a run, and lazy data.
 
 """
 MAX_RUN.name = lambda: "max_run"
@@ -1826,7 +1825,7 @@ def interp_order(length):
 
     result = cube.collapsed('longitude', iris.analysis.GMEAN)
 
-This aggregator handles masked data.
+This aggregator handles masked data, but NOT lazy data.
 
 """
 
@@ -1848,7 +1847,7 @@ def interp_order(length):
     The harmonic mean is only valid if all data values are greater
     than zero.
 
-This aggregator handles masked data.
+This aggregator handles masked data, but NOT lazy data.
 
 """
 
@@ -1914,7 +1913,8 @@ def interp_order(length):
 
     result = cube.collapsed('longitude', iris.analysis.MEDIAN)
 
-This aggregator handles masked data.
+This aggregator handles masked data, but NOT lazy data.  For lazy aggregation,
+please try :obj:`~.PERCENTILE`.
 
 """
 
@@ -1933,7 +1933,7 @@ def interp_order(length):
 
     result = cube.collapsed('longitude', iris.analysis.MIN)
 
-This aggregator handles masked data.
+This aggregator handles masked data and lazy data.
 
 """
 
@@ -1952,7 +1952,7 @@ def interp_order(length):
 
     result = cube.collapsed('longitude', iris.analysis.MAX)
 
-This aggregator handles masked data.
+This aggregator handles masked data and lazy data.
 
 """
 
@@ -1978,7 +1978,7 @@ def interp_order(length):
 
     result = cube.collapsed('time', iris.analysis.PEAK)
 
-This aggregator handles masked data.
+This aggregator handles masked data but NOT lazy data.
 
 """
 
@@ -2058,7 +2058,7 @@ def interp_order(length):
 
 .. seealso:: The :func:`~iris.analysis.COUNT` aggregator.
 
-This aggregator handles masked data.
+This aggregator handles masked data, but NOT lazy data.
 
 """
 
@@ -2084,7 +2084,7 @@ def interp_order(length):
 
     result = cube.collapsed('longitude', iris.analysis.RMS)
 
-This aggregator handles masked data.
+This aggregator handles masked data and lazy data.
 
 """
 
@@ -2118,7 +2118,7 @@ def interp_order(length):
 
 .. note::
 
-    Lazy operation is supported, via :func:`dask.array.nanstd`.
+    Lazy operation is supported, via :func:`dask.array.std`.
 
 This aggregator handles masked data.
 
@@ -2157,7 +2157,7 @@ def interp_order(length):
     result = cube.rolling_window('time', iris.analysis.SUM,
                                  len(weights), weights=weights)
 
-This aggregator handles masked data.
+This aggregator handles masked data and lazy data.
 
 """
 
@@ -2192,9 +2192,9 @@ def interp_order(length):
 
 .. note::
 
-    Lazy operation is supported, via :func:`dask.array.nanvar`.
+    Lazy operation is supported, via :func:`dask.array.var`.
 
-This aggregator handles masked data.
+This aggregator handles masked data and lazy data.
 
 """
 
@@ -2226,6 +2226,11 @@ def interp_order(length):
     :func:`scipy.interpolate.interp1d` Defaults to "linear", which is
     equivalent to alphap=0.5, betap=0.5 in `iris.analysis.PERCENTILE`
 
+Notes
+------
+This function does not maintain laziness when called; it realises data.
+See more at :doc:`/userguide/real_and_lazy_data`.
+
 """
 
 
@@ -2619,6 +2624,11 @@ def clear_phenomenon_identity(cube):
     Helper function to clear the standard_name, attributes, and
     cell_methods of a cube.
 
+
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     cube.rename(None)
     cube.attributes.clear()
diff --git a/lib/iris/analysis/_area_weighted.py b/lib/iris/analysis/_area_weighted.py
index 8381185e58..3b728e9a43 100644
--- a/lib/iris/analysis/_area_weighted.py
+++ b/lib/iris/analysis/_area_weighted.py
@@ -11,7 +11,7 @@
 
 from iris._lazy_data import map_complete_blocks
 from iris.analysis._interpolation import get_xy_dim_coords, snapshot_grid
-from iris.analysis._regrid import RectilinearRegridder
+from iris.analysis._regrid import RectilinearRegridder, _create_cube
 import iris.analysis.cartography
 import iris.coord_systems
 from iris.util import _meshgrid
@@ -853,7 +853,7 @@ def _calculate_regrid_area_weighted_weights(
         cached_x_bounds = []
         cached_x_indices = []
         max_x_indices = 0
-        for (x_0, x_1) in grid_x_bounds:
+        for x_0, x_1 in grid_x_bounds:
             if grid_x_decreasing:
                 x_0, x_1 = x_1, x_0
             x_bounds, x_indices = _cropped_bounds(src_x_bounds, x_0, x_1)
@@ -1111,18 +1111,32 @@ def _regrid_area_weighted_rectilinear_src_and_grid__perform(
     )
 
     # Wrap up the data as a Cube.
-    regrid_callback = RectilinearRegridder._regrid
-    new_cube = RectilinearRegridder._create_cube(
+
+    _regrid_callback = functools.partial(
+        RectilinearRegridder._regrid,
+        src_x_coord=src_x,
+        src_y_coord=src_y,
+        sample_grid_x=meshgrid_x,
+        sample_grid_y=meshgrid_y,
+    )
+    # TODO: investigate if an area weighted callback would be more appropriate.
+    # _regrid_callback = functools.partial(
+    #     _regrid_area_weighted_array,
+    #     weights_info=weights_info,
+    #     index_info=index_info,
+    #     mdtol=mdtol,
+    # )
+
+    def regrid_callback(*args, **kwargs):
+        _data, dims = args
+        return _regrid_callback(_data, *dims, **kwargs)
+
+    new_cube = _create_cube(
         new_data,
         src_cube,
-        src_x_dim,
-        src_y_dim,
-        src_x,
-        src_y,
-        grid_x,
-        grid_y,
-        meshgrid_x,
-        meshgrid_y,
+        [src_x_dim, src_y_dim],
+        [grid_x, grid_y],
+        2,
         regrid_callback,
     )
 
diff --git a/lib/iris/analysis/_grid_angles.py b/lib/iris/analysis/_grid_angles.py
index 0b52f54568..4cb449ae51 100644
--- a/lib/iris/analysis/_grid_angles.py
+++ b/lib/iris/analysis/_grid_angles.py
@@ -449,6 +449,11 @@ def rotate_grid_vectors(
 
             Vector magnitudes will always be the same as the inputs.
 
+    .. note::
+
+        This function does not maintain laziness when called; it realises data.
+        See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     u_out, v_out = (cube.copy() for cube in (u_cube, v_cube))
     if not grid_angles_cube:
diff --git a/lib/iris/analysis/_interpolation.py b/lib/iris/analysis/_interpolation.py
index 2a7dfa6e62..f5e89a9e51 100644
--- a/lib/iris/analysis/_interpolation.py
+++ b/lib/iris/analysis/_interpolation.py
@@ -268,7 +268,7 @@ def _account_for_circular(self, points, data):
         """
         from iris.analysis.cartography import wrap_lons
 
-        for (circular, modulus, index, dim, offset) in self._circulars:
+        for circular, modulus, index, dim, offset in self._circulars:
             if modulus:
                 # Map all the requested values into the range of the source
                 # data (centred over the centre of the source data to allow
diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py
index 5c7439b0ce..f1891a48e4 100644
--- a/lib/iris/analysis/_regrid.py
+++ b/lib/iris/analysis/_regrid.py
@@ -11,7 +11,6 @@
 import numpy as np
 import numpy.ma as ma
 from scipy.sparse import csc_matrix
-from scipy.sparse import diags as sparse_diags
 
 from iris._lazy_data import map_complete_blocks
 from iris.analysis._interpolation import (
@@ -21,7 +20,7 @@
     snapshot_grid,
 )
 from iris.analysis._scipy_interpolate import _RegularGridInterpolator
-from iris.util import _meshgrid
+from iris.util import _meshgrid, guess_coord_axis
 
 
 def _transform_xy_arrays(crs_from, x, y, crs_to):
@@ -52,18 +51,20 @@ def _regrid_weighted_curvilinear_to_rectilinear__prepare(
     First (setup) part of 'regrid_weighted_curvilinear_to_rectilinear'.
 
     Check inputs and calculate the sparse regrid matrix and related info.
-    The 'regrid info' returned can be re-used over many 2d slices.
+    The 'regrid info' returned can be re-used over many cubes.
 
     """
-    if src_cube.aux_factories:
-        msg = "All source cube derived coordinates will be ignored."
-        warnings.warn(msg)
 
     # Get the source cube x and y 2D auxiliary coordinates.
     sx, sy = src_cube.coord(axis="x"), src_cube.coord(axis="y")
     # Get the target grid cube x and y dimension coordinates.
     tx, ty = get_xy_dim_coords(grid_cube)
 
+    sl = [0] * grid_cube.ndim
+    sl[grid_cube.coord_dims(tx)[0]] = np.s_[:]
+    sl[grid_cube.coord_dims(ty)[0]] = np.s_[:]
+    grid_cube = grid_cube[tuple(sl)]
+
     if sx.units != sy.units:
         msg = (
             "The source cube x ({!r}) and y ({!r}) coordinates must "
@@ -287,83 +288,108 @@ def _regrid_indices(cells, depth, points):
     return regrid_info
 
 
-def _regrid_weighted_curvilinear_to_rectilinear__perform(
-    src_cube, regrid_info
+def _curvilinear_to_rectilinear_regrid_data(
+    data,
+    dims,
+    regrid_info,
 ):
     """
-    Second (regrid) part of 'regrid_weighted_curvilinear_to_rectilinear'.
+    Part of 'regrid_weighted_curvilinear_to_rectilinear' which acts on the data.
 
-    Perform the prepared regrid calculation on a single 2d cube.
+    Perform the prepared regrid calculation on an array.
 
     """
-    from iris.cube import Cube
-
     sparse_matrix, sum_weights, rows, grid_cube = regrid_info
 
+    inds = list(range(-len(dims), 0))
+    data = np.moveaxis(data, dims, inds)
+    data_shape = data.shape
+    grid_size = np.prod([data_shape[ind] for ind in inds])
+
     # Calculate the numerator of the weighted mean (M, 1).
-    is_masked = ma.isMaskedArray(src_cube.data)
+    is_masked = ma.isMaskedArray(data)
+    sum_weights = None
     if not is_masked:
-        data = src_cube.data
+        data = data
     else:
         # Use raw data array
-        data = src_cube.data.data
+        r_data = data.data
         # Check if there are any masked source points to take account of.
-        is_masked = np.ma.is_masked(src_cube.data)
+        is_masked = ma.is_masked(data)
         if is_masked:
             # Zero any masked source points so they add nothing in output sums.
-            mask = src_cube.data.mask
-            data[mask] = 0.0
+            mask = data.mask
+            r_data[mask] = 0.0
             # Calculate a new 'sum_weights' to allow for missing source points.
             # N.B. it is more efficient to use the original once-calculated
             # sparse matrix, but in this case we can't.
             # Hopefully, this post-multiplying by the validities is less costly
             # than repeating the whole sparse calculation.
-            valid_src_cells = ~mask.flat[:]
-            src_cell_validity_factors = sparse_diags(
-                np.array(valid_src_cells, dtype=int), 0
-            )
-            valid_weights = sparse_matrix * src_cell_validity_factors
-            sum_weights = valid_weights.sum(axis=1).getA()
-            # Work out where output cells are missing all contributions.
-            # This allows for where 'rows' contains output cells that have no
-            # data because of missing input points.
-            zero_sums = sum_weights == 0.0
-            # Make sure we can still divide by sum_weights[rows].
-            sum_weights[zero_sums] = 1.0
+            valid_src_cells = ~mask.reshape(-1, grid_size)
+            sum_weights = valid_src_cells @ sparse_matrix.T
+        data = r_data
+    if sum_weights is None:
+        sum_weights = (
+            np.ones(data_shape).reshape(-1, grid_size) @ sparse_matrix.T
+        )
+    # Work out where output cells are missing all contributions.
+    # This allows for where 'rows' contains output cells that have no
+    # data because of missing input points.
+    zero_sums = sum_weights == 0.0
+    # Make sure we can still divide by sum_weights[rows].
+    sum_weights[zero_sums] = 1.0
 
     # Calculate sum in each target cell, over contributions from each source
     # cell.
-    numerator = sparse_matrix * data.reshape(-1, 1)
-
-    # Create a template for the weighted mean result.
-    weighted_mean = ma.masked_all(numerator.shape, dtype=numerator.dtype)
-
-    # Calculate final results in all relevant places.
-    weighted_mean[rows] = numerator[rows] / sum_weights[rows]
-    if is_masked:
-        # Ensure masked points where relevant source cells were all missing.
-        if np.any(zero_sums):
-            # Make masked if it wasn't.
-            weighted_mean = np.ma.asarray(weighted_mean)
-            # Mask where contributing sums were zero.
-            weighted_mean[zero_sums] = np.ma.masked
-
-    # Construct the final regridded weighted mean cube.
+    numerator = data.reshape(-1, grid_size) @ sparse_matrix.T
+
+    weighted_mean = numerator / sum_weights
+    # Ensure masked points where relevant source cells were all missing.
+    weighted_mean = ma.asarray(weighted_mean)
+    if np.any(zero_sums):
+        # Mask where contributing sums were zero.
+        weighted_mean[zero_sums] = ma.masked
+
+    new_data_shape = list(data_shape)
+    for dim, length in zip(inds, grid_cube.shape):
+        new_data_shape[dim] = length
+    if len(dims) == 1:
+        new_data_shape.append(grid_cube.shape[1])
+        dims = (dims[0], dims[0] + 1)
+    if len(dims) > 2:
+        new_data_shape = new_data_shape[: 2 - len(dims)]
+        dims = dims[:2]
+
+    result = weighted_mean.reshape(new_data_shape)
+    result = np.moveaxis(result, [-2, -1], dims)
+    return result
+
+
+def _regrid_weighted_curvilinear_to_rectilinear__perform(
+    src_cube, regrid_info
+):
+    """
+    Second (regrid) part of 'regrid_weighted_curvilinear_to_rectilinear'.
+
+    Perform the prepared regrid calculation on a single cube.
+
+    """
+    dims = src_cube.coord_dims(
+        CurvilinearRegridder._get_horizontal_coord(src_cube, "x")
+    )
+    result_data = _curvilinear_to_rectilinear_regrid_data(
+        src_cube.data, dims, regrid_info
+    )
+    grid_cube = regrid_info[-1]
     tx = grid_cube.coord(axis="x", dim_coords=True)
     ty = grid_cube.coord(axis="y", dim_coords=True)
-    (tx_dim,) = grid_cube.coord_dims(tx)
-    (ty_dim,) = grid_cube.coord_dims(ty)
-    dim_coords_and_dims = list(zip((ty.copy(), tx.copy()), (ty_dim, tx_dim)))
-    cube = Cube(
-        weighted_mean.reshape(grid_cube.shape),
-        dim_coords_and_dims=dim_coords_and_dims,
+    regrid_callback = functools.partial(
+        _curvilinear_to_rectilinear_regrid_data, regrid_info=regrid_info
     )
-    cube.metadata = copy.deepcopy(src_cube.metadata)
-
-    for coord in src_cube.coords(dimensions=()):
-        cube.add_aux_coord(coord.copy())
-
-    return cube
+    result = _create_cube(
+        result_data, src_cube, dims, (ty.copy(), tx.copy()), 2, regrid_callback
+    )
+    return result
 
 
 class CurvilinearRegridder:
@@ -457,7 +483,7 @@ def __call__(self, src):
             point-in-cell regridding.
 
         """
-        from iris.cube import Cube, CubeList
+        from iris.cube import Cube
 
         # Validity checks.
         if not isinstance(src, Cube):
@@ -473,30 +499,18 @@ def __call__(self, src):
                 "The given cube is not defined on the same "
                 "source grid as this regridder."
             )
-
-        # Call the regridder function.
-        # This includes repeating over any non-XY dimensions, because the
-        # underlying routine does not support this.
-        # FOR NOW: we will use cube.slices and merge to achieve this,
-        # though that is not a terribly efficient method ...
-        # TODO: create a template result cube and paste data slices into it,
-        # which would be more efficient.
-        result_slices = CubeList([])
-        for slice_cube in src.slices(sx):
-            if self._regrid_info is None:
-                # Calculate the basic regrid info just once.
-                self._regrid_info = (
-                    _regrid_weighted_curvilinear_to_rectilinear__prepare(
-                        slice_cube, self.weights, self._target_cube
-                    )
-                )
-            slice_result = (
-                _regrid_weighted_curvilinear_to_rectilinear__perform(
-                    slice_cube, self._regrid_info
+        slice_cube = next(src.slices(sx))
+        if self._regrid_info is None:
+            # Calculate the basic regrid info just once.
+            self._regrid_info = (
+                _regrid_weighted_curvilinear_to_rectilinear__prepare(
+                    slice_cube, self.weights, self._target_cube
                 )
             )
-            result_slices.append(slice_result)
-        result = result_slices.merge_cube()
+        result = _regrid_weighted_curvilinear_to_rectilinear__perform(
+            src, self._regrid_info
+        )
+
         return result
 
 
@@ -688,11 +702,23 @@ def _regrid(
 
         # Prepare the result data array
         shape = list(src_data.shape)
-        assert shape[x_dim] == src_x_coord.shape[0]
-        assert shape[y_dim] == src_y_coord.shape[0]
-
-        shape[y_dim] = sample_grid_x.shape[0]
-        shape[x_dim] = sample_grid_x.shape[1]
+        final_shape = shape.copy()
+        if x_dim is not None:
+            assert shape[x_dim] == src_x_coord.shape[0]
+            shape[x_dim] = sample_grid_x.shape[1]
+            final_shape[x_dim] = shape[x_dim]
+        else:
+            shape.append(1)
+            x_dim = len(shape) - 1
+            src_data = np.expand_dims(src_data, -1)
+        if y_dim is not None:
+            assert shape[y_dim] == src_y_coord.shape[0]
+            shape[y_dim] = sample_grid_x.shape[0]
+            final_shape[y_dim] = shape[y_dim]
+        else:
+            shape.append(1)
+            y_dim = len(shape) - 1
+            src_data = np.expand_dims(src_data, -1)
 
         dtype = src_data.dtype
         if method == "linear":
@@ -714,7 +740,11 @@ def _regrid(
             if src_x_coord.points.size > 1
             else False
         )
-        reverse_y = src_y_coord.points[0] > src_y_coord.points[1]
+        reverse_y = (
+            src_y_coord.points[0] > src_y_coord.points[1]
+            if src_y_coord.points.size > 1
+            else False
+        )
         flip_index = [slice(None)] * src_data.ndim
         if reverse_x:
             src_x_coord = src_x_coord[::-1]
@@ -733,7 +763,7 @@ def _regrid(
 
         # Slice out the first full 2D piece of data for construction of the
         # interpolator.
-        index = [0] * src_data.ndim
+        index = [0] * len(shape)
         index[x_dim] = index[y_dim] = slice(None)
         initial_data = src_data[tuple(index)]
         if y_dim < x_dim:
@@ -808,166 +838,21 @@ def interpolate(data):
             if ma.isMaskedArray(data) or mode.force_mask:
                 # NB. np.ma.getmaskarray returns an array of `False` if
                 # `src_subset` is not a masked array.
-                src_mask = np.ma.getmaskarray(src_subset)
+                src_mask = ma.getmaskarray(src_subset)
                 interpolator.fill_value = mode.mask_fill_value
                 mask_fraction = interpolate(src_mask)
                 new_mask = mask_fraction > 0
 
-                if np.ma.isMaskedArray(data):
+                if ma.isMaskedArray(data):
                     data.mask[tuple(index)] = new_mask
                 elif np.any(new_mask):
                     # Set mask=False to ensure we have an expanded mask array.
-                    data = np.ma.MaskedArray(data, mask=False)
+                    data = ma.MaskedArray(data, mask=False)
                     data.mask[tuple(index)] = new_mask
 
+        data = data.reshape(final_shape)
         return data
 
-    @staticmethod
-    def _create_cube(
-        data,
-        src,
-        x_dim,
-        y_dim,
-        src_x_coord,
-        src_y_coord,
-        grid_x_coord,
-        grid_y_coord,
-        sample_grid_x,
-        sample_grid_y,
-        regrid_callback,
-    ):
-        """
-        Return a new Cube for the result of regridding the source Cube onto
-        the new grid.
-
-        All the metadata and coordinates of the result Cube are copied from
-        the source Cube, with two exceptions:
-            - Grid dimension coordinates are copied from the grid Cube.
-            - Auxiliary coordinates which span the grid dimensions are
-              ignored, except where they provide a reference surface for an
-              :class:`iris.aux_factory.AuxCoordFactory`.
-
-        Args:
-
-        * data:
-            The regridded data as an N-dimensional NumPy array.
-        * src:
-            The source Cube.
-        * x_dim:
-            The X dimension within the source Cube.
-        * y_dim:
-            The Y dimension within the source Cube.
-        * src_x_coord:
-            The X :class:`iris.coords.DimCoord`.
-        * src_y_coord:
-            The Y :class:`iris.coords.DimCoord`.
-        * grid_x_coord:
-            The :class:`iris.coords.DimCoord` for the new grid's X
-            coordinate.
-        * grid_y_coord:
-            The :class:`iris.coords.DimCoord` for the new grid's Y
-            coordinate.
-        * sample_grid_x:
-            A 2-dimensional array of sample X values.
-        * sample_grid_y:
-            A 2-dimensional array of sample Y values.
-        * regrid_callback:
-            The routine that will be used to calculate the interpolated
-            values of any reference surfaces.
-
-        Returns:
-            The new, regridded Cube.
-
-        """
-        from iris.cube import Cube
-
-        #
-        # XXX: At the moment requires to be a static method as used by
-        # experimental regrid_area_weighted_rectilinear_src_and_grid
-        #
-        # Create a result cube with the appropriate metadata
-        result = Cube(data)
-        result.metadata = copy.deepcopy(src.metadata)
-
-        # Copy across all the coordinates which don't span the grid.
-        # Record a mapping from old coordinate IDs to new coordinates,
-        # for subsequent use in creating updated aux_factories.
-        coord_mapping = {}
-
-        def copy_coords(src_coords, add_method):
-            for coord in src_coords:
-                dims = src.coord_dims(coord)
-                if coord == src_x_coord:
-                    coord = grid_x_coord
-                elif coord == src_y_coord:
-                    coord = grid_y_coord
-                elif x_dim in dims or y_dim in dims:
-                    continue
-                result_coord = coord.copy()
-                add_method(result_coord, dims)
-                coord_mapping[id(coord)] = result_coord
-
-        copy_coords(src.dim_coords, result.add_dim_coord)
-        copy_coords(src.aux_coords, result.add_aux_coord)
-
-        def regrid_reference_surface(
-            src_surface_coord,
-            surface_dims,
-            x_dim,
-            y_dim,
-            src_x_coord,
-            src_y_coord,
-            sample_grid_x,
-            sample_grid_y,
-            regrid_callback,
-        ):
-            # Determine which of the reference surface's dimensions span the X
-            # and Y dimensions of the source cube.
-            surface_x_dim = surface_dims.index(x_dim)
-            surface_y_dim = surface_dims.index(y_dim)
-            surface = regrid_callback(
-                src_surface_coord.points,
-                surface_x_dim,
-                surface_y_dim,
-                src_x_coord,
-                src_y_coord,
-                sample_grid_x,
-                sample_grid_y,
-            )
-            surface_coord = src_surface_coord.copy(surface)
-            return surface_coord
-
-        # Copy across any AuxFactory instances, and regrid their reference
-        # surfaces where required.
-        for factory in src.aux_factories:
-            for coord in factory.dependencies.values():
-                if coord is None:
-                    continue
-                dims = src.coord_dims(coord)
-                if x_dim in dims and y_dim in dims:
-                    result_coord = regrid_reference_surface(
-                        coord,
-                        dims,
-                        x_dim,
-                        y_dim,
-                        src_x_coord,
-                        src_y_coord,
-                        sample_grid_x,
-                        sample_grid_y,
-                        regrid_callback,
-                    )
-                    result.add_aux_coord(result_coord, dims)
-                    coord_mapping[id(coord)] = result_coord
-            try:
-                result.add_aux_factory(factory.updated(coord_mapping))
-            except KeyError:
-                msg = (
-                    "Cannot update aux_factory {!r} because of dropped"
-                    " coordinates.".format(factory.name())
-                )
-                warnings.warn(msg)
-        return result
-
     def _check_units(self, coord):
         from iris.coord_systems import GeogCS, RotatedGeogCS
 
@@ -1089,20 +974,168 @@ def __call__(self, src):
         )
 
         # Wrap up the data as a Cube.
-        regrid_callback = functools.partial(
-            self._regrid, method=self._method, extrapolation_mode="nan"
+        _regrid_callback = functools.partial(
+            self._regrid,
+            src_x_coord=src_x_coord,
+            src_y_coord=src_y_coord,
+            sample_grid_x=sample_grid_x,
+            sample_grid_y=sample_grid_y,
+            method=self._method,
+            extrapolation_mode="nan",
         )
-        result = self._create_cube(
+
+        def regrid_callback(*args, **kwargs):
+            _data, dims = args
+            return _regrid_callback(_data, *dims, **kwargs)
+
+        result = _create_cube(
             data,
             src,
-            x_dim,
-            y_dim,
-            src_x_coord,
-            src_y_coord,
-            grid_x_coord,
-            grid_y_coord,
-            sample_grid_x,
-            sample_grid_y,
+            [x_dim, y_dim],
+            [grid_x_coord, grid_y_coord],
+            2,
             regrid_callback,
         )
         return result
+
+
+def _create_cube(
+    data, src, src_dims, tgt_coords, num_tgt_dims, regrid_callback
+):
+    r"""
+    Return a new cube for the result of regridding.
+    Returned cube represents the result of regridding the source cube
+    onto the horizontal coordinates (e.g. latitude) of the target cube.
+    All the metadata and coordinates of the result cube are copied from
+    the source cube, with two exceptions:
+        - Horizontal coordinates are copied from the target cube.
+        - Auxiliary coordinates which span the grid dimensions are
+          ignored.
+    Parameters
+    ----------
+    data : array
+        The regridded data as an N-dimensional NumPy array.
+    src : cube
+        The source Cube.
+    src_dims : tuple of int
+        The dimensions of the X and Y coordinate within the source Cube.
+    tgt_coords : tuple of :class:`iris.coords.Coord`\\ 's
+        Either two 1D :class:`iris.coords.DimCoord`\\ 's, two 1D
+        :class:`iris.experimental.ugrid.DimCoord`\\ 's or two ND
+        :class:`iris.coords.AuxCoord`\\ 's representing the new grid's
+        X and Y coordinates.
+    num_tgt_dims : int
+        The number of dimensions that the `tgt_coords` span.
+    regrid_callback : callable
+        The routine that will be used to calculate the interpolated
+        values of any reference surfaces.
+    Returns
+    -------
+    cube
+        A new iris.cube.Cube instance.
+    """
+    from iris.coords import DimCoord
+    from iris.cube import Cube
+
+    result = Cube(data)
+
+    if len(src_dims) >= 2:
+        grid_dim_x, grid_dim_y = src_dims[:2]
+    elif len(src_dims) == 1:
+        grid_dim_x = src_dims[0]
+        grid_dim_y = grid_dim_x + 1
+
+    if num_tgt_dims == 1:
+        grid_dim_x = grid_dim_y = min(src_dims)
+    for tgt_coord, dim in zip(tgt_coords, (grid_dim_x, grid_dim_y)):
+        if len(tgt_coord.shape) == 1:
+            if isinstance(tgt_coord, DimCoord) and dim is not None:
+                result.add_dim_coord(tgt_coord, dim)
+            else:
+                result.add_aux_coord(tgt_coord, dim)
+        else:
+            result.add_aux_coord(tgt_coord, (grid_dim_y, grid_dim_x))
+
+    result.metadata = copy.deepcopy(src.metadata)
+
+    # Copy across all the coordinates which don't span the grid.
+    # Record a mapping from old coordinate IDs to new coordinates,
+    # for subsequent use in creating updated aux_factories.
+
+    coord_mapping = {}
+
+    def copy_coords(src_coords, add_method):
+        for coord in src_coords:
+            dims = src.coord_dims(coord)
+            if set(src_dims).intersection(set(dims)):
+                continue
+            if guess_coord_axis(coord) in ["X", "Y"]:
+                continue
+
+            def dim_offset(dim):
+                offset = sum(
+                    [
+                        d <= dim
+                        for d in (grid_dim_x, grid_dim_y)
+                        if d is not None
+                    ]
+                )
+                if offset and num_tgt_dims == 1:
+                    offset -= 1
+                offset -= sum([d <= dim for d in src_dims if d is not None])
+                return dim + offset
+
+            dims = [dim_offset(dim) for dim in dims]
+            result_coord = coord.copy()
+            # Add result_coord to the owner of add_method.
+            add_method(result_coord, dims)
+            coord_mapping[id(coord)] = result_coord
+
+    copy_coords(src.dim_coords, result.add_dim_coord)
+    copy_coords(src.aux_coords, result.add_aux_coord)
+
+    def regrid_reference_surface(
+        src_surface_coord,
+        surface_dims,
+        src_dims,
+        regrid_callback,
+    ):
+        # Determine which of the reference surface's dimensions span the X
+        # and Y dimensions of the source cube.
+        relative_surface_dims = [
+            surface_dims.index(dim) if dim is not None else None
+            for dim in src_dims
+        ]
+        surface = regrid_callback(
+            src_surface_coord.points,
+            relative_surface_dims,
+        )
+        surface_coord = src_surface_coord.copy(surface)
+        return surface_coord
+
+    # Copy across any AuxFactory instances, and regrid their reference
+    # surfaces where required.
+    for factory in src.aux_factories:
+        for coord in factory.dependencies.values():
+            if coord is None:
+                continue
+            dims = src.coord_dims(coord)
+            if set(src_dims).intersection(dims):
+                result_coord = regrid_reference_surface(
+                    coord,
+                    dims,
+                    src_dims,
+                    regrid_callback,
+                )
+                result.add_aux_coord(result_coord, dims)
+                coord_mapping[id(coord)] = result_coord
+        try:
+            result.add_aux_factory(factory.updated(coord_mapping))
+        except KeyError:
+            msg = (
+                "Cannot update aux_factory {!r} because of dropped"
+                " coordinates.".format(factory.name())
+            )
+            warnings.warn(msg)
+
+    return result
diff --git a/lib/iris/analysis/_scipy_interpolate.py b/lib/iris/analysis/_scipy_interpolate.py
index fc64249729..bfa070c7c7 100644
--- a/lib/iris/analysis/_scipy_interpolate.py
+++ b/lib/iris/analysis/_scipy_interpolate.py
@@ -225,7 +225,6 @@ def compute_interp_weights(self, xi, method=None):
         prepared = (xi_shape, method) + self._find_indices(xi.T)
 
         if method == "linear":
-
             xi_shape, method, indices, norm_distances, out_of_bounds = prepared
 
             # Allocate arrays for describing the sparse matrix.
diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py
index 4630f47967..75b7d86406 100644
--- a/lib/iris/analysis/calculus.py
+++ b/lib/iris/analysis/calculus.py
@@ -147,6 +147,12 @@ def cube_delta(cube, coord):
 
     .. note:: Missing data support not yet implemented.
 
+    .. note::
+
+            This function does not maintain laziness when called; it realises data.
+            See more at :doc:`/userguide/real_and_lazy_data`.
+
+
     """
     # handle the case where a user passes a coordinate name
     if isinstance(coord, str):
@@ -251,6 +257,11 @@ def differentiate(cube, coord_to_differentiate):
 
     .. note:: Spherical differentiation does not occur in this routine.
 
+    .. note::
+
+            This function does not maintain laziness when called; it realises data.
+            See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     # Get the delta cube in the required differential direction.
     # This operation results in a copy of the original cube.
@@ -532,6 +543,12 @@ def curl(i_cube, j_cube, k_cube=None):
 
             where phi is longitude, theta is latitude.
 
+    .. note::
+
+            This function does not maintain laziness when called; it realises data.
+            See more at :doc:`/userguide/real_and_lazy_data`.
+
+
     """
     # Get the vector quantity names.
     # (i.e. ['easterly', 'northerly', 'vertical'])
@@ -577,7 +594,6 @@ def curl(i_cube, j_cube, k_cube=None):
         horiz_cs, (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS)
     )
     if not spherical_coords:
-
         # TODO Implement some mechanism for conforming to a common grid
         dj_dx = _curl_differentiate(j_cube, x_coord)
         prototype_diff = dj_dx
@@ -741,6 +757,12 @@ def spatial_vectors_with_phenom_name(i_cube, j_cube, k_cube=None):
 #doctest: +SKIP
         (['u', 'v', 'w'], 'wind')
 
+    Notes
+    ------
+            This function maintains laziness when called; it does not realise data.
+            See more at :doc:`/userguide/real_and_lazy_data`.
+
+
     """
     directional_names = (
         ("u", "v", "w"),
diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py
index f38e48354d..a8e90a63ad 100644
--- a/lib/iris/analysis/cartography.py
+++ b/lib/iris/analysis/cartography.py
@@ -66,6 +66,10 @@ def wrap_lons(lons, base, period):
         >>> print(wrap_lons(np.array([185, 30, -200, 75]), -180, 360))
         [-175.   30.  160.   75.]
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     # It is important to use 64bit floating precision when changing a floats
     # numbers range.
@@ -169,20 +173,25 @@ def rotate_pole(lons, lats, pole_lon, pole_lat):
 
 
 def _get_lon_lat_coords(cube):
-    lat_coords = [
-        coord for coord in cube.coords() if "latitude" in coord.name()
-    ]
-    lon_coords = [
-        coord for coord in cube.coords() if "longitude" in coord.name()
-    ]
+    def search_for_coord(coord_iterable, coord_name):
+        return [
+            coord for coord in coord_iterable if coord_name in coord.name()
+        ]
+
+    lat_coords = search_for_coord(
+        cube.dim_coords, "latitude"
+    ) or search_for_coord(cube.coords(), "latitude")
+    lon_coords = search_for_coord(
+        cube.dim_coords, "longitude"
+    ) or search_for_coord(cube.coords(), "longitude")
     if len(lat_coords) > 1 or len(lon_coords) > 1:
         raise ValueError(
-            "Calling `_get_lon_lat_coords` with multiple lat or lon coords"
+            "Calling `_get_lon_lat_coords` with multiple same-type (i.e. dim/aux) lat or lon coords"
             " is currently disallowed"
         )
     lat_coord = lat_coords[0]
     lon_coord = lon_coords[0]
-    return (lon_coord, lat_coord)
+    return lon_coord, lat_coord
 
 
 def _xy_range(cube, mode=None):
@@ -266,6 +275,10 @@ def get_xy_grids(cube):
 
         x, y = get_xy_grids(cube)
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y")
 
@@ -294,6 +307,11 @@ def get_xy_contiguous_bounded_grids(cube):
 
         xs, ys = get_xy_contiguous_bounded_grids(cube)
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y")
 
@@ -493,6 +511,10 @@ def cosine_latitude_weights(cube):
         cube = iris.load_cube(iris.sample_data_path('air_temp.pp'))
         weights = np.sqrt(cosine_latitude_weights(cube))
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     # Find all latitude coordinates, we want one and only one.
     lat_coords = [
@@ -578,6 +600,11 @@ def project(cube, target_proj, nx=None, ny=None):
         An instance of :class:`iris.cube.Cube` and a list describing the
         extent of the projection.
 
+    .. note::
+
+        If there are both dim and aux latitude-longitude coordinates, only
+        the dim coordinates will be used.
+
     .. note::
 
         This function assumes global data and will if necessary extrapolate
@@ -591,6 +618,11 @@ def project(cube, target_proj, nx=None, ny=None):
         resulting nearest neighbour values.  If masked, the value in the
         resulting cube is set to 0.
 
+    .. note::
+
+        This function does not maintain laziness when called; it realises data.
+        See more at :doc:`/userguide/real_and_lazy_data`.
+
     .. warning::
 
         This function uses a nearest neighbour approach rather than any form
@@ -1065,6 +1097,11 @@ def rotate_winds(u_cube, v_cube, target_cs):
         The names of the output cubes are those of the inputs, prefixed with
         'transformed\_' (e.g. 'transformed_x_wind').
 
+    .. note::
+
+            This function does not maintain laziness when called; it realises data.
+            See more at :doc:`/userguide/real_and_lazy_data`.
+
     .. warning::
 
         Conversion between rotated-pole and non-rotated systems can be
diff --git a/lib/iris/analysis/geometry.py b/lib/iris/analysis/geometry.py
index a412a26ebc..b246b518d4 100644
--- a/lib/iris/analysis/geometry.py
+++ b/lib/iris/analysis/geometry.py
@@ -160,6 +160,11 @@ def geometry_area_weights(cube, geometry, normalize=False):
         calculation might be wrong. In this case, a UserWarning will
         be issued.
 
+    .. note::
+
+        This function does not maintain laziness when called; it realises data.
+        See more at :doc:`/userguide/real_and_lazy_data`.
+
     Args:
 
     * cube (:class:`iris.cube.Cube`):
diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py
index 468847bca2..09a02ad51c 100644
--- a/lib/iris/analysis/maths.py
+++ b/lib/iris/analysis/maths.py
@@ -115,6 +115,11 @@ def abs(cube, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(np.abs, cube.dtype, in_place=in_place)
@@ -160,6 +165,11 @@ def intersection_of_cubes(cube, other_cube):
           intersections = cubes.extract_overlapping(coords)
           cube1, cube2 = (intersections[0], intersections[1])
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     wmsg = (
         "iris.analysis.maths.intersection_of_cubes has been deprecated and will "
@@ -243,6 +253,11 @@ def add(cube, other, dim=None, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(
@@ -292,6 +307,11 @@ def subtract(cube, other, dim=None, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(
@@ -383,6 +403,10 @@ def multiply(cube, other, dim=None, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     _assert_is_cube(cube)
 
@@ -456,6 +480,10 @@ def divide(cube, other, dim=None, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     _assert_is_cube(cube)
 
@@ -519,6 +547,10 @@ def exponentiate(cube, exponent, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(
@@ -567,6 +599,11 @@ def exp(cube, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(np.exp, cube.dtype, in_place=in_place)
@@ -593,6 +630,11 @@ def log(cube, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(np.log, cube.dtype, in_place=in_place)
@@ -623,6 +665,11 @@ def log2(cube, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(np.log2, cube.dtype, in_place=in_place)
@@ -649,6 +696,11 @@ def log10(cube, in_place=False):
     Returns:
         An instance of :class:`iris.cube.Cube`.
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     _assert_is_cube(cube)
     new_dtype = _output_dtype(np.log10, cube.dtype, in_place=in_place)
@@ -703,6 +755,12 @@ def apply_ufunc(
 
         cube = apply_ufunc(numpy.sin, cube, in_place=True)
 
+    .. note::
+
+        This function maintains laziness when called; it does not realise data. This is dependent on `ufunc` argument
+        being a numpy operation that is compatible with lazy operation.
+        See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
 
     if not isinstance(ufunc, np.ufunc):
diff --git a/lib/iris/analysis/trajectory.py b/lib/iris/analysis/trajectory.py
index 946ae1cb2c..24f7a9dede 100644
--- a/lib/iris/analysis/trajectory.py
+++ b/lib/iris/analysis/trajectory.py
@@ -85,7 +85,6 @@ def __init__(self, waypoints, sample_count=10):
         cur_seg = segments[cur_seg_i]
         len_accum = cur_seg.length
         for p in range(self.sample_count):
-
             # calculate the sample position along our total length
             sample_at_len = p * sample_step
 
@@ -216,6 +215,10 @@ def interpolate(cube, sample_points, method=None):
         ('longitude', [-60, -50, -40])]
         interpolated_cube = interpolate(cube, sample_points)
 
+    Notes
+    ------
+    This function does not maintain laziness when called; it realises data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     from iris.analysis import Linear
 
@@ -443,21 +446,7 @@ def interpolate(cube, sample_points, method=None):
         ]
 
         # Apply the fancy indexing to get all the result data points.
-        source_data = source_data[tuple(fancy_source_indices)]
-
-        # "Fix" problems with missing datapoints producing odd values
-        # when copied from a masked into an unmasked array.
-        # TODO: proper masked data handling.
-        if np.ma.isMaskedArray(source_data):
-            # This is **not** proper mask handling, because we cannot produce a
-            # masked result, but it ensures we use a "filled" version of the
-            # input in this case.
-            source_data = source_data.filled()
-        new_cube.data[:] = source_data
-        # NOTE: we assign to "new_cube.data[:]" and *not* just "new_cube.data",
-        # because the existing code produces a default dtype from 'np.empty'
-        # instead of preserving the input dtype.
-        # TODO: maybe this should be fixed -- i.e. to preserve input dtype ??
+        new_cube.data = source_data[tuple(fancy_source_indices)]
 
         # Fill in the empty squashed (non derived) coords.
         column_coords = [
diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py
index 8ec39bb4b1..cb3149fe58 100644
--- a/lib/iris/common/metadata.py
+++ b/lib/iris/common/metadata.py
@@ -969,6 +969,7 @@ def _combine_lenient(self, other):
             A list of combined metadata member values.
 
         """
+
         # Perform "strict" combination for "coord_system" and "climatological".
         def func(field):
             left = getattr(self, field)
@@ -1024,6 +1025,7 @@ def _difference_lenient(self, other):
             A list of difference metadata member values.
 
         """
+
         # Perform "strict" difference for "coord_system" and "climatological".
         def func(field):
             left = getattr(self, field)
diff --git a/lib/iris/coord_categorisation.py b/lib/iris/coord_categorisation.py
index 72019b4b87..698b4828f1 100644
--- a/lib/iris/coord_categorisation.py
+++ b/lib/iris/coord_categorisation.py
@@ -90,6 +90,7 @@ def vectorised_fn(*args):
 # coordinates only
 #
 
+
 # Private "helper" function
 def _pt_date(coord, time):
     """
diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py
index 802571925e..edf0c1871b 100644
--- a/lib/iris/coord_systems.py
+++ b/lib/iris/coord_systems.py
@@ -478,7 +478,6 @@ def datum(self, value):
 
     @classmethod
     def from_datum(cls, datum, longitude_of_prime_meridian=None):
-
         crs = super().__new__(cls)
 
         crs._semi_major_axis = None
@@ -949,7 +948,6 @@ def __init__(
         false_northing=None,
         ellipsoid=None,
     ):
-
         """
         Constructs a Geostationary coord system.
 
diff --git a/lib/iris/coords.py b/lib/iris/coords.py
index d0d471a634..91bb786ae8 100644
--- a/lib/iris/coords.py
+++ b/lib/iris/coords.py
@@ -18,7 +18,6 @@
 import warnings
 import zlib
 
-import cftime
 import dask.array as da
 import numpy as np
 import numpy.ma as ma
@@ -1345,7 +1344,14 @@ def __add__(self, mod):
         return Cell(point, bound)
 
     def __hash__(self):
-        return super().__hash__()
+        # See __eq__ for the definition of when two cells are equal.
+        if self.bound is None:
+            return hash(self.point)
+        bound = self.bound
+        rbound = bound[::-1]
+        if rbound < bound:
+            bound = rbound
+        return hash((self.point, bound))
 
     def __eq__(self, other):
         """
@@ -1411,16 +1417,6 @@ def __common_cmp__(self, other, operator_method):
         ):
             raise ValueError("Unexpected operator_method")
 
-        # Prevent silent errors resulting from missing cftime
-        # behaviour.
-        if isinstance(other, cftime.datetime) or (
-            isinstance(self.point, cftime.datetime)
-            and not isinstance(other, iris.time.PartialDateTime)
-        ):
-            raise TypeError(
-                "Cannot determine the order of " "cftime.datetime objects"
-            )
-
         if isinstance(other, Cell):
             # Cell vs Cell comparison for providing a strict sort order
             if self.bound is None:
@@ -1485,19 +1481,7 @@ def __common_cmp__(self, other, operator_method):
                 else:
                     me = max(self.bound)
 
-            # Work around to handle cftime.datetime comparison, which
-            # doesn't return NotImplemented on failure in some versions of the
-            # library
-            try:
-                result = operator_method(me, other)
-            except TypeError:
-                rop = {
-                    operator.lt: operator.gt,
-                    operator.gt: operator.lt,
-                    operator.le: operator.ge,
-                    operator.ge: operator.le,
-                }[operator_method]
-                result = rop(other, me)
+            result = operator_method(me, other)
 
         return result
 
@@ -1895,7 +1879,22 @@ def cells(self):
               ...
 
         """
-        return _CellIterator(self)
+        if self.ndim != 1:
+            raise iris.exceptions.CoordinateMultiDimError(self)
+
+        points = self.points
+        bounds = self.bounds
+        if self.units.is_time_reference():
+            points = self.units.num2date(points)
+            if self.has_bounds():
+                bounds = self.units.num2date(bounds)
+
+        if self.has_bounds():
+            for point, bound in zip(points, bounds):
+                yield Cell(point, bound)
+        else:
+            for point in points:
+                yield Cell(point)
 
     def _sanity_check_bounds(self):
         if self.ndim == 1:
@@ -2382,18 +2381,16 @@ def intersect(self, other, return_indices=False):
             )
             raise ValueError(msg)
 
-        # Cache self.cells for speed. We can also use the index operation on a
-        # list conveniently.
-        self_cells = [cell for cell in self.cells()]
+        # Cache self.cells for speed. We can also use the dict for fast index
+        # lookup.
+        self_cells = {cell: idx for idx, cell in enumerate(self.cells())}
 
         # Maintain a list of indices on self for which cells exist in both self
         # and other.
         self_intersect_indices = []
         for cell in other.cells():
-            try:
-                self_intersect_indices.append(self_cells.index(cell))
-            except ValueError:
-                pass
+            if cell in self_cells:
+                self_intersect_indices.append(self_cells[cell])
 
         if return_indices is False and self_intersect_indices == []:
             raise ValueError(
@@ -2849,7 +2846,6 @@ def _new_bounds_requirements(self, bounds):
             n_bounds = bounds.shape[-1]
             n_points = bounds.shape[0]
             if n_points > 1:
-
                 directions = set()
                 for b_index in range(n_bounds):
                     monotonic, direction = iris.util.monotonic(
@@ -3137,22 +3133,6 @@ def xml_element(self, doc):
         return cellMethod_xml_element
 
 
-# See Coord.cells() for the description/context.
-class _CellIterator(Iterator):
-    def __init__(self, coord):
-        self._coord = coord
-        if coord.ndim != 1:
-            raise iris.exceptions.CoordinateMultiDimError(coord)
-        self._indices = iter(range(coord.shape[0]))
-
-    def __next__(self):
-        # NB. When self._indices runs out it will raise StopIteration for us.
-        i = next(self._indices)
-        return self._coord.cell(i)
-
-    next = __next__
-
-
 # See ExplicitCoord._group() for the description/context.
 class _GroupIterator(Iterator):
     def __init__(self, points):
diff --git a/lib/iris/cube.py b/lib/iris/cube.py
index 8879ade621..abe37c35fb 100644
--- a/lib/iris/cube.py
+++ b/lib/iris/cube.py
@@ -185,6 +185,12 @@ def _assert_is_cube(obj):
             )
             raise ValueError(msg)
 
+    def _repr_html_(self):
+        from iris.experimental.representation import CubeListRepresentation
+
+        representer = CubeListRepresentation(self)
+        return representer.repr_html()
+
     # TODO #370 Which operators need overloads?
 
     def __add__(self, other):
@@ -878,7 +884,8 @@ def __init__(
             This object defines the shape of the cube and the phenomenon
             value in each cell.
 
-            ``data`` can be a dask array, a NumPy array, a NumPy array
+            ``data`` can be a :class:`dask.array.Array`, a
+            :class:`numpy.ndarray`, a NumPy array
             subclass (such as :class:`numpy.ma.MaskedArray`), or
             array_like (as described in :func:`numpy.asarray`).
 
@@ -1152,7 +1159,9 @@ def add_aux_coord(self, coord, data_dims=None):
 
         """
         if self.coords(coord):  # TODO: just fail on duplicate object
-            raise ValueError("Duplicate coordinates are not permitted.")
+            raise iris.exceptions.CannotAddError(
+                "Duplicate coordinates are not permitted."
+            )
         self._add_unique_aux_coord(coord, data_dims)
 
     def _check_multi_dim_metadata(self, metadata, data_dims):
@@ -1172,7 +1181,7 @@ def _check_multi_dim_metadata(self, metadata, data_dims):
                         len(data_dims), metadata.ndim, metadata.name()
                     )
                 )
-                raise ValueError(msg)
+                raise iris.exceptions.CannotAddError(msg)
             # Check compatibility with the shape of the data
             for i, dim in enumerate(data_dims):
                 if metadata.shape[i] != self.shape[dim]:
@@ -1180,7 +1189,7 @@ def _check_multi_dim_metadata(self, metadata, data_dims):
                         "Unequal lengths. Cube dimension {} => {};"
                         " metadata {!r} dimension {} => {}."
                     )
-                    raise ValueError(
+                    raise iris.exceptions.CannotAddError(
                         msg.format(
                             dim,
                             self.shape[dim],
@@ -1192,7 +1201,7 @@ def _check_multi_dim_metadata(self, metadata, data_dims):
         elif metadata.shape != (1,):
             msg = "Missing data dimensions for multi-valued {} {!r}"
             msg = msg.format(metadata.__class__.__name__, metadata.name())
-            raise ValueError(msg)
+            raise iris.exceptions.CannotAddError(msg)
         return data_dims
 
     def _add_unique_aux_coord(self, coord, data_dims):
@@ -1206,7 +1215,7 @@ def _add_unique_aux_coord(self, coord, data_dims):
                     "cube {item} of {ownval!r}."
                 )
                 if coord.mesh != mesh:
-                    raise ValueError(
+                    raise iris.exceptions.CannotAddError(
                         msg.format(
                             item="mesh",
                             coord=coord,
@@ -1216,7 +1225,7 @@ def _add_unique_aux_coord(self, coord, data_dims):
                     )
                 location = self.location
                 if coord.location != location:
-                    raise ValueError(
+                    raise iris.exceptions.CannotAddError(
                         msg.format(
                             item="location",
                             coord=coord,
@@ -1226,7 +1235,7 @@ def _add_unique_aux_coord(self, coord, data_dims):
                     )
                 mesh_dims = (self.mesh_dim(),)
                 if data_dims != mesh_dims:
-                    raise ValueError(
+                    raise iris.exceptions.CannotAddError(
                         msg.format(
                             item="mesh dimension",
                             coord=coord,
@@ -1266,7 +1275,9 @@ def coordsonly(coords_and_dims):
             ref_coord = aux_factory.dependencies[dependency]
             if ref_coord is not None and ref_coord not in cube_coords:
                 msg = "{} coordinate for factory is not present on cube {}"
-                raise ValueError(msg.format(ref_coord.name(), self.name()))
+                raise iris.exceptions.CannotAddError(
+                    msg.format(ref_coord.name(), self.name())
+                )
         self._aux_factories.append(aux_factory)
 
     def add_cell_measure(self, cell_measure, data_dims=None):
@@ -1293,7 +1304,9 @@ def add_cell_measure(self, cell_measure, data_dims=None):
 
         """
         if self.cell_measures(cell_measure):
-            raise ValueError("Duplicate cell_measures are not permitted.")
+            raise iris.exceptions.CannotAddError(
+                "Duplicate cell_measures are not permitted."
+            )
         data_dims = self._check_multi_dim_metadata(cell_measure, data_dims)
         self._cell_measures_and_dims.append((cell_measure, data_dims))
         self._cell_measures_and_dims.sort(
@@ -1321,7 +1334,9 @@ def add_ancillary_variable(self, ancillary_variable, data_dims=None):
         """
 
         if self.ancillary_variables(ancillary_variable):
-            raise ValueError("Duplicate ancillary variables not permitted")
+            raise iris.exceptions.CannotAddError(
+                "Duplicate ancillary variables not permitted"
+            )
 
         data_dims = self._check_multi_dim_metadata(
             ancillary_variable, data_dims
@@ -1352,13 +1367,13 @@ def add_dim_coord(self, dim_coord, data_dim):
 
         """
         if self.coords(dim_coord):
-            raise ValueError(
+            raise iris.exceptions.CannotAddError(
                 "The coordinate already exists on the cube. "
                 "Duplicate coordinates are not permitted."
             )
         # Check dimension is available
         if self.coords(dimensions=data_dim, dim_coords=True):
-            raise ValueError(
+            raise iris.exceptions.CannotAddError(
                 "A dim_coord is already associated with "
                 "dimension %d." % data_dim
             )
@@ -1366,12 +1381,14 @@ def add_dim_coord(self, dim_coord, data_dim):
 
     def _add_unique_dim_coord(self, dim_coord, data_dim):
         if isinstance(dim_coord, iris.coords.AuxCoord):
-            raise ValueError("The dim_coord may not be an AuxCoord instance.")
+            raise iris.exceptions.CannotAddError(
+                "The dim_coord may not be an AuxCoord instance."
+            )
 
         # Convert data_dim to a single integer
         if isinstance(data_dim, Container):
             if len(data_dim) != 1:
-                raise ValueError(
+                raise iris.exceptions.CannotAddError(
                     "The supplied data dimension must be a" " single number."
                 )
             data_dim = int(list(data_dim)[0])
@@ -1380,7 +1397,7 @@ def _add_unique_dim_coord(self, dim_coord, data_dim):
 
         # Check data_dim value is valid
         if data_dim < 0 or data_dim >= self.ndim:
-            raise ValueError(
+            raise iris.exceptions.CannotAddError(
                 "The cube does not have the specified dimension "
                 "(%d)" % data_dim
             )
@@ -1388,7 +1405,7 @@ def _add_unique_dim_coord(self, dim_coord, data_dim):
         # Check compatibility with the shape of the data
         if dim_coord.shape[0] != self.shape[data_dim]:
             msg = "Unequal lengths. Cube dimension {} => {}; coord {!r} => {}."
-            raise ValueError(
+            raise iris.exceptions.CannotAddError(
                 msg.format(
                     data_dim,
                     self.shape[data_dim],
@@ -1984,6 +2001,12 @@ def coord(
             if name_or_coord is not None:
                 if not isinstance(name_or_coord, str):
                     _name = name_or_coord.name()
+                    emsg = (
+                        "Expected to find exactly 1 coordinate matching the given "
+                        f"{_name!r} coordinate's metadata, but found none."
+                    )
+                    raise iris.exceptions.CoordinateNotFoundError(emsg)
+
             bad_name = _name or standard_name or long_name or ""
             emsg = (
                 f"Expected to find exactly 1 {bad_name!r} coordinate, "
@@ -2188,9 +2211,15 @@ def cell_measure(self, name_or_cell_measure=None):
                 bad_name = (
                     name_or_cell_measure and name_or_cell_measure.name()
                 ) or ""
+                if name_or_cell_measure is not None:
+                    emsg = (
+                        "Expected to find exactly 1 cell measure matching the given "
+                        f"{bad_name!r} cell measure's metadata, but found none."
+                    )
+                    raise iris.exceptions.CellMeasureNotFoundError(emsg)
             msg = (
-                "Expected to find exactly 1 %s cell_measure, but found "
-                "none." % bad_name
+                f"Expected to find exactly 1 {bad_name!r} cell measure, "
+                "but found none."
             )
             raise iris.exceptions.CellMeasureNotFoundError(msg)
 
@@ -2275,9 +2304,16 @@ def ancillary_variable(self, name_or_ancillary_variable=None):
                     name_or_ancillary_variable
                     and name_or_ancillary_variable.name()
                 ) or ""
+                if name_or_ancillary_variable is not None:
+                    emsg = (
+                        "Expected to find exactly 1 ancillary_variable matching the "
+                        f"given {bad_name!r} ancillary_variable's metadata, but found "
+                        "none."
+                    )
+                    raise iris.exceptions.AncillaryVariableNotFoundError(emsg)
             msg = (
-                "Expected to find exactly 1 {!s} ancillary_variable, but "
-                "found none.".format(bad_name)
+                f"Expected to find exactly 1 {bad_name!r} ancillary_variable, "
+                "but found none."
             )
             raise iris.exceptions.AncillaryVariableNotFoundError(msg)
 
@@ -2293,10 +2329,23 @@ def cell_methods(self):
         return self._metadata_manager.cell_methods
 
     @cell_methods.setter
-    def cell_methods(self, cell_methods):
-        self._metadata_manager.cell_methods = (
-            tuple(cell_methods) if cell_methods else tuple()
-        )
+    def cell_methods(self, cell_methods: Iterable):
+        if not cell_methods:
+            # For backwards compatibility: Empty or null value is equivalent to ().
+            cell_methods = ()
+        else:
+            # Can supply any iterable, which is converted (copied) to a tuple.
+            cell_methods = tuple(cell_methods)
+            for cell_method in cell_methods:
+                # All contents should be CellMethods.  Requiring class membership is
+                # somewhat non-Pythonic, but simple, and not a problem for now.
+                if not isinstance(cell_method, iris.coords.CellMethod):
+                    msg = (
+                        f"Cube.cell_methods assigned value includes {cell_method}, "
+                        "which is not an iris.coords.CellMethod."
+                    )
+                    raise ValueError(msg)
+        self._metadata_manager.cell_methods = cell_methods
 
     def core_data(self):
         """
@@ -2639,7 +2688,6 @@ def subset(self, coord):
             coord_to_extract in self.aux_coords
             and len(coord_to_extract.points) == 1
         ):
-
             # Default to returning None
             result = None
 
@@ -4038,8 +4086,9 @@ def aggregated_by(
         # coordinate dimension.
         shared_coords = list(
             filter(
-                lambda coord_: coord_ not in groupby_coords,
-                self.coords(contains_dimension=dimension_to_groupby),
+                lambda coord_: coord_ not in groupby_coords
+                and dimension_to_groupby in self.coord_dims(coord_),
+                self.dim_coords + self.aux_coords,
             )
         )
 
@@ -4071,6 +4120,11 @@ def aggregated_by(
         for coord in groupby_coords + shared_coords:
             aggregateby_cube.remove_coord(coord)
 
+        coord_mapping = {}
+        for coord in aggregateby_cube.coords():
+            orig_id = id(self.coord(coord))
+            coord_mapping[orig_id] = coord
+
         # Determine the group-by cube data shape.
         data_shape = list(self.shape + aggregator.aggregate_shape(**kwargs))
         data_shape[dimension_to_groupby] = len(groupby)
@@ -4199,6 +4253,11 @@ def aggregated_by(
                 aggregateby_cube.add_aux_coord(
                     new_coord, self.coord_dims(lookup_coord)
                 )
+            coord_mapping[id(self.coord(lookup_coord))] = new_coord
+
+        aggregateby_cube._aux_factories = []
+        for factory in self.aux_factories:
+            aggregateby_cube.add_aux_factory(factory.updated(coord_mapping))
 
         # Attach the aggregate-by data into the aggregate-by cube.
         if aggregateby_weights is None:
diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py
index 12d24ef70f..5d3da3349e 100644
--- a/lib/iris/exceptions.py
+++ b/lib/iris/exceptions.py
@@ -174,3 +174,9 @@ class UnitConversionError(IrisError):
     """Raised when Iris is unable to convert a unit."""
 
     pass
+
+
+class CannotAddError(ValueError):
+    """Raised when an object (e.g. coord) cannot be added to a :class:`~iris.cube.Cube`."""
+
+    pass
diff --git a/lib/iris/experimental/animate.py b/lib/iris/experimental/animate.py
index fb2e2af590..1b6c2d46be 100644
--- a/lib/iris/experimental/animate.py
+++ b/lib/iris/experimental/animate.py
@@ -6,118 +6,32 @@
 """
 Wrapper for animating iris cubes using iris or matplotlib plotting functions
 
-"""
-
-import warnings
+Notes
+-----
+.. deprecated:: 3.4.0
 
-import matplotlib.animation as animation
-import matplotlib.pyplot as plt
+``iris.experimental.animate.animate()`` has been moved to
+:func:`iris.plot.animate`. This module will therefore be removed in a future
+release.
 
-import iris
+"""
 
 
 def animate(cube_iterator, plot_func, fig=None, **kwargs):
     """
     Animates the given cube iterator.
 
-    Args:
-
-    * cube_iterator (iterable of :class:`iris.cube.Cube` objects):
-        Each animation frame corresponds to each :class:`iris.cube.Cube`
-        object. See :meth:`iris.cube.Cube.slices`.
-
-    * plot_func (:mod:`iris.plot` or :mod:`iris.quickplot` plotting function):
-        Plotting function used to animate. Must accept the signature
-        ``plot_func(cube, vmin=vmin, vmax=vmax, coords=coords)``.
-        :func:`~iris.plot.contourf`, :func:`~iris.plot.contour`,
-        :func:`~iris.plot.pcolor` and :func:`~iris.plot.pcolormesh`
-        all conform to this signature.
-
-    Kwargs:
-
-    * fig (:class:`matplotlib.figure.Figure` instance):
-        By default, the current figure will be used or a new figure instance
-        created if no figure is available. See :func:`matplotlib.pyplot.gcf`.
-
-    * coords (list of :class:`~iris.coords.Coord` objects or coordinate names):
-        Use the given coordinates as the axes for the plot. The order of the
-        given coordinates indicates which axis to use for each, where the first
-        element is the horizontal axis of the plot and the second element is
-        the vertical axis of the plot.
-
-    * interval (int, float or long):
-        Defines the time interval in milliseconds between successive frames.
-        A default interval of 100ms is set.
-
-    * vmin, vmax (int, float or long):
-        Color scaling values, see :class:`matplotlib.colors.Normalize` for
-        further details. Default values are determined by the min-max across
-        the data set over the entire sequence.
-
-    See :class:`matplotlib.animation.FuncAnimation` for details of other valid
-    keyword arguments.
+    Warnings
+    --------
+    This function is now **disabled**.
 
-    Returns:
-        :class:`~matplotlib.animation.FuncAnimation` object suitable for
-        saving and or plotting.
-
-    For example, to animate along a set of cube slices::
-
-        cube_iter = cubes.slices(('grid_longitude', 'grid_latitude'))
-        ani = animate(cube_iter, qplt.contourf)
-        plt.show()
+    The functionality has been moved to :func:`iris.plot.animate`.
 
     """
-    kwargs.setdefault("interval", 100)
-    coords = kwargs.pop("coords", None)
-
-    if fig is None:
-        fig = plt.gcf()
-
-    def update_animation_iris(i, cubes, vmin, vmax, coords):
-        # Clearing the figure is currently necessary for compatibility with
-        # the iris quickploting module - due to the colorbar.
-        plt.gcf().clf()
-        plot_func(cubes[i], vmin=vmin, vmax=vmax, coords=coords)
-
-    # Turn cube iterator into a list to determine plot ranges.
-    # NOTE: we check that we are not providing a cube as this has a deprecated
-    # iter special method.
-    if hasattr(cube_iterator, "__iter__") and not isinstance(
-        cube_iterator, iris.cube.Cube
-    ):
-        cubes = iris.cube.CubeList(cube_iterator)
-    else:
-        msg = "iterable type object required for animation, {} given".format(
-            type(cube_iterator)
-        )
-        raise TypeError(msg)
-
-    supported = ["iris.plot", "iris.quickplot"]
-    if plot_func.__module__ not in supported:
-        msg = (
-            'Given plotting module "{}" may not be supported, intended '
-            "use: {}."
-        )
-        msg = msg.format(plot_func.__module__, supported)
-        warnings.warn(msg, UserWarning)
-
-    supported = ["contour", "contourf", "pcolor", "pcolormesh"]
-    if plot_func.__name__ not in supported:
-        msg = (
-            'Given plotting function "{}" may not be supported, intended '
-            "use: {}."
-        )
-        msg = msg.format(plot_func.__name__, supported)
-        warnings.warn(msg, UserWarning)
-
-    # Determine plot range.
-    vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes]))
-    vmax = kwargs.pop("vmax", max([cc.data.max() for cc in cubes]))
-
-    update = update_animation_iris
-    frames = range(len(cubes))
-
-    return animation.FuncAnimation(
-        fig, update, frames=frames, fargs=(cubes, vmin, vmax, coords), **kwargs
+    msg = (
+        "The function 'iris.experimental.animate.animate()' has been moved, "
+        "and is now at 'iris.plot.animate()'.\n"
+        "Please replace 'iris.experimental.animate.animate' with "
+        "'iris.plot.animate'."
     )
+    raise Exception(msg)
diff --git a/lib/iris/experimental/regrid_conservative.py b/lib/iris/experimental/regrid_conservative.py
index bfa048ddf0..fdc23c7bc4 100644
--- a/lib/iris/experimental/regrid_conservative.py
+++ b/lib/iris/experimental/regrid_conservative.py
@@ -17,13 +17,15 @@
 
 """
 
+import functools
+
 import cartopy.crs as ccrs
 import numpy as np
 
 import iris
 from iris._deprecation import warn_deprecated
 from iris.analysis._interpolation import get_xy_dim_coords
-from iris.analysis._regrid import RectilinearRegridder
+from iris.analysis._regrid import RectilinearRegridder, _create_cube
 from iris.util import _meshgrid
 
 wmsg = (
@@ -329,16 +331,23 @@ def _valid_units(coord):
 
     # Return result as a new cube based on the source.
     # TODO: please tidy this interface !!!
-    return RectilinearRegridder._create_cube(
-        fullcube_data,
-        src=source_cube,
-        x_dim=src_dims_xy[0],
-        y_dim=src_dims_xy[1],
+    _regrid_callback = functools.partial(
+        RectilinearRegridder._regrid,
         src_x_coord=src_coords[0],
         src_y_coord=src_coords[1],
-        grid_x_coord=dst_coords[0],
-        grid_y_coord=dst_coords[1],
         sample_grid_x=sample_grid_x,
         sample_grid_y=sample_grid_y,
-        regrid_callback=RectilinearRegridder._regrid,
+    )
+
+    def regrid_callback(*args, **kwargs):
+        _data, dims = args
+        return _regrid_callback(_data, *dims, **kwargs)
+
+    return _create_cube(
+        fullcube_data,
+        source_cube,
+        [src_dims_xy[0], src_dims_xy[1]],
+        [dst_coords[0], dst_coords[1]],
+        2,
+        regrid_callback,
     )
diff --git a/lib/iris/experimental/ugrid/mesh.py b/lib/iris/experimental/ugrid/mesh.py
index 974a563046..0d566da73f 100644
--- a/lib/iris/experimental/ugrid/mesh.py
+++ b/lib/iris/experimental/ugrid/mesh.py
@@ -131,7 +131,7 @@ def __init__(
 
         Args:
 
-        * indices (numpy.ndarray or numpy.ma.core.MaskedArray or dask.array.Array):
+        * indices (:class:`numpy.ndarray` or :class:`numpy.ma.core.MaskedArray` or :class:`dask.array.Array`):
             2D array giving the topological connection relationship between
             :attr:`location` elements and :attr:`connected` elements.
             The :attr:`location_axis` dimension indexes over the
@@ -501,7 +501,7 @@ def core_indices(self):
         NumPy array or a Dask array.
 
         Returns:
-            numpy.ndarray or numpy.ma.core.MaskedArray or dask.array.Array
+            :class:`numpy.ndarray` or :class:`numpy.ma.core.MaskedArray` or :class:`dask.array.Array`
 
         """
         return super()._core_values()
@@ -2841,16 +2841,60 @@ def __init__(
 
         # Get the 'coord identity' metadata from the relevant node-coordinate.
         node_coord = self.mesh.coord(include_nodes=True, axis=self.axis)
+        node_metadict = node_coord.metadata._asdict()
+        # Use node metadata, unless location is face/edge.
+        use_metadict = node_metadict.copy()
+        if location != "node":
+            # Location is either "edge" or "face" - get the relevant coord.
+            kwargs = {f"include_{location}s": True, "axis": axis}
+            location_coord = self.mesh.coord(**kwargs)
+
+            # Take the MeshCoord metadata from the 'location' coord.
+            use_metadict = location_coord.metadata._asdict()
+            unit_unknown = Unit(None)
+
+            # N.B. at present, coords in a Mesh are stored+accessed by 'axis', which
+            # means they must have a standard_name.  So ...
+            # (a) the 'location' (face/edge) coord *always* has a useable phenomenon
+            #     identity.
+            # (b) we still want to check that location+node coords have the same
+            #     phenomenon (i.e. physical meaning identity + units), **but** ...
+            # (c) we will accept/ignore some differences : not just "var_name", but
+            #     also "long_name" *and* "attributes".  So it is *only* "standard_name"
+            #     and "units" that cause an error if they differ.
+            for key in ("standard_name", "units"):
+                bounds_value = use_metadict[key]
+                nodes_value = node_metadict[key]
+                if key == "units" and (
+                    bounds_value == unit_unknown or nodes_value == unit_unknown
+                ):
+                    # Allow "any" unit to match no-units (for now)
+                    continue
+                if bounds_value != nodes_value:
+
+                    def fix_repr(val):
+                        # Tidy values appearance by converting Unit to string, and
+                        # wrapping strings in '', but leaving other types as a
+                        # plain str() representation.
+                        if isinstance(val, Unit):
+                            val = str(val)
+                        if isinstance(val, str):
+                            val = repr(val)
+                        return val
+
+                    nodes_value, bounds_value = [
+                        fix_repr(val) for val in (nodes_value, bounds_value)
+                    ]
+                    msg = (
+                        f"Node coordinate {node_coord!r} disagrees with the "
+                        f"{location} coordinate {location_coord!r}, "
+                        f'in having a "{key}" value of {nodes_value} '
+                        f"instead of {bounds_value}."
+                    )
+                    raise ValueError(msg)
+
         # Call parent constructor to handle the common constructor args.
-        super().__init__(
-            points,
-            bounds=bounds,
-            standard_name=node_coord.standard_name,
-            long_name=node_coord.long_name,
-            var_name=None,  # We *don't* "represent" the underlying node var
-            units=node_coord.units,
-            attributes=node_coord.attributes,
-        )
+        super().__init__(points, bounds=bounds, **use_metadict)
 
     # Define accessors for MeshCoord-specific properties mesh/location/axis.
     # These are all read-only.
@@ -3083,9 +3127,7 @@ def _construct_access_arrays(self):
             flat_inds_safe = al.where(missing_inds, 0, flat_inds_nomask)
             # Here's the core indexing operation.
             # The comma applies all inds-array values to the *first* dimension.
-            bounds = node_points[
-                flat_inds_safe,
-            ]
+            bounds = node_points[flat_inds_safe,]
             # Fix 'missing' locations, and restore the proper shape.
             bounds = al.ma.masked_array(bounds, missing_inds)
             bounds = bounds.reshape(indices.shape)
diff --git a/lib/iris/experimental/ugrid/metadata.py b/lib/iris/experimental/ugrid/metadata.py
index ae0b787908..44bbe04fe9 100644
--- a/lib/iris/experimental/ugrid/metadata.py
+++ b/lib/iris/experimental/ugrid/metadata.py
@@ -53,6 +53,7 @@ def _combine_lenient(self, other):
             A list of combined metadata member values.
 
         """
+
         # Perform "strict" combination for "cf_role", "start_index", "location_axis".
         def func(field):
             left = getattr(self, field)
@@ -113,6 +114,7 @@ def _difference_lenient(self, other):
             A list of difference metadata member values.
 
         """
+
         # Perform "strict" difference for "cf_role", "start_index", "location_axis".
         def func(field):
             left = getattr(self, field)
@@ -233,6 +235,7 @@ def _difference_lenient(self, other):
             A list of difference metadata member values.
 
         """
+
         # Perform "strict" difference for "topology_dimension",
         # "node_dimension", "edge_dimension" and "face_dimension".
         def func(field):
@@ -297,6 +300,7 @@ def _combine_lenient(self, other):
             A list of combined metadata member values.
 
         """
+
         # It is actually "strict" : return None except where members are equal.
         def func(field):
             left = getattr(self, field)
@@ -352,6 +356,7 @@ def _difference_lenient(self, other):
             A list of different metadata member values.
 
         """
+
         # Perform "strict" difference for location / axis.
         def func(field):
             left = getattr(self, field)
diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py
index c075a659ac..35163c47d5 100644
--- a/lib/iris/fileformats/_nc_load_rules/helpers.py
+++ b/lib/iris/fileformats/_nc_load_rules/helpers.py
@@ -13,7 +13,6 @@
 build routines, and which it does not use.
 
 """
-
 import warnings
 
 import cf_units
@@ -37,6 +36,8 @@
 import iris.std_names
 import iris.util
 
+# TODO: should un-addable coords / cell measures / etcetera be skipped? iris#5068.
+
 #
 # UD Units Constants (based on Unidata udunits.dat definition file)
 #
@@ -853,6 +854,12 @@ def build_dimension_coordinate(
         cf_coord_var, coord_name, attributes
     )
 
+    coord_skipped_msg = (
+        f"{cf_coord_var.cf_name} coordinate not added to Cube: "
+    )
+    coord_skipped_msg += "{error}"
+    coord_skipped = False
+
     # Create the coordinate.
     try:
         coord = iris.coords.DimCoord(
@@ -869,6 +876,11 @@ def build_dimension_coordinate(
         )
     except ValueError as e_msg:
         # Attempt graceful loading.
+        msg = (
+            "Failed to create {name!r} dimension coordinate: {error}\n"
+            "Gracefully creating {name!r} auxiliary coordinate instead."
+        )
+        warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg))
         coord = iris.coords.AuxCoord(
             points_data,
             standard_name=standard_name,
@@ -880,22 +892,26 @@ def build_dimension_coordinate(
             coord_system=coord_system,
             climatological=climatological,
         )
-        cube.add_aux_coord(coord, data_dims)
-        msg = (
-            "Failed to create {name!r} dimension coordinate: {error}\n"
-            "Gracefully creating {name!r} auxiliary coordinate instead."
-        )
-        warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg))
+        try:
+            cube.add_aux_coord(coord, data_dims)
+        except iris.exceptions.CannotAddError as e_msg:
+            warnings.warn(coord_skipped_msg.format(error=e_msg))
+            coord_skipped = True
     else:
         # Add the dimension coordinate to the cube.
-        if data_dims:
-            cube.add_dim_coord(coord, data_dims)
-        else:
-            # Scalar coords are placed in the aux_coords container.
-            cube.add_aux_coord(coord, data_dims)
+        try:
+            if data_dims:
+                cube.add_dim_coord(coord, data_dims)
+            else:
+                # Scalar coords are placed in the aux_coords container.
+                cube.add_aux_coord(coord, data_dims)
+        except iris.exceptions.CannotAddError as e_msg:
+            warnings.warn(coord_skipped_msg.format(error=e_msg))
+            coord_skipped = True
 
-    # Update the coordinate to CF-netCDF variable mapping.
-    engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name))
+    if not coord_skipped:
+        # Update the coordinate to CF-netCDF variable mapping.
+        engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name))
 
 
 ################################################################################
@@ -964,10 +980,14 @@ def build_auxiliary_coordinate(
     )
 
     # Add it to the cube
-    cube.add_aux_coord(coord, data_dims)
-
-    # Make a list with names, stored on the engine, so we can find them all later.
-    engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name))
+    try:
+        cube.add_aux_coord(coord, data_dims)
+    except iris.exceptions.CannotAddError as e_msg:
+        msg = "{name!r} coordinate not added to Cube: {error}"
+        warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg))
+    else:
+        # Make a list with names, stored on the engine, so we can find them all later.
+        engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name))
 
 
 ################################################################################
@@ -1011,12 +1031,16 @@ def build_cell_measures(engine, cf_cm_var):
     )
 
     # Add it to the cube
-    cube.add_cell_measure(cell_measure, data_dims)
-
-    # Make a list with names, stored on the engine, so we can find them all later.
-    engine.cube_parts["cell_measures"].append(
-        (cell_measure, cf_cm_var.cf_name)
-    )
+    try:
+        cube.add_cell_measure(cell_measure, data_dims)
+    except iris.exceptions.CannotAddError as e_msg:
+        msg = "{name!r} cell measure not added to Cube: {error}"
+        warnings.warn(msg.format(name=str(cf_cm_var.cf_name), error=e_msg))
+    else:
+        # Make a list with names, stored on the engine, so we can find them all later.
+        engine.cube_parts["cell_measures"].append(
+            (cell_measure, cf_cm_var.cf_name)
+        )
 
 
 ################################################################################
@@ -1056,10 +1080,16 @@ def build_ancil_var(engine, cf_av_var):
     )
 
     # Add it to the cube
-    cube.add_ancillary_variable(av, data_dims)
-
-    # Make a list with names, stored on the engine, so we can find them all later.
-    engine.cube_parts["ancillary_variables"].append((av, cf_av_var.cf_name))
+    try:
+        cube.add_ancillary_variable(av, data_dims)
+    except iris.exceptions.CannotAddError as e_msg:
+        msg = "{name!r} ancillary variable not added to Cube: {error}"
+        warnings.warn(msg.format(name=str(cf_av_var.cf_name), error=e_msg))
+    else:
+        # Make a list with names, stored on the engine, so we can find them all later.
+        engine.cube_parts["ancillary_variables"].append(
+            (av, cf_av_var.cf_name)
+        )
 
 
 ################################################################################
diff --git a/lib/iris/fileformats/abf.py b/lib/iris/fileformats/abf.py
index 5c70c5acf2..4dcd5ce6aa 100644
--- a/lib/iris/fileformats/abf.py
+++ b/lib/iris/fileformats/abf.py
@@ -219,7 +219,6 @@ def load_cubes(filespecs, callback=None):
 
     for filespec in filespecs:
         for filename in glob.glob(filespec):
-
             field = ABFField(filename)
             cube = field.to_cube()
 
diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py
index 3aaba3679e..b9b64a343e 100644
--- a/lib/iris/fileformats/name_loaders.py
+++ b/lib/iris/fileformats/name_loaders.py
@@ -571,7 +571,9 @@ def _generate_cubes(
                 cube.attributes[key] = value
 
         if cell_methods is not None:
-            cube.add_cell_method(cell_methods[i])
+            cell_method = cell_methods[i]
+            if cell_method is not None:
+                cube.add_cell_method(cell_method)
 
         yield cube
 
@@ -610,7 +612,7 @@ def _build_cell_methods(av_or_ints, coord):
             cell_method = None
             msg = "Unknown {} statistic: {!r}. Unable to create cell method."
             warnings.warn(msg.format(coord, av_or_int))
-        cell_methods.append(cell_method)
+        cell_methods.append(cell_method)  # NOTE: this can be a None
     return cell_methods
 
 
@@ -992,7 +994,6 @@ def load_NAMEIII_version2(filename):
     # using the next() method. This will come in handy as we wish to
     # progress through the file line by line.
     with open(filename, "r") as file_handle:
-
         # define a dictionary to hold the header metadata about this file
         header = read_header(file_handle)
 
@@ -1003,7 +1004,6 @@ def load_NAMEIII_version2(filename):
         column_headings = {}
         datacol1 = header["Number of preliminary cols"]
         for line in file_handle:
-
             data = [col.strip() for col in line.split(",")][:-1]
 
             # If first column is not zero we have reached the end
diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py
index 1fb7d4e178..cff088cf89 100644
--- a/lib/iris/fileformats/pp.py
+++ b/lib/iris/fileformats/pp.py
@@ -625,7 +625,7 @@ def __getstate__(self):
     def __setstate__(self, state):
         # Because we have __slots__, this is needed to support Pickle.load()
         # (Use setattr, as there is no object dictionary.)
-        for (key, value) in state:
+        for key, value in state:
             setattr(self, key, value)
 
     def __eq__(self, other):
@@ -767,6 +767,18 @@ def _data_bytes_to_shaped_array(
 
     else:
         # Reform in row-column order
+        actual_length = np.prod(data.shape)
+        if (expected_length := np.prod(data_shape)) != actual_length:
+            if (expected_length < actual_length) and (data.ndim == 1):
+                # known use case where mule adds padding to data payload
+                # for a collapsed field.
+                data = data[:expected_length]
+            else:
+                emsg = (
+                    f"PP field data containing {actual_length} words does not "
+                    f"match expected length of {expected_length} words."
+                )
+                raise ValueError(emsg)
         data.shape = data_shape
 
     # Mask the array
@@ -2017,10 +2029,8 @@ def pp_filter(field):
         res = True
         if field.stash not in _STASH_ALLOW:
             if pp_constraints.get("stash"):
-
                 res = False
                 for call_func in pp_constraints["stash"]:
-
                     if call_func(str(field.stash)):
                         res = True
                         break
diff --git a/lib/iris/fileformats/pp_load_rules.py b/lib/iris/fileformats/pp_load_rules.py
index ebccec47ee..11d03e978a 100644
--- a/lib/iris/fileformats/pp_load_rules.py
+++ b/lib/iris/fileformats/pp_load_rules.py
@@ -756,7 +756,6 @@ def date2year(t_in):
             )
         )
     ):
-
         coords_and_dims.append(
             _new_coord_and_dims(
                 do_vector,
diff --git a/lib/iris/fileformats/pp_save_rules.py b/lib/iris/fileformats/pp_save_rules.py
index e6b3748f9b..0369fc9fd0 100644
--- a/lib/iris/fileformats/pp_save_rules.py
+++ b/lib/iris/fileformats/pp_save_rules.py
@@ -422,7 +422,16 @@ def _grid_and_pole_rules(cube, pp):
     lat_coord = vector_coord(cube, "latitude")
     grid_lat_coord = vector_coord(cube, "grid_latitude")
 
-    if lon_coord and not is_regular(lon_coord):
+    scalar_lon_coord = scalar_coord(cube, "longitude")
+
+    if lon_coord is None and grid_lon_coord is None and scalar_lon_coord:
+        # default value of 360.0 degrees to specify a circular wrap of
+        # the collapsed scalar longitude coordinate, based on examples
+        # of model output for several different diagnostics
+        pp.bdx = (unit := scalar_lon_coord.units) and unit.modulus or 360.0
+        pp.bzx = scalar_lon_coord.points[0] - pp.bdx
+        pp.lbnpt = scalar_lon_coord.shape[0]
+    elif lon_coord and not is_regular(lon_coord):
         pp.bzx = 0
         pp.bdx = 0
         pp.lbnpt = lon_coord.shape[0]
diff --git a/lib/iris/fileformats/rules.py b/lib/iris/fileformats/rules.py
index 07ed5eb8ce..51940b7c4d 100644
--- a/lib/iris/fileformats/rules.py
+++ b/lib/iris/fileformats/rules.py
@@ -394,7 +394,7 @@ def _load_pairs_from_fields_and_filenames(
             yield (cube, field)
 
     regrid_cache = {}
-    for (cube, factories, field) in results_needing_reference:
+    for cube, factories, field in results_needing_reference:
         _resolve_factory_references(
             cube, factories, concrete_reference_targets, regrid_cache
         )
diff --git a/lib/iris/fileformats/um/_fast_load_structured_fields.py b/lib/iris/fileformats/um/_fast_load_structured_fields.py
index d193aa30ce..64b7f8e891 100644
--- a/lib/iris/fileformats/um/_fast_load_structured_fields.py
+++ b/lib/iris/fileformats/um/_fast_load_structured_fields.py
@@ -133,6 +133,7 @@ def element_arrays_and_dims(self):
 
     def _field_vector_element_arrays(self):
         """Define the field components used in the structure analysis."""
+
         # Define functions to make t1 and t2 values as date-time tuples.
         # These depend on header version (PPField2 has no seconds values).
         def t1_fn(fld):
diff --git a/lib/iris/fileformats/um_cf_map.py b/lib/iris/fileformats/um_cf_map.py
index 8aee67ae3e..01539960a5 100644
--- a/lib/iris/fileformats/um_cf_map.py
+++ b/lib/iris/fileformats/um_cf_map.py
@@ -16,10 +16,12 @@
 LBFC_TO_CF = {
     5: CFName('atmosphere_boundary_layer_thickness', None, 'm'),
     16: CFName('air_temperature', None, 'K'),
+    22: CFName('wet_bulb_potential_temperature', None, 'K'),
     23: CFName('soil_temperature', None, 'K'),
     27: CFName('air_density', None, 'kg m-3'),
     36: CFName('land_area_fraction', None, '1'),
     37: CFName('sea_ice_area_fraction', None, '1'),
+    42: CFName('upward_air_velocity', None, 'm s-1'),
     50: CFName('wind_speed', None, 'm s-1'),
     56: CFName('x_wind', None, 'm s-1'),
     57: CFName('y_wind', None, 'm s-1'),
@@ -28,11 +30,16 @@
     83: CFName('potential_vorticity_of_atmosphere_layer', None, 'Pa-1 s-1'),
     94: CFName('convective_rainfall_amount', None, 'kg m-2'),
     97: CFName('rainfall_flux', None, 'kg m-2 s-1'),
+    98: CFName('convective_rainfall_flux', None, 'kg m-2 s-1'),
+    99: CFName('stratiform_rainfall_flux', None, 'kg m-2 s-1'),
     102: CFName('stratiform_rainfall_amount', None, 'kg m-2'),
+    106: CFName('soil_moisture_content', None, 'kg m-2'),
     108: CFName('snowfall_flux', None, 'kg m-2 s-1'),
     111: CFName('surface_runoff_amount', None, 'kg m-2'),
     116: CFName('stratiform_snowfall_amount', None, 'kg m-2'),
     117: CFName('convective_snowfall_amount', None, 'kg m-2'),
+    118: CFName('stratiform_snowfall_flux', None, 'kg m-2 s-1'),
+    119: CFName('convective_snowfall_flux', None, 'kg m-2 s-1'),
     122: CFName('moisture_content_of_soil_layer', None, 'kg m-2'),
     183: CFName('wind_speed', None, 'm s-1'),
     200: CFName('toa_incoming_shortwave_flux', None, 'W m-2'),
@@ -899,7 +906,7 @@
     'm01s30i301': CFName(None, 'Heavyside function on pressure levels', '1'),
     'm01s30i302': CFName('virtual_temperature', None, 'K'),
     'm01s30i310': CFName('northward_transformed_eulerian_mean_air_velocity', None, 'm s-1'),
-    'm01s30i311': CFName('northward_transformed_eulerian_mean_air_velocity', None, 'm s-1'),
+    'm01s30i311': CFName('upward_transformed_eulerian_mean_air_velocity', None, 'm s-1'),
     'm01s30i312': CFName('northward_eliassen_palm_flux_in_air', None, 'kg s-2'),
     'm01s30i313': CFName('upward_eliassen_palm_flux_in_air', None, 'kg s-2'),
     'm01s30i314': CFName('tendency_of_eastward_wind_due_to_eliassen_palm_flux_divergence', None, 'm s-2'),
@@ -1157,7 +1164,9 @@
     CFName('cloud_area_fraction_in_atmosphere_layer', None, '1'): 1720,
     CFName('convective_cloud_area_fraction', None, '1'): 34,
     CFName('convective_rainfall_amount', None, 'kg m-2'): 94,
+    CFName('convective_rainfall_flux', None, 'kg m-2 s-1'): 98,
     CFName('convective_snowfall_amount', None, 'kg m-2'): 117,
+    CFName('convective_snowfall_flux', None, 'kg m-2 s-1'): 119,
     CFName('dimensionless_exner_function', None, '1'): 7,
     CFName('divergence_of_wind', None, 's-1'): 74,
     CFName('downward_heat_flux_in_sea_ice', None, 'W m-2'): 261,
@@ -1203,6 +1212,7 @@
     CFName('soil_albedo', None, '1'): 1395,
     CFName('soil_carbon_content', None, 'kg m-2'): 1397,
     CFName('soil_hydraulic_conductivity_at_saturation', None, 'm s-1'): 333,
+    CFName('soil_moisture_content', None, 'kg m-2'): 106,
     CFName('soil_moisture_content_at_field_capacity', None, 'kg m-2'): 1559,
     CFName('soil_porosity', None, '1'): 332,
     CFName('soil_suction_at_saturation', None, 'Pa'): 342,
@@ -1212,8 +1222,10 @@
     CFName('specific_kinetic_energy_of_air', None, 'm2 s-2'): 60,
     CFName('stratiform_cloud_area_fraction_in_atmosphere_layer', None, '1'): 220,
     CFName('stratiform_rainfall_amount', None, 'kg m-2'): 102,
+    CFName('stratiform_rainfall_flux', None, 'kg m-2 s-1'): 99,
     CFName('stratiform_rainfall_rate', None, 'kg m-2 s-1'): 99,
     CFName('stratiform_snowfall_amount', None, 'kg m-2'): 116,
+    CFName('stratiform_snowfall_flux', None, 'kg m-2 s-1'): 118,
     CFName('subsurface_runoff_amount', None, 'kg m-2'): 112,
     CFName('subsurface_runoff_flux', None, 'kg m-2 s-1'): 1533,
     CFName('surface_albedo_assuming_deep_snow', None, '1'): 328,
@@ -1260,6 +1272,7 @@
     CFName('volume_fraction_of_condensed_water_in_soil_at_critical_point', None, '1'): 330,
     CFName('volume_fraction_of_condensed_water_in_soil_at_wilting_point', None, '1'): 329,
     CFName('water_potential_evaporation_flux', None, 'kg m-2 s-1'): 115,
+    CFName('wet_bulb_potential_temperature', None, 'K'): 22,
     CFName('wind_mixing_energy_flux_into_sea_water', None, 'W m-2'): 182,
     CFName('wind_speed', None, 'm s-1'): 50,
     CFName('x_wind', None, 'm s-1'): 56,
diff --git a/lib/iris/io/__init__.py b/lib/iris/io/__init__.py
index 4659f70ae3..7dd08c723c 100644
--- a/lib/iris/io/__init__.py
+++ b/lib/iris/io/__init__.py
@@ -59,6 +59,11 @@ def run_callback(callback, cube, field, filename):
         It is possible that this function returns None for certain callbacks,
         the caller of this function should handle this case.
 
+    .. note::
+
+        This function maintains laziness when called; it does not realise data.
+        See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     from iris.cube import Cube
 
@@ -424,6 +429,11 @@ def save(source, target, saver=None, **kwargs):
     >>> # Save a cube list to netCDF, using the NETCDF3_CLASSIC storage option
     >>> iris.save(my_cube_list, "myfile.nc", netcdf_format="NETCDF3_CLASSIC")
 
+    Notes
+    ------
+
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
 
     """
     from iris.cube import Cube, CubeList
diff --git a/lib/iris/io/format_picker.py b/lib/iris/io/format_picker.py
index edf448e95b..a8e333c566 100644
--- a/lib/iris/io/format_picker.py
+++ b/lib/iris/io/format_picker.py
@@ -134,8 +134,9 @@ def get_spec(self, basename, buffer_obj):
                 value = value[:50] + "..."
             printable_values[key] = value
         msg = (
-            "No format specification could be found for the given buffer."
-            " File element cache:\n {}".format(printable_values)
+            "No format specification could be found for the given buffer. "
+            "Perhaps a plugin is missing or has not been loaded. "
+            "File element cache:\n {}".format(printable_values)
         )
         raise ValueError(msg)
 
diff --git a/lib/iris/iterate.py b/lib/iris/iterate.py
index 636635ee78..d6bac77d3b 100644
--- a/lib/iris/iterate.py
+++ b/lib/iris/iterate.py
@@ -58,6 +58,10 @@ def izip(*cubes, **kwargs):
         ...                                                 'grid_longitude']):
         ...    pass
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
     """
     if not cubes:
         raise TypeError("Expected one or more cubes.")
diff --git a/lib/iris/palette.py b/lib/iris/palette.py
index 626ae4e341..a1c0a1e878 100644
--- a/lib/iris/palette.py
+++ b/lib/iris/palette.py
@@ -15,6 +15,7 @@
 import re
 
 import cf_units
+from matplotlib import colormaps as mpl_colormaps
 import matplotlib.cm as mpl_cm
 import matplotlib.colors as mpl_colors
 import numpy as np
@@ -120,6 +121,11 @@ def cmap_norm(cube):
         Tuple of :class:`matplotlib.colors.LinearSegmentedColormap` and
         :class:`iris.palette.SymmetricNormalize`
 
+    Notes
+    ------
+    This function maintains laziness when called; it does not realise data.
+    See more at :doc:`/userguide/real_and_lazy_data`.
+
     """
     args, kwargs = _default_cmap_norm((cube,), {})
     return kwargs.get("cmap"), kwargs.get("norm")
@@ -337,7 +343,7 @@ def _load_palette():
             )
 
         # Register the color map for use.
-        mpl_cm.register_cmap(cmap=cmap)
+        mpl_colormaps.register(cmap)
 
 
 # Ensure to load the color map palettes.
diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py
index b00eb3f117..417b6b11de 100644
--- a/lib/iris/pandas.py
+++ b/lib/iris/pandas.py
@@ -9,7 +9,6 @@
 See also: http://pandas.pydata.org/
 
 """
-
 import datetime
 from itertools import chain, combinations
 import warnings
@@ -157,8 +156,8 @@ def as_cube(
 
     Example usage::
 
-        as_cube(series, calendars={0: cf_units.CALENDAR_360_DAY})
-        as_cube(data_frame, calendars={1: cf_units.CALENDAR_STANDARD})
+            as_cube(series, calendars={0: cf_units.CALENDAR_360_DAY})
+            as_cube(data_frame, calendars={1: cf_units.CALENDAR_STANDARD})
 
     """
     message = (
@@ -239,7 +238,7 @@ def as_cubes(
     A :class:`~pandas.DataFrame` using columns as a second data dimension will
     need to be 'melted' before conversion. See the Examples for how.
 
-    Dask ``DataFrame``\\s are not supported.
+    :class:`dask.dataframe.DataFrame`\\ s are not supported.
 
     Examples
     --------
@@ -379,7 +378,10 @@ def as_cubes(
         )
         raise ValueError(message)
 
-    if not pandas_index.is_monotonic:
+    if not (
+        pandas_index.is_monotonic_increasing
+        or pandas_index.is_monotonic_decreasing
+    ):
         # Need monotonic index for use in DimCoord(s).
         # This function doesn't sort_index itself since that breaks the
         #  option to return a data view instead of a copy.
@@ -518,26 +520,92 @@ def _get_base(array):
         raise AssertionError(msg)
 
 
+def _make_dim_coord_list(cube):
+    """Get Dimension coordinates."""
+    outlist = []
+    for dimn in range(cube.ndim):
+        dimn_coord = cube.coords(dimensions=dimn, dim_coords=True)
+        if dimn_coord:
+            outlist += [
+                [dimn_coord[0].name(), _as_pandas_coord(dimn_coord[0])]
+            ]
+        else:
+            outlist += [[f"dim{dimn}", range(cube.shape[dimn])]]
+    return list(zip(*outlist))
+
+
+def _make_aux_coord_list(cube):
+    """Get Auxiliary coordinates."""
+    outlist = []
+    for aux_coord in cube.coords(dim_coords=False):
+        outlist += [
+            [
+                aux_coord.name(),
+                cube.coord_dims(aux_coord),
+                _as_pandas_coord(aux_coord),
+            ]
+        ]
+    return list(chain.from_iterable([outlist]))
+
+
+def _make_ancillary_variables_list(cube):
+    """Get Ancillary variables."""
+    outlist = []
+    for ancil_var in cube.ancillary_variables():
+        outlist += [
+            [
+                ancil_var.name(),
+                cube.ancillary_variable_dims(ancil_var),
+                ancil_var.data,
+            ]
+        ]
+    return list(chain.from_iterable([outlist]))
+
+
+def _make_cell_measures_list(cube):
+    """Get cell measures."""
+    outlist = []
+    for cell_measure in cube.cell_measures():
+        outlist += [
+            [
+                cell_measure.name(),
+                cube.cell_measure_dims(cell_measure),
+                cell_measure.data,
+            ]
+        ]
+    return list(chain.from_iterable([outlist]))
+
+
 def as_series(cube, copy=True):
     """
     Convert a 1D cube to a Pandas Series.
 
-    Args:
-
-        * cube - The cube to convert to a Pandas Series.
-
-    Kwargs:
-
-        * copy - Whether to make a copy of the data.
-                 Defaults to True. Must be True for masked data.
+    .. deprecated:: 3.4.0
+        This function is scheduled for removal in a future release, being
+        replaced by :func:`iris.pandas.as_data_frame`, which offers improved
+        multi dimension handling.
 
-    .. note::
+    Parameters
+    ----------
+    cube: :class:`Cube`
+        The cube to convert to a Pandas Series.
+    copy : bool, default=True
+        Whether to make a copy of the data.
+        Defaults to True. Must be True for masked data.
 
-        This function will copy your data by default.
-        If you have a large array that cannot be copied,
-        make sure it is not masked and use copy=False.
+    Notes
+    -----
+    This function will copy your data by default.
+    If you have a large array that cannot be copied,
+    make sure it is not masked and use copy=False.
 
     """
+    message = (
+        "iris.pandas.as_series has been deprecated, and will be removed in a "
+        "future release. Please use iris.pandas.as_data_frame instead."
+    )
+    warn_deprecated(message)
+
     data = cube.data
     if ma.isMaskedArray(data):
         if not copy:
@@ -545,61 +613,297 @@ def as_series(cube, copy=True):
         data = data.astype("f").filled(np.nan)
     elif copy:
         data = data.copy()
-
     index = None
     if cube.dim_coords:
         index = _as_pandas_coord(cube.dim_coords[0])
-
     series = pandas.Series(data, index)
     if not copy:
         _assert_shared(data, series)
-
     return series
 
 
-def as_data_frame(cube, copy=True):
+def as_data_frame(
+    cube,
+    copy=True,
+    add_aux_coords=False,
+    add_cell_measures=False,
+    add_ancillary_variables=False,
+):
     """
-    Convert a 2D cube to a Pandas DataFrame.
+    Convert a :class:`~iris.cube.Cube` to a :class:`pandas.DataFrame`.
 
-    Args:
+    :attr:`~iris.cube.Cube.dim_coords` and :attr:`~iris.cube.Cube.data` are
+    flattened into a long-style :class:`~pandas.DataFrame`.  Other
+    :attr:`~iris.cube.Cube.aux_coords`, :attr:`~iris.cube.Cube.aux_coords` and :attr:`~iris.cube.Cube.attributes`
+    may be optionally added as additional :class:`~pandas.DataFrame` columns.
 
-        * cube - The cube to convert to a Pandas DataFrame.
+    Parameters
+    ----------
+    cube: :class:`~iris.cube.Cube`
+        The :class:`~iris.cube.Cube` to be converted to a :class:`pandas.DataFrame`.
+    copy : bool, default=True
+        Whether the :class:`pandas.DataFrame` is a copy of the the Cube
+        :attr:`~iris.cube.Cube.data`. This option is provided to help with memory
+        size concerns.
+    add_aux_coords : bool, default=False
+        If True, add all :attr:`~iris.cube.Cube.aux_coords` (including scalar
+        coordinates) to the returned :class:`pandas.DataFrame`.
+    add_cell_measures : bool, default=False
+        If True, add :attr:`~iris.cube.Cube.cell_measures` to the returned
+        :class:`pandas.DataFrame`.
+    add_ancillary_variables: bool, default=False
+        If True, add :attr:`~iris.cube.Cube.ancillary_variables` to the returned
+        :class:`pandas.DataFrame`.
+
+    Returns
+    -------
+    :class:`~pandas.DataFrame`
+        A :class:`~pandas.DataFrame` with :class:`~iris.cube.Cube` dimensions
+        forming a :class:`~pandas.MultiIndex`
+
+    Warnings
+    --------
+    #. This documentation is for the new ``as_data_frame()`` behaviour, which
+       is **currently opt-in** to preserve backwards compatibility. The default
+       legacy behaviour is documented in pre-``v3.4`` documentation (summary:
+       limited to 2-dimensional :class:`~iris.cube.Cube`\\ s, with only the
+       :attr:`~iris.cube.Cube.data` and :attr:`~iris.cube.Cube.dim_coords`
+       being added). The legacy behaviour will be removed in a future version
+       of Iris, so please opt-in to the new behaviour at your earliest
+       convenience, via :class:`iris.Future`:
+
+           >>> iris.FUTURE.pandas_ndim = True
 
-    Kwargs:
+       **Breaking change:** to enable the improvements, the new opt-in
+       behaviour flattens multi-dimensional data into a single
+       :class:`~pandas.DataFrame` column (the legacy behaviour preserves 2
+       dimensions via rows and columns).
 
-        * copy - Whether to make a copy of the data.
-                 Defaults to True. Must be True for masked data
-                 and some data types (see notes below).
+       |
 
-    .. note::
+    #. Where the :class:`~iris.cube.Cube` contains masked values, these become
+       :data:`numpy.nan` in the returned :class:`~pandas.DataFrame`.
+
+    Notes
+    -----
+    :class:`dask.dataframe.DataFrame`\\ s are not supported.
 
-        This function will copy your data by default.
-        If you have a large array that cannot be copied,
-        make sure it is not masked and use copy=False.
+    A :class:`~pandas.MultiIndex` :class:`~pandas.DataFrame` is returned by default.
+    Use the :meth:`~pandas.DataFrame.reset_index` to return a
+    :class:`~pandas.DataFrame` without :class:`~pandas.MultiIndex` levels. Use
+    'inplace=True` to preserve memory object reference.
 
-    .. note::
+    :class:`~iris.cube.Cube` data `dtype` is preserved.
 
-        Pandas will sometimes make a copy of the array,
-        for example when creating from an int32 array.
-        Iris will detect this and raise an exception if copy=False.
+    Examples
+    --------
+    >>> import iris
+    >>> from iris.pandas import as_data_frame
+    >>> import pandas as pd
+    >>> pd.set_option('display.width', 1000)
+    >>> pd.set_option('display.max_columns', 1000)
+
+    Convert a simple :class:`~iris.cube.Cube`:
+
+    >>> path = iris.sample_data_path('ostia_monthly.nc')
+    >>> cube = iris.load_cube(path)
+    >>> df = as_data_frame(cube)
+    >>> print(df)
+    ... # doctest: +NORMALIZE_WHITESPACE
+                                              surface_temperature
+    time                latitude  longitude
+    2006-04-16 00:00:00 -4.999992 0.000000             301.659271
+                                  0.833333             301.785004
+                                  1.666667             301.820984
+                                  2.500000             301.865234
+                                  3.333333             301.926819
+    ...                                                       ...
+    2010-09-16 00:00:00  4.444450 355.833313           298.779938
+                                  356.666656           298.913147
+                                  357.500000                  NaN
+                                  358.333313                  NaN
+                                  359.166656           298.995148
+    <BLANKLINE>
+    [419904 rows x 1 columns]
+
+    Using ``add_aux_coords=True`` maps :class:`~iris.coords.AuxCoord` and scalar
+    coordinate information to the :class:`~pandas.DataFrame`:
+
+    >>> df = as_data_frame(cube, add_aux_coords=True)
+    >>> print(df)
+    ... # doctest: +NORMALIZE_WHITESPACE
+                                              surface_temperature  forecast_period forecast_reference_time
+    time                latitude  longitude
+    2006-04-16 00:00:00 -4.999992 0.000000             301.659271                0     2006-04-16 12:00:00
+                                  0.833333             301.785004                0     2006-04-16 12:00:00
+                                  1.666667             301.820984                0     2006-04-16 12:00:00
+                                  2.500000             301.865234                0     2006-04-16 12:00:00
+                                  3.333333             301.926819                0     2006-04-16 12:00:00
+    ...                                                       ...              ...                     ...
+    2010-09-16 00:00:00  4.444450 355.833313           298.779938                0     2010-09-16 12:00:00
+                                  356.666656           298.913147                0     2010-09-16 12:00:00
+                                  357.500000                  NaN                0     2010-09-16 12:00:00
+                                  358.333313                  NaN                0     2010-09-16 12:00:00
+                                  359.166656           298.995148                0     2010-09-16 12:00:00
+    <BLANKLINE>
+    [419904 rows x 3 columns]
+
+    To add netCDF global attribution information to the :class:`~pandas.DataFrame`,
+    add a column directly to the :class:`~pandas.DataFrame`:
+
+    >>> df['STASH'] = str(cube.attributes['STASH'])
+    >>> print(df)
+    ... # doctest: +NORMALIZE_WHITESPACE
+                                              surface_temperature  forecast_period forecast_reference_time       STASH
+    time                latitude  longitude
+    2006-04-16 00:00:00 -4.999992 0.000000             301.659271                0     2006-04-16 12:00:00  m01s00i024
+                                  0.833333             301.785004                0     2006-04-16 12:00:00  m01s00i024
+                                  1.666667             301.820984                0     2006-04-16 12:00:00  m01s00i024
+                                  2.500000             301.865234                0     2006-04-16 12:00:00  m01s00i024
+                                  3.333333             301.926819                0     2006-04-16 12:00:00  m01s00i024
+    ...                                                       ...              ...                     ...         ...
+    2010-09-16 00:00:00  4.444450 355.833313           298.779938                0     2010-09-16 12:00:00  m01s00i024
+                                  356.666656           298.913147                0     2010-09-16 12:00:00  m01s00i024
+                                  357.500000                  NaN                0     2010-09-16 12:00:00  m01s00i024
+                                  358.333313                  NaN                0     2010-09-16 12:00:00  m01s00i024
+                                  359.166656           298.995148                0     2010-09-16 12:00:00  m01s00i024
+    <BLANKLINE>
+    [419904 rows x 4 columns]
+
+    To return a :class:`~pandas.DataFrame` without a :class:`~pandas.MultiIndex`
+    use :meth:`~pandas.DataFrame.reset_index`. Optionally use `inplace=True` keyword
+    to modify the DataFrame rather than creating a new one:
+
+    >>> df.reset_index(inplace=True)
+    >>> print(df)
+    ... # doctest: +NORMALIZE_WHITESPACE
+                           time  latitude   longitude  surface_temperature  forecast_period forecast_reference_time       STASH
+    0       2006-04-16 00:00:00 -4.999992    0.000000           301.659271                0     2006-04-16 12:00:00  m01s00i024
+    1       2006-04-16 00:00:00 -4.999992    0.833333           301.785004                0     2006-04-16 12:00:00  m01s00i024
+    2       2006-04-16 00:00:00 -4.999992    1.666667           301.820984                0     2006-04-16 12:00:00  m01s00i024
+    3       2006-04-16 00:00:00 -4.999992    2.500000           301.865234                0     2006-04-16 12:00:00  m01s00i024
+    4       2006-04-16 00:00:00 -4.999992    3.333333           301.926819                0     2006-04-16 12:00:00  m01s00i024
+                         ...       ...         ...                  ...              ...                     ...         ...
+    419899  2010-09-16 00:00:00  4.444450  355.833313           298.779938                0     2010-09-16 12:00:00  m01s00i024
+    419900  2010-09-16 00:00:00  4.444450  356.666656           298.913147                0     2010-09-16 12:00:00  m01s00i024
+    419901  2010-09-16 00:00:00  4.444450  357.500000                  NaN                0     2010-09-16 12:00:00  m01s00i024
+    419902  2010-09-16 00:00:00  4.444450  358.333313                  NaN                0     2010-09-16 12:00:00  m01s00i024
+    419903  2010-09-16 00:00:00  4.444450  359.166656           298.995148                0     2010-09-16 12:00:00  m01s00i024
+    <BLANKLINE>
+    [419904 rows x 7 columns]
+
+    To retrieve a :class:`~pandas.Series` from `df` :class:`~pandas.DataFrame`,
+    subselect a column:
+
+    >>> df['surface_temperature']
+    0         301.659271
+    1         301.785004
+    2         301.820984
+    3         301.865234
+    4         301.926819
+                ...
+    419899    298.779938
+    419900    298.913147
+    419901           NaN
+    419902           NaN
+    419903    298.995148
+    Name: surface_temperature, Length: 419904, dtype: float32
 
     """
-    data = cube.data
-    if ma.isMaskedArray(data):
-        if not copy:
-            raise ValueError("Masked arrays must always be copied.")
-        data = data.astype("f").filled(np.nan)
-    elif copy:
-        data = data.copy()
 
-    index = columns = None
-    if cube.coords(dimensions=[0]):
-        index = _as_pandas_coord(cube.coord(dimensions=[0]))
-    if cube.coords(dimensions=[1]):
-        columns = _as_pandas_coord(cube.coord(dimensions=[1]))
+    def merge_metadata(meta_var_list):
+        """Add auxiliary cube metadata to the DataFrame"""
+        nonlocal data_frame
+        for meta_var_name, meta_var_index, meta_var in meta_var_list:
+            if not meta_var_index:
+                # Broadcast any meta var informtation without an associated
+                # dimension over the whole DataFrame
+                data_frame[meta_var_name] = meta_var.squeeze()
+            else:
+                meta_df = pandas.DataFrame(
+                    meta_var.ravel(),
+                    columns=[meta_var_name],
+                    index=pandas.MultiIndex.from_product(
+                        [coords[i] for i in meta_var_index],
+                        names=[coord_names[i] for i in meta_var_index],
+                    ),
+                )
+                # Merge to main data frame
+                data_frame = pandas.merge(
+                    data_frame,
+                    meta_df,
+                    left_index=True,
+                    right_index=True,
+                    sort=False,
+                )
+        return data_frame
+
+    if iris.FUTURE.pandas_ndim:
+        # Checks
+        if not isinstance(cube, iris.cube.Cube):
+            raise TypeError(
+                f"Expected input to be iris.cube.Cube instance, got: {type(cube)}"
+            )
+        if copy:
+            data = cube.data.copy()
+        else:
+            data = cube.data
+        if ma.isMaskedArray(data):
+            if not copy:
+                raise ValueError("Masked arrays must always be copied.")
+            data = data.astype("f").filled(np.nan)
+
+        # Extract dim coord information: separate lists for dim names and dim values
+        coord_names, coords = _make_dim_coord_list(cube)
+        # Make base DataFrame
+        index = pandas.MultiIndex.from_product(coords, names=coord_names)
+        data_frame = pandas.DataFrame(
+            data.ravel(), columns=[cube.name()], index=index
+        )
+
+        if add_aux_coords:
+            data_frame = merge_metadata(_make_aux_coord_list(cube))
+        if add_ancillary_variables:
+            data_frame = merge_metadata(_make_ancillary_variables_list(cube))
+        if add_cell_measures:
+            data_frame = merge_metadata(_make_cell_measures_list(cube))
 
-    data_frame = pandas.DataFrame(data, index, columns)
-    if not copy:
-        _assert_shared(data, data_frame)
+        if copy:
+            result = data_frame.reorder_levels(coord_names).sort_index()
+        else:
+            data_frame.reorder_levels(coord_names).sort_index(inplace=True)
+            result = data_frame
 
-    return data_frame
+    else:
+        message = (
+            "You are using legacy 2-dimensional behaviour in"
+            "'iris.pandas.as_data_frame()'. This will be removed in a future"
+            "version of Iris. Please opt-in to the improved "
+            "n-dimensional behaviour at your earliest convenience by setting: "
+            "'iris.FUTURE.pandas_ndim = True'. More info is in the "
+            "documentation."
+        )
+        warnings.warn(message, FutureWarning)
+
+        # The legacy behaviour.
+        data = cube.data
+        if ma.isMaskedArray(data):
+            if not copy:
+                raise ValueError("Masked arrays must always be copied.")
+            data = data.astype("f").filled(np.nan)
+        elif copy:
+            data = data.copy()
+
+        index = columns = None
+        if cube.coords(dimensions=[0]):
+            index = _as_pandas_coord(cube.coord(dimensions=[0]))
+        if cube.coords(dimensions=[1]):
+            columns = _as_pandas_coord(cube.coord(dimensions=[1]))
+
+        data_frame = pandas.DataFrame(data, index, columns)
+        if not copy:
+            _assert_shared(data, data_frame)
+
+        result = data_frame
+
+    return result
diff --git a/lib/iris/plot.py b/lib/iris/plot.py
index 2da91e8c67..8cd849b716 100644
--- a/lib/iris/plot.py
+++ b/lib/iris/plot.py
@@ -13,11 +13,13 @@
 
 import collections
 import datetime
+import warnings
 
 import cartopy.crs as ccrs
 from cartopy.geodesic import Geodesic
 import cartopy.mpl.geoaxes
 import cftime
+import matplotlib.animation as animation
 import matplotlib.axes
 import matplotlib.collections as mpl_collections
 import matplotlib.dates as mpl_dates
@@ -1344,11 +1346,6 @@ def outline(cube, coords=None, color="k", linewidth=None, axes=None):
         axes=axes,
     )
 
-    # set the _is_stroked property to get a single color grid.
-    # See https://github.com/matplotlib/matplotlib/issues/1302
-    result._is_stroked = False
-    if hasattr(result, "_wrapped_collection_fix"):
-        result._wrapped_collection_fix._is_stroked = False
     return result
 
 
@@ -1808,3 +1805,114 @@ def citation(text, figure=None, axes=None):
         anchor.patch.set_boxstyle("round, pad=0, rounding_size=0.2")
         axes = axes if axes else figure.gca()
         axes.add_artist(anchor)
+
+
+def animate(cube_iterator, plot_func, fig=None, **kwargs):
+    """
+    Animates the given cube iterator.
+
+    Parameters
+    ----------
+    cube_iterator : iterable of :class:`iris.cube.Cube` objects
+        Each animation frame corresponds to each :class:`iris.cube.Cube`
+        object. See :meth:`iris.cube.Cube.slices`.
+    plot_func : :mod:`iris.plot` or :mod:`iris.quickplot` plotting function
+        Plotting function used to animate. Must accept the signature
+        ``plot_func(cube, vmin=vmin, vmax=vmax, coords=coords)``.
+        :func:`~iris.plot.contourf`, :func:`~iris.plot.contour`,
+        :func:`~iris.plot.pcolor` and :func:`~iris.plot.pcolormesh`
+        all conform to this signature.
+    fig : :class:`matplotlib.figure.Figure` instance, optional
+        By default, the current figure will be used or a new figure instance
+        created if no figure is available. See :func:`matplotlib.pyplot.gcf`.
+    **kwargs : dict, optional
+        Valid keyword arguments:
+
+        coords: list of :class:`~iris.coords.Coord` objects or coordinate names
+            Use the given coordinates as the axes for the plot. The order of the
+            given coordinates indicates which axis to use for each, where the first
+            element is the horizontal axis of the plot and the second element is
+            the vertical axis of the plot.
+        interval: int, float or long
+            Defines the time interval in milliseconds between successive frames.
+            A default interval of 100ms is set.
+        vmin, vmax: int, float or long
+            Color scaling values, see :class:`matplotlib.colors.Normalize` for
+            further details. Default values are determined by the min-max across
+            the data set over the entire sequence.
+
+        See :class:`matplotlib.animation.FuncAnimation` for details of other
+        valid keyword arguments.
+
+    Returns
+    -------
+    :class:`~matplotlib.animation.FuncAnimation` object suitable for
+    saving and or plotting.
+
+    Examples
+    --------
+    >>> import iris
+    >>> from iris import plot as iplt
+    >>> from iris import quickplot as qplt
+    >>> my_cube = iris.load_cube(iris.sample_data_path("A1B_north_america.nc"))
+
+    To animate along a set of :class:`~iris.cube.Cube` slices :
+
+    >>> cube_iter = my_cube.slices(("longitude", "latitude"))
+    >>> ani = iplt.animate(cube_iter, qplt.contourf)
+    >>> iplt.show()
+
+    """
+    kwargs.setdefault("interval", 100)
+    coords = kwargs.pop("coords", None)
+
+    if fig is None:
+        fig = plt.gcf()
+
+    def update_animation_iris(i, cubes, vmin, vmax, coords):
+        # Clearing the figure is currently necessary for compatibility with
+        # the iris quickploting module - due to the colorbar.
+        plt.gcf().clf()
+        plot_func(cubes[i], vmin=vmin, vmax=vmax, coords=coords)
+
+    # Turn cube iterator into a list to determine plot ranges.
+    # NOTE: we check that we are not providing a cube as this has a deprecated
+    # iter special method.
+    if hasattr(cube_iterator, "__iter__") and not isinstance(
+        cube_iterator, iris.cube.Cube
+    ):
+        cubes = iris.cube.CubeList(cube_iterator)
+    else:
+        msg = "iterable type object required for animation, {} given".format(
+            type(cube_iterator)
+        )
+        raise TypeError(msg)
+
+    supported = ["iris.plot", "iris.quickplot"]
+    if plot_func.__module__ not in supported:
+        msg = (
+            'Given plotting module "{}" may not be supported, intended '
+            "use: {}."
+        )
+        msg = msg.format(plot_func.__module__, supported)
+        warnings.warn(msg, UserWarning)
+
+    supported = ["contour", "contourf", "pcolor", "pcolormesh"]
+    if plot_func.__name__ not in supported:
+        msg = (
+            'Given plotting function "{}" may not be supported, intended '
+            "use: {}."
+        )
+        msg = msg.format(plot_func.__name__, supported)
+        warnings.warn(msg, UserWarning)
+
+    # Determine plot range.
+    vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes]))
+    vmax = kwargs.pop("vmax", max([cc.data.max() for cc in cubes]))
+
+    update = update_animation_iris
+    frames = range(len(cubes))
+
+    return animation.FuncAnimation(
+        fig, update, frames=frames, fargs=(cubes, vmin, vmax, coords), **kwargs
+    )
diff --git a/lib/iris/plugins/README.md b/lib/iris/plugins/README.md
new file mode 100644
index 0000000000..e8dee1de2c
--- /dev/null
+++ b/lib/iris/plugins/README.md
@@ -0,0 +1,10 @@
+# Iris plugins
+
+`iris.plugins` is a [namespace package] allowing arbitrary plugins to be
+installed alongside Iris.
+
+See [the Iris documentation][plugins] for more information.
+
+
+[namespace package]: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/
+[plugins]: https://scitools-iris.readthedocs.io/en/latest/community/plugins.html
diff --git a/lib/iris/quickplot.py b/lib/iris/quickplot.py
index 18ed2554a3..6006314265 100644
--- a/lib/iris/quickplot.py
+++ b/lib/iris/quickplot.py
@@ -45,7 +45,6 @@ def _title(cube_or_coord, with_units):
             or units.is_no_unit()
             or units == cf_units.Unit("1")
         ):
-
             if _use_symbol(units):
                 units = units.symbol
             elif units.is_time_reference():
diff --git a/lib/iris/util.py b/lib/iris/util.py
index 3d82ea68c5..9e0db9e66e 100644
--- a/lib/iris/util.py
+++ b/lib/iris/util.py
@@ -735,7 +735,6 @@ def _build_full_slice_given_keys(keys, ndim):
 
     for i, key in enumerate(keys):
         if key is Ellipsis:
-
             # replace any subsequent Ellipsis objects in keys with
             # slice(None, None) as per Numpy
             keys = keys[:i] + tuple(
@@ -1815,8 +1814,9 @@ def _mask_array(array, points_to_mask, in_place=False):
 
     If array is lazy then in_place is ignored: _math_op_common will use the
     returned value regardless of in_place, so we do not need to implement it
-    here.  If in_place is True then array must be a np.ma.MaskedArray or dask
-    array (must be a dask array if points_to_mask is lazy).
+    here.  If in_place is True then array must be a
+    :class:`numpy.ma.MaskedArray` or :class:`dask.array.Array`
+    (must be a dask array if points_to_mask is lazy).
 
     """
     # Decide which array library to use.
@@ -1978,7 +1978,7 @@ def is_masked(array):
 
     Parameters
     ----------
-    array : :class:`numpy.Array` or `dask.array.Array`
+    array : :class:`numpy.Array` or :class:`dask.array.Array`
             The array to be checked for masks.
 
     Returns
diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock
index b73b8af3da..910a390493 100644
--- a/requirements/ci/nox.lock/py310-linux-64.lock
+++ b/requirements/ci/nox.lock/py310-linux-64.lock
@@ -1,60 +1,65 @@
 # Generated by conda-lock.
 # platform: linux-64
-# input_hash: 9bcbc5c76124fc238f88ac16184aebeb8fac11fe9d4df03e70a7f50e2d24aa9f
+# input_hash: 234b47d943728b5abe70fba0fd74c6adc10e4f1e2a14b919344f8a693b5b3e6f
 @EXPLICIT
 https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
-https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
-https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60
 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf
-https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-3_cp310.conda#4eb33d14d794b0f4be116443ffed3853
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022g-h191b570_0.conda#51fc4fcfb19f5d95ffc8c339db5068e8
 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
-https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
-https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d
+https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373
 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d
 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
-https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
-https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2#e4c94f80aef025c17ab0828cd85ef535
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2#be733e69048951df1e4b4b7bb8c7666f
 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00
 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a
-https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f
-https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad
+https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_106.conda#d7407e695358f068a2a7f8295cde0567
 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8
-https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80
-https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f
+https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37
 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
 https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f
 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed
-https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda#c7a069243e1fbe9a556ed2ec030e6407
 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51
 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-hcb278e6_1.conda#0f683578378cddb223e7fd24f785ab2a
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4
 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd
-https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda#5cc781fd91968b11a8a7fdbee0982676
 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3
 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
-https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d
 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d
 https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35
 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
-https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee
-https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334
+https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.7-h27087fc_0.conda#f204c8ba400ec475452737094fb81d52
+https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5
 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
 https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37
-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9
-https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0
+https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.2-hcb278e6_0.conda#08efb1e1813f1a151b7a945b972a049b
+https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1
 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
-https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
-https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717
-https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1
+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.8-h0b41bf4_0.conda#e043403cd18faf815bf7705ab6c1e092
 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19
 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a
@@ -64,196 +69,201 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.t
 https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534
 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98
 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15
-https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605
+https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27
 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0
 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae
-https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa
+https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.22-h11f4161_0.conda#504fa9e712b99494a9cf4630e3ca7d78
 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
-https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25
+https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657
 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
-https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336
-https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef
-https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
-https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe
-https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c
-https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c
-https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b
-https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd
+https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0
+https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.46-h620e276_0.conda#27e745f6f2e4b757e95dd7225fbe6bdb
+https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416
+https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f
+https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906
 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e
-https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2
-https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d
-https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8
+https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.32-ha901b37_0.conda#6a39818710235826181e104aada40c75
+https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b
 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3
-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb
-https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74
-https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685
-https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
-https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
-https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce
-https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
-https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363
-https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda#6b63daed8feeca47be78f323e793d555
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06
+https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78
 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719
-https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c
+https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe
 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad
-https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a
-https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91
-https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
-https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16
-https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00
-https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e
+https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.7-hadd5161_0.conda#70cbb0c2033665f2a7339bf0ec51a67f
+https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.0-hb75c966_0.conda#c648d19cd9c8625898d5d370414de7c7
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda#2e648a34072eb39d7c4fc2a9981c5f0c
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.5.0-h79f4944_0.conda#3f67368c9b0e77a693acad193310baf1
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.32-hd7da12d_0.conda#b05d7ea8b76f1172d5fe4f30e03277ea
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.88-he45b914_0.conda#d7a81dfb99ad8fbb88872fb7ec646e6c
+https://conda.anaconda.org/conda-forge/linux-64/python-3.10.9-he550d4f_0_cpython.conda#3cb3e91b3fe66baa68a12c85f39b9b40
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
-https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f
-https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
-https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
+https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31
+https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
+https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b
+https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418
+https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6
 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c
 https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e
-https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb
-https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433
+https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf
+https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda#b325bfc4cff7d7f8a868f1f7ecc4ed16
+https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99
 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
-https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7
+https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c
+https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a
 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
-https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526
-https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee
-https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93
-https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6
-https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
+https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d
+https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.1.0-pyhd8ed1ab_0.conda#44f6828b8f7cc3433d68d1d1c0e9add2
+https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815
+https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9
-https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba
-https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
-https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001
-https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617
-https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1
-https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d
-https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97
+https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77
+https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f
+https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd
+https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc
+https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4
 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4
+https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py310h1fa729e_0.conda#a1f0db6709778b77b5903541eeac4032
+https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c
 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
-https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
-https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2
-https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.2-py310h8deb116_0.conda#b7085457309e206174b8e234d90a7605
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
+https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9
 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727
-https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f
 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
 https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
 https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174
 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
-https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da
+https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py310h1fa729e_0.conda#8d155ac95b1dfe585bcb6bec6a91c73b
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
+https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.bz2#9e68d2ff6d98737c855b65f48dd3c597
+https://conda.anaconda.org/conda-forge/noarch/setuptools-67.3.2-pyhd8ed1ab_0.conda#543af74c4042aee5702a033e03a216d0
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
 https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae
-https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.4-pyhd8ed1ab_0.conda#5a31a7d564f551d0e6dff52fd8cb5b16
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9
-https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.1-pyhd8ed1ab_0.conda#6c8c4d6eb2325e59290ac6dbbeacd5f0
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7
 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36
-https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98
-https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_1.tar.bz2#be4a201ac582c11d89ed7d15b3157cc3
+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py310h5764c6d_0.tar.bz2#e972c5a1f472561cf4a91962cb01f4b4
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb
-https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5
-https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31
-https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda
-https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d
+https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13
+https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba
+https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1
 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0
-https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9
-https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7
-https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798
-https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053
-https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e
-https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e
-https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b
-https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5
-https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002
-https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a
+https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_3.conda#800596144bb613cd7ac58b80900ce835
+https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b
+https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py310hdf3cbec_0.conda#7bf9d8c765b6b04882c719509652c6d6
+https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4
+https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0
+https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62
+https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
+https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.7-default_had23c3d_1.conda#36c65ed73b7c92589bd9562ef8a6023d
+https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572
+https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7
 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c
-https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py310h53a5b5f_0.tar.bz2#0a60ccaed9ad236cc7463322fe742eb6
-https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54
-https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741
-https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836
-https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py310h023d228_1.conda#bbea829b541aa15df5c65bd40b8c1981
+https://conda.anaconda.org/conda-forge/noarch/pip-23.0.1-pyhd8ed1ab_0.conda#8025ca83b8ba5430b640b83917c2a6f7
 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364
-https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7
-https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py310h5764c6d_0.tar.bz2#6ac13c26fe4f9d8d6b38657664c37fd3
-https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf
-https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed
+https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.1-h8ffa02c_2.conda#c264aea0e16bba26afa0a0940e954492
+https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-ha8d29e2_1.conda#dbfc2a8d63a43a11acf4c704e1ef9d0c
+https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1
+https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b
 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
-https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a
-https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf
-https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d
-https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6
-https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743
-https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8
-https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319
-https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_0.tar.bz2#6290f1bc763ed75a42aaea29384f9858
-https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe
-https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679
-https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5
-https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py310h5764c6d_0.tar.bz2#e12fa8a9fee03765d98a93234ef5a901
-https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753
-https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6
-https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
-https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014
-https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63
-https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py310h769672d_0.tar.bz2#06efc4b5f4b418b78de14d1db4a65cad
-https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_1.tar.bz2#0ad6207e9d553c67984a5b0b06bbd2a3
-https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py310hff52083_0.tar.bz2#18ef27d620d67af2feef22acfd42cf4a
-https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762
-https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247
-https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py310hdfbd76f_0.tar.bz2#bfb55d07ad9d15d2f2f8e59afcbcf578
-https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb
-https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py310h5e49deb_0.tar.bz2#2f2c225d04e99ff99d6d3a86692ce968
-https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f
+https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f
+https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py310h0a54255_0.conda#b9e952fe3f7528ab603d2776175ba8d2
+https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.1-py310h8b84c32_0.conda#965113c401c7dc9b7a4cd5f9af57e185
+https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.7-py310heca2aa9_0.conda#142c074701cf90c88667b461678aee81
+https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026
+https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf
+https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18
+https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.1-py310h34c0648_0.conda#763b301155631438b09e6f2072d3ffaa
+https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.2.0-pyhd8ed1ab_0.conda#156fb994a4e07091c4fad2c148589eb2
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.22.0-h25f0c4b_0.conda#d764367398de61c0d5531dd912e6cc96
+https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38
+https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py310he60537e_0.conda#83a21bbd1c6fbeb339ba914fb5e5c02d
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py310h9b08913_0.conda#467244b0dbb7da40927ac6ee0e9491de
+https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af
+https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py310h15e2413_1.conda#5be35366687def87437d210fd673100c
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310heca2aa9_3.conda#3b1946b676534472ce65181dda0b9554
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423
+https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749
-https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d
-https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334
-https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce
-https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209
-https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6
-https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821
-https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d
-https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6
-https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e
-https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095
-https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
-https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7
-https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_0.tar.bz2#3e81d6afa50895d6dee115ac5d34c2ea
-https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441
+https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0_0.conda#81c20b15d2281a1ea48eac5b4eee8cfa
+https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771
+https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369
+https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557
+https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h0a86a1f_103.conda#7f69695b684f2595d9ba1ce26d693b7d
+https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046
+https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
+https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84
+https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_104.conda#ed3526a8b7f37a7ee04ab0de2a0ac314
 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422
 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a
-https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369
-https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43
-https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238
-https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
-https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1
-https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a
-https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2
-https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/linux-64/pre-commit-3.0.4-py310hff52083_0.conda#099815f9de141008e85f4ede8c55991c
+https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-h5d23da1_6.conda#59c73debd9405771690ddbbad6c57b69
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
+https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py310h515c5ea_102.conda#bf8276009073388b7159736877eccd79
+https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.1.0-h2e5815a_0.conda#e7ecda996c443142a0e9c379f3b28e48
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310hab646b1_3.conda#d049da3204bf5ecb54a852b622f2d7d2
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.7.0-py310hff52083_0.conda#215e2a4504900bef6d68f520c12ef800
+https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429
 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345
-https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba
+https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py310h8deb116_2.conda#a12933d43fc0e55c2e5e00f56196108c
 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c
 https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8
 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a
+https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py310hcb7e713_0.conda#bd14eaad9bbf54b78e48ecb8b644fcf6
+https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a
diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock
index 096162793f..e87b21a994 100644
--- a/requirements/ci/nox.lock/py38-linux-64.lock
+++ b/requirements/ci/nox.lock/py38-linux-64.lock
@@ -1,59 +1,64 @@
 # Generated by conda-lock.
 # platform: linux-64
-# input_hash: 34099f3b69d60b791c26fcde2961739ff7cb0f9c144a37335b9f2183abe0dda3
+# input_hash: 0543fd9bbb31e9f896ccf547f3b155d68bb748634268c28dde6ff3ac77aa74d3
 @EXPLICIT
 https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
-https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
-https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60
 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-3_cp38.conda#2f3f7af062b42d664117662612022204
 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
-https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
-https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d
+https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373
 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d
 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
-https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
-https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2#e4c94f80aef025c17ab0828cd85ef535
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2#be733e69048951df1e4b4b7bb8c7666f
 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00
 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a
-https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f
-https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad
+https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_106.conda#d7407e695358f068a2a7f8295cde0567
 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8
-https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80
-https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f
+https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37
 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
 https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f
 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed
-https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda#c7a069243e1fbe9a556ed2ec030e6407
 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51
 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-hcb278e6_1.conda#0f683578378cddb223e7fd24f785ab2a
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4
 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd
-https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda#5cc781fd91968b11a8a7fdbee0982676
 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3
 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
-https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d
 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d
 https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35
 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
-https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee
-https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334
+https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.7-h27087fc_0.conda#f204c8ba400ec475452737094fb81d52
+https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5
 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
 https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37
-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9
-https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0
+https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.2-hcb278e6_0.conda#08efb1e1813f1a151b7a945b972a049b
+https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1
 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
-https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
-https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717
-https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1
+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.8-h0b41bf4_0.conda#e043403cd18faf815bf7705ab6c1e092
 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19
 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a
@@ -63,196 +68,203 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.t
 https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534
 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98
 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15
-https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605
+https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27
 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0
 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae
-https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa
+https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.22-h11f4161_0.conda#504fa9e712b99494a9cf4630e3ca7d78
 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
-https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25
+https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657
 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
-https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336
-https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef
-https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
-https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe
-https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c
-https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c
-https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b
-https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd
+https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0
+https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.46-h620e276_0.conda#27e745f6f2e4b757e95dd7225fbe6bdb
+https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416
+https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f
+https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906
 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e
-https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2
-https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d
-https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8
+https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.32-ha901b37_0.conda#6a39818710235826181e104aada40c75
+https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b
 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3
-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb
-https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74
-https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685
-https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
-https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
-https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce
-https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
-https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363
-https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda#6b63daed8feeca47be78f323e793d555
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06
+https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78
 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719
-https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c
+https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe
 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad
-https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a
-https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91
-https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
-https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16
-https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e
+https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.7-hadd5161_0.conda#70cbb0c2033665f2a7339bf0ec51a67f
+https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.0-hb75c966_0.conda#c648d19cd9c8625898d5d370414de7c7
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda#2e648a34072eb39d7c4fc2a9981c5f0c
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.5.0-h79f4944_0.conda#3f67368c9b0e77a693acad193310baf1
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.32-hd7da12d_0.conda#b05d7ea8b76f1172d5fe4f30e03277ea
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.88-he45b914_0.conda#d7a81dfb99ad8fbb88872fb7ec646e6c
+https://conda.anaconda.org/conda-forge/linux-64/python-3.8.16-he550d4f_1_cpython.conda#9de84cccfbc5f8350a3667bb6ef6fc30
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790
-https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
-https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee
-https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6
-https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba
-https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
-https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001
-https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617
-https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1
-https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d
-https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98
-https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
-https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2
-https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce
-https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
-https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4
-https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
-https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f
-https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0
-https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
+https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a
+https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
+https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b
+https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418
+https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6
 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c
 https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e
-https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb
-https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433
-https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7
+https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf
+https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda#b325bfc4cff7d7f8a868f1f7ecc4ed16
+https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99
 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
-https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7
+https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34
+https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a
 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
-https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526
-https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93
-https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
-https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e
+https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d
+https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.1.0-pyhd8ed1ab_0.conda#44f6828b8f7cc3433d68d1d1c0e9add2
+https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815
+https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9
-https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97
+https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77
+https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f
+https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd
+https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc
+https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4
 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4
+https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py38h1de0b5d_0.conda#6d97b5d6f06933ab653f1862ddf6e33e
+https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d
 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
-https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.2-py38h10c12cc_0.conda#05592c85b9f6931dc2df1e80c0d56294
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
+https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9
 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727
-https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7
-https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf
-https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7
 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
 https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
 https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174
 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
-https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da
+https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py38h1de0b5d_0.conda#7db73572d4f7e10a759bad609a228ad0
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
+https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz2#0856c59f9ddb710c640dc0428d66b1b7
+https://conda.anaconda.org/conda-forge/noarch/setuptools-67.3.2-pyhd8ed1ab_0.conda#543af74c4042aee5702a033e03a216d0
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
 https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae
-https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.4-pyhd8ed1ab_0.conda#5a31a7d564f551d0e6dff52fd8cb5b16
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9
-https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.1-pyhd8ed1ab_0.conda#6c8c4d6eb2325e59290ac6dbbeacd5f0
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7
 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36
-https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98
-https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
-https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5
-https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a
-https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda
-https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d
-https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2
-https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be
-https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053
-https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6
-https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8
-https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258
-https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014
-https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05
-https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_1.tar.bz2#358beb228a53b5e1031862de3525d1d3
+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py38h0a891b7_0.tar.bz2#44421904760e9f5ae2035193e04360f0
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb
+https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13
+https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba
+https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1
+https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0
+https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_3.conda#3ac112151c6b6cfe457e976de41af0c5
+https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9
+https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py38hfbd4bf9_0.conda#638537863b298151635c05c762a997ab
+https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4
+https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0
+https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62
+https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
+https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
+https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.7-default_had23c3d_1.conda#36c65ed73b7c92589bd9562ef8a6023d
+https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572
+https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd
 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c
-https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py38h3a7f9d9_0.tar.bz2#83ba913fc1174925d4e862eccb53db59
-https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54
-https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38ha3b2c9c_2.tar.bz2#a077cc2bb9d854074b1cf4607252da7a
-https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836
-https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py38hde6dc18_1.conda#3de5619d3f556f966189e5251a266125
+https://conda.anaconda.org/conda-forge/noarch/pip-23.0.1-pyhd8ed1ab_0.conda#8025ca83b8ba5430b640b83917c2a6f7
 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364
-https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py38h0a891b7_0.tar.bz2#907a39b6d7443f770ed755885694f864
-https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed
-https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hd7890fc_1.tar.bz2#f851bb08c85122fd0e1f66d2072ebf0b
+https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.1-h8ffa02c_2.conda#c264aea0e16bba26afa0a0940e954492
+https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-ha8d29e2_1.conda#dbfc2a8d63a43a11acf4c704e1ef9d0c
+https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1
+https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b
 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
-https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051
-https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3
-https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f
-https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6
-https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465
-https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5
-https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0
-https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_0.tar.bz2#df081ec90a13f53fe522c8e876d3f0cf
-https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8
-https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b
-https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5
-https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py38h0a891b7_0.tar.bz2#ff4c112a78161241ca8a7af74de6a50b
-https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753
-https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
-https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1
-https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821
-https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py38h8f669ce_0.tar.bz2#f91da48c62c91659da28bd95559c75ff
-https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6
-https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2
-https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b
-https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe
-https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py38hea3f02b_0.tar.bz2#b232edb409c6a79e5921b3591c56b716
-https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb
-https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py38h3b45516_0.tar.bz2#d8621497bcc7b369ef9cce25d5a58aeb
-https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43
+https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669
+https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py38h7e4f40d_0.conda#17f682c947f9cabd348e7276f00c6d85
+https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.1-py38hd07e089_0.conda#84c9262ab4057ed9f80888fcfc4bf60a
+https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.7-py38h8dc9893_0.conda#ea242937718f3dacf253355e1d634535
+https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026
+https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759
+https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e
+https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.1-py38h3d167d9_0.conda#375c00c98c36b0e79aaaf2149e51f27d
+https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.2.0-pyhd8ed1ab_0.conda#156fb994a4e07091c4fad2c148589eb2
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.22.0-h25f0c4b_0.conda#d764367398de61c0d5531dd912e6cc96
+https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38
+https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.10.2-pyhd8ed1ab_0.conda#ebf8b116aac3fe86270bfe5f61fe2b80
+https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py38hdc8b05c_0.conda#5073966d63a54434d2a2fc41d325b072
+https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af
+https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py38h58d5fe2_1.conda#5286eaec7e93586e4ae05e7d658cd3e2
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38h8dc9893_3.conda#7bb0328b4a0f857aeb432426b9a5f908
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423
+https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749
-https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62
-https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128
-https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441
+https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0_0.conda#81c20b15d2281a1ea48eac5b4eee8cfa
+https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py38hd6c3c57_0.conda#dd63f6486ba95c036b6bfe0b5c53d875
+https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557
+https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h6b4b75c_103.conda#ea3d2204fc3a7db7d831daa437a58717
+https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046
+https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
+https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84
+https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_104.conda#ed3526a8b7f37a7ee04ab0de2a0ac314
 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422
-https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce
-https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209
 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a
-https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600
-https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc
-https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e
-https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4
-https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
-https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7
-https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38h606536b_0.tar.bz2#38fc3704565e44fb9fcdfaded03eee76
-https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257
-https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2
 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369
-https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1
-https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae
-https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
-https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1
-https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py38h578d9bd_0.tar.bz2#602eb908e81892115c1405c9d99abd56
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/linux-64/pre-commit-3.0.4-py38h578d9bd_0.conda#ae802cf221c9549ce9924e1a3718342d
+https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-h5d23da1_6.conda#59c73debd9405771690ddbbad6c57b69
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
+https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py38h4407c66_102.conda#9a5c841acef11d7e4f0bf98cbc6308b3
+https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.1.0-h2e5815a_0.conda#e7ecda996c443142a0e9c379f3b28e48
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38ha0d8c90_3.conda#e965dc172d67920d058ac2b3a0e27565
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.7.0-py38h578d9bd_0.conda#7fb6ab52eb5de5023445561d86dbd602
+https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429
 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345
-https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba
+https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py38h10c12cc_2.conda#d6a3defdc4ab4acd69c04c8ef73d9b57
 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c
 https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8
 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a
+https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py38h3d2c718_0.conda#55ba6e3a49c4293302262286a49607d8
+https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a
diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock
index 9d454a2569..f2eb79bc0a 100644
--- a/requirements/ci/nox.lock/py39-linux-64.lock
+++ b/requirements/ci/nox.lock/py39-linux-64.lock
@@ -1,60 +1,65 @@
 # Generated by conda-lock.
 # platform: linux-64
-# input_hash: 44cf413042165b62fe105f738e80b926629f61c1763d74df419910081521225b
+# input_hash: de178c2d53980747bafc10c4a4387eeb8c700311af7b35a2fcb49f1b441b960b
 @EXPLICIT
 https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81
-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c
+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb
 https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5
-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee
-https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760
-https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7
+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3
+https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60
 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf
-https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979
+https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-3_cp39.conda#0dd193187d54e585cac7eab942a8847e
+https://conda.anaconda.org/conda-forge/noarch/tzdata-2022g-h191b570_0.conda#51fc4fcfb19f5d95ffc8c339db5068e8
 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29
-https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2
-https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab
+https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d
+https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373
 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d
 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab
-https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c
-https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7
+https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2#e4c94f80aef025c17ab0828cd85ef535
+https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2#be733e69048951df1e4b4b7bb8c7666f
 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00
 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54
 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a
-https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f
-https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f
+https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad
+https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_106.conda#d7407e695358f068a2a7f8295cde0567
 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8
-https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80
-https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f
+https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55
+https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37
 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d
 https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f
 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed
-https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268
+https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda#c7a069243e1fbe9a556ed2ec030e6407
 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3
+https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51
 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c
+https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-hcb278e6_1.conda#0f683578378cddb223e7fd24f785ab2a
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4
 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd
-https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734
+https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda#5cc781fd91968b11a8a7fdbee0982676
 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3
 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3
-https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211
+https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d
 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d
 https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206
 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680
 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35
 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f
-https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee
-https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334
+https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.7-h27087fc_0.conda#f204c8ba400ec475452737094fb81d52
+https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5
 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d
 https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37
-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9
-https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696
+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41
+https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0
+https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.2-hcb278e6_0.conda#08efb1e1813f1a151b7a945b972a049b
+https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1
 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238
-https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e
-https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717
-https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa
+https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1
+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.8-h0b41bf4_0.conda#e043403cd18faf815bf7705ab6c1e092
 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19
 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036
 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a
@@ -64,196 +69,203 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.t
 https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534
 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98
 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15
-https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605
+https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27
 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0
 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae
-https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa
+https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.22-h11f4161_0.conda#504fa9e712b99494a9cf4630e3ca7d78
 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0
-https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f
-https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82
+https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25
+https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657
 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1
-https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336
-https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef
-https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2
-https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe
-https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c
-https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c
-https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b
-https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd
+https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd
+https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0
+https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.46-h620e276_0.conda#27e745f6f2e4b757e95dd7225fbe6bdb
+https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e
+https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416
+https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f
+https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906
 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0
 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e
-https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2
-https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d
-https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8
+https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf
+https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.32-ha901b37_0.conda#6a39818710235826181e104aada40c75
+https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b
 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa
 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168
 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3
-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb
-https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74
-https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685
-https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e
-https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
-https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce
-https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571
-https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363
-https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8
+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295
+https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda#6b63daed8feeca47be78f323e793d555
+https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4
+https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06
+https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336
+https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78
 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719
-https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c
+https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9
+https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe
 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad
-https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a
-https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91
-https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b
-https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16
-https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e
+https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.7-hadd5161_0.conda#70cbb0c2033665f2a7339bf0ec51a67f
+https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.0-hb75c966_0.conda#c648d19cd9c8625898d5d370414de7c7
+https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda#2e648a34072eb39d7c4fc2a9981c5f0c
+https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.5.0-h79f4944_0.conda#3f67368c9b0e77a693acad193310baf1
+https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.32-hd7da12d_0.conda#b05d7ea8b76f1172d5fe4f30e03277ea
+https://conda.anaconda.org/conda-forge/linux-64/nss-3.88-he45b914_0.conda#d7a81dfb99ad8fbb88872fb7ec646e6c
+https://conda.anaconda.org/conda-forge/linux-64/python-3.9.16-h2782a2a_0_cpython.conda#95c9b7c96a7fd7342e0c9d0a917b8f78
+https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7
 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c
 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790
-https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1
-https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee
-https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6
-https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba
-https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f
-https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001
-https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617
-https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1
-https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d
-https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98
-https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc
-https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2
-https://conda.anaconda.org/conda-forge/linux-64/python-3.9.13-h9a8a25e_0_cpython.tar.bz2#69bc307cc4d7396c5fccb26bbcc9c379
-https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
-https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4
-https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
-https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f
-https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0
-https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
+https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb
+https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
+https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b
+https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
+https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418
+https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6
 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c
 https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e
-https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb
-https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433
-https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7
+https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf
+https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda#b325bfc4cff7d7f8a868f1f7ecc4ed16
+https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99
 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb
-https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251
+https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d
+https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7
+https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c
+https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a
 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
-https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526
-https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93
-https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5
-https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e
+https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506
+https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d
+https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.1.0-pyhd8ed1ab_0.conda#44f6828b8f7cc3433d68d1d1c0e9add2
+https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b
+https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815
+https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905
+https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9
-https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5
+https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411
+https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97
+https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77
+https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f
+https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd
+https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc
+https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5
+https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4
 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4
+https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py39h72bdee0_0.conda#35514f5320206df9f4661c138c02e1c1
+https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437
 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19
-https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950
+https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.2-py39h7360e5f_0.conda#757070dc7cc33003254888808cd34f1e
+https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
+https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9
 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727
-https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7
-https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf
-https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054
+https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed
 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff
 https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc
 https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174
 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
-https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da
+https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py39h72bdee0_0.conda#18927f971926b7271600368de71de557
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
+https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2#ef9db3c38ae7275f6b14491cfe61a248
+https://conda.anaconda.org/conda-forge/noarch/setuptools-67.3.2-pyhd8ed1ab_0.conda#543af74c4042aee5702a033e03a216d0
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
 https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae
-https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.4-pyhd8ed1ab_0.conda#5a31a7d564f551d0e6dff52fd8cb5b16
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9
-https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708
+https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.1-pyhd8ed1ab_0.conda#6c8c4d6eb2325e59290ac6dbbeacd5f0
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7
 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095
 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36
-https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98
-https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022
-https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5
-https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb
-https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda
-https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d
-https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe
-https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4
-https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053
-https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6
-https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb
-https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe
-https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014
-https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c
-https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600
+https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_1.tar.bz2#8a7d309b08cff6386fe384aa10dd3748
+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a
+https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2#230d65004135bf312504a1bbcb0c7a08
+https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940
+https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4
+https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb
+https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13
+https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba
+https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1
+https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0
+https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_3.conda#20080319ef73fbad74dcd6d62f2a3ffe
+https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1
+https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py39h4b4f3f3_0.conda#c5387f3fb1f5b8b71e1c865fc55f4951
+https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602
+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7
+https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4
+https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0
+https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62
+https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
+https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
+https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.7-default_had23c3d_1.conda#36c65ed73b7c92589bd9562ef8a6023d
+https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572
+https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3
 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c
-https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py39hba7629e_0.tar.bz2#320e25179733ec4a2ecffcebc8abbc80
-https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85
 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54
-https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hd5dbb17_2.tar.bz2#3b74a959f6a8008f5901de60b3572c09
-https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836
-https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265
+https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py39h2320bf1_1.conda#d2f79132b9c8e416058a4cd84ef27b3d
+https://conda.anaconda.org/conda-forge/noarch/pip-23.0.1-pyhd8ed1ab_0.conda#8025ca83b8ba5430b640b83917c2a6f7
 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364
-https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py39hb9d737c_0.tar.bz2#1e7ffe59e21862559e06b981817e5058
-https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed
-https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h2c22827_1.tar.bz2#a1ca42c2a746601d42f27bbcb7f6acfc
+https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.1-h8ffa02c_2.conda#c264aea0e16bba26afa0a0940e954492
+https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-ha8d29e2_1.conda#dbfc2a8d63a43a11acf4c704e1ef9d0c
+https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1
+https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b
 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
-https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc
-https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5
-https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd
-https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6
-https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f
-https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392
-https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08
-https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_0.tar.bz2#4b108127973b66b36edd6449aa6afde0
-https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa
-https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478
-https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5
-https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py39hb9d737c_0.tar.bz2#21622fe576fcce5b861036e8d7282470
-https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753
-https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
-https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480
-https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821
-https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py39h4661b88_0.tar.bz2#ae807099430cd22b09b869b0536425b7
-https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6
-https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff
-https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1
-https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4
-https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py39h8ba3f38_0.tar.bz2#beed054d4979cd70690aea2b257a6d55
-https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb
-https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py39h68ae834_0.tar.bz2#e871ee7de5bfa95095256e95e30be2a6
-https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0
+https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3
+https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py39h389d5f1_0.conda#9eeb2b2549f836ca196c6cbd22344122
+https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.1-py39hc9151fd_0.conda#d26cc40830285883abaa766a7f7798bf
+https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.7-py39h227be39_0.conda#7d9a35091552af3655151f164ddd64a3
+https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026
+https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045
+https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4
+https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.1-py39h079d5ae_0.conda#3245013812dfbff6a22e57533ac6f69d
+https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.2.0-pyhd8ed1ab_0.conda#156fb994a4e07091c4fad2c148589eb2
+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.22.0-h25f0c4b_0.conda#d764367398de61c0d5531dd912e6cc96
+https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38
+https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.10.2-pyhd8ed1ab_0.conda#ebf8b116aac3fe86270bfe5f61fe2b80
+https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d
+https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py39h2ad29b5_0.conda#3ea96adbbc2a66fa45178102a9cfbecc
+https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af
+https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py39hf14cbfd_1.conda#67766c515601b3ee1514072d6fd060bb
+https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h227be39_3.conda#9e381db00691e26bcf670c3586397be1
+https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423
+https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1
 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749
-https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6
-https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469
-https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441
+https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a
+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0_0.conda#81c20b15d2281a1ea48eac5b4eee8cfa
+https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py39he190548_0.conda#62d6ddd9e534f4d325d12470cc4961ab
+https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557
+https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h94a714e_103.conda#ee29e7176b5854fa09ec17b101945401
+https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046
+https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934
+https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
+https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84
+https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_104.conda#ed3526a8b7f37a7ee04ab0de2a0ac314
 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422
-https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce
-https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209
 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a
-https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0
-https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6
-https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e
-https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc
-https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f
-https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7
-https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39hf5d525c_0.tar.bz2#b99ba7383d1c9dd18445dfff08439c48
-https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf
-https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2
 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369
-https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2
-https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04
-https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e
-https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1
-https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py39hf3d152e_0.tar.bz2#93f29e4d6f852de18384412b0e0d03b5
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/linux-64/pre-commit-3.0.4-py39hf3d152e_0.conda#8a98273ee904735747a8f6706b187f3e
+https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-h5d23da1_6.conda#59c73debd9405771690ddbbad6c57b69
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
+https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py39h3088dd8_102.conda#a022e48c8b12bc56083bcce841978519
+https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.1.0-h2e5815a_0.conda#e7ecda996c443142a0e9c379f3b28e48
+https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h5c7b992_3.conda#19e30314fe824605750da905febb8ee6
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
+https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.7.0-py39hf3d152e_0.conda#0967228e228ebeded6a36a6f4d5509ed
+https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429
 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345
-https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba
+https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py39h7360e5f_2.conda#fbee2ab3fe7729f2ff5c5699d58e40b9
 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c
 https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8
 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a
+https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py39h6e7ad6e_0.conda#7cb72bd5b1e7c5a23a062db90889356b
+https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a
diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml
index 76ca9e4f58..d79015c055 100644
--- a/requirements/ci/py310.yml
+++ b/requirements/ci/py310.yml
@@ -15,8 +15,8 @@ dependencies:
   - cf-units >=3.1
   - cftime >=1.5
   - dask-core >=2.26
-  - matplotlib
-  - netcdf4 !=1.6.1
+  - matplotlib >=3.5
+  - netcdf4 <1.6.1
   - numpy >=1.19
   - python-xxhash
   - pyproj
@@ -48,4 +48,8 @@ dependencies:
   - sphinx-copybutton
   - sphinx-gallery >=0.11.0
   - sphinx-panels
-  - pydata-sphinx-theme = 0.8.1
+  - pydata-sphinx-theme
+
+# Temporary minimum pins.
+# See https://github.com/SciTools/iris/pull/5051
+  - graphviz >=6.0.0
diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml
index 5a8c878ee1..b68e8ccf45 100644
--- a/requirements/ci/py38.yml
+++ b/requirements/ci/py38.yml
@@ -15,8 +15,8 @@ dependencies:
   - cf-units >=3.1
   - cftime >=1.5
   - dask-core >=2.26
-  - matplotlib
-  - netcdf4 !=1.6.1
+  - matplotlib >=3.5
+  - netcdf4 <1.6.1
   - numpy >=1.19
   - python-xxhash
   - pyproj
@@ -48,4 +48,8 @@ dependencies:
   - sphinx-copybutton
   - sphinx-gallery >=0.11.0
   - sphinx-panels
-  - pydata-sphinx-theme = 0.8.1
+  - pydata-sphinx-theme
+
+# Temporary minimum pins.
+# See https://github.com/SciTools/iris/pull/5051
+  - graphviz >=6.0.0
diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml
index 7931e20336..9fec76cfde 100644
--- a/requirements/ci/py39.yml
+++ b/requirements/ci/py39.yml
@@ -15,8 +15,8 @@ dependencies:
   - cf-units >=3.1
   - cftime >=1.5
   - dask-core >=2.26
-  - matplotlib
-  - netcdf4 !=1.6.1
+  - matplotlib >=3.5
+  - netcdf4 <1.6.1
   - numpy >=1.19
   - python-xxhash
   - pyproj
@@ -48,4 +48,8 @@ dependencies:
   - sphinx-copybutton
   - sphinx-gallery >=0.11.0
   - sphinx-panels
-  - pydata-sphinx-theme = 0.8.1
+  - pydata-sphinx-theme
+
+# Temporary minimum pins.
+# See https://github.com/SciTools/iris/pull/5051
+  - graphviz >=6.0.0
diff --git a/setup.cfg b/setup.cfg
index 92cbe4747c..75647e6623 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,8 +51,8 @@ install_requires =
     cf-units>=3.1
     cftime>=1.5.0
     dask[array]>=2.26
-    matplotlib
-    netcdf4!=1.6.1
+    matplotlib>=3.5
+    netcdf4<1.6.1
     numpy>=1.19
     scipy
     shapely!=1.8.3
@@ -69,7 +69,7 @@ where = lib
 
 [options.extras_require]
 docs =
-    sphinx
+    sphinx<5
     sphinx-copybutton
     sphinx-gallery>=0.11.0
     sphinx_rtd_theme


Test patch:
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 43bacd3ec5..cee98dc33d 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -46,7 +46,7 @@ jobs:
             session: "tests"
 
     env:
-      IRIS_TEST_DATA_VERSION: "2.16"
+      IRIS_TEST_DATA_VERSION: "2.18"
       ENV_NAME: "ci-tests"
 
     steps:
diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst
index d2eadb17d6..a38e426e6a 100644
--- a/docs/src/whatsnew/latest.rst
+++ b/docs/src/whatsnew/latest.rst
@@ -16,7 +16,7 @@ This document explains the changes made to Iris for this release
 
    The highlights for this major/minor release of Iris include:
 
-   * N/A
+   * We're so proud to fully support `@ed-hawkins`_ and `#ShowYourStripes`_ ❤️
 
    And finally, get in touch with us on :issue:`GitHub<new/choose>` if you have
    any issues or feature requests for improving Iris. Enjoy!
@@ -25,37 +25,22 @@ This document explains the changes made to Iris for this release
 📢 Announcements
 ================
 
-#. Welcome to `@ESadek-MO`_ and `@TTV-Intrepid`_  who made their first contributions to Iris 🎉
+#. Congratulations to `@ESadek-MO`_ who has become a core developer for Iris! 🎉
+#. Welcome and congratulations to `@HGWright`_ for making his first contribution to Iris! 🎉
 
 
 ✨ Features
 ===========
 
-#. `@ESadek-MO`_ edited :func:`~iris.io.expand_filespecs` to allow expansion of
-   non-existing paths, and added expansion functionality to :func:`~iris.io.save`.
-   (:issue:`4772`, :pull:`4913`)
+#. `@bsherratt`_ added support for plugins - see the corresponding
+   :ref:`documentation page<community_plugins>` for further information.
+   (:pull:`5144`)
 
 
 🐛 Bugs Fixed
 =============
 
-#. `@rcomer`_ and `@pp-mo`_ (reviewer) factored masking into the returned
-   sum-of-weights calculation from :obj:`~iris.analysis.SUM`. (:pull:`4905`)
-
-#. `@schlunma`_ fixed a bug which prevented using
-   :meth:`iris.cube.Cube.collapsed` on coordinates whose number of bounds
-   differs from 0 or 2. This enables the use of this method on mesh
-   coordinates. (:issue:`4672`, :pull:`4870`)
-
-#. `@bjlittle`_ and `@lbdreyer`_ (reviewer) fixed the building of the CF
-   Standard Names module ``iris.std_names`` for the ``setup.py`` commands
-   ``develop`` and ``std_names``. (:issue:`4951`, :pull:`4952`)
-
-#. `@lbdreyer`_ and `@pp-mo`_ (reviewer) fixed the cube print out such that
-   scalar ancillary variables are displayed in a dedicated section rather than
-   being added to the vector ancillary variables section. Further, ancillary
-   variables and cell measures that map to a cube dimension of length 1 are now
-   included in the respective vector sections. (:pull:`4945`)
+#. N/A
 
 
 💣 Incompatible Changes
@@ -67,9 +52,7 @@ This document explains the changes made to Iris for this release
 🚀 Performance Enhancements
 ===========================
 
-#. `@rcomer`_ and `@pp-mo`_ (reviewer) increased aggregation speed for
-   :obj:`~iris.analysis.SUM`, :obj:`~iris.analysis.COUNT` and
-   :obj:`~iris.analysis.PROPORTION` on real data. (:pull:`4905`)
+#. N/A
 
 
 🔥 Deprecations
@@ -81,53 +64,60 @@ This document explains the changes made to Iris for this release
 🔗 Dependencies
 ===============
 
-#. `@rcomer`_ introduced the ``dask >=2.26`` minimum pin, so that Iris can benefit
-   from Dask's support for `NEP13`_ and `NEP18`_. (:pull:`4905`)
-#. `@trexfeathers`_ advanced the Cartopy pin to ``>=0.21``, as Cartopy's
-   change to default Transverse Mercator projection affects an Iris test.
-   See `SciTools/cartopy@fcb784d`_ and `SciTools/cartopy@8860a81`_ for more
-   details.
-   (:pull:`4968`)
-#. `@trexfeathers`_ introduced the ``netcdf4!=1.6.1`` pin to avoid a problem
-   with segfaults. (:pull:`4968`)
+#. N/A
 
 
 📚 Documentation
 ================
 
-#. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal
-   means plotted parallel to a cartographic plot. (:pull:`4871`)
+#. `@rcomer`_ clarified instructions for updating gallery tests. (:pull:`5100`)
+#. `@tkknight`_ unpinned ``pydata-sphinx-theme`` and set the default to use
+   the light version (not dark) while we make the docs dark mode friendly
+   (:pull:`5129`)
+
+#. `@jonseddon`_ updated the citation to a more recent version of Iris. (:pull:`5116`)
+
+#. `@rcomer`_ linked the :obj:`~iris.analysis.PERCENTILE` aggregator from the
+   :obj:`~iris.analysis.MEDIAN` docstring, noting that the former handles lazy
+   data. (:pull:`5128`)
 
+#. `@trexfeathers`_ updated the WSL link to Microsoft's latest documentation,
+   and removed an ECMWF link in the ``v1.0`` What's New that was failing the
+   linkcheck CI. (:pull:`5109`)
+
+#. `@trexfeathers`_ added a new top-level :doc:`/community/index` section,
+   as a one-stop place to find out about getting involved, and how we relate
+   to other projects. (:pull:`5025`)
+
+#. The **Iris community**, with help from the **Xarray community**, produced
+   the :doc:`/community/iris_xarray` page, highlighting the similarities and
+   differences between the two packages. (:pull:`5025`)
+
+#. `@bjlittle`_ added a new section to the `README.md`_ to show our support
+   for the outstanding work of `@ed-hawkins`_ et al for `#ShowYourStripes`_.
+   (:pull:`5141`)
+
+#. `@HGWright`_ fixed some typo's from Gitwash. (:pull:`5145`)
 
 💼 Internal
 ===========
 
-#. `@rcomer`_ removed the obsolete ``setUpClass`` method from Iris testing.
-   (:pull:`4927`)
+#. `@fnattino`_ changed the order of ``ncgen`` arguments in the command to
+   create NetCDF files for testing  (caused errors on OS X). (:pull:`5105`)
 
-#. `@bjlittle`_ and `@lbdreyer`_ (reviewer) removed support for
-   ``python setup.py test``, which is a deprecated approach to executing
-   package tests, see `pypa/setuptools#1684`_.  Also performed assorted
-   ``setup.py`` script hygiene. (:pull:`4948`, :pull:`4949`, :pull:`4950`)
-
-#. `@pp-mo`_ split the module :mod:`iris.fileformats.netcdf` into separate
-   :mod:`~iris.fileformats.netcdf.loader` and :mod:`~iris.fileformats.netcdf.saver`
-   submodules, just to make the code easier to handle.
+#. `@rcomer`_ removed some old infrastructure that printed test timings.
+   (:pull:`5101`)
 
 
 .. comment
     Whatsnew author names (@github name) in alphabetical order. Note that,
     core dev names are automatically included by the common_links.inc:
 
-.. _@TTV-Intrepid: https://github.com/TTV-Intrepid
-
-
+.. _@fnattino: https://github.com/fnattino
+.. _@ed-hawkins: https://github.com/ed-hawkins
 
 .. comment
     Whatsnew resources in alphabetical order:
 
-.. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html
-.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html
-.. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684
-.. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108
-.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e
\ No newline at end of file
+.. _#ShowYourStripes: https://showyourstripes.info/s/globe/
+.. _README.md: https://github.com/SciTools/iris#-----
diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template
index 661ee47f50..a0ce415a65 100644
--- a/docs/src/whatsnew/latest.rst.template
+++ b/docs/src/whatsnew/latest.rst.template
@@ -109,4 +109,3 @@ NOTE: section above is a template for bugfix patches
 .. comment
     Whatsnew resources in alphabetical order:
 
-
diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py
index 4840de8cdb..5529b899c5 100644
--- a/lib/iris/tests/__init__.py
+++ b/lib/iris/tests/__init__.py
@@ -16,7 +16,6 @@
 import collections
 from collections.abc import Mapping
 import contextlib
-import datetime
 import difflib
 import filecmp
 import functools
@@ -208,7 +207,7 @@ def assert_masked_array_almost_equal(a, b, decimal=6, strict=False):
     )
 
 
-class IrisTest_nometa(unittest.TestCase):
+class IrisTest(unittest.TestCase):
     """A subclass of unittest.TestCase which provides Iris specific testing functionality."""
 
     _assertion_counts = collections.defaultdict(int)
@@ -937,80 +936,6 @@ def assertEqualAndKind(self, value, expected):
         )
 
 
-# An environment variable controls whether test timings are output.
-#
-# NOTE: to run tests with timing output, nosetests cannot be used.
-# At present, that includes not using "python setup.py test"
-# The typically best way is like this :
-#    $ export IRIS_TEST_TIMINGS=1
-#    $ python -m unittest discover -s iris.tests
-# and commonly adding ...
-#    | grep "TIMING TEST" >iris_test_output.txt
-#
-_PRINT_TEST_TIMINGS = bool(int(os.environ.get("IRIS_TEST_TIMINGS", 0)))
-
-
-def _method_path(meth, cls):
-    return ".".join([cls.__module__, cls.__name__, meth.__name__])
-
-
-def _testfunction_timing_decorator(fn, cls):
-    # Function decorator for making a testcase print its execution time.
-    @functools.wraps(fn)
-    def inner(*args, **kwargs):
-        start_time = datetime.datetime.now()
-        try:
-            result = fn(*args, **kwargs)
-        finally:
-            end_time = datetime.datetime.now()
-            elapsed_time = (end_time - start_time).total_seconds()
-            msg = '\n  TEST TIMING -- "{}" took : {:12.6f} sec.'
-            name = _method_path(fn, cls)
-            print(msg.format(name, elapsed_time))
-        return result
-
-    return inner
-
-
-def iristest_timing_decorator(cls):
-    # Class decorator to make all "test_.." functions print execution timings.
-    if _PRINT_TEST_TIMINGS:
-        # NOTE: 'dir' scans *all* class properties, including inherited ones.
-        attr_names = dir(cls)
-        for attr_name in attr_names:
-            attr = getattr(cls, attr_name)
-            if callable(attr) and attr_name.startswith("test"):
-                attr = _testfunction_timing_decorator(attr, cls)
-                setattr(cls, attr_name, attr)
-    return cls
-
-
-class _TestTimingsMetaclass(type):
-    # An alternative metaclass for IrisTest subclasses, which makes
-    # them print execution timings for all the testcases.
-    # This is equivalent to applying the @iristest_timing_decorator to
-    # every test class that inherits from IrisTest.
-    # NOTE: however, it means you *cannot* specify a different metaclass for
-    # your test class inheriting from IrisTest.
-    # See below for how to solve that where needed.
-    def __new__(cls, clsname, base_classes, attrs):
-        result = type.__new__(cls, clsname, base_classes, attrs)
-        if _PRINT_TEST_TIMINGS:
-            result = iristest_timing_decorator(result)
-        return result
-
-
-class IrisTest(IrisTest_nometa, metaclass=_TestTimingsMetaclass):
-    # Derive the 'ordinary' IrisTest from IrisTest_nometa, but add the
-    # metaclass that enables test timings output.
-    # This means that all subclasses also get the timing behaviour.
-    # However, if a different metaclass is *wanted* for an IrisTest subclass,
-    # this would cause a metaclass conflict.
-    # Instead, you can inherit from IrisTest_nometa and apply the
-    # @iristest_timing_decorator explicitly to your new testclass.
-    pass
-
-
 get_data_path = IrisTest.get_data_path
 get_result_path = IrisTest.get_result_path
 
@@ -1019,11 +944,6 @@ class GraphicsTest(graphics.GraphicsTestMixin, IrisTest):
     pass
 
 
-class GraphicsTest_nometa(graphics.GraphicsTestMixin, IrisTest_nometa):
-    # Graphicstest without the metaclass providing test timings.
-    pass
-
-
 def skip_data(fn):
     """
     Decorator to choose whether to run tests, based on the availability of
diff --git a/lib/iris/tests/graphics/README.md b/lib/iris/tests/graphics/README.md
index b26f1720e8..069fc01f70 100755
--- a/lib/iris/tests/graphics/README.md
+++ b/lib/iris/tests/graphics/README.md
@@ -24,7 +24,7 @@ perceived as it may be a simple pixel shift.
 
 ## Testing Strategy
 
-The `iris.tests.IrisTest_nometa.check_graphic` test routine calls out to
+The `iris.tests.IrisTest.check_graphic` test routine calls out to
 `iris.tests.graphics.check_graphic` which tests against the **acceptable**
 result. It does this using an image **hash** comparison technique which allows
 us to be robust against minor variations based on underlying library updates.
diff --git a/lib/iris/tests/graphics/__init__.py b/lib/iris/tests/graphics/__init__.py
index a083de3934..544d989564 100755
--- a/lib/iris/tests/graphics/__init__.py
+++ b/lib/iris/tests/graphics/__init__.py
@@ -187,7 +187,6 @@ def check_graphic(test_id: str, results_dir: Union[str, Path]) -> None:
     try:
 
         def _create_missing(phash: str) -> None:
-
             output_path = test_output_dir / (test_id + ".png")
 
             print(f"Creating image file: {output_path}")
@@ -214,7 +213,6 @@ def _create_missing(phash: str) -> None:
         phash = get_phash(buffer)
 
         if test_id in repo:
-
             expected = hex_to_hash(repo[test_id])
 
             # Calculate hamming distance vector for the result hash.
diff --git a/lib/iris/tests/experimental/test_animate.py b/lib/iris/tests/integration/plot/test_animate.py
similarity index 94%
rename from lib/iris/tests/experimental/test_animate.py
rename to lib/iris/tests/integration/plot/test_animate.py
index d8010767b8..ef19dbb108 100644
--- a/lib/iris/tests/experimental/test_animate.py
+++ b/lib/iris/tests/integration/plot/test_animate.py
@@ -4,7 +4,7 @@
 # See COPYING and COPYING.LESSER in the root of the repository for full
 # licensing details.
 """
-Test the animation of cubes within iris.
+Integration tests for :func:`iris.plot.animate`.
 
 """
 
@@ -19,7 +19,6 @@
 
 # Run tests in no graphics mode if matplotlib is not available.
 if tests.MPL_AVAILABLE:
-    import iris.experimental.animate as animate
     import iris.plot as iplt
 
 
@@ -57,7 +56,7 @@ def test_cube_animation(self):
         # the animation.
         cube_iter = self.cube.slices(("latitude", "longitude"))
 
-        ani = animate.animate(cube_iter, iplt.contourf)
+        ani = iplt.animate(cube_iter, iplt.contourf)
 
         # Disconnect the first draw callback to stop the animation.
         ani._fig.canvas.mpl_disconnect(ani._first_draw_id)
diff --git a/lib/iris/tests/integration/plot/test_plot_2d_coords.py b/lib/iris/tests/integration/plot/test_plot_2d_coords.py
index b8fbc5e31a..1b95899803 100644
--- a/lib/iris/tests/integration/plot/test_plot_2d_coords.py
+++ b/lib/iris/tests/integration/plot/test_plot_2d_coords.py
@@ -38,10 +38,17 @@ def simple_cube_w_2d_coords():
 class Test(tests.GraphicsTest):
     def test_2d_coord_bounds_platecarree(self):
         # To avoid a problem with Cartopy smearing the data where the
-        # longitude wraps, we set the central_longitude
+        #  longitude wraps, we set the central_longitude.
+        #  SciTools/cartopy#1421
         cube = simple_cube_w_2d_coords()[0, 0]
         ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180))
         qplt.pcolormesh(cube)
+
+        # Cartopy can't reliably set y-limits with curvilinear plotting.
+        #  SciTools/cartopy#2121
+        y_lims = [m(cube.coord("latitude").points) for m in (np.min, np.max)]
+        ax.set_ylim(*y_lims)
+
         ax.coastlines(resolution="110m", color="red")
         self.check_graphic()
 
diff --git a/lib/iris/tests/integration/test_Datums.py b/lib/iris/tests/integration/test_Datums.py
index 77b7f28249..6953534f2d 100755
--- a/lib/iris/tests/integration/test_Datums.py
+++ b/lib/iris/tests/integration/test_Datums.py
@@ -23,7 +23,6 @@ def setUp(self):
         self.start_crs = ccrs.OSGB(False)
 
     def test_transform_points_datum(self):
-
         # Iris version
         wgs84 = GeogCS.from_datum("WGS84")
         iris_cs = LambertConformal(
diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py
index 3feb637bf8..851c539ade 100644
--- a/lib/iris/tests/integration/test_netcdf.py
+++ b/lib/iris/tests/integration/test_netcdf.py
@@ -21,11 +21,13 @@
 import netCDF4 as nc
 import numpy as np
 import numpy.ma as ma
+import pytest
 
 import iris
 import iris.coord_systems
 from iris.coords import CellMethod, DimCoord
 from iris.cube import Cube, CubeList
+import iris.exceptions
 from iris.fileformats.netcdf import (
     CF_CONVENTIONS_VERSION,
     Saver,
@@ -903,5 +905,54 @@ def test_netcdf_with_no_constraint(self):
         self.assertEqual(len(cubes), 3)
 
 
+class TestSkippedCoord:
+    # If a coord/cell measure/etcetera cannot be added to the loaded Cube, a
+    #  Warning is raised and the coord is skipped.
+    # This 'catching' is generic to all CannotAddErrors, but currently the only
+    #  such problem that can exist in a NetCDF file is a mismatch of dimensions
+    #  between phenomenon and coord.
+
+    cdl_core = """
+dimensions:
+    length_scale = 1 ;
+    lat = 3 ;
+variables:
+    float lat(lat) ;
+        lat:standard_name = "latitude" ;
+        lat:units = "degrees_north" ;
+    short lst_unc_sys(length_scale) ;
+        lst_unc_sys:long_name = "uncertainty from large-scale systematic
+        errors" ;
+        lst_unc_sys:units = "kelvin" ;
+        lst_unc_sys:coordinates = "lat" ;
+
+data:
+    lat = 0, 1, 2;
+    """
+
+    @pytest.fixture(autouse=True)
+    def create_nc_file(self, tmp_path):
+        file_name = "dim_mismatch"
+        cdl = f"netcdf {file_name}" + "{\n" + self.cdl_core + "\n}"
+        self.nc_path = (tmp_path / file_name).with_suffix(".nc")
+        ncgen_from_cdl(
+            cdl_str=cdl,
+            cdl_path=None,
+            nc_path=str(self.nc_path),
+        )
+        yield
+        self.nc_path.unlink()
+
+    def test_lat_not_loaded(self):
+        # iris#5068 includes discussion of possible retention of the skipped
+        #  coords in the future.
+        with pytest.warns(
+            match="Missing data dimensions for multi-valued DimCoord"
+        ):
+            cube = iris.load_cube(self.nc_path)
+        with pytest.raises(iris.exceptions.CoordinateNotFoundError):
+            _ = cube.coord("lat")
+
+
 if __name__ == "__main__":
     tests.main()
diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml
index b41c0e48c7..cc9deb4260 100644
--- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml
+++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml
@@ -5,6 +5,60 @@
       <attribute name="source" value="Iris test case"/>
     </attributes>
     <coords>
+      <coord datadims="[0, 1]">
+        <auxCoord bounds="[[[367.88, 380.648],
+		 [372.945, 385.705],
+		 [369.978, 382.743],
+		 ..., 
+		 [319.021, 331.865],
+		 [299.908, 312.781],
+		 [273.08, 285.993]],
+
+		[[380.648, 399.802],
+		 [385.705, 404.847],
+		 [382.743, 401.891],
+		 ..., 
+		 [331.865, 351.13],
+		 [312.781, 332.091],
+		 [285.993, 305.365]],
+
+		[[399.802, 425.341],
+		 [404.847, 430.37],
+		 [401.891, 427.423],
+		 ..., 
+		 [351.13, 376.819],
+		 [332.091, 357.838],
+		 [305.365, 331.194]],
+
+		[[425.341, 457.267],
+		 [430.37, 462.277],
+		 [427.423, 459.342],
+		 ..., 
+		 [376.819, 408.932],
+		 [357.838, 390.024],
+		 [331.194, 363.483]],
+
+		[[457.267, 495.582],
+		 [462.277, 500.569],
+		 [459.342, 497.647],
+		 ..., 
+		 [408.932, 447.471],
+		 [390.024, 428.651],
+		 [363.483, 402.233]]]" id="9041e969" points="[[372.668, 377.73, 374.764, ..., 323.838,
+		 304.736, 277.922],
+		[388.629, 393.681, 390.721, ..., 339.892,
+		 320.827, 294.065],
+		[410.975, 416.013, 413.061, ..., 362.369,
+		 343.355, 316.665],
+		[439.707, 444.728, 441.786, ..., 391.27,
+		 372.322, 345.724],
+		[474.828, 479.827, 476.898, ..., 426.595,
+		 407.728, 381.243]]" shape="(5, 18)" standard_name="altitude" units="Unit('m')" value_type="float32">
+          <attributes>
+            <attribute name="positive" value="up"/>
+          </attributes>
+        </auxCoord>
+      </coord>
       <coord>
         <dimCoord id="1d45e087" points="[0.0]" shape="(1,)" standard_name="forecast_period" units="Unit('hours')" value_type="float64"/>
       </coord>
@@ -65,6 +119,12 @@
 		[0.993097, 0.989272],
 		[0.989272, 0.984692]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482, 0.991375, 0.987171]" shape="(5,)" units="Unit('1')" value_type="float32"/>
       </coord>
+      <coord datadims="[1]">
+        <auxCoord id="b15e3a2e" points="[367.88, 372.945, 369.978, 364.397, 351.799,
+		331.411, 311.032, 291.492, 267.047, 251.732,
+		253.644, 276.966, 304.615, 329.212, 332.834,
+		319.021, 299.908, 273.08]" shape="(18,)" standard_name="surface_altitude" units="Unit('m')" value_type="float32"/>
+      </coord>
       <coord>
         <dimCoord id="cb784457" points="[347921.166667]" shape="(1,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/>
       </coord>
diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml
index 8617be9372..fb3d2cdbcf 100644
--- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml
+++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml
@@ -5,6 +5,65 @@
       <attribute name="source" value="Iris test case"/>
     </attributes>
     <coords>
+      <coord datadims="[0, 1]">
+        <auxCoord bounds="[[[385.034, 397.775],
+		 [375.867, 388.623],
+		 [333.195, 346.016],
+		 ..., 
+		 [399.275, 411.995],
+		 [411.788, 424.489],
+		 [384.595, 397.337]],
+
+		[[397.775, 416.889],
+		 [388.623, 407.758],
+		 [346.016, 365.249],
+		 ..., 
+		 [411.995, 431.076],
+		 [424.489, 443.541],
+		 [397.337, 416.452]],
+
+		[[416.889, 442.376],
+		 [407.758, 433.273],
+		 [365.249, 390.895],
+		 ..., 
+		 [431.076, 456.519],
+		 [443.541, 468.945],
+		 [416.452, 441.94]],
+
+		[[442.376, 474.236],
+		 [433.273, 465.168],
+		 [390.895, 422.953],
+		 ..., 
+		 [456.519, 488.325],
+		 [468.945, 500.704],
+		 [441.94, 473.802]],
+
+		[[474.236, 512.473],
+		 [465.168, 503.447],
+		 [422.953, 461.427],
+		 ..., 
+		 [488.325, 526.496],
+		 [500.704, 538.818],
+		 [473.802, 512.041]]]" id="9041e969" points="[[389.812, 380.651, 338.003, 295.528, 278.922,
+		 280.889, 327.146, 342.957, 368.656, 404.045,
+		 416.551, 389.373],
+		[405.739, 396.596, 354.03, 311.637, 295.063,
+		 297.026, 343.194, 358.975, 384.624, 419.945,
+		 432.427, 405.302],
+		[428.039, 418.92, 376.469, 334.19, 317.661,
+		 319.618, 365.662, 381.4, 406.981, 442.207,
+		 454.655, 427.603],
+		[456.712, 447.625, 405.321, 363.188, 346.716,
+		 348.667, 394.551, 410.235, 435.727, 470.831,
+		 483.236, 456.277],
+		[491.761, 482.712, 440.587, 398.633, 382.231,
+		 384.173, 429.863, 445.481, 470.864, 505.819,
+		 518.172, 491.328]]" shape="(5, 12)" standard_name="altitude" units="Unit('m')" value_type="float32">
+          <attributes>
+            <attribute name="positive" value="up"/>
+          </attributes>
+        </auxCoord>
+      </coord>
       <coord>
         <dimCoord id="1d45e087" points="[0.0]" shape="(1,)" standard_name="forecast_period" units="Unit('hours')" value_type="float64"/>
       </coord>
@@ -59,6 +118,11 @@
 		[0.993097, 0.989272],
 		[0.989272, 0.984692]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482, 0.991375, 0.987171]" shape="(5,)" units="Unit('1')" value_type="float32"/>
       </coord>
+      <coord datadims="[1]">
+        <auxCoord id="b15e3a2e" points="[385.034, 375.867, 333.195, 290.696, 274.08,
+		276.048, 322.332, 338.152, 363.866, 399.275,
+		411.788, 384.595]" shape="(12,)" standard_name="surface_altitude" units="Unit('m')" value_type="float32"/>
+      </coord>
       <coord>
         <dimCoord id="cb784457" points="[347921.166667]" shape="(1,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/>
       </coord>
diff --git a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml
index b863adcf55..7422bfe044 100644
--- a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -30,8 +30,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml
index b46908a648..f9e0511ccb 100644
--- a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -30,8 +30,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[300.0, 300.0],
diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml
index 57209e4ba7..2fb8b6e1f0 100644
--- a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml
@@ -31,8 +31,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_full_levels_face_y"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -41,8 +41,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_full_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml
index c260587921..9a819eee9e 100644
--- a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml
@@ -31,8 +31,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -41,8 +41,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml
index e545e05fdc..9133d98e73 100644
--- a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -30,8 +30,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml
index 4eedfc21b3..05aeab9ccb 100644
--- a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -30,8 +30,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml
index 55155047bb..9dc3e08ee6 100644
--- a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -30,8 +30,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml
index fc52fce0b3..7bb47c5296 100644
--- a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml
+++ b/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-42.7342, -40.8934, -46.161, -48.912],
 		[-40.8934, -38.4268, -42.6612, -46.161],
-		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
-		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791,
+		-42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[-45.0, -37.5, -37.5, -45.0],
@@ -30,8 +30,8 @@
 		..., 
 		[-112.5, -120.0, -126.958, -118.361],
 		[-120.0, -127.5, -135.0, -126.958],
-		[-127.5, -135.0, -142.5, -135.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
-		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377,
+		-127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[21600.0, 21600.0]]" id="17a27d05" long_name="Time axis" points="[21600.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant">
diff --git a/lib/iris/tests/results/experimental/ugrid/surface_mean.cml b/lib/iris/tests/results/experimental/ugrid/surface_mean.cml
index 368b3508e3..8ccd602c11 100644
--- a/lib/iris/tests/results/experimental/ugrid/surface_mean.cml
+++ b/lib/iris/tests/results/experimental/ugrid/surface_mean.cml
@@ -20,8 +20,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -30,8 +30,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -71,8 +71,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -81,8 +81,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -122,8 +122,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -132,8 +132,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -173,8 +173,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -183,8 +183,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -224,8 +224,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -234,8 +234,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -275,8 +275,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -285,8 +285,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -326,8 +326,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -336,8 +336,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -377,8 +377,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -387,8 +387,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -428,8 +428,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -438,8 +438,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -479,8 +479,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -489,8 +489,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -530,8 +530,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -540,8 +540,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -581,8 +581,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -591,8 +591,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -632,8 +632,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -642,8 +642,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -683,8 +683,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -693,8 +693,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -734,8 +734,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -744,8 +744,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -785,8 +785,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -795,8 +795,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -836,8 +836,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -846,8 +846,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
@@ -887,8 +887,8 @@
 		..., 
 		[-37.7044, -36.9373, -37.9318, -38.7655],
 		[-36.9373, -36.1244, -37.0517, -37.9318],
-		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
-		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/>
+		[-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421,
+		-37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/>
       </coord>
       <coord datadims="[1]">
         <meshCoord bounds="[[315.0, 316.875, 316.875, 315.0],
@@ -897,8 +897,8 @@
 		..., 
 		[230.625, 228.75, 226.883, 228.774],
 		[228.75, 226.875, 225.0, 226.883],
-		[226.875, 225.0, 223.125, 225.0]]" id="fac9555b" long_name="Longitude of mesh nodes." points="[315.933, 317.808, 319.683, ..., 228.759,
-		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/>
+		[226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759,
+		226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/>
       </coord>
       <coord datadims="[0]">
         <auxCoord bounds="[[15508800.0, 15595200.0]]" id="17a27d05" long_name="Time axis" points="[15552000.0]" shape="(1,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='360_day')" value_type="float64" var_name="time_centered">
diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json
index e5c2ad863a..92f0d8fc20 100644
--- a/lib/iris/tests/results/imagerepo.json
+++ b/lib/iris/tests/results/imagerepo.json
@@ -35,9 +35,9 @@
     "gallery_tests.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01",
     "gallery_tests.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01",
     "gallery_tests.test_plot_zonal_means.0": "b45b3071c9a4c9a6c69c363cc327cbb3cb9634d8c9e63cf336738c6634d8c384",
-    "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e",
-    "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e",
-    "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f",
+    "iris.tests.integration.plot.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e",
+    "iris.tests.integration.plot.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e",
+    "iris.tests.integration.plot.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f",
     "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_northpolarstereo.0": "e59661969e699659c0f719a6c967339a1992c07f3649c09c3f612669c07b3f66",
     "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_platecarree.0": "ee856299954a1da699b6915ec25b6e419729c42c3f84bd9fa6d262d1d1dac076",
     "iris.tests.integration.plot.test_plot_2d_coords.Test2dContour.test_2d_coords_contour.0": "b4b2643ecb05cb43b0f23d80c53c4e1d3e5990eb1f81c19f2f983cb1c4ff3e42",
diff --git a/lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml
new file mode 100644
index 0000000000..9bc2c0d1ac
--- /dev/null
+++ b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml
@@ -0,0 +1,47 @@
+<?xml version="1.0" ?>
+<cubes xmlns="urn:x-iris:cubeml-0.2">
+  <cube dtype="float32" long_name="VOLCANIC_ASH_AIR_CONCENTRATION" units="g/m2">
+    <attributes>
+      <attribute name="End of release" value="0000UTC 13/01/2011"/>
+      <attribute name="Forecast duration" value="6 hours"/>
+      <attribute name="Met data" value="NWP Flow.ECMWF ERAInt Regional"/>
+      <attribute name="NAME Version" value="NAME III (version 7.2)"/>
+      <attribute name="Quantity" value="Air Concentration"/>
+      <attribute name="Release height" value="Multiple Sources"/>
+      <attribute name="Release location" value="Multiple Sources"/>
+      <attribute name="Release rate" value="Multiple Sources"/>
+      <attribute name="Run time" value="1333UTC 07/12/2018"/>
+      <attribute name="Species" value="VOLCANIC_ASH"/>
+      <attribute name="Species Category" value="VOLCANIC"/>
+      <attribute name="Start of release" value="2200UTC 12/01/2011"/>
+      <attribute name="Time Av or Int" value="No time averaging"/>
+      <attribute name="Title" value="VA_Tutorial"/>
+    </attributes>
+    <coords>
+      <coord datadims="[0]">
+        <dimCoord bounds="[[29.9375, 30.0625],
+		[30.0625, 30.1875]]" id="77a50eb5" points="[30.0, 30.125]" shape="(2,)" standard_name="latitude" units="Unit('degrees')" value_type="float64">
+          <geogCS earth_radius="6371229.0"/>
+        </dimCoord>
+      </coord>
+      <coord datadims="[1]">
+        <dimCoord bounds="[[9.9375, 10.0625],
+		[10.0625, 10.1875]]" id="f913a8b3" points="[10.0, 10.125]" shape="(2,)" standard_name="longitude" units="Unit('degrees')" value_type="float64">
+          <geogCS earth_radius="6371229.0"/>
+        </dimCoord>
+      </coord>
+      <coord>
+        <dimCoord bounds="[[359686.0, 359686.0]]" id="cb784457" points="[359686.0]" shape="(1,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/>
+      </coord>
+      <coord>
+        <auxCoord id="e92ff7b7" long_name="z" points="[Vertical integral]" shape="(1,)" units="Unit('no_unit')" value_type="string">
+          <attributes>
+            <attribute name="positive" value="up"/>
+          </attributes>
+        </auxCoord>
+      </coord>
+    </coords>
+    <cellMethods/>
+    <data dtype="float32" shape="(2, 2)" state="loaded"/>
+  </cube>
+</cubes>
diff --git a/lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml
new file mode 100644
index 0000000000..8d1ad620d0
--- /dev/null
+++ b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml
@@ -0,0 +1,47 @@
+<?xml version="1.0" ?>
+<cubes xmlns="urn:x-iris:cubeml-0.2">
+  <cube dtype="float32" long_name="VOLCANIC_ASH_AIR_CONCENTRATION" units="g/m2">
+    <attributes>
+      <attribute name="End of release" value="0000UTC 13/01/2011"/>
+      <attribute name="Forecast duration" value="6 hours"/>
+      <attribute name="Met data" value="NWP Flow.ECMWF ERAInt Regional"/>
+      <attribute name="NAME Version" value="NAME III (version 7.2)"/>
+      <attribute name="Quantity" value="Air Concentration"/>
+      <attribute name="Release height" value="Multiple Sources"/>
+      <attribute name="Release location" value="Multiple Sources"/>
+      <attribute name="Release rate" value="Multiple Sources"/>
+      <attribute name="Run time" value="1333UTC 07/12/2018"/>
+      <attribute name="Species" value="VOLCANIC_ASH"/>
+      <attribute name="Species Category" value="VOLCANIC"/>
+      <attribute name="Start of release" value="2200UTC 12/01/2011"/>
+      <attribute name="Time Av or Int" value="No time averaging"/>
+      <attribute name="Title" value="VA_Tutorial"/>
+    </attributes>
+    <coords>
+      <coord datadims="[0]">
+        <dimCoord bounds="[[29.9375, 30.0625],
+		[30.0625, 30.1875]]" id="77a50eb5" points="[30.0, 30.125]" shape="(2,)" standard_name="latitude" units="Unit('degrees')" value_type="float64">
+          <geogCS earth_radius="6371229.0"/>
+        </dimCoord>
+      </coord>
+      <coord datadims="[1]">
+        <dimCoord bounds="[[9.9375, 10.0625],
+		[10.0625, 10.1875]]" id="f913a8b3" points="[10.0, 10.125]" shape="(2,)" standard_name="longitude" units="Unit('degrees')" value_type="float64">
+          <geogCS earth_radius="6371229.0"/>
+        </dimCoord>
+      </coord>
+      <coord>
+        <dimCoord bounds="[[359686.0, 359686.0]]" id="cb784457" points="[359686.0]" shape="(1,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/>
+      </coord>
+      <coord>
+        <auxCoord id="e92ff7b7" long_name="z" points="[Vertical integral]" shape="(1,)" units="Unit('no_unit')" value_type="string">
+          <attributes>
+            <attribute name="positive" value="up"/>
+          </attributes>
+        </auxCoord>
+      </coord>
+    </coords>
+    <cellMethods/>
+    <data checksum="0xecbb4b55" dtype="float32" shape="(2, 2)"/>
+  </cube>
+</cubes>
diff --git a/lib/iris/tests/results/trajectory/hybrid_height.cml b/lib/iris/tests/results/trajectory/hybrid_height.cml
index 972fa7b330..28e821b900 100644
--- a/lib/iris/tests/results/trajectory/hybrid_height.cml
+++ b/lib/iris/tests/results/trajectory/hybrid_height.cml
@@ -60,7 +60,7 @@
     <cellMethods/>
     <data checksum="0x2acdccca" dtype="int64" shape="(3, 4, 5, 6)"/>
   </cube>
-  <cube dtype="float64" standard_name="air_temperature" units="K">
+  <cube dtype="int64" standard_name="air_temperature" units="K">
     <coords>
       <coord datadims="[1, 2]">
         <auxCoord id="9041e969" points="[[5090, 5740, 6090, 6440],
@@ -95,6 +95,6 @@
       </coord>
     </coords>
     <cellMethods/>
-    <data checksum="0x11cdc1c8" dtype="float64" shape="(3, 4, 4)"/>
+    <data checksum="0x07fcebe7" dtype="int64" shape="(3, 4, 4)"/>
   </cube>
 </cubes>
diff --git a/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml b/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml
index 750d597493..7b5bbfc086 100644
--- a/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml
+++ b/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml
@@ -1,6 +1,6 @@
 <?xml version="1.0" ?>
 <cubes xmlns="urn:x-iris:cubeml-0.2">
-  <cube dtype="float64" long_name="Temperature" standard_name="sea_water_potential_temperature" units="degC" var_name="votemper">
+  <cube dtype="float32" long_name="Temperature" standard_name="sea_water_potential_temperature" units="degC" var_name="votemper">
     <attributes>
       <attribute name="Conventions" value="CF-1.1"/>
       <attribute name="DOMAIN_DIM_N001" value="x"/>
@@ -144,6 +144,6 @@
         <coord name="time"/>
       </cellMethod>
     </cellMethods>
-    <data checksum="0x3a8195d5" dtype="float64" shape="(1, 31, 85)"/>
+    <data checksum="0x49d623f6" dtype="float32" mask_checksum="0x117b1ebc" shape="(1, 31, 85)"/>
   </cube>
 </cubes>
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml
index 9fc80a0e4d..e318abad67 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml
@@ -50,12 +50,7 @@
         </dimCoord>
       </coord>
       <coord>
-        <auxCoord bounds="[[3100, 3199]]" id="81f04efd" long_name="long-name" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </auxCoord>
+        <auxCoord bounds="[[3100, 3199]]" id="e0db29d6" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[1100, 1101, 1102, 1103],
@@ -64,12 +59,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml
index 9fc80a0e4d..e318abad67 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml
@@ -50,12 +50,7 @@
         </dimCoord>
       </coord>
       <coord>
-        <auxCoord bounds="[[3100, 3199]]" id="81f04efd" long_name="long-name" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </auxCoord>
+        <auxCoord bounds="[[3100, 3199]]" id="e0db29d6" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[1100, 1101, 1102, 1103],
@@ -64,12 +59,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml
index 9fc80a0e4d..e318abad67 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml
@@ -50,12 +50,7 @@
         </dimCoord>
       </coord>
       <coord>
-        <auxCoord bounds="[[3100, 3199]]" id="81f04efd" long_name="long-name" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </auxCoord>
+        <auxCoord bounds="[[3100, 3199]]" id="e0db29d6" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[1100, 1101, 1102, 1103],
@@ -64,12 +59,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml
index 9fc80a0e4d..e318abad67 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml
@@ -50,12 +50,7 @@
         </dimCoord>
       </coord>
       <coord>
-        <auxCoord bounds="[[3100, 3199]]" id="81f04efd" long_name="long-name" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </auxCoord>
+        <auxCoord bounds="[[3100, 3199]]" id="e0db29d6" points="[3149]" shape="(1,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[2]">
         <meshCoord bounds="[[1100, 1101, 1102, 1103],
@@ -64,12 +59,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml
index 82b7e25aa3..82c79a7577 100644
--- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml
+++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml
@@ -53,12 +53,7 @@
 		..., 
 		[1188, 1189, 1190, 1191],
 		[1192, 1193, 1194, 1195],
-		[1196, 1197, 1198, 1199]]" id="81f04efd" long_name="long-name" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('degrees_east')" value_type="int64">
-          <attributes>
-            <attribute name="a" value="1"/>
-            <attribute name="b" value="c"/>
-          </attributes>
-        </meshCoord>
+		[1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/>
       </coord>
       <coord datadims="[1]">
         <dimCoord id="38fd0305" points="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py
index 8a448f7d34..e32f065625 100644
--- a/lib/iris/tests/stock/netcdf.py
+++ b/lib/iris/tests/stock/netcdf.py
@@ -51,7 +51,7 @@ def ncgen_from_cdl(
             f_out.write(cdl_str)
     if cdl_path:
         # Create netcdf from stored CDL file.
-        call_args = [NCGEN_PATHSTR, cdl_path, "-k3", "-o", nc_path]
+        call_args = [NCGEN_PATHSTR, "-k3", "-o", nc_path, cdl_path]
         call_kwargs = {}
     else:
         # No CDL file : pipe 'cdl_str' directly into the ncgen program.
diff --git a/lib/iris/tests/test_cdm.py b/lib/iris/tests/test_cdm.py
index 0615dc39bf..8f2a9b474d 100644
--- a/lib/iris/tests/test_cdm.py
+++ b/lib/iris/tests/test_cdm.py
@@ -349,7 +349,6 @@ def test_similar_coord(self):
         )
 
     def test_cube_summary_cell_methods(self):
-
         cube = self.cube_2d.copy()
 
         # Create a list of values used to create cell methods
diff --git a/lib/iris/tests/test_cell.py b/lib/iris/tests/test_cell.py
index 03d3fa7d7c..21d2603072 100644
--- a/lib/iris/tests/test_cell.py
+++ b/lib/iris/tests/test_cell.py
@@ -169,9 +169,35 @@ def test_coord_bounds_cmp(self):
         self.assertTrue(self.e < 2)
 
     def test_cell_cell_cmp(self):
+        self.e = iris.coords.Cell(1)
+        self.f = iris.coords.Cell(1)
+
+        self.assertTrue(self.e == self.f)
+        self.assertEqual(hash(self.e), hash(self.f))
+
+        self.e = iris.coords.Cell(1)
+        self.f = iris.coords.Cell(1, [0, 2])
+
+        self.assertFalse(self.e == self.f)
+        self.assertNotEqual(hash(self.e), hash(self.f))
+
+        self.e = iris.coords.Cell(1, [0, 2])
+        self.f = iris.coords.Cell(1, [0, 2])
+
+        self.assertTrue(self.e == self.f)
+        self.assertEqual(hash(self.e), hash(self.f))
+
+        self.e = iris.coords.Cell(1, [0, 2])
+        self.f = iris.coords.Cell(1, [2, 0])
+
+        self.assertTrue(self.e == self.f)
+        self.assertEqual(hash(self.e), hash(self.f))
+
         self.e = iris.coords.Cell(0.7, [1.1, 1.9])
         self.f = iris.coords.Cell(0.8, [1.1, 1.9])
 
+        self.assertFalse(self.e == self.f)
+        self.assertNotEqual(hash(self.e), hash(self.f))
         self.assertFalse(self.e > self.f)
         self.assertTrue(self.e <= self.f)
         self.assertTrue(self.f >= self.e)
diff --git a/lib/iris/tests/test_cf.py b/lib/iris/tests/test_cf.py
index 034fb1dbda..bf3cddb8b7 100644
--- a/lib/iris/tests/test_cf.py
+++ b/lib/iris/tests/test_cf.py
@@ -276,9 +276,7 @@ def test_destructor(self):
         didn't exist because opening the dataset had failed.
         """
         with self.temp_filename(suffix=".nc") as fn:
-
             with open(fn, "wb+") as fh:
-
                 fh.write(
                     b"\x89HDF\r\n\x1a\nBroken file with correct signature"
                 )
diff --git a/lib/iris/tests/test_constraints.py b/lib/iris/tests/test_constraints.py
index 1972cdeb90..e568105f91 100644
--- a/lib/iris/tests/test_constraints.py
+++ b/lib/iris/tests/test_constraints.py
@@ -67,6 +67,27 @@ def test_constraints(self):
         sub_list = self.slices.extract(constraint)
         self.assertEqual(len(sub_list), 70 * 6)
 
+    def test_coord_availability(self):
+        # "model_level_number" coordinate available
+        constraint = iris.Constraint(model_level_number=lambda x: True)
+        result = self.slices.extract(constraint)
+        self.assertTrue(result)
+
+        # "wibble" coordinate is not available
+        constraint = iris.Constraint(wibble=lambda x: False)
+        result = self.slices.extract(constraint)
+        self.assertFalse(result)
+
+        # "wibble" coordinate is not available
+        constraint = iris.Constraint(wibble=lambda x: True)
+        result = self.slices.extract(constraint)
+        self.assertFalse(result)
+
+        # "lambda x: False" always (confusingly) throws away the cube
+        constraint = iris.Constraint(model_level_number=lambda x: False)
+        result = self.slices.extract(constraint)
+        self.assertFalse(result)
+
     def test_mismatched_type(self):
         constraint = iris.Constraint(model_level_number="aardvark")
         sub_list = self.slices.extract(constraint)
diff --git a/lib/iris/tests/test_io_init.py b/lib/iris/tests/test_io_init.py
index d33b76ddeb..82da82cfa9 100644
--- a/lib/iris/tests/test_io_init.py
+++ b/lib/iris/tests/test_io_init.py
@@ -126,7 +126,7 @@ def test_format_picker(self):
         ]
 
         # test that each filespec is identified as the expected format
-        for (expected_format_name, file_spec) in test_specs:
+        for expected_format_name, file_spec in test_specs:
             test_path = tests.get_data_path(file_spec)
             with open(test_path, "rb") as test_file:
                 a = iff.FORMAT_AGENT.get_spec(test_path, test_file)
diff --git a/lib/iris/tests/test_mapping.py b/lib/iris/tests/test_mapping.py
index a71385b5bc..202c319b61 100644
--- a/lib/iris/tests/test_mapping.py
+++ b/lib/iris/tests/test_mapping.py
@@ -242,12 +242,6 @@ def test_pcolormesh(self):
         iplt.pcolormesh(self.cube)
         self.check_graphic()
 
-    def test_grid(self):
-        iplt.pcolormesh(self.cube, facecolors="none", edgecolors="blue")
-        # the result is a graphic which has coloured edges. This is a mpl bug,
-        # see https://github.com/matplotlib/matplotlib/issues/1302
-        self.check_graphic()
-
     def test_outline(self):
         iplt.outline(self.cube)
         self.check_graphic()
diff --git a/lib/iris/tests/test_merge.py b/lib/iris/tests/test_merge.py
index c209d68da0..e53bbfb5f3 100644
--- a/lib/iris/tests/test_merge.py
+++ b/lib/iris/tests/test_merge.py
@@ -190,7 +190,7 @@ def setUp(self):
         )
 
     def test__ndarray_ndarray(self):
-        for (lazy0, lazy1) in self.lazy_combos:
+        for lazy0, lazy1 in self.lazy_combos:
             cubes = iris.cube.CubeList()
             cubes.append(self._make_cube(0, dtype=self.dtype, lazy=lazy0))
             cubes.append(self._make_cube(1, dtype=self.dtype, lazy=lazy1))
diff --git a/lib/iris/tests/test_name.py b/lib/iris/tests/test_name.py
index 2843673da8..b4e91bafd7 100644
--- a/lib/iris/tests/test_name.py
+++ b/lib/iris/tests/test_name.py
@@ -8,6 +8,9 @@
 # import iris tests first so that some things can be initialised before
 # importing anything else
 import iris.tests as tests  # isort:skip
+
+import tempfile
+
 import iris
 
 
@@ -39,7 +42,7 @@ def test_NAMEIII_version2(self):
         )
         self.assertCMLApproxData(cubes, ("name", "NAMEIII_version2.cml"))
 
-    def test_NAMEII_trajectory(self):
+    def test_NAMEIII_trajectory(self):
         cubes = iris.load(
             tests.get_data_path(("NAME", "NAMEIII_trajectory.txt"))
         )
@@ -48,6 +51,32 @@ def test_NAMEII_trajectory(self):
             cubes, ("name", "NAMEIII_trajectory.cml"), checksum=False
         )
 
+    def test_NAMEII__no_time_averaging(self):
+        cubes = iris.load(
+            tests.get_data_path(("NAME", "NAMEII_no_time_averaging.txt"))
+        )
+
+        # Also check that it saves without error.
+        # This was previously failing, see https://github.com/SciTools/iris/issues/3288
+        with tempfile.TemporaryDirectory() as temp_dirpath:
+            iris.save(cubes, temp_dirpath + "/tmp.nc")
+
+        self.assertCML(
+            cubes[0],
+            (
+                "name",
+                "NAMEII_field__no_time_averaging_0.cml",
+            ),
+        )
+        self.assertCML(
+            cubes,
+            (
+                "name",
+                "NAMEII_field__no_time_averaging.cml",
+            ),
+            checksum=False,
+        )
+
 
 if __name__ == "__main__":
     tests.main()
diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py
index 5017698a22..92e15a414a 100644
--- a/lib/iris/tests/test_netcdf.py
+++ b/lib/iris/tests/test_netcdf.py
@@ -313,9 +313,7 @@ def test_deferred_loading(self):
             cube[((0, 8, 4, 2, 14, 12),)][((0, 2, 4, 1),)],
             ("netcdf", "netcdf_deferred_tuple_1.cml"),
         )
-        subcube = cube[((0, 8, 4, 2, 14, 12),)][((0, 2, 4, 1),)][
-            (1, 3),
-        ]
+        subcube = cube[((0, 8, 4, 2, 14, 12),)][((0, 2, 4, 1),)][(1, 3),]
         self.assertCML(subcube, ("netcdf", "netcdf_deferred_tuple_2.cml"))
 
         # Consecutive mixture on same dimension.
@@ -1417,7 +1415,6 @@ def test_process_flags(self):
         }
 
         for bits, descriptions in multiple_map.items():
-
             ll_cube = stock.lat_lon_cube()
             ll_cube.attributes["ukmo__process_flags"] = descriptions
 
diff --git a/lib/iris/tests/test_nimrod.py b/lib/iris/tests/test_nimrod.py
index a1d7bb298f..6d62623198 100644
--- a/lib/iris/tests/test_nimrod.py
+++ b/lib/iris/tests/test_nimrod.py
@@ -80,7 +80,8 @@ def test_huge_field_load(self):
     @tests.skip_data
     def test_load_kwarg(self):
         """Tests that the handle_metadata_errors kwarg is effective by setting it to
-        False with a file with known incomplete meta-data (missing ellipsoid)."""
+        False with a file with known incomplete meta-data (missing ellipsoid).
+        """
         datafile = "u1096_ng_ek00_pressure_2km"
         with self.assertRaisesRegex(
             TranslationError,
diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py
index 77aea2b6b6..c9eba31e58 100644
--- a/lib/iris/tests/test_plot.py
+++ b/lib/iris/tests/test_plot.py
@@ -712,9 +712,8 @@ def override_with_decorated_methods(attr_dict, target_dict, decorator):
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
 class TestPcolorNoBounds(
-    tests.GraphicsTest_nometa, SliceMixin, metaclass=CheckForWarningsMetaclass
+    tests.GraphicsTest, SliceMixin, metaclass=CheckForWarningsMetaclass
 ):
     """
     Test the iris.plot.pcolor routine on a cube with coordinates
@@ -729,9 +728,8 @@ def setUp(self):
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
 class TestPcolormeshNoBounds(
-    tests.GraphicsTest_nometa, SliceMixin, metaclass=CheckForWarningsMetaclass
+    tests.GraphicsTest, SliceMixin, metaclass=CheckForWarningsMetaclass
 ):
     """
     Test the iris.plot.pcolormesh routine on a cube with coordinates
diff --git a/lib/iris/tests/test_pp_stash.py b/lib/iris/tests/test_pp_stash.py
index b153aef0d4..42390ab2b3 100644
--- a/lib/iris/tests/test_pp_stash.py
+++ b/lib/iris/tests/test_pp_stash.py
@@ -86,7 +86,6 @@ def test_irregular_stash_str(self):
         )
 
     def test_illegal_stash_str_range(self):
-
         self.assertEqual(iris.fileformats.pp.STASH(0, 2, 3), "m??s02i003")
         self.assertNotEqual(iris.fileformats.pp.STASH(0, 2, 3), "m01s02i003")
 
@@ -124,7 +123,7 @@ def test_illegal_stash_format(self):
             ("m01s02003", (1, 2, 3)),
         )
 
-        for (test_value, reference) in test_values:
+        for test_value, reference in test_values:
             msg = "Expected STASH code .* {!r}".format(test_value)
             with self.assertRaisesRegex(ValueError, msg):
                 test_value == iris.fileformats.pp.STASH(*reference)
@@ -137,7 +136,7 @@ def test_illegal_stash_type(self):
             (["m01s02i003"], "m01s02i003"),
         )
 
-        for (test_value, reference) in test_values:
+        for test_value, reference in test_values:
             msg = "Expected STASH code .* {!r}".format(test_value)
             with self.assertRaisesRegex(TypeError, msg):
                 iris.fileformats.pp.STASH.from_msi(test_value) == reference
diff --git a/lib/iris/tests/test_util.py b/lib/iris/tests/test_util.py
index db182ae3f3..d8d5d73e95 100644
--- a/lib/iris/tests/test_util.py
+++ b/lib/iris/tests/test_util.py
@@ -161,7 +161,6 @@ def test_default_values(self):
         )
 
     def test_trim_string_with_no_spaces(self):
-
         clip_length = 200
         no_space_string = "a" * 500
 
diff --git a/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py b/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py
new file mode 100644
index 0000000000..612e5d8ecf
--- /dev/null
+++ b/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py
@@ -0,0 +1,114 @@
+# Copyright Iris contributors
+#
+# This file is part of Iris and is released under the LGPL license.
+# See COPYING and COPYING.LESSER in the root of the repository for full
+# licensing details.
+"""Test function :func:`iris.analysis.cartography._get_lon_lat_coords"""
+
+import pytest
+
+from iris.analysis.cartography import _get_lon_lat_coords as g_lon_lat
+from iris.coords import AuxCoord
+from iris.tests.stock import lat_lon_cube
+
+
+@pytest.fixture
+def dim_only_cube():
+    return lat_lon_cube()
+
+
+def test_dim_only(dim_only_cube):
+    t_lat, t_lon = dim_only_cube.dim_coords
+
+    lon, lat = g_lon_lat(dim_only_cube)
+
+    assert lon == t_lon
+    assert lat == t_lat
+
+
+@pytest.fixture
+def dim_aux_cube(dim_only_cube):
+    lat_dim, lon_dim = dim_only_cube.dim_coords
+
+    lat_aux = AuxCoord.from_coord(lat_dim)
+    lat_aux.standard_name = "grid_latitude"
+    lon_aux = AuxCoord.from_coord(lon_dim)
+    lon_aux.standard_name = "grid_longitude"
+
+    dim_aux_cube = dim_only_cube
+    dim_aux_cube.add_aux_coord(lat_aux, 0)
+    dim_aux_cube.add_aux_coord(lon_aux, 1)
+
+    return dim_aux_cube
+
+
+def test_dim_aux(dim_aux_cube):
+    t_lat_dim, t_lon_dim = dim_aux_cube.dim_coords
+
+    lon, lat = g_lon_lat(dim_aux_cube)
+
+    assert lon == t_lon_dim
+    assert lat == t_lat_dim
+
+
+@pytest.fixture
+def aux_only_cube(dim_aux_cube):
+    lon_dim, lat_dim = dim_aux_cube.dim_coords
+
+    aux_only_cube = dim_aux_cube
+    aux_only_cube.remove_coord(lon_dim)
+    aux_only_cube.remove_coord(lat_dim)
+
+    return dim_aux_cube
+
+
+def test_aux_only(aux_only_cube):
+    aux_lat, aux_lon = aux_only_cube.aux_coords
+
+    lon, lat = g_lon_lat(aux_only_cube)
+
+    assert lon == aux_lon
+    assert lat == aux_lat
+
+
+@pytest.fixture
+def double_dim_cube(dim_only_cube):
+    double_dim_cube = dim_only_cube
+    double_dim_cube.coord("latitude").standard_name = "grid_longitude"
+
+    return double_dim_cube
+
+
+def test_double_dim(double_dim_cube):
+    t_error_message = "with multiple.*is currently disallowed"
+
+    with pytest.raises(ValueError, match=t_error_message):
+        g_lon_lat(double_dim_cube)
+
+
+@pytest.fixture
+def double_aux_cube(aux_only_cube):
+    double_aux_cube = aux_only_cube
+    double_aux_cube.coord("grid_latitude").standard_name = "longitude"
+
+    return double_aux_cube
+
+
+def test_double_aux(double_aux_cube):
+    t_error_message = "with multiple.*is currently disallowed"
+
+    with pytest.raises(ValueError, match=t_error_message):
+        g_lon_lat(double_aux_cube)
+
+
+@pytest.fixture
+def missing_lat_cube(dim_only_cube):
+    missing_lat_cube = dim_only_cube
+    missing_lat_cube.remove_coord("latitude")
+
+    return missing_lat_cube
+
+
+def test_missing_coord(missing_lat_cube):
+    with pytest.raises(IndexError):
+        g_lon_lat(missing_lat_cube)
diff --git a/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py b/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py
index 6b957baec6..810851362e 100644
--- a/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py
+++ b/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py
@@ -94,7 +94,6 @@ def _check_multiple_orientations_and_latitudes(
         atol_degrees=0.005,
         cellsize_degrees=1.0,
     ):
-
         cube = _2d_multicells_testcube(cellsize_degrees=cellsize_degrees)
 
         # Calculate gridcell angles at each point.
diff --git a/lib/iris/tests/unit/analysis/maths/__init__.py b/lib/iris/tests/unit/analysis/maths/__init__.py
index 311da8a0e6..558a6fccfe 100644
--- a/lib/iris/tests/unit/analysis/maths/__init__.py
+++ b/lib/iris/tests/unit/analysis/maths/__init__.py
@@ -247,7 +247,7 @@ def test_partial_mask_second_lazy_not_in_place(self):
 
     def test_in_place_introduces_mask(self):
         # If second cube is masked, result should also be masked.
-        data1 = np.arange(4, dtype=np.float)
+        data1 = np.arange(4, dtype=float)
         data2 = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 1, 0, 0])
         cube1 = Cube(data1)
         cube2 = Cube(data2)
diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py
index 51f71affb0..57e012e1c9 100644
--- a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py
+++ b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py
@@ -16,9 +16,8 @@
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
 class TestBroadcastingDerived(
-    tests.IrisTest_nometa,
+    tests.IrisTest,
     MathsAddOperationMixin,
     CubeArithmeticBroadcastingTestMixin,
 ):
diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py
index 1d81e7b480..e1255ef9d8 100644
--- a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py
+++ b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py
@@ -54,9 +54,8 @@ def _base_testcube(self):
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
 class TestBroadcastingWithMesh(
-    tests.IrisTest_nometa,
+    tests.IrisTest,
     MeshLocationsMixin,
     MathsAddOperationMixin,
     CubeArithmeticBroadcastingTestMixin,
@@ -71,9 +70,8 @@ class TestBroadcastingWithMesh(
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
 class TestBroadcastingWithMeshAndDerived(
-    tests.IrisTest_nometa,
+    tests.IrisTest,
     MeshLocationsMixin,
     MathsAddOperationMixin,
     CubeArithmeticBroadcastingTestMixin,
diff --git a/lib/iris/tests/unit/analysis/maths/test_add.py b/lib/iris/tests/unit/analysis/maths/test_add.py
index 77dd7877bf..1ca7f7c244 100644
--- a/lib/iris/tests/unit/analysis/maths/test_add.py
+++ b/lib/iris/tests/unit/analysis/maths/test_add.py
@@ -21,10 +21,7 @@
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
-class TestBroadcasting(
-    tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin
-):
+class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin):
     @property
     def data_op(self):
         return operator.add
@@ -34,8 +31,7 @@ def cube_func(self):
         return add
 
 
-@tests.iristest_timing_decorator
-class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin):
+class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin):
     @property
     def data_op(self):
         return operator.add
@@ -57,9 +53,8 @@ def test_reversed_points(self):
             add(cube1, cube2)
 
 
-@tests.iristest_timing_decorator
 class TestMaskedConstant(
-    tests.IrisTest_nometa, CubeArithmeticMaskedConstantTestMixin
+    tests.IrisTest, CubeArithmeticMaskedConstantTestMixin
 ):
     @property
     def data_op(self):
diff --git a/lib/iris/tests/unit/analysis/maths/test_divide.py b/lib/iris/tests/unit/analysis/maths/test_divide.py
index 1763f223b0..4bd202e037 100644
--- a/lib/iris/tests/unit/analysis/maths/test_divide.py
+++ b/lib/iris/tests/unit/analysis/maths/test_divide.py
@@ -23,10 +23,7 @@
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
-class TestBroadcasting(
-    tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin
-):
+class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin):
     @property
     def data_op(self):
         return operator.truediv
@@ -36,8 +33,7 @@ def cube_func(self):
         return divide
 
 
-@tests.iristest_timing_decorator
-class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin):
+class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin):
     @property
     def data_op(self):
         return operator.truediv
diff --git a/lib/iris/tests/unit/analysis/maths/test_multiply.py b/lib/iris/tests/unit/analysis/maths/test_multiply.py
index 600593c64b..266342605a 100644
--- a/lib/iris/tests/unit/analysis/maths/test_multiply.py
+++ b/lib/iris/tests/unit/analysis/maths/test_multiply.py
@@ -21,10 +21,7 @@
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
-class TestBroadcasting(
-    tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin
-):
+class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin):
     @property
     def data_op(self):
         return operator.mul
@@ -34,8 +31,7 @@ def cube_func(self):
         return multiply
 
 
-@tests.iristest_timing_decorator
-class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin):
+class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin):
     @property
     def data_op(self):
         return operator.mul
@@ -57,9 +53,8 @@ def test_reversed_points(self):
             multiply(cube1, cube2)
 
 
-@tests.iristest_timing_decorator
 class TestMaskedConstant(
-    tests.IrisTest_nometa, CubeArithmeticMaskedConstantTestMixin
+    tests.IrisTest, CubeArithmeticMaskedConstantTestMixin
 ):
     @property
     def data_op(self):
diff --git a/lib/iris/tests/unit/analysis/maths/test_subtract.py b/lib/iris/tests/unit/analysis/maths/test_subtract.py
index 964e8c04c7..f7a9df34d0 100644
--- a/lib/iris/tests/unit/analysis/maths/test_subtract.py
+++ b/lib/iris/tests/unit/analysis/maths/test_subtract.py
@@ -21,10 +21,7 @@
 
 
 @tests.skip_data
-@tests.iristest_timing_decorator
-class TestBroadcasting(
-    tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin
-):
+class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin):
     @property
     def data_op(self):
         return operator.sub
@@ -34,8 +31,7 @@ def cube_func(self):
         return subtract
 
 
-@tests.iristest_timing_decorator
-class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin):
+class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin):
     @property
     def data_op(self):
         return operator.sub
@@ -57,9 +53,8 @@ def test_reversed_points(self):
             subtract(cube1, cube2)
 
 
-@tests.iristest_timing_decorator
 class TestMaskedConstant(
-    tests.IrisTest_nometa, CubeArithmeticMaskedConstantTestMixin
+    tests.IrisTest, CubeArithmeticMaskedConstantTestMixin
 ):
     @property
     def data_op(self):
diff --git a/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py b/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py
index 68db839d06..9b0160aee4 100644
--- a/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py
+++ b/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py
@@ -15,11 +15,12 @@
 
 from iris.analysis._regrid import CurvilinearRegridder as Regridder
 from iris.analysis.cartography import rotate_pole
+from iris.aux_factory import HybridHeightFactory
 from iris.coord_systems import GeogCS, RotatedGeogCS
 from iris.coords import AuxCoord, DimCoord
 from iris.cube import Cube
 from iris.fileformats.pp import EARTH_RADIUS
-from iris.tests.stock import global_pp, lat_lon_cube
+from iris.tests.stock import global_pp, lat_lon_cube, realistic_4d
 
 RESULT_DIR = ("analysis", "regrid")
 
@@ -169,6 +170,88 @@ def test_caching(self):
         )
 
 
+class Test__derived_coord(tests.IrisTest):
+    def setUp(self):
+        src = realistic_4d()[0]
+        tgt = realistic_4d()
+        new_lon, new_lat = np.meshgrid(
+            src.coord("grid_longitude").points,
+            src.coord("grid_latitude").points,
+        )
+        coord_system = src.coord("grid_latitude").coord_system
+        lat = AuxCoord(
+            new_lat, standard_name="latitude", coord_system=coord_system
+        )
+        lon = AuxCoord(
+            new_lon, standard_name="longitude", coord_system=coord_system
+        )
+        lat_t = AuxCoord(
+            new_lat.T, standard_name="latitude", coord_system=coord_system
+        )
+        lon_t = AuxCoord(
+            new_lon.T, standard_name="longitude", coord_system=coord_system
+        )
+
+        src.remove_coord("grid_latitude")
+        src.remove_coord("grid_longitude")
+        src_t = src.copy()
+        src.add_aux_coord(lat, [1, 2])
+        src.add_aux_coord(lon, [1, 2])
+        src_t.add_aux_coord(lat_t, [2, 1])
+        src_t.add_aux_coord(lon_t, [2, 1])
+        self.src = src.copy()
+        self.src_t = src_t
+        self.tgt = tgt
+        self.altitude = src.coord("altitude")
+        transposed_src = src.copy()
+        transposed_src.transpose([0, 2, 1])
+        self.altitude_transposed = transposed_src.coord("altitude")
+
+    def test_no_transpose(self):
+        rg = Regridder(self.src, self.tgt)
+        res = rg(self.src)
+
+        assert len(res.aux_factories) == 1 and isinstance(
+            res.aux_factories[0], HybridHeightFactory
+        )
+        assert np.allclose(res.coord("altitude").points, self.altitude.points)
+
+    def test_cube_transposed(self):
+        rg = Regridder(self.src, self.tgt)
+        transposed_cube = self.src.copy()
+        transposed_cube.transpose([0, 2, 1])
+        res = rg(transposed_cube)
+
+        assert len(res.aux_factories) == 1 and isinstance(
+            res.aux_factories[0], HybridHeightFactory
+        )
+        assert np.allclose(
+            res.coord("altitude").points, self.altitude_transposed.points
+        )
+
+    def test_coord_transposed(self):
+        rg = Regridder(self.src_t, self.tgt)
+        res = rg(self.src_t)
+
+        assert len(res.aux_factories) == 1 and isinstance(
+            res.aux_factories[0], HybridHeightFactory
+        )
+        assert np.allclose(
+            res.coord("altitude").points, self.altitude_transposed.points
+        )
+
+    def test_both_transposed(self):
+        rg = Regridder(self.src_t, self.tgt)
+        transposed_cube = self.src_t.copy()
+        transposed_cube.transpose([0, 2, 1])
+        res = rg(transposed_cube)
+
+        assert len(res.aux_factories) == 1 and isinstance(
+            res.aux_factories[0], HybridHeightFactory
+        )
+        assert np.allclose(res.coord("altitude").points, self.altitude.points)
+
+
 @tests.skip_data
 class Test___call____bad_src(tests.IrisTest):
     def setUp(self):
@@ -219,7 +302,7 @@ def test_multidim(self):
         grid_cube.add_dim_coord(grid_y_coord, 0)
         grid_cube.add_dim_coord(grid_x_coord, 1)
 
-        # Define some key points in true-lat/lon thta have known positions
+        # Define some key points in true-lat/lon that have known positions
         # First 3x2 points in the centre of each output cell.
         x_centres, y_centres = np.meshgrid(
             grid_x_coord.points, grid_y_coord.points
diff --git a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py
index dad781ed74..f1b9711068 100644
--- a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py
+++ b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py
@@ -12,7 +12,10 @@
 # importing anything else.
 import iris.tests as tests  # isort:skip
 
+from collections import namedtuple
+
 import numpy as np
+import pytest
 
 from iris.analysis.trajectory import interpolate
 from iris.coords import AuxCoord, DimCoord
@@ -38,15 +41,15 @@ def test_unknown_method(self):
             interpolate(cube, sample_point, method="linekar")
 
 
-class TestNearest(tests.IrisTest):
+class TestNearest:
     # Test interpolation with 'nearest' method.
     # This is basically a wrapper to the routine:
     #   'analysis._interpolate_private._nearest_neighbour_indices_ndcoords'.
     # That has its own test, so we don't test the basic calculation
     # exhaustively here.  Instead we check the way it handles the source and
     # result cubes (especially coordinates).
-
-    def setUp(self):
+    @pytest.fixture
+    def src_cube(self):
         cube = iris.tests.stock.simple_3d()
         # Actually, this cube *isn't* terribly realistic, as the lat+lon coords
         # have integer type, which in this case produces some peculiar results.
@@ -54,46 +57,43 @@ def setUp(self):
         for coord_name in ("longitude", "latitude"):
             coord = cube.coord(coord_name)
             coord.points = coord.points.astype(float)
-        self.test_cube = cube
+        return cube
+
+    @pytest.fixture
+    def single_point(self, src_cube):
         # Define coordinates for a single-point testcase.
         y_val, x_val = 0, -90
-        # Work out cube indices of the testpoint.
-        self.single_point_iy = np.where(
-            cube.coord("latitude").points == y_val
-        )[0][0]
-        self.single_point_ix = np.where(
-            cube.coord("longitude").points == x_val
-        )[0][0]
+
         # Use slightly-different values to test nearest-neighbour operation.
-        self.single_sample_point = [
+        sample_point = [
             ("latitude", [y_val + 19.23]),
             ("longitude", [x_val - 17.54]),
         ]
 
-    def test_single_point_same_cube(self):
-        # Check exact result matching for a single point.
-        cube = self.test_cube
-        result = interpolate(cube, self.single_sample_point, method="nearest")
-        # Check that the result is a single trajectory point, exactly equal to
-        # the expected part of the original data.
-        self.assertEqual(result.shape[-1], 1)
-        result = result[..., 0]
-        expected = cube[:, self.single_point_iy, self.single_point_ix]
-        self.assertEqual(result, expected)
+        # Work out cube indices of the testpoint.
+        single_point_iy = np.where(src_cube.coord("latitude").points == y_val)[
+            0
+        ][0]
+        single_point_ix = np.where(
+            src_cube.coord("longitude").points == x_val
+        )[0][0]
 
-    def test_multi_point_same_cube(self):
-        # Check an exact result for multiple points.
-        cube = self.test_cube
+        point = namedtuple("point", "ix iy sample_point")
+        return point(single_point_ix, single_point_iy, sample_point)
+
+    @pytest.fixture
+    def multi_sample_points(self):
         # Use latitude selection to recreate a whole row of the original cube.
-        sample_points = [
+        return [
             ("longitude", [-180, -90, 0, 90]),
             ("latitude", [0, 0, 0, 0]),
         ]
-        result = interpolate(cube, sample_points, method="nearest")
 
+    @pytest.fixture
+    def expected_multipoint_cube(self, src_cube):
         # The result should be identical to a single latitude section of the
         # original, but with modified coords (latitude has 4 repeated zeros).
-        expected = cube[:, 1, :]
+        expected = src_cube[:, 1, :]
         # Result 'longitude' is now an aux coord.
         co_x = expected.coord("longitude")
         expected.remove_coord(co_x)
@@ -104,36 +104,86 @@ def test_multi_point_same_cube(self):
             [0, 0, 0, 0], standard_name="latitude", units="degrees"
         )
         expected.add_aux_coord(co_y, 1)
-        self.assertEqual(result, expected)
 
-    def test_aux_coord_noninterpolation_dim(self):
+        return expected
+
+    def test_single_point_same_cube(self, src_cube, single_point):
+        # Check exact result matching for a single point.
+        result = interpolate(
+            src_cube, single_point.sample_point, method="nearest"
+        )
+        # Check that the result is a single trajectory point, exactly equal to
+        # the expected part of the original data.
+        assert result.shape[-1] == 1
+        result = result[..., 0]
+        expected = src_cube[:, single_point.iy, single_point.ix]
+        assert result == expected
+
+    def test_multi_point_same_cube(
+        self, src_cube, multi_sample_points, expected_multipoint_cube
+    ):
+        # Check an exact result for multiple points.
+        result = interpolate(src_cube, multi_sample_points, method="nearest")
+        assert result == expected_multipoint_cube
+
+    def test_mask_preserved(
+        self, src_cube, multi_sample_points, expected_multipoint_cube
+    ):
+        mask = np.zeros_like(src_cube.data)
+        mask[:, :, 1] = 1
+        src_cube.data = np.ma.array(src_cube.data, mask=mask)
+
+        expected_multipoint_cube.data = np.ma.array(
+            expected_multipoint_cube.data, mask=mask[:, 0]
+        )
+
+        result = interpolate(src_cube, multi_sample_points, method="nearest")
+        assert result == expected_multipoint_cube
+        assert np.allclose(
+            result.data.mask, expected_multipoint_cube.data.mask
+        )
+
+    def test_dtype_preserved(
+        self, src_cube, multi_sample_points, expected_multipoint_cube
+    ):
+        src_cube.data = src_cube.data.astype(np.int16)
+
+        result = interpolate(src_cube, multi_sample_points, method="nearest")
+        assert result == expected_multipoint_cube
+        assert np.allclose(result.data, expected_multipoint_cube.data)
+        assert result.data.dtype == np.int16
+
+    def test_aux_coord_noninterpolation_dim(self, src_cube, single_point):
         # Check exact result with an aux-coord mapped to an uninterpolated dim.
-        cube = self.test_cube
-        cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0)
+        src_cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0)
 
         # The result cube should exactly equal a single source point.
-        result = interpolate(cube, self.single_sample_point, method="nearest")
-        self.assertEqual(result.shape[-1], 1)
+        result = interpolate(
+            src_cube, single_point.sample_point, method="nearest"
+        )
+        assert result.shape[-1] == 1
         result = result[..., 0]
-        expected = cube[:, self.single_point_iy, self.single_point_ix]
-        self.assertEqual(result, expected)
+        expected = src_cube[:, single_point.iy, single_point.ix]
+        assert result == expected
 
-    def test_aux_coord_one_interp_dim(self):
+    def test_aux_coord_one_interp_dim(self, src_cube, single_point):
         # Check exact result with an aux-coord over one interpolation dims.
-        cube = self.test_cube
-        cube.add_aux_coord(AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2)
+        src_cube.add_aux_coord(
+            AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2
+        )
 
         # The result cube should exactly equal a single source point.
-        result = interpolate(cube, self.single_sample_point, method="nearest")
-        self.assertEqual(result.shape[-1], 1)
+        result = interpolate(
+            src_cube, single_point.sample_point, method="nearest"
+        )
+        assert result.shape[-1] == 1
         result = result[..., 0]
-        expected = cube[:, self.single_point_iy, self.single_point_ix]
-        self.assertEqual(result, expected)
+        expected = src_cube[:, single_point.iy, single_point.ix]
+        assert result == expected
 
-    def test_aux_coord_both_interp_dims(self):
+    def test_aux_coord_both_interp_dims(self, src_cube, single_point):
         # Check exact result with an aux-coord over both interpolation dims.
-        cube = self.test_cube
-        cube.add_aux_coord(
+        src_cube.add_aux_coord(
             AuxCoord(
                 [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]],
                 long_name="aux_xy",
@@ -142,17 +192,18 @@ def test_aux_coord_both_interp_dims(self):
         )
 
         # The result cube should exactly equal a single source point.
-        result = interpolate(cube, self.single_sample_point, method="nearest")
-        self.assertEqual(result.shape[-1], 1)
+        result = interpolate(
+            src_cube, single_point.sample_point, method="nearest"
+        )
+        assert result.shape[-1] == 1
         result = result[..., 0]
-        expected = cube[:, self.single_point_iy, self.single_point_ix]
-        self.assertEqual(result, expected)
+        expected = src_cube[:, single_point.iy, single_point.ix]
+        assert result == expected
 
-    def test_aux_coord_fail_mixed_dims(self):
+    def test_aux_coord_fail_mixed_dims(self, src_cube, single_point):
         # Check behaviour with an aux-coord mapped over both interpolation and
         # non-interpolation dims : not supported.
-        cube = self.test_cube
-        cube.add_aux_coord(
+        src_cube.add_aux_coord(
             AuxCoord(
                 [[111, 112, 113, 114], [211, 212, 213, 214]],
                 long_name="aux_0x",
@@ -163,22 +214,23 @@ def test_aux_coord_fail_mixed_dims(self):
             "Coord aux_0x at one x-y position has the shape.*"
             "instead of being a single point"
         )
-        with self.assertRaisesRegex(ValueError, msg):
-            interpolate(cube, self.single_sample_point, method="nearest")
+        with pytest.raises(ValueError, match=msg):
+            interpolate(src_cube, single_point.sample_point, method="nearest")
 
-    def test_metadata(self):
+    def test_metadata(self, src_cube, single_point):
         # Check exact result matching for a single point, with additional
         # attributes and cell-methods.
-        cube = self.test_cube
-        cube.attributes["ODD_ATTR"] = "string-value-example"
-        cube.add_cell_method(iris.coords.CellMethod("mean", "area"))
-        result = interpolate(cube, self.single_sample_point, method="nearest")
+        src_cube.attributes["ODD_ATTR"] = "string-value-example"
+        src_cube.add_cell_method(iris.coords.CellMethod("mean", "area"))
+        result = interpolate(
+            src_cube, single_point.sample_point, method="nearest"
+        )
         # Check that the result is a single trajectory point, exactly equal to
         # the expected part of the original data.
-        self.assertEqual(result.shape[-1], 1)
+        assert result.shape[-1] == 1
         result = result[..., 0]
-        expected = cube[:, self.single_point_iy, self.single_point_ix]
-        self.assertEqual(result, expected)
+        expected = src_cube[:, single_point.iy, single_point.ix]
+        assert result == expected
 
 
 class TestLinear(tests.IrisTest):
diff --git a/lib/iris/tests/unit/constraints/test_Constraint_equality.py b/lib/iris/tests/unit/constraints/test_Constraint_equality.py
new file mode 100644
index 0000000000..01e61b70a7
--- /dev/null
+++ b/lib/iris/tests/unit/constraints/test_Constraint_equality.py
@@ -0,0 +1,274 @@
+# Copyright Iris contributors
+#
+# This file is part of Iris and is released under the LGPL license.
+# See COPYING and COPYING.LESSER in the root of the repository for full
+# licensing details.
+"""Unit tests for equality testing of different constraint types."""
+
+# Import iris.tests first so that some things can be initialised before
+# importing anything else.
+import iris.tests as tests  # isort:skip
+
+from iris._constraints import AttributeConstraint, Constraint, NameConstraint
+
+
+class Test_Constraint__hash__(tests.IrisTest):
+    def test_empty(self):
+        c1 = Constraint()
+        c2 = Constraint()
+        self.assertEqual(hash(c1), hash(c1))
+        self.assertNotEqual(hash(c1), hash(c2))
+
+
+class Test_Constraint__eq__(tests.IrisTest):
+    def test_empty_same(self):
+        c1 = Constraint()
+        c2 = Constraint()
+        self.assertEqual(c1, c2)
+        self.assertIsNot(c1, c2)
+
+    def test_emptyname_same(self):
+        c1 = Constraint("")
+        c2 = Constraint("")
+        self.assertEqual(c1, c2)
+
+    def test_empty_emptyname_differ(self):
+        c1 = Constraint()
+        c2 = Constraint("")
+        self.assertNotEqual(c1, c2)
+
+    def test_names_same(self):
+        c1 = Constraint("a")
+        c2 = Constraint("a")
+        self.assertEqual(c1, c2)
+
+    def test_names_differ(self):
+        c1 = Constraint("a")
+        c2 = Constraint("b")
+        self.assertNotEqual(c1, c2)
+
+    def test_funcs_same(self):
+        # *Same* functions match
+        def func(cube):
+            return False
+
+        c1 = Constraint(cube_func=func)
+        c2 = Constraint(cube_func=func)
+        self.assertEqual(c1, c2)
+
+    def test_funcs_differ(self):
+        # Identical but different funcs do not match.
+        c1 = Constraint(cube_func=lambda c: False)
+        c2 = Constraint(cube_func=lambda c: False)
+        self.assertNotEqual(c1, c2)
+
+    def test_coord_names_same(self):
+        c1 = Constraint(some_coordname=3)
+        c2 = Constraint(some_coordname=3)
+        self.assertEqual(c1, c2)
+
+    def test_coord_names_differ(self):
+        c1 = Constraint(some_coordname_A=3)
+        c2 = Constraint(some_coordname_B=3)
+        self.assertNotEqual(c1, c2)
+
+    def test_coord_values_differ(self):
+        c1 = Constraint(some_coordname=3)
+        c2 = Constraint(some_coordname=4)
+        self.assertNotEqual(c1, c2)
+
+    def test_coord_orders_differ(self):
+        # We *could* maybe ignore Coordinate order, but at present we don't.
+        c1 = Constraint(coordname_1=1, coordname_2=2)
+        c2 = Constraint(coordname_2=2, coordname_1=1)
+        self.assertNotEqual(c1, c2)
+
+    def test_coord_values_functions_same(self):
+        def func(coord):
+            return False
+
+        c1 = Constraint(some_coordname=func)
+        c2 = Constraint(some_coordname=func)
+        self.assertEqual(c1, c2)
+
+    def test_coord_values_functions_differ(self):
+        # Identical functions are not the same.
+        c1 = Constraint(some_coordname=lambda c: True)
+        c2 = Constraint(some_coordname=lambda c: True)
+        self.assertNotEqual(c1, c2)
+
+    def test_coord_values_and_keys_same(self):
+        # **kwargs and 'coord_values=' are combined without distinction.
+        c1 = Constraint(coord_values={"a": [2, 3]})
+        c2 = Constraint(a=[2, 3])
+        self.assertEqual(c1, c2)
+
+
+class Test_AttributeConstraint__hash__(tests.IrisTest):
+    def test_empty(self):
+        c1 = AttributeConstraint()
+        c2 = AttributeConstraint()
+        self.assertEqual(hash(c1), hash(c1))
+        self.assertNotEqual(hash(c1), hash(c2))
+
+
+class Test_AttributeConstraint__eq__(tests.IrisTest):
+    def test_empty_same(self):
+        c1 = AttributeConstraint()
+        c2 = AttributeConstraint()
+        self.assertEqual(c1, c2)
+        self.assertIsNot(c1, c2)
+
+    def test_attribute_plain_empty_diff(self):
+        c1 = AttributeConstraint()
+        c2 = Constraint()
+        self.assertNotEqual(c1, c2)
+
+    def test_names_same(self):
+        c1 = AttributeConstraint(a=1)
+        c2 = AttributeConstraint(a=1)
+        self.assertEqual(c1, c2)
+
+    def test_names_diff(self):
+        c1 = AttributeConstraint(a=1)
+        c2 = AttributeConstraint(a=1, b=1)
+        self.assertNotEqual(c1, c2)
+
+    def test_values_diff(self):
+        c1 = AttributeConstraint(a=1, b=1)
+        c2 = AttributeConstraint(a=1, b=2)
+        self.assertNotEqual(c1, c2)
+
+    def test_func_same(self):
+        def func(attrs):
+            return False
+
+        c1 = AttributeConstraint(a=func)
+        c2 = AttributeConstraint(a=func)
+        self.assertEqual(c1, c2)
+
+    def test_func_diff(self):
+        c1 = AttributeConstraint(a=lambda a: False)
+        c2 = AttributeConstraint(a=lambda a: False)
+        self.assertNotEqual(c1, c2)
+
+
+class Test_NameConstraint__hash__(tests.IrisTest):
+    def test_empty(self):
+        c1 = NameConstraint()
+        c2 = NameConstraint()
+        self.assertEqual(hash(c1), hash(c1))
+        self.assertNotEqual(hash(c1), hash(c2))
+
+
+class Test_NameConstraint__eq__(tests.IrisTest):
+    def test_empty_same(self):
+        c1 = NameConstraint()
+        c2 = NameConstraint()
+        self.assertEqual(c1, c2)
+        self.assertIsNot(c1, c2)
+
+    def test_attribute_plain_empty_diff(self):
+        c1 = NameConstraint()
+        c2 = Constraint()
+        self.assertNotEqual(c1, c2)
+
+    def test_names_same(self):
+        c1 = NameConstraint(standard_name="air_temperature")
+        c2 = NameConstraint(standard_name="air_temperature")
+        self.assertEqual(c1, c2)
+
+    def test_full_same(self):
+        c1 = NameConstraint(
+            standard_name="air_temperature",
+            long_name="temp",
+            var_name="tair",
+            STASH="m01s02i003",
+        )
+        c2 = NameConstraint(
+            standard_name="air_temperature",
+            long_name="temp",
+            var_name="tair",
+            STASH="m01s02i003",
+        )
+        self.assertEqual(c1, c2)
+
+    def test_missing_diff(self):
+        c1 = NameConstraint(standard_name="air_temperature", var_name="tair")
+        c2 = NameConstraint(standard_name="air_temperature")
+        self.assertNotEqual(c1, c2)
+
+    def test_standard_name_diff(self):
+        c1 = NameConstraint(standard_name="air_temperature")
+        c2 = NameConstraint(standard_name="height")
+        self.assertNotEqual(c1, c2)
+
+    def test_long_name_diff(self):
+        c1 = NameConstraint(long_name="temp")
+        c2 = NameConstraint(long_name="t3")
+        self.assertNotEqual(c1, c2)
+
+    def test_var_name_diff(self):
+        c1 = NameConstraint(var_name="tair")
+        c2 = NameConstraint(var_name="xxx")
+        self.assertNotEqual(c1, c2)
+
+    def test_stash_diff(self):
+        c1 = NameConstraint(STASH="m01s02i003")
+        c2 = NameConstraint(STASH="m01s02i777")
+        self.assertNotEqual(c1, c2)
+
+    def test_func_same(self):
+        def func(name):
+            return True
+
+        c1 = NameConstraint(STASH="m01s02i003", long_name=func)
+        c2 = NameConstraint(STASH="m01s02i003", long_name=func)
+        self.assertEqual(c1, c2)
+
+    def test_func_diff(self):
+        c1 = NameConstraint(STASH="m01s02i003", long_name=lambda n: True)
+        c2 = NameConstraint(STASH="m01s02i003", long_name=lambda n: True)
+        self.assertNotEqual(c1, c2)
+
+
+class Test_ConstraintCombination__hash__(tests.IrisTest):
+    def test_empty(self):
+        c1 = Constraint() & Constraint()
+        c2 = Constraint() & Constraint()
+        self.assertEqual(hash(c1), hash(c1))
+        self.assertNotEqual(hash(c1), hash(c2))
+
+    def test_identical_construction(self):
+        c1, c2 = Constraint(a=1), Constraint(b=1)
+        cc1 = c1 & c2
+        cc2 = c1 & c2
+        self.assertNotEqual(hash(cc1), hash(cc2))
+
+
+class Test_ConstraintCombination__eq__(tests.IrisTest):
+    def test_empty_same(self):
+        c1 = Constraint() & Constraint()
+        c2 = Constraint() & Constraint()
+        self.assertEqual(c1, c2)
+        self.assertIsNot(c1, c2)
+
+    def test_multi_components_same(self):
+        c1 = Constraint("a") & Constraint(b=1)
+        c2 = Constraint("a") & Constraint(b=1)
+        self.assertEqual(c1, c2)
+
+    def test_multi_components_diff(self):
+        c1 = Constraint("a") & Constraint(b=1, c=2)
+        c2 = Constraint("a") & Constraint(b=1)
+        self.assertNotEqual(c1, c2)
+
+    def test_different_component_order(self):
+        c1, c2 = Constraint("a"), Constraint(b=1)
+        cc1 = c1 & c2
+        cc2 = c2 & c1
+        self.assertNotEqual(cc1, cc2)
+
+
+if __name__ == "__main__":
+    tests.main()
diff --git a/lib/iris/tests/unit/coords/test_AncillaryVariable.py b/lib/iris/tests/unit/coords/test_AncillaryVariable.py
index 75b6250449..e5fc8fd28a 100644
--- a/lib/iris/tests/unit/coords/test_AncillaryVariable.py
+++ b/lib/iris/tests/unit/coords/test_AncillaryVariable.py
@@ -68,7 +68,7 @@ def setUp(self):
         self.setupTestArrays(masked=True)
 
     def test_lazyness_and_dtype_combinations(self):
-        for (ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses(
+        for ancill_var, data_lazyness in data_all_dtypes_and_lazynesses(
             self,
         ):
             data = ancill_var.core_data()
@@ -225,10 +225,9 @@ def test_dtypes(self):
         # floating dtype.
         # Check that dtypes remain the same in all cases, taking the dtypes
         # directly from the core data as we have no masking).
-        for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses(
+        for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses(
             self
         ):
-
             sub_ancill_var = main_ancill_var[:2, 1]
 
             ancill_var_dtype = main_ancill_var.dtype
@@ -250,10 +249,9 @@ def test_lazyness(self):
         # Index ancillary variables with real+lazy data, and either an int or
         # floating dtype.
         # Check that lazy data stays lazy and real stays real, in all cases.
-        for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses(
+        for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses(
             self
         ):
-
             sub_ancill_var = main_ancill_var[:2, 1]
 
             msg = (
@@ -277,10 +275,9 @@ def test_lazyness(self):
     def test_real_data_copies(self):
         # Index ancillary variables with real+lazy data.
         # In all cases, check that any real arrays are copied by the indexing.
-        for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses(
+        for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses(
             self
         ):
-
             sub_ancill_var = main_ancill_var[:2, 1]
 
             msg = (
@@ -308,10 +305,9 @@ def test_lazyness(self):
         # Copy ancillary variables with real+lazy data, and either an int or
         # floating dtype.
         # Check that lazy data stays lazy and real stays real, in all cases.
-        for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses(
+        for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses(
             self
         ):
-
             ancill_var_dtype = main_ancill_var.dtype
             copied_ancill_var = main_ancill_var.copy()
 
@@ -338,10 +334,9 @@ def test_lazyness(self):
     def test_realdata_copies(self):
         # Copy ancillary variables with real+lazy data.
         # In all cases, check that any real arrays are copies, not views.
-        for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses(
+        for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses(
             self
         ):
-
             copied_ancill_var = main_ancill_var.copy()
 
             msg = (
@@ -520,79 +515,79 @@ def _check(self, result_ancill_var, expected_data, lazyness):
             self.assertEqualLazyArraysAndDtypes(expected_data, data)
 
     def test_add(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = ancill_var + 10
             expected_data = orig_data + 10
             self._check(result, expected_data, data_lazyness)
 
     def test_add_inplace(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             ancill_var += 10
             expected_data = orig_data + 10
             self._check(ancill_var, expected_data, data_lazyness)
 
     def test_right_add(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = 10 + ancill_var
             expected_data = 10 + orig_data
             self._check(result, expected_data, data_lazyness)
 
     def test_subtract(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = ancill_var - 10
             expected_data = orig_data - 10
             self._check(result, expected_data, data_lazyness)
 
     def test_subtract_inplace(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             ancill_var -= 10
             expected_data = orig_data - 10
             self._check(ancill_var, expected_data, data_lazyness)
 
     def test_right_subtract(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = 10 - ancill_var
             expected_data = 10 - orig_data
             self._check(result, expected_data, data_lazyness)
 
     def test_multiply(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = ancill_var * 10
             expected_data = orig_data * 10
             self._check(result, expected_data, data_lazyness)
 
     def test_multiply_inplace(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             ancill_var *= 10
             expected_data = orig_data * 10
             self._check(ancill_var, expected_data, data_lazyness)
 
     def test_right_multiply(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = 10 * ancill_var
             expected_data = 10 * orig_data
             self._check(result, expected_data, data_lazyness)
 
     def test_divide(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = ancill_var / 10
             expected_data = orig_data / 10
             self._check(result, expected_data, data_lazyness)
 
     def test_divide_inplace(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             ancill_var /= 10
             expected_data = orig_data / 10
             self._check(ancill_var, expected_data, data_lazyness)
 
     def test_right_divide(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = 10 / ancill_var
             expected_data = 10 / orig_data
             self._check(result, expected_data, data_lazyness)
 
     def test_negative(self):
-        for (ancill_var, orig_data, data_lazyness) in self.test_combinations:
+        for ancill_var, orig_data, data_lazyness in self.test_combinations:
             result = -ancill_var
             expected_data = -orig_data
             self._check(result, expected_data, data_lazyness)
diff --git a/lib/iris/tests/unit/coords/test_AuxCoord.py b/lib/iris/tests/unit/coords/test_AuxCoord.py
index e6cd8ac821..e5147659fc 100644
--- a/lib/iris/tests/unit/coords/test_AuxCoord.py
+++ b/lib/iris/tests/unit/coords/test_AuxCoord.py
@@ -370,7 +370,6 @@ def test_dtypes(self):
             points_type_name,
             bounds_type_name,
         ) in coords_all_dtypes_and_lazynesses(self, AuxCoord):
-
             sub_coord = main_coord[:2, 1]
 
             coord_dtype = main_coord.dtype
@@ -417,7 +416,6 @@ def test_lazyness(self):
             points_type_name,
             bounds_type_name,
         ) in coords_all_dtypes_and_lazynesses(self, AuxCoord):
-
             sub_coord = main_coord[:2, 1]
 
             msg = (
@@ -463,7 +461,6 @@ def test_real_data_copies(self):
             points_lazyness,
             bounds_lazyness,
         ) in coords_all_dtypes_and_lazynesses(self, AuxCoord):
-
             sub_coord = main_coord[:2, 1]
 
             msg = (
@@ -511,7 +508,6 @@ def test_lazyness(self):
             points_lazyness,
             bounds_lazyness,
         ) in coords_all_dtypes_and_lazynesses(self, AuxCoord):
-
             coord_dtype = main_coord.dtype
             copied_coord = main_coord.copy()
 
@@ -558,7 +554,6 @@ def test_realdata_copies(self):
             points_lazyness,
             bounds_lazyness,
         ) in coords_all_dtypes_and_lazynesses(self, AuxCoord):
-
             copied_coord = main_coord.copy()
 
             msg = (
diff --git a/lib/iris/tests/unit/coords/test_Cell.py b/lib/iris/tests/unit/coords/test_Cell.py
index 650f9ded6c..81370bd0de 100644
--- a/lib/iris/tests/unit/coords/test_Cell.py
+++ b/lib/iris/tests/unit/coords/test_Cell.py
@@ -30,33 +30,6 @@ def assert_raises_on_comparison(self, cell, other, exception_type, regexp):
         with self.assertRaisesRegex(exception_type, regexp):
             cell >= other
 
-    def test_cftime_cell(self):
-        # Check that cell comparison when the cell contains
-        # cftime.datetime objects raises an exception otherwise
-        # this will fall back to id comparison producing unreliable
-        # results.
-        cell = Cell(cftime.datetime(2010, 3, 21))
-        dt = mock.Mock(timetuple=mock.Mock())
-        self.assert_raises_on_comparison(
-            cell, dt, TypeError, "determine the order of cftime"
-        )
-        self.assert_raises_on_comparison(
-            cell, 23, TypeError, "determine the order of cftime"
-        )
-        self.assert_raises_on_comparison(
-            cell, "hello", TypeError, "Unexpected type.*str"
-        )
-
-    def test_cftime_other(self):
-        # Check that cell comparison to a cftime.datetime object
-        # raises an exception otherwise this will fall back to id comparison
-        # producing unreliable results.
-        dt = cftime.datetime(2010, 3, 21)
-        cell = Cell(mock.Mock(timetuple=mock.Mock()))
-        self.assert_raises_on_comparison(
-            cell, dt, TypeError, "determine the order of cftime"
-        )
-
     def test_PartialDateTime_bounded_cell(self):
         # Check that bounded comparisions to a PartialDateTime
         # raise an exception. These are not supported as they
@@ -85,10 +58,9 @@ def test_PartialDateTime_unbounded_cell(self):
     def test_datetime_unbounded_cell(self):
         # Check that cell comparison works with datetimes.
         dt = datetime.datetime(2000, 6, 15)
-        cell = Cell(datetime.datetime(2000, 1, 1))
-        # Note the absence of the inverse of these
-        # e.g. self.assertGreater(dt, cell).
-        # See http://bugs.python.org/issue8005
+        cell = Cell(cftime.datetime(2000, 1, 1))
+        self.assertGreater(dt, cell)
+        self.assertGreaterEqual(dt, cell)
         self.assertLess(cell, dt)
         self.assertLessEqual(cell, dt)
 
diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py
index dca6ed3c1b..72a48437ec 100644
--- a/lib/iris/tests/unit/coords/test_Coord.py
+++ b/lib/iris/tests/unit/coords/test_Coord.py
@@ -463,7 +463,6 @@ def test_lazy_nd_bounds_last(self):
         )
 
     def test_lazy_nd_points_and_bounds(self):
-
         self.setupTestArrays((3, 4))
         coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy)
 
@@ -520,7 +519,6 @@ def test_lazy_nd_noncontiguous_bounds_warning(self):
             coord.collapsed()
 
     def test_numeric_3_bounds(self):
-
         points = np.array([2.0, 6.0, 4.0])
         bounds = np.array([[1.0, 0.0, 3.0], [5.0, 4.0, 7.0], [3.0, 2.0, 5.0]])
 
@@ -544,7 +542,6 @@ def test_numeric_3_bounds(self):
         )
 
     def test_lazy_3_bounds(self):
-
         points = da.arange(3) * 2.0
         bounds = da.arange(3 * 3).reshape(3, 3)
 
diff --git a/lib/iris/tests/unit/coords/test_DimCoord.py b/lib/iris/tests/unit/coords/test_DimCoord.py
index 4298b140ea..dd0ba48f3d 100644
--- a/lib/iris/tests/unit/coords/test_DimCoord.py
+++ b/lib/iris/tests/unit/coords/test_DimCoord.py
@@ -304,7 +304,6 @@ def test_dtypes(self):
             points_type_name,
             bounds_type_name,
         ) in coords_all_dtypes_and_lazynesses(self, DimCoord):
-
             sub_coord = main_coord[:2]
 
             coord_dtype = main_coord.dtype
@@ -404,7 +403,6 @@ def test_real_data_copies(self):
             points_lazyness,
             bounds_lazyness,
         ) in coords_all_dtypes_and_lazynesses(self, DimCoord):
-
             sub_coord = main_coord[:2]
 
             msg = (
@@ -470,7 +468,6 @@ def test_realdata_readonly(self):
             points_type_name,
             bounds_type_name,
         ) in coords_all_dtypes_and_lazynesses(self, DimCoord):
-
             copied_coord = main_coord.copy()
 
             copied_points = copied_coord.core_points()
diff --git a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py
index f9316ff92c..83fcbc4512 100644
--- a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py
+++ b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py
@@ -911,11 +911,11 @@ def test_meshcoord(self):
         result = self.repr_str_strings(meshco)
         expected = [
             (
-                "<MeshCoord: longitude / (degrees_east)  "
+                "<MeshCoord: longitude / (unknown)  "
                 "mesh(test_mesh) location(face)  "
                 "[...]+bounds  shape(3,)>"
             ),
-            "MeshCoord :  longitude / (degrees_east)",
+            "MeshCoord :  longitude / (unknown)",
             "    mesh: <Mesh: 'test_mesh'>",
             "    location: 'face'",
             "    points: [3100, 3101, 3102]",
@@ -926,10 +926,6 @@ def test_meshcoord(self):
             "    shape: (3,)  bounds(3, 4)",
             "    dtype: int64",
             "    standard_name: 'longitude'",
-            "    long_name: 'long-name'",
-            "    attributes:",
-            "        a  1",
-            "        b  'c'",
             "    axis: 'x'",
         ]
         self.assertLines(expected, result)
diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py
index f38d6ef35d..8e9e00dce8 100644
--- a/lib/iris/tests/unit/cube/test_Cube.py
+++ b/lib/iris/tests/unit/cube/test_Cube.py
@@ -9,6 +9,7 @@
 # importing anything else.
 import iris.tests as tests  # isort:skip
 
+from collections import namedtuple
 from itertools import permutations
 from unittest import mock
 
@@ -1864,12 +1865,14 @@ class Test_copy(tests.IrisTest):
     def _check_copy(self, cube, cube_copy):
         self.assertIsNot(cube_copy, cube)
         self.assertEqual(cube_copy, cube)
-        self.assertIsNot(cube_copy.data, cube.data)
+        self.assertIsNot(cube_copy.core_data(), cube.core_data())
         if ma.isMaskedArray(cube.data):
             self.assertMaskedArrayEqual(cube_copy.data, cube.data)
             if cube.data.mask is not ma.nomask:
                 # "No mask" is a constant : all other cases must be distinct.
-                self.assertIsNot(cube_copy.data.mask, cube.data.mask)
+                self.assertIsNot(
+                    cube_copy.core_data().mask, cube.core_data().mask
+                )
         else:
             self.assertArrayEqual(cube_copy.data, cube.data)
 
@@ -1910,6 +1913,9 @@ def test__masked_scalar_arraymask(self):
         self._check_copy(cube, cube.copy())
 
     def test__lazy(self):
+        # 2022-11-02: Dask's current behaviour is that the computed array will
+        #  be the same for cube and cube.copy(), even if the Dask arrays are
+        #  different.
         cube = Cube(as_lazy_data(np.array([1, 0])))
         self._check_copy(cube, cube.copy())
 
@@ -1956,6 +1962,7 @@ def _assert_lists_equal(self, items_a, items_b):
         a different order.
 
         """
+
         # Compare (and thus sort) by their *common* metadata.
         def sortkey(item):
             return BaseMetadata.from_metadata(item.metadata)
@@ -2527,6 +2534,25 @@ def test_fail_remove_ancilliary_variable_by_name(self):
             self.cube.remove_ancillary_variable("notname")
 
 
+class TestCoords(tests.IrisTest):
+    def setUp(self):
+        cube = Cube(np.arange(6).reshape(2, 3))
+        x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x")
+        cube.add_dim_coord(x_coord, 1)
+        self.x_coord = x_coord
+        self.cube = cube
+
+    def test_bad_coord(self):
+        bad_coord = self.x_coord.copy()
+        bad_coord.attributes = {"bad": "attribute"}
+        re = (
+            "Expected to find exactly 1 coordinate matching the given "
+            "'x' coordinate's metadata, but found none."
+        )
+        with self.assertRaisesRegex(CoordinateNotFoundError, re):
+            _ = self.cube.coord(bad_coord)
+
+
 class Test__getitem_CellMeasure(tests.IrisTest):
     def setUp(self):
         cube = Cube(np.arange(6).reshape(2, 3))
@@ -2937,64 +2963,254 @@ def test_cell_method_correct_order(self):
         self.assertTrue(cube1 == cube2)
 
 
+@pytest.fixture
+def simplecube():
+    return stock.simple_2d_w_cell_measure_ancil_var()
+
+
 class Test__dimensional_metadata:
-    @pytest.fixture
-    def cube(self):
-        return stock.simple_2d_w_cell_measure_ancil_var()
+    """
+    Tests for the "Cube._dimensional_data" method.
 
-    def test_not_found(self, cube):
+    NOTE: test could all be static methods, but that adds a line to each definition.
+    """
+
+    def test_not_found(self, simplecube):
         with pytest.raises(KeyError, match="was not found in"):
-            cube._dimensional_metadata("grid_latitude")
+            simplecube._dimensional_metadata("grid_latitude")
 
-    def test_dim_coord_name_found(self, cube):
-        res = cube._dimensional_metadata("bar")
-        assert res == cube.coord("bar")
+    def test_dim_coord_name_found(self, simplecube):
+        res = simplecube._dimensional_metadata("bar")
+        assert res == simplecube.coord("bar")
 
-    def test_dim_coord_instance_found(self, cube):
-        res = cube._dimensional_metadata(cube.coord("bar"))
-        assert res == cube.coord("bar")
+    def test_dim_coord_instance_found(self, simplecube):
+        res = simplecube._dimensional_metadata(simplecube.coord("bar"))
+        assert res == simplecube.coord("bar")
 
-    def test_aux_coord_name_found(self, cube):
-        res = cube._dimensional_metadata("wibble")
-        assert res == cube.coord("wibble")
+    def test_aux_coord_name_found(self, simplecube):
+        res = simplecube._dimensional_metadata("wibble")
+        assert res == simplecube.coord("wibble")
 
-    def test_aux_coord_instance_found(self, cube):
-        res = cube._dimensional_metadata(cube.coord("wibble"))
-        assert res == cube.coord("wibble")
+    def test_aux_coord_instance_found(self, simplecube):
+        res = simplecube._dimensional_metadata(simplecube.coord("wibble"))
+        assert res == simplecube.coord("wibble")
 
-    def test_cell_measure_name_found(self, cube):
-        res = cube._dimensional_metadata("cell_area")
-        assert res == cube.cell_measure("cell_area")
+    def test_cell_measure_name_found(self, simplecube):
+        res = simplecube._dimensional_metadata("cell_area")
+        assert res == simplecube.cell_measure("cell_area")
 
-    def test_cell_measure_instance_found(self, cube):
-        res = cube._dimensional_metadata(cube.cell_measure("cell_area"))
-        assert res == cube.cell_measure("cell_area")
+    def test_cell_measure_instance_found(self, simplecube):
+        res = simplecube._dimensional_metadata(
+            simplecube.cell_measure("cell_area")
+        )
+        assert res == simplecube.cell_measure("cell_area")
 
-    def test_ancillary_var_name_found(self, cube):
-        res = cube._dimensional_metadata("quality_flag")
-        assert res == cube.ancillary_variable("quality_flag")
+    def test_ancillary_var_name_found(self, simplecube):
+        res = simplecube._dimensional_metadata("quality_flag")
+        assert res == simplecube.ancillary_variable("quality_flag")
 
-    def test_ancillary_var_instance_found(self, cube):
-        res = cube._dimensional_metadata(
-            cube.ancillary_variable("quality_flag")
+    def test_ancillary_var_instance_found(self, simplecube):
+        res = simplecube._dimensional_metadata(
+            simplecube.ancillary_variable("quality_flag")
         )
-        assert res == cube.ancillary_variable("quality_flag")
+        assert res == simplecube.ancillary_variable("quality_flag")
 
-    def test_two_with_same_name(self, cube):
+    def test_two_with_same_name(self, simplecube):
         # If a cube has two _DimensionalMetadata objects with the same name, the
         # current behaviour results in _dimensional_metadata returning the first
         # one it finds.
-        cube.cell_measure("cell_area").rename("wibble")
-        res = cube._dimensional_metadata("wibble")
-        assert res == cube.coord("wibble")
+        simplecube.cell_measure("cell_area").rename("wibble")
+        res = simplecube._dimensional_metadata("wibble")
+        assert res == simplecube.coord("wibble")
 
-    def test_two_with_same_name_specify_instance(self, cube):
+    def test_two_with_same_name_specify_instance(self, simplecube):
         # The cube has two _DimensionalMetadata objects with the same name so
         # we specify the _DimensionalMetadata instance to ensure it returns the
         # correct one.
-        cube.cell_measure("cell_area").rename("wibble")
-        res = cube._dimensional_metadata(cube.cell_measure("wibble"))
-        assert res == cube.cell_measure("wibble")
+        simplecube.cell_measure("cell_area").rename("wibble")
+        res = simplecube._dimensional_metadata(
+            simplecube.cell_measure("wibble")
+        )
+        assert res == simplecube.cell_measure("wibble")
+
+
+class TestReprs:
+    """
+    Confirm that str(cube), repr(cube) and cube.summary() work by creating a fresh
+    :class:`iris._representation.cube_printout.CubePrinter` object, and using it
+    in the expected ways.
+
+    Notes
+    -----
+    This only tests code connectivity.  The functionality is tested elsewhere, in
+    `iris.tests.unit._representation.cube_printout.test_CubePrintout`.
+    """
+
+    # Note: logically this could be a staticmethod, but that seems to upset Pytest
+    @pytest.fixture
+    def patched_cubeprinter(self):
+        target = "iris._representation.cube_printout.CubePrinter"
+        instance_mock = mock.MagicMock(
+            to_string=mock.MagicMock(
+                return_value=""
+            )  # NB this must return a string
+        )
+        with mock.patch(target, return_value=instance_mock) as class_mock:
+            yield class_mock, instance_mock
+
+    @staticmethod
+    def _check_expected_effects(
+        simplecube, patched_cubeprinter, oneline, padding
+    ):
+        class_mock, instance_mock = patched_cubeprinter
+        assert class_mock.call_args_list == [
+            # "CubePrinter()" was called exactly once, with the cube as arg
+            mock.call(simplecube)
+        ]
+        assert instance_mock.to_string.call_args_list == [
+            # "CubePrinter(cube).to_string()" was called exactly once, with these args
+            mock.call(oneline=oneline, name_padding=padding)
+        ]
+
+    def test_str_effects(self, simplecube, patched_cubeprinter):
+        str(simplecube)
+        self._check_expected_effects(
+            simplecube, patched_cubeprinter, oneline=False, padding=35
+        )
+
+    def test_repr_effects(self, simplecube, patched_cubeprinter):
+        repr(simplecube)
+        self._check_expected_effects(
+            simplecube, patched_cubeprinter, oneline=True, padding=1
+        )
+
+    def test_summary_effects(self, simplecube, patched_cubeprinter):
+        simplecube.summary(
+            shorten=mock.sentinel.oneliner, name_padding=mock.sentinel.padding
+        )
+        self._check_expected_effects(
+            simplecube,
+            patched_cubeprinter,
+            oneline=mock.sentinel.oneliner,
+            padding=mock.sentinel.padding,
+        )
+
+
+class TestHtmlRepr:
+    """
+    Confirm that Cube._repr_html_() creates a fresh
+    :class:`iris.experimental.representation.CubeRepresentation` object, and uses it
+    in the expected way.
+
+    Notes
+    -----
+    This only tests code connectivity.  The functionality is tested elsewhere, in
+    `iris.tests.unit.experimental.representation.test_CubeRepresentation`.
+    """
+
+    # Note: logically this could be a staticmethod, but that seems to upset Pytest
+    @pytest.fixture
+    def patched_cubehtml(self):
+        target = "iris.experimental.representation.CubeRepresentation"
+        instance_mock = mock.MagicMock(
+            repr_html=mock.MagicMock(
+                return_value=""
+            )  # NB this must return a string
+        )
+        with mock.patch(target, return_value=instance_mock) as class_mock:
+            yield class_mock, instance_mock
+
+    @staticmethod
+    def test__repr_html__effects(simplecube, patched_cubehtml):
+        simplecube._repr_html_()
+
+        class_mock, instance_mock = patched_cubehtml
+        assert class_mock.call_args_list == [
+            # "CubeRepresentation()" was called exactly once, with the cube as arg
+            mock.call(simplecube)
+        ]
+        assert instance_mock.repr_html.call_args_list == [
+            # "CubeRepresentation(cube).repr_html()" was called exactly once, with no args
+            mock.call()
+        ]
+
+
+class Test__cell_methods:
+    @pytest.fixture(autouse=True)
+    def cell_measures_testdata(self):
+        self.cube = Cube([0])
+        self.cm = CellMethod("mean", "time", "6hr")
+        self.cm2 = CellMethod("max", "latitude", "4hr")
+
+    def test_none(self):
+        assert self.cube.cell_methods == ()
+
+    def test_one(self):
+        cube = Cube([0], cell_methods=[self.cm])
+        expected = (self.cm,)
+        assert expected == cube.cell_methods
+
+    def test_empty_assigns(self):
+        testargs = [(), [], {}, 0, 0.0, False, None]
+        results = []
+        for arg in testargs:
+            cube = self.cube.copy()
+            cube.cell_methods = arg  # assign test object
+            results.append(cube.cell_methods)  # capture what is read back
+        expected_results = [()] * len(testargs)
+        assert expected_results == results
+
+    def test_single_assigns(self):
+        cms = (self.cm, self.cm2)
+        # Any type of iterable ought to work
+        # But N.B. *not* testing sets, as order is not stable
+        testargs = [cms, list(cms), {cm: 1 for cm in cms}]
+        results = []
+        for arg in testargs:
+            cube = self.cube.copy()
+            cube.cell_methods = arg  # assign test object
+            results.append(cube.cell_methods)  # capture what is read back
+        expected_results = [cms] * len(testargs)
+        assert expected_results == results
+
+    def test_fail_assign_noniterable(self):
+        test_object = object()
+        with pytest.raises(TypeError, match="not iterable"):
+            self.cube.cell_methods = test_object
+
+    def test_fail_create_noniterable(self):
+        test_object = object()
+        with pytest.raises(TypeError, match="not iterable"):
+            Cube([0], cell_methods=test_object)
+
+    def test_fail_assign_noncellmethod(self):
+        test_object = object()
+        with pytest.raises(ValueError, match="not an iris.coords.CellMethod"):
+            self.cube.cell_methods = (test_object,)
+
+    def test_fail_create_noncellmethod(self):
+        test_object = object()
+        with pytest.raises(ValueError, match="not an iris.coords.CellMethod"):
+            Cube([0], cell_methods=[test_object])
+
+    def test_assign_derivedcellmethod(self):
+        class DerivedCellMethod(CellMethod):
+            pass
+
+        test_object = DerivedCellMethod("mean", "time", "6hr")
+        cms = (test_object,)
+        self.cube.cell_methods = (test_object,)
+        assert cms == self.cube.cell_methods
+
+    def test_fail_assign_duckcellmethod(self):
+        # Can't currently assign a "duck-typed" CellMethod replacement, since
+        # implementation requires class membership (boo!)
+        DuckCellMethod = namedtuple("DuckCellMethod", CellMethod._names)
+        test_object = DuckCellMethod(
+            *CellMethod._names
+        )  # fill props with value==name
+        with pytest.raises(ValueError, match="not an iris.coords.CellMethod"):
+            self.cube.cell_methods = (test_object,)
 
 
 if __name__ == "__main__":
diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py
index 1ebfe57773..86457d3888 100644
--- a/lib/iris/tests/unit/cube/test_CubeList.py
+++ b/lib/iris/tests/unit/cube/test_CubeList.py
@@ -735,5 +735,36 @@ def test_copy(self):
         self.assertIsInstance(self.copied_cube_list, iris.cube.CubeList)
 
 
+class TestHtmlRepr:
+    """
+    Confirm that Cubelist._repr_html_() creates a fresh
+    :class:`iris.experimental.representation.CubeListRepresentation` object, and uses
+    it in the expected way.
+
+    Notes
+    -----
+    This only tests code connectivity.  The functionality is tested elsewhere, at
+    `iris.tests.unit.experimental.representation.test_CubeListRepresentation`
+    """
+
+    @staticmethod
+    def test__repr_html_():
+        test_cubelist = CubeList([])
+
+        target = "iris.experimental.representation.CubeListRepresentation"
+        with mock.patch(target) as class_mock:
+            # Exercise the function-under-test.
+            test_cubelist._repr_html_()
+
+        assert class_mock.call_args_list == [
+            # "CubeListRepresentation()" was called exactly once, with the cubelist as arg
+            mock.call(test_cubelist)
+        ]
+        assert class_mock.return_value.repr_html.call_args_list == [
+            # "CubeListRepresentation(cubelist).repr_html()" was called exactly once, with no args
+            mock.call()
+        ]
+
+
 if __name__ == "__main__":
     tests.main()
diff --git a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py
index 3230e3de00..9e60631c33 100644
--- a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py
+++ b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py
@@ -22,6 +22,7 @@
 from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord
 from iris.cube import Cube
 import iris.exceptions
+from iris.tests.stock import realistic_4d
 
 
 class Test_aggregated_by(tests.IrisTest):
@@ -841,5 +842,52 @@ def test_clim_in_no_clim_op(self):
         self.assertFalse(categorised_coord.climatological)
 
 
+class Test_aggregated_by__derived(tests.IrisTest):
+    def setUp(self):
+        self.cube = realistic_4d()[:, :10, :6, :8]
+        self.time_cat_coord = AuxCoord(
+            [0, 0, 1, 1, 2, 2], long_name="time_cat"
+        )
+        self.cube.add_aux_coord(self.time_cat_coord, 0)
+        height_data = np.zeros(self.cube.shape[1])
+        height_data[5:] = 1
+        self.height_cat_coord = AuxCoord(height_data, long_name="height_cat")
+        self.cube.add_aux_coord(self.height_cat_coord, 1)
+        self.aggregator = iris.analysis.MEAN
+
+    def test_grouped_dim(self):
+        """
+        Check that derived coordinates are maintained when the coordinates they
+        derive from are aggregated.
+        """
+        result = self.cube.aggregated_by(
+            self.height_cat_coord,
+            self.aggregator,
+        )
+        assert len(result.aux_factories) == 1
+        altitude = result.coord("altitude")
+        assert altitude.shape == (2, 6, 8)
+
+        # Check the bounds are derived as expected.
+        orig_alt_bounds = self.cube.coord("altitude").bounds
+        bounds_0 = orig_alt_bounds[0::5, :, :, 0]
+        bounds_1 = orig_alt_bounds[4::5, :, :, 1]
+        expected_bounds = np.stack([bounds_0, bounds_1], axis=-1)
+        assert np.array_equal(expected_bounds, result.coord("altitude").bounds)
+
+    def test_ungrouped_dim(self):
+        """
+        Check that derived coordinates are preserved when aggregating along a
+        different axis.
+        """
+        result = self.cube.aggregated_by(
+            self.time_cat_coord,
+            self.aggregator,
+        )
+        assert len(result.aux_factories) == 1
+        altitude = result.coord("altitude")
+        assert altitude == self.cube.coord("altitude")
+
+
 if __name__ == "__main__":
     tests.main()
diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py
index ce99a8b4be..03e2793fd9 100644
--- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py
+++ b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py
@@ -16,9 +16,10 @@
 
 import dask.array as da
 import numpy as np
+import pytest
 
 from iris._lazy_data import as_lazy_data, is_lazy_data
-from iris.common.metadata import BaseMetadata
+from iris.common.metadata import BaseMetadata, CoordMetadata
 from iris.coords import AuxCoord, Coord
 from iris.cube import Cube
 from iris.experimental.ugrid.mesh import Connectivity, Mesh, MeshCoord
@@ -45,16 +46,11 @@ def test_derived_properties(self):
         # underlying mesh coordinate.
         for axis in Mesh.AXES:
             meshcoord = sample_meshcoord(axis=axis)
-            # N.B.
-            node_x_coord = meshcoord.mesh.coord(include_nodes=True, axis=axis)
-            for key in node_x_coord.metadata._fields:
+            face_x_coord = meshcoord.mesh.coord(include_faces=True, axis=axis)
+            for key in face_x_coord.metadata._fields:
                 meshval = getattr(meshcoord, key)
-                if key == "var_name":
-                    # var_name is unused.
-                    self.assertIsNone(meshval)
-                else:
-                    # names, units and attributes are derived from the node coord.
-                    self.assertEqual(meshval, getattr(node_x_coord, key))
+                # All relevant attributes are derived from the face coord.
+                self.assertEqual(meshval, getattr(face_x_coord, key))
 
     def test_fail_bad_mesh(self):
         with self.assertRaisesRegex(TypeError, "must be a.*Mesh"):
@@ -239,9 +235,7 @@ class Test__getitem__(tests.IrisTest):
     def test_slice_wholeslice_1tuple(self):
         # The only slicing case that we support, to enable cube slicing.
         meshcoord = sample_meshcoord()
-        meshcoord2 = meshcoord[
-            :,
-        ]
+        meshcoord2 = meshcoord[:,]
         self.assertIsNot(meshcoord2, meshcoord)
         self.assertEqual(meshcoord2, meshcoord)
         # In this case, we should *NOT* copy the linked Mesh object.
@@ -270,10 +264,11 @@ def setUp(self):
     def _expected_elements_regexp(
         self,
         standard_name="longitude",
-        long_name="long-name",
-        attributes=True,
+        long_name=None,
+        attributes=False,
         location="face",
         axis="x",
+        var_name=None,
     ):
         # Printed name is standard or long -- we don't have a case with neither
         coord_name = standard_name or long_name
@@ -282,24 +277,30 @@ def _expected_elements_regexp(
         regexp = f"MeshCoord :  {coord_name} / [^\n]+\n *"
         regexp += r"mesh: \<Mesh: 'test_mesh'>\n *"
         regexp += f"location: '{location}'\n *"
+
         # Now some optional sections : whichever comes first will match
         # arbitrary content leading up to it.
-        matched_any_upto = False
-        if standard_name:
-            regexp += ".*"
-            matched_any_upto = True
-            regexp += f"standard_name: '{standard_name}'\n *"
-        if long_name:
+        matched_upto = False
+
+        def upto_first_expected(regexp, matched_any_upto):
             if not matched_any_upto:
                 regexp += ".*"
                 matched_any_upto = True
+            return regexp, matched_any_upto
+
+        if standard_name:
+            regexp, matched_upto = upto_first_expected(regexp, matched_upto)
+            regexp += f"standard_name: '{standard_name}'\n *"
+        if long_name:
+            regexp, matched_upto = upto_first_expected(regexp, matched_upto)
             regexp += f"long_name: '{long_name}'\n *"
+        if var_name:
+            regexp, matched_upto = upto_first_expected(regexp, matched_upto)
+            regexp += f"var_name: '{var_name}'\n *"
         if attributes:
             # if we expected attributes, they should come next
             # TODO: change this when each attribute goes on a new line
-            if not matched_any_upto:
-                regexp += ".*"
-                matched_any_upto = True
+            regexp, matched_upto = upto_first_expected(regexp, matched_upto)
             # match 'attributes:' followed by N*lines with larger indent
             regexp += "attributes:(\n        [^ \n]+ +[^ \n]+)+\n    "
         # After those items, expect 'axis' next
@@ -314,7 +315,7 @@ def test_repr(self):
         # A simple check for the condensed form.
         result = repr(self.meshcoord)
         expected = (
-            "<MeshCoord: longitude / (degrees_east)  "
+            "<MeshCoord: longitude / (unknown)  "
             "mesh(test_mesh) location(face)  [...]+bounds  shape(3,)>"
         )
         self.assertEqual(expected, result)
@@ -331,7 +332,7 @@ def test_repr_lazy(self):
         self.assertTrue(self.meshcoord.has_lazy_bounds())
 
         expected = (
-            "<MeshCoord: longitude / (degrees_east)  "
+            "<MeshCoord: longitude / (unknown)  "
             "mesh(test_mesh) location(face)  <lazy>+bounds  shape(3,)>"
         )
         self.assertEqual(expected, result)
@@ -342,7 +343,7 @@ def test_repr__nameless_mesh(self):
         assert self.mesh.name() == "unknown"
         result = repr(self.meshcoord)
         re_expected = (
-            r".MeshCoord: longitude / \(degrees_east\)  "
+            r".MeshCoord: longitude / \(unknown\)  "
             r"mesh\(.Mesh object at 0x[^>]+.\) location\(face\) "
         )
         self.assertRegex(result, re_expected)
@@ -392,18 +393,6 @@ def test_str_no_long_name(self):
         re_expected = self._expected_elements_regexp(long_name=False)
         self.assertRegex(result, re_expected)
 
-    def test_str_no_standard_name(self):
-        mesh = self.mesh
-        # Remove the standard_name of the node coord in the mesh.
-        node_coord = mesh.coord(include_nodes=True, axis="x")
-        node_coord.standard_name = None
-        node_coord.axis = "x"  # This is required : but it's a kludge !!
-        # Make a new meshcoord, based on the modified mesh.
-        meshcoord = sample_meshcoord(mesh=self.mesh)
-        result = str(meshcoord)
-        re_expected = self._expected_elements_regexp(standard_name=False)
-        self.assertRegex(result, re_expected)
-
     def test_str_no_attributes(self):
         mesh = self.mesh
         # No attributes on the node coord in the mesh.
@@ -451,9 +440,11 @@ def test_cube_dims(self):
 
     def test_find_by_name(self):
         meshcoord = self.meshcoord
+        # hack to give it a long name
+        meshcoord.long_name = "odd_case"
         cube = self.cube
         self.assertIs(cube.coord(standard_name="longitude"), meshcoord)
-        self.assertIs(cube.coord(long_name="long-name"), meshcoord)
+        self.assertIs(cube.coord(long_name="odd_case"), meshcoord)
 
     def test_find_by_axis(self):
         meshcoord = self.meshcoord
@@ -796,5 +787,157 @@ def test_bounds_badvalues__lazy(self):
         self._check_bounds_bad_index_values(lazy=True)
 
 
+class Test__metadata:
+    def setup_mesh(self, location, axis):
+        # Create a standard test mesh + attach it to the test instance.
+        mesh = sample_mesh()
+
+        # Modify the metadata of specific coordinates used in this test.
+        def select_coord(location, axis):
+            kwargs = {f"include_{location}s": True, "axis": axis}
+            return mesh.coord(**kwargs)
+
+        node_coord = select_coord("node", axis)
+        location_coord = select_coord(location, axis)
+        for i_place, coord in enumerate((node_coord, location_coord)):
+            coord.standard_name = "longitude" if axis == "x" else "latitude"
+            coord.units = "degrees"
+            coord.long_name = f"long_name_{i_place}"
+            coord.var_name = f"var_name_{i_place}"
+            coord.attributes = {"att": i_place}
+
+        # attach all the relevant testcase context to the test instance.
+        self.mesh = mesh
+        self.location = location
+        self.axis = axis
+        self.location_coord = location_coord
+        self.node_coord = node_coord
+
+    def coord_metadata_matches(self, test_coord, ref_coord):
+        # Check that two coords match, in all the basic Coord identity/phenomenon
+        # metadata fields -- so it works even between coords of different subclasses.
+        for key in CoordMetadata._fields:
+            assert getattr(test_coord, key) == getattr(ref_coord, key)
+
+    @pytest.fixture(params=["face", "edge"])
+    def location_face_or_edge(self, request):
+        # Fixture to parametrise over location = face/edge
+        return request.param
+
+    @pytest.fixture(params=["x", "y"])
+    def axis_x_or_y(self, request):
+        # Fixture to parametrise over axis = X/Y
+        return request.param
+
+    def test_node_meshcoord(self, axis_x_or_y):
+        # MeshCoord metadata matches that of the relevant node coord.
+        self.setup_mesh(location="node", axis=axis_x_or_y)
+        meshcoord = self.mesh.to_MeshCoord(
+            location=self.location, axis=self.axis
+        )
+        self.coord_metadata_matches(meshcoord, self.node_coord)
+
+    def test_faceedge_basic(self, location_face_or_edge, axis_x_or_y):
+        # MeshCoord metadata matches that of the face/edge ("points") coord.
+        self.setup_mesh(location_face_or_edge, axis_x_or_y)
+        meshcoord = self.mesh.to_MeshCoord(
+            location=self.location, axis=self.axis
+        )
+        self.coord_metadata_matches(meshcoord, self.location_coord)
+
+    @pytest.mark.parametrize(
+        "fieldname", ["long_name", "var_name", "attributes"]
+    )
+    def test_faceedge_dontcare_fields(
+        self, location_face_or_edge, axis_x_or_y, fieldname
+    ):
+        # Check that it's ok for the face/edge and node coords to have different
+        # long-name, var-name or attributes.
+        self.setup_mesh(location_face_or_edge, axis_x_or_y)
+        if fieldname == "attributes":
+            different_value = {"myattrib": "different attributes"}
+        else:
+            # others are just arbitrary strings.
+            different_value = "different"
+        setattr(self.location_coord, fieldname, different_value)
+        # Mostly.. just check this does not cause an error, as it would do if we
+        # modified "standard_name" or "units" (see other tests) ...
+        meshcoord = self.mesh.to_MeshCoord(
+            location=self.location, axis=self.axis
+        )
+        # ... but also, check that the result matches the expected face/edge coord.
+        self.coord_metadata_matches(meshcoord, self.location_coord)
+
+    def test_faceedge_fail_mismatched_stdnames(
+        self, location_face_or_edge, axis_x_or_y
+    ):
+        # Different "standard_name" for node and face/edge causes an error.
+        self.setup_mesh(location_face_or_edge, axis_x_or_y)
+        node_name = f"projection_{axis_x_or_y}_coordinate"
+        self.node_coord.standard_name = node_name
+        location_name = "longitude" if axis_x_or_y == "x" else "latitude"
+        msg = (
+            "Node coordinate .*"
+            f"disagrees with the {location_face_or_edge} coordinate .*, "
+            'in having a "standard_name" value of '
+            f"'{node_name}' instead of '{location_name}'"
+        )
+        with pytest.raises(ValueError, match=msg):
+            self.mesh.to_MeshCoord(
+                location=location_face_or_edge, axis=axis_x_or_y
+            )
+
+    def test_faceedge_fail_missing_stdnames(
+        self, location_face_or_edge, axis_x_or_y
+    ):
+        # "standard_name" compared with None also causes an error.
+        self.setup_mesh(location_face_or_edge, axis_x_or_y)
+        self.node_coord.standard_name = None
+        # N.B. in the absence of a standard-name, we **must** provide an extra ".axis"
+        # property, or the coordinate cannot be correctly identified in the Mesh.
+        # This is a bit of a kludge, but works with current code.
+        self.node_coord.axis = axis_x_or_y
+
+        location_name = "longitude" if axis_x_or_y == "x" else "latitude"
+        msg = (
+            "Node coordinate .*"
+            f"disagrees with the {location_face_or_edge} coordinate .*, "
+            'in having a "standard_name" value of '
+            f"None instead of '{location_name}'"
+        )
+        with pytest.raises(ValueError, match=msg):
+            self.mesh.to_MeshCoord(
+                location=location_face_or_edge, axis=axis_x_or_y
+            )
+
+    def test_faceedge_fail_mismatched_units(
+        self, location_face_or_edge, axis_x_or_y
+    ):
+        # Different "units" for node and face/edge causes an error.
+        self.setup_mesh(location_face_or_edge, axis_x_or_y)
+        self.node_coord.units = "hPa"
+        msg = (
+            "Node coordinate .*"
+            f"disagrees with the {location_face_or_edge} coordinate .*, "
+            'in having a "units" value of '
+            "'hPa' instead of 'degrees'"
+        )
+        with pytest.raises(ValueError, match=msg):
+            self.mesh.to_MeshCoord(
+                location=location_face_or_edge, axis=axis_x_or_y
+            )
+
+    def test_faceedge_missing_units(self, location_face_or_edge, axis_x_or_y):
+        # Units compared with a None ("unknown") is not an error.
+        self.setup_mesh(location_face_or_edge, axis_x_or_y)
+        self.node_coord.units = None
+        # This is OK
+        meshcoord = self.mesh.to_MeshCoord(
+            location=self.location, axis=self.axis
+        )
+        # ... but also, check that the result matches the expected face/edge coord.
+        self.coord_metadata_matches(meshcoord, self.location_coord)
+
+
 if __name__ == "__main__":
     tests.main()
diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py
index a8e44747dd..ffe00c8c19 100644
--- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py
+++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py
@@ -127,7 +127,6 @@ def _make_testcase_cdl(
         include_cellmeasure=False,
         include_ancil=False,
     ):
-
         phenom_extra_attrs_string = ""
         extra_vars_string = ""
 
diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py
new file mode 100644
index 0000000000..b057a41a3e
--- /dev/null
+++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py
@@ -0,0 +1,77 @@
+# Copyright Iris contributors
+#
+# This file is part of Iris and is released under the LGPL license.
+# See COPYING and COPYING.LESSER in the root of the repository for full
+# licensing details.
+"""
+Test function :func:`iris.fileformats._nc_load_rules.helpers.build_ancil_var`.
+
+"""
+
+from unittest import mock
+
+import numpy as np
+import pytest
+
+from iris.exceptions import CannotAddError
+from iris.fileformats._nc_load_rules.helpers import build_ancil_var
+
+
+@pytest.fixture
+def mock_engine():
+    return mock.Mock(
+        cube=mock.Mock(),
+        cf_var=mock.Mock(dimensions=("foo", "bar")),
+        filename="DUMMY",
+        cube_parts=dict(ancillary_variables=[]),
+    )
+
+
+@pytest.fixture
+def mock_cf_av_var(monkeypatch):
+    data = np.arange(6)
+    output = mock.Mock(
+        dimensions=("foo",),
+        scale_factor=1,
+        add_offset=0,
+        cf_name="wibble",
+        cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None), spec=[]),
+        standard_name=None,
+        long_name="wibble",
+        units="m2",
+        shape=data.shape,
+        dtype=data.dtype,
+        __getitem__=lambda self, key: data[key],
+    )
+
+    # Create patch for deferred loading that prevents attempted
+    # file access. This assumes that output is defined in the test case.
+    def patched__getitem__(proxy_self, keys):
+        if proxy_self.variable_name == output.cf_name:
+            return output[keys]
+        raise RuntimeError()
+
+    monkeypatch.setattr(
+        "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__",
+        patched__getitem__,
+    )
+
+    return output
+
+
+def test_not_added(monkeypatch, mock_engine, mock_cf_av_var):
+    # Confirm that the ancillary variable will be skipped if a CannotAddError
+    #  is raised when attempting to add.
+    def mock_add_ancillary_variable(_, __):
+        raise CannotAddError("foo")
+
+    with monkeypatch.context() as m:
+        m.setattr(
+            mock_engine.cube,
+            "add_ancillary_variable",
+            mock_add_ancillary_variable,
+        )
+        with pytest.warns(match="ancillary variable not added to Cube: foo"):
+            build_ancil_var(mock_engine, mock_cf_av_var)
+
+    assert mock_engine.cube_parts["ancillary_variables"] == []
diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py
index 95f892454b..13622b72e2 100644
--- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py
+++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py
@@ -16,8 +16,10 @@
 from unittest import mock
 
 import numpy as np
+import pytest
 
 from iris.coords import AuxCoord
+from iris.exceptions import CannotAddError
 from iris.fileformats._nc_load_rules.helpers import build_auxiliary_coordinate
 from iris.fileformats.cf import CFVariable
 
@@ -280,6 +282,11 @@ def get_cf_bounds_var(coord_var):
             new=get_cf_bounds_var,
         )
 
+        # test_not_added() has been written in pytest-style, but the rest of
+        #  the class is pending migration. Defining self.monkeypatch (not the
+        #  typical practice in pure pytest) allows this transitional state.
+        self.monkeypatch = pytest.MonkeyPatch()
+
     def check_case_aux_coord_construction(self, climatology=False):
         # Test a generic auxiliary coordinate, with or without
         # a climatological coord.
@@ -305,6 +312,19 @@ def test_aux_coord_construction(self):
     def test_aux_coord_construction__climatology(self):
         self.check_case_aux_coord_construction(climatology=True)
 
+    def test_not_added(self):
+        # Confirm that the coord will be skipped if a CannotAddError is raised
+        #  when attempting to add.
+        def mock_add_aux_coord(_, __):
+            raise CannotAddError("foo")
+
+        with self.monkeypatch.context() as m:
+            m.setattr(self.engine.cube, "add_aux_coord", mock_add_aux_coord)
+            with pytest.warns(match="coordinate not added to Cube: foo"):
+                build_auxiliary_coordinate(self.engine, self.cf_coord_var)
+
+        assert self.engine.cube_parts["coordinates"] == []
+
 
 if __name__ == "__main__":
     tests.main()
diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py
new file mode 100644
index 0000000000..efbb0649c9
--- /dev/null
+++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py
@@ -0,0 +1,74 @@
+# Copyright Iris contributors
+#
+# This file is part of Iris and is released under the LGPL license.
+# See COPYING and COPYING.LESSER in the root of the repository for full
+# licensing details.
+"""
+Test function :func:`iris.fileformats._nc_load_rules.helpers.build_cell_measure`.
+
+"""
+
+from unittest import mock
+
+import numpy as np
+import pytest
+
+from iris.exceptions import CannotAddError
+from iris.fileformats._nc_load_rules.helpers import build_cell_measures
+
+
+@pytest.fixture
+def mock_engine():
+    return mock.Mock(
+        cube=mock.Mock(),
+        cf_var=mock.Mock(dimensions=("foo", "bar")),
+        filename="DUMMY",
+        cube_parts=dict(cell_measures=[]),
+    )
+
+
+@pytest.fixture
+def mock_cf_cm_var(monkeypatch):
+    data = np.arange(6)
+    output = mock.Mock(
+        dimensions=("foo",),
+        scale_factor=1,
+        add_offset=0,
+        cf_name="wibble",
+        cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None), spec=[]),
+        standard_name=None,
+        long_name="wibble",
+        units="m2",
+        shape=data.shape,
+        dtype=data.dtype,
+        __getitem__=lambda self, key: data[key],
+        cf_measure="area",
+    )
+
+    # Create patch for deferred loading that prevents attempted
+    # file access. This assumes that output is defined in the test case.
+    def patched__getitem__(proxy_self, keys):
+        if proxy_self.variable_name == output.cf_name:
+            return output[keys]
+        raise RuntimeError()
+
+    monkeypatch.setattr(
+        "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__",
+        patched__getitem__,
+    )
+
+    return output
+
+
+def test_not_added(monkeypatch, mock_engine, mock_cf_cm_var):
+    # Confirm that the cell measure will be skipped if a CannotAddError is
+    #  raised when attempting to add.
+    def mock_add_cell_measure(_, __):
+        raise CannotAddError("foo")
+
+    with monkeypatch.context() as m:
+        m.setattr(mock_engine.cube, "add_cell_measure", mock_add_cell_measure)
+        with pytest.warns(match="cell measure not added to Cube: foo"):
+            build_cell_measures(mock_engine, mock_cf_cm_var)
+
+    assert mock_engine.cube_parts["cell_measures"] == []
diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py
index a75678d923..bc13975441 100644
--- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py
+++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py
@@ -17,8 +17,10 @@
 import warnings
 
 import numpy as np
+import pytest
 
 from iris.coords import AuxCoord, DimCoord
+from iris.exceptions import CannotAddError
 from iris.fileformats._nc_load_rules.helpers import build_dimension_coordinate
 
 
@@ -73,6 +75,12 @@ def setUp(self):
         )
         self.bounds = bounds
 
+        # test_dimcoord_not_added() and test_auxcoord_not_added have been
+        #  written in pytest-style, but the rest of the class is pending
+        #  migration. Defining self.monkeypatch (not the
+        #  typical practice in pure pytest) allows this transitional state.
+        self.monkeypatch = pytest.MonkeyPatch()
+
     def _set_cf_coord_var(self, points):
         self.cf_coord_var = mock.Mock(
             dimensions=("foo",),
@@ -221,7 +229,9 @@ def test_aux_coord_construction(self):
         warning_patch = mock.patch("warnings.warn")
 
         # Asserts must lie within context manager because of deferred loading.
-        with warning_patch, self.deferred_load_patch, self.get_cf_bounds_var_patch:
+        with (
+            warning_patch
+        ), self.deferred_load_patch, self.get_cf_bounds_var_patch:
             build_dimension_coordinate(self.engine, self.cf_coord_var)
 
             # Test that expected coord is built and added to cube.
@@ -233,6 +243,40 @@ def test_aux_coord_construction(self):
                 warnings.warn.call_args[0][0],
             )
 
+    def test_dimcoord_not_added(self):
+        # Confirm that the coord will be skipped if a CannotAddError is raised
+        #  when attempting to add.
+        def mock_add_dim_coord(_, __):
+            raise CannotAddError("foo")
+
+        with self.monkeypatch.context() as m:
+            m.setattr(self.engine.cube, "add_dim_coord", mock_add_dim_coord)
+
+            self._set_cf_coord_var(np.arange(6))
+
+            with self.deferred_load_patch, self.get_cf_bounds_var_patch:
+                with pytest.warns(match="coordinate not added to Cube: foo"):
+                    build_dimension_coordinate(self.engine, self.cf_coord_var)
+
+        assert self.engine.cube_parts["coordinates"] == []
+
+    def test_auxcoord_not_added(self):
+        # Confirm that a gracefully-created auxiliary coord will also be
+        #  skipped if a CannotAddError is raised when attempting to add.
+        def mock_add_aux_coord(_, __):
+            raise CannotAddError("foo")
+
+        with self.monkeypatch.context() as m:
+            m.setattr(self.engine.cube, "add_aux_coord", mock_add_aux_coord)
+
+            self._set_cf_coord_var(np.array([1, 3, 2, 4, 6, 5]))
+
+            with self.deferred_load_patch, self.get_cf_bounds_var_patch:
+                with pytest.warns(match="coordinate not added to Cube: foo"):
+                    build_dimension_coordinate(self.engine, self.cf_coord_var)
+
+        assert self.engine.cube_parts["coordinates"] == []
+
 
 class TestBoundsVertexDim(tests.IrisTest, RulesTestMixin):
     def setUp(self):
diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py
index 575c852ece..18e86a9f57 100644
--- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py
+++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py
@@ -1082,7 +1082,6 @@ def test_mesh_dim_names(self):
             ("dim invalid-name &%!", "dim_invalid_name____"),
         ]
         for given_name, expected_name in dim_names_tests:
-
             mesh = make_mesh(mesh_kwargs={"face_dimension": given_name})
 
             filepath = self.check_save_mesh(mesh)
diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py
index 5e2bbcaa2c..316894ded1 100644
--- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py
+++ b/lib/iris/tests/unit/fileformats/pp/test_PPField.py
@@ -44,7 +44,6 @@
 
 
 class DummyPPField(PPField):
-
     HEADER_DEFN = DUMMY_HEADER
     HEADER_DICT = dict(DUMMY_HEADER)
 
diff --git a/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py b/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py
index 83475c6782..73913c6219 100644
--- a/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py
+++ b/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py
@@ -17,10 +17,49 @@
 
 import numpy as np
 import numpy.ma as ma
+import pytest
 
 import iris.fileformats.pp as pp
 
 
+@pytest.mark.parametrize("data_shape", [(2, 3)])
+@pytest.mark.parametrize(
+    "expected_shape", [(2, 3), (3, 2), (1, 3), (2, 2), (3, 3), (2, 4)]
+)
+@pytest.mark.parametrize(
+    "data_type", [np.float32, np.int32, np.int16, np.int8]
+)
+def test_data_padding__no_compression(data_shape, expected_shape, data_type):
+    data = np.empty(data_shape, dtype=data_type)
+
+    # create the field data buffer
+    buffer = io.BytesIO()
+    buffer.write(data)
+    buffer.seek(0)
+    data_bytes = buffer.read()
+
+    lbpack = pp.SplittableInt(0, dict(n1=0, n2=1))
+    boundary_packing = None
+    mdi = -1
+    args = (
+        data_bytes,
+        lbpack,
+        boundary_packing,
+        expected_shape,
+        data_type,
+        mdi,
+    )
+    data_length, expected_length = np.prod(data_shape), np.prod(expected_shape)
+
+    if expected_length <= data_length:
+        result = pp._data_bytes_to_shaped_array(*args)
+        assert result.shape == expected_shape
+    else:
+        emsg = r"data containing \d+ words does not match expected length"
+        with pytest.raises(ValueError, match=emsg):
+            _ = pp._data_bytes_to_shaped_array(*args)
+
+
 class Test__data_bytes_to_shaped_array__lateral_boundary_compression(
     tests.IrisTest
 ):
diff --git a/lib/iris/tests/unit/fileformats/pp/test_save.py b/lib/iris/tests/unit/fileformats/pp/test_save.py
index 45012dc8bd..8200259cca 100644
--- a/lib/iris/tests/unit/fileformats/pp/test_save.py
+++ b/lib/iris/tests/unit/fileformats/pp/test_save.py
@@ -13,6 +13,8 @@
 
 import cf_units
 import cftime
+import numpy as np
+import pytest
 
 from iris.coords import CellMethod, DimCoord
 from iris.fileformats._ff_cross_references import STASH_TRANS
@@ -21,6 +23,27 @@
 import iris.tests.stock as stock
 
 
+@pytest.mark.parametrize(
+    "unit,modulus",
+    [
+        (cf_units.Unit("radians"), 2 * np.pi),
+        (cf_units.Unit("degrees"), 360.0),
+        (None, 360.0),
+    ],
+)
+def test_grid_and_pole__scalar_dim_longitude(unit, modulus):
+    cube = stock.lat_lon_cube()[:, -1:]
+    assert cube.ndim == 2
+    lon = cube.coord("longitude")
+    lon.units = unit
+
+    field = _pp_save_ppfield_values(cube)
+    bdx = modulus
+    assert field.bdx == bdx
+    assert field.bzx == (lon.points[0] - bdx)
+    assert field.lbnpt == lon.points.size
+
+
 def _pp_save_ppfield_values(cube):
     """
     Emulate saving a cube as PP, and capture the resulting PP field values.
diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py
index 62eb7ff019..e194e240c6 100644
--- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py
+++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py
@@ -211,7 +211,6 @@ def test_lbcode3x23(self):
 
 
 class TestLBTIMx2x_ZeroYears(TestField):
-
     _spec = [
         "lbtim",
         "lbcode",
diff --git a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py
index 5aeebd6045..5f9dece153 100644
--- a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py
+++ b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py
@@ -68,7 +68,7 @@ def test_chunk_size_limiting(self):
             ((11, 2, 1011, 1022), (5, 2, 1011, 1022)),
         ]
         err_fmt = "Result of optimising chunks {} was {}, expected {}"
-        for (shape, expected) in given_shapes_and_resulting_chunks:
+        for shape, expected in given_shapes_and_resulting_chunks:
             chunks = _optimum_chunksize(
                 shape, shape, limit=self.FIXED_CHUNKSIZE_LIMIT
             )
@@ -86,7 +86,7 @@ def test_chunk_size_expanding(self):
             ((3, 300, 200), (117, 300, 1000), (39, 300, 1000)),
         ]
         err_fmt = "Result of optimising shape={};chunks={} was {}, expected {}"
-        for (shape, fullshape, expected) in given_shapes_and_resulting_chunks:
+        for shape, fullshape, expected in given_shapes_and_resulting_chunks:
             chunks = _optimum_chunksize(
                 chunks=shape, shape=fullshape, limit=self.FIXED_CHUNKSIZE_LIMIT
             )
diff --git a/lib/iris/tests/unit/merge/test_ProtoCube.py b/lib/iris/tests/unit/merge/test_ProtoCube.py
index 31b1efb3fd..0fca726b28 100644
--- a/lib/iris/tests/unit/merge/test_ProtoCube.py
+++ b/lib/iris/tests/unit/merge/test_ProtoCube.py
@@ -77,8 +77,7 @@ def test_error(self):
             self.assertTrue(result)
 
 
-@tests.iristest_timing_decorator
-class Test_register__match(Mixin_register, tests.IrisTest_nometa):
+class Test_register__match(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return []
@@ -88,8 +87,7 @@ def cube2(self):
         return example_cube()
 
 
-@tests.iristest_timing_decorator
-class Test_register__standard_name(Mixin_register, tests.IrisTest_nometa):
+class Test_register__standard_name(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.standard_name", "air_temperature", "air_density"]
@@ -101,8 +99,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__long_name(Mixin_register, tests.IrisTest_nometa):
+class Test_register__long_name(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.long_name", "screen_air_temp", "Belling"]
@@ -114,8 +111,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__var_name(Mixin_register, tests.IrisTest_nometa):
+class Test_register__var_name(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.var_name", "'airtemp'", "'airtemp2'"]
@@ -127,8 +123,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__units(Mixin_register, tests.IrisTest_nometa):
+class Test_register__units(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.units", "'K'", "'C'"]
@@ -140,8 +135,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__attributes_unequal(Mixin_register, tests.IrisTest_nometa):
+class Test_register__attributes_unequal(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.attributes", "'mint'"]
@@ -153,10 +147,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__attributes_unequal_array(
-    Mixin_register, tests.IrisTest_nometa
-):
+class Test_register__attributes_unequal_array(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.attributes", "'mint'"]
@@ -174,10 +165,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__attributes_superset(
-    Mixin_register, tests.IrisTest_nometa
-):
+class Test_register__attributes_superset(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.attributes", "'stuffed'"]
@@ -189,10 +177,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__attributes_multi_diff(
-    Mixin_register, tests.IrisTest_nometa
-):
+class Test_register__attributes_multi_diff(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.attributes", "'sam'", "'mint'"]
@@ -215,8 +200,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__cell_method(Mixin_register, tests.IrisTest_nometa):
+class Test_register__cell_method(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.cell_methods"]
@@ -228,8 +212,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__data_shape(Mixin_register, tests.IrisTest_nometa):
+class Test_register__data_shape(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube.shape", "(2,)", "(3,)"]
@@ -241,8 +224,7 @@ def cube2(self):
         return cube
 
 
-@tests.iristest_timing_decorator
-class Test_register__data_dtype(Mixin_register, tests.IrisTest_nometa):
+class Test_register__data_dtype(Mixin_register, tests.IrisTest):
     @property
     def fragments(self):
         return ["cube data dtype", "int32", "int8"]
@@ -307,7 +289,6 @@ def test_noise(self):
 
 
 class Test_register__CoordSig_general(_MergeTest, tests.IrisTest):
-
     _mergetest_type = "coord"
 
     def setUp(self):
@@ -462,7 +443,6 @@ def test_coord_system(self):
 
 
 class Test_register__CoordSig_scalar(_MergeTest_coordprops, tests.IrisTest):
-
     _mergetest_type = "aux_coords (scalar)"
 
     def setUp(self):
@@ -504,7 +484,6 @@ def test_dims(self):
 
 
 class Test_register__CoordSig_dim(_MergeTest_coordprops_vect, tests.IrisTest):
-
     _mergetest_type = "dim_coords"
     _coord_typename = "dim_coord"
 
@@ -533,7 +512,6 @@ def test_circular(self):
 
 
 class Test_register__CoordSig_aux(_MergeTest_coordprops_vect, tests.IrisTest):
-
     _mergetest_type = "aux_coords (non-scalar)"
     _coord_typename = "aux_coord"
 
diff --git a/lib/iris/tests/unit/pandas/__init__.py b/lib/iris/tests/unit/pandas/__init__.py
new file mode 100644
index 0000000000..103a264839
--- /dev/null
+++ b/lib/iris/tests/unit/pandas/__init__.py
@@ -0,0 +1,6 @@
+# Copyright Iris contributors
+#
+# This file is part of Iris and is released under the LGPL license.
+# See COPYING and COPYING.LESSER in the root of the repository for full
+# licensing details.
+"""Unit tests for the :mod:`iris.pandas` module."""
diff --git a/lib/iris/tests/test_pandas.py b/lib/iris/tests/unit/pandas/test_pandas.py
similarity index 67%
rename from lib/iris/tests/test_pandas.py
rename to lib/iris/tests/unit/pandas/test_pandas.py
index f47df75def..fd716bd7c9 100644
--- a/lib/iris/tests/test_pandas.py
+++ b/lib/iris/tests/unit/pandas/test_pandas.py
@@ -3,6 +3,7 @@
 # This file is part of Iris and is released under the LGPL license.
 # See COPYING and COPYING.LESSER in the root of the repository for full
 # licensing details.
+"""All unit tests for the :mod:`iris.pandas` module."""
 
 # import iris tests first so that some things can be initialised before
 # importing anything else
@@ -11,6 +12,7 @@
 import copy
 import datetime
 from termios import IXOFF  # noqa: F401
+import warnings
 
 import cf_units
 import cftime
@@ -42,7 +44,17 @@
     import iris.pandas
 
 
+@pytest.fixture
+def activate_pandas_ndim():
+    iris.FUTURE.pandas_ndim = True
+    yield None
+    iris.FUTURE.pandas_ndim = False
+
+
 @skip_pandas
+@pytest.mark.filterwarnings(
+    "ignore:.*as_series has been deprecated.*:iris._deprecation.IrisDeprecation"
+)
 class TestAsSeries(tests.IrisTest):
     """Test conversion of 1D cubes to Pandas using as_series()"""
 
@@ -121,7 +133,7 @@ def test_copy_int32_false(self):
         assert cube.data[0] == 99
 
     def test_copy_int64_false(self):
-        cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo")
+        cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int64), long_name="foo")
         series = iris.pandas.as_series(cube, copy=False)
         series[0] = 99
         assert cube.data[0] == 99
@@ -147,6 +159,9 @@ def test_copy_masked_false(self):
 
 
 @skip_pandas
+@pytest.mark.filterwarnings(
+    "ignore:You are using legacy 2-dimensional behaviour.*:FutureWarning"
+)
 class TestAsDataFrame(tests.IrisTest):
     """Test conversion of 2D cubes to Pandas using as_data_frame()"""
 
@@ -321,6 +336,363 @@ def test_copy_false_with_cube_view(self):
         assert cube.data[0, 0] == 99
 
 
+@skip_pandas
+class TestAsDataFrameNDim(tests.IrisTest):
+    """Test conversion of n-dimensional cubes to Pandas using as_data_frame()"""
+
+    @pytest.fixture(autouse=True)
+    def _activate_pandas_ndim(self, activate_pandas_ndim):
+        pass
+
+    def test_no_dim_coords(self):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo"
+        )
+        expected_dim0 = np.repeat([0, 1], 5)
+        expected_dim1 = np.tile([0, 1, 2, 3, 4], 2)
+        expected_foo = np.arange(0, 10)
+        data_frame = iris.pandas.as_data_frame(cube)
+        self.assertArrayEqual(data_frame.foo.values, expected_foo)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("dim0"), expected_dim0
+        )
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("dim1"), expected_dim1
+        )
+
+    def test_no_x_coord(self):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo"
+        )
+        dim0 = DimCoord([10, 11], long_name="bar")
+        cube.add_dim_coord(dim0, 0)
+        expected_bar = np.repeat([10, 11], 5)
+        expected_dim1 = np.tile([0, 1, 2, 3, 4], 2)
+        expected_foo = np.arange(0, 10)
+        data_frame = iris.pandas.as_data_frame(cube)
+        self.assertArrayEqual(data_frame.foo, expected_foo)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("bar"), expected_bar
+        )
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("dim1"), expected_dim1
+        )
+
+    def test_no_y_coord(self):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo"
+        )
+        dim1 = DimCoord([10, 11, 12, 13, 14], long_name="bar")
+        cube.add_dim_coord(dim1, 1)
+        expected_dim0 = np.repeat([0, 1], 5)
+        expected_bar = np.tile([10, 11, 12, 13, 14], 2)
+        expected_foo = np.arange(0, 10)
+        data_frame = iris.pandas.as_data_frame(cube)
+        self.assertArrayEqual(data_frame.foo, expected_foo.data)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("dim0"), expected_dim0
+        )
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("bar"), expected_bar
+        )
+
+    def test_simple1D(self):
+        cube = Cube(np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), long_name="foo")
+        dim_coord = DimCoord(
+            [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], long_name="bar"
+        )
+        cube.add_dim_coord(dim_coord, 0)
+        expected_bar = np.arange(10, 20)
+        expected_foo = np.arange(0, 10)
+        data_frame = iris.pandas.as_data_frame(cube)
+        self.assertArrayEqual(data_frame.foo, expected_foo)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("bar"), expected_bar
+        )
+
+    def test_simple2D(self):
+        cube2d = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo"
+        )
+        dim0_coord = DimCoord([15, 16], long_name="milk")
+        dim1_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar")
+        cube2d.add_dim_coord(dim0_coord, 0)
+        cube2d.add_dim_coord(dim1_coord, 1)
+        expected_milk = np.repeat([15, 16], 5)
+        expected_bar = np.tile([10, 11, 12, 13, 14], 2)
+        expected_foo = np.arange(0, 10)
+        data_frame = iris.pandas.as_data_frame(cube2d)
+        self.assertArrayEqual(data_frame.foo, expected_foo)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("milk"), expected_milk
+        )
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("bar"), expected_bar
+        )
+
+    def test_simple3D(self):
+        cube3d = Cube(
+            np.array(
+                [
+                    [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]],
+                    [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19]],
+                    [[20, 21, 22, 23, 24], [25, 26, 27, 28, 29]],
+                ]
+            ),
+            long_name="foo",
+        )
+        dim0_coord = DimCoord([1, 2, 3], long_name="milk")
+        dim1_coord = DimCoord([10, 11], long_name="bar")
+        dim2_coord = DimCoord([20, 21, 22, 23, 24], long_name="kid")
+        cube3d.add_dim_coord(dim0_coord, 0)
+        cube3d.add_dim_coord(dim1_coord, 1)
+        cube3d.add_dim_coord(dim2_coord, 2)
+        expected_milk = np.repeat([1, 2, 3], 10)
+        expected_bar = np.tile(np.repeat([10, 11], 5), 3)
+        expected_kid = np.tile([20, 21, 22, 23, 24], 6)
+        expected_foo = np.arange(0, 30)
+        data_frame = iris.pandas.as_data_frame(cube3d)
+        self.assertArrayEqual(data_frame.foo, expected_foo)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("milk"), expected_milk
+        )
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("bar"), expected_bar
+        )
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("kid"), expected_kid
+        )
+
+    def test_copy_false(self):
+        cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo")
+        data_frame = iris.pandas.as_data_frame(cube, copy=False)
+        cube.data[2] = 99
+        assert cube.data[2] == data_frame.foo[2]
+
+    def test_copy_true(self):
+        cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo")
+        data_frame = iris.pandas.as_data_frame(cube, copy=True)
+        cube.data[2] = 99
+        assert cube.data[2] != data_frame.foo[2]
+
+    def test_time_standard(self):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="ts"
+        )
+        day_offsets = [0, 100.1, 200.2, 300.3, 400.4]
+        time_coord = DimCoord(
+            day_offsets, long_name="time", units="days since 2000-01-01 00:00"
+        )
+        cube.add_dim_coord(time_coord, 1)
+        expected_ts = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+        expected_time = np.array(
+            [
+                cftime.DatetimeGregorian(
+                    2000, 1, 1, 0, 0, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 4, 10, 2, 24, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 7, 19, 4, 48, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 10, 27, 7, 12, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2001, 2, 4, 9, 36, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 1, 1, 0, 0, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 4, 10, 2, 24, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 7, 19, 4, 48, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2000, 10, 27, 7, 12, 0, 0, has_year_zero=False
+                ),
+                cftime.DatetimeGregorian(
+                    2001, 2, 4, 9, 36, 0, 0, has_year_zero=False
+                ),
+            ],
+            dtype=object,
+        )
+        data_frame = iris.pandas.as_data_frame(cube)
+        self.assertArrayEqual(data_frame.ts, expected_ts)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("time"), expected_time
+        )
+
+    def test_time_360(self):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="ts"
+        )
+        time_unit = cf_units.Unit(
+            "days since 2000-01-01 00:00", calendar=cf_units.CALENDAR_360_DAY
+        )
+        time_coord = DimCoord(
+            [100.1, 200.2], long_name="time", units=time_unit
+        )
+        cube.add_dim_coord(time_coord, 0)
+        expected_time = np.array(
+            [
+                cftime.Datetime360Day(
+                    2000, 4, 11, 2, 24, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 4, 11, 2, 24, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 4, 11, 2, 24, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 4, 11, 2, 24, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 4, 11, 2, 24, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 7, 21, 4, 48, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 7, 21, 4, 48, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 7, 21, 4, 48, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 7, 21, 4, 48, 0, 0, has_year_zero=True
+                ),
+                cftime.Datetime360Day(
+                    2000, 7, 21, 4, 48, 0, 0, has_year_zero=True
+                ),
+            ],
+            dtype=object,
+        )
+        expected_ts = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+        data_frame = iris.pandas.as_data_frame(cube)
+        self.assertArrayEqual(data_frame.ts, expected_ts)
+        self.assertArrayEqual(
+            data_frame.index.get_level_values("time"), expected_time
+        )
+
+    def test_aux_coord(self):
+        cube = Cube(np.array([[0, 1], [5, 6]]), long_name="foo")
+        dim0_coord = DimCoord([15, 16], long_name="milk")
+        dim1_coord = DimCoord([10, 11], long_name="bar")
+        aux0_coord = AuxCoord(["fiveteen", "sixteen"], long_name="words0")
+        aux1_coord = AuxCoord(["ten", "eleven"], long_name="words1")
+        cube.add_dim_coord(dim0_coord, 0)
+        cube.add_dim_coord(dim1_coord, 1)
+        cube.add_aux_coord(aux0_coord, 0)
+        cube.add_aux_coord(aux1_coord, 1)
+        expected_foo = np.array([0, 1, 5, 6])
+        expected_words0 = np.repeat(["fiveteen", "sixteen"], 2)
+        expected_words1 = np.tile(["ten", "eleven"], 2)
+        data_frame = iris.pandas.as_data_frame(cube, add_aux_coords=True)
+        self.assertArrayEqual(data_frame.foo, expected_foo)
+        self.assertArrayEqual(data_frame.words0, expected_words0)
+        self.assertArrayEqual(data_frame.words1, expected_words1)
+
+    def test_aux_coord2(self):
+        cube = Cube(np.array([[0, 1], [5, 6]]), long_name="foo")
+        dim0_coord = DimCoord([15, 16], long_name="milk")
+        dim1_coord = DimCoord([10, 11], long_name="bar")
+        aux0_coord = AuxCoord(["fiveteen0", "sixteen0"], long_name="words0")
+        aux1_coord = AuxCoord(["fiveteen1", "sixteen1"], long_name="words1")
+        aux2_coord = AuxCoord(["ten", "eleven"], long_name="words2")
+        cube.add_dim_coord(dim0_coord, 0)
+        cube.add_dim_coord(dim1_coord, 1)
+        # Two aux coords associated with dim0
+        cube.add_aux_coord(aux0_coord, 0)
+        cube.add_aux_coord(aux1_coord, 0)
+        # One aux coords associated with dim1
+        cube.add_aux_coord(aux2_coord, 1)
+        expected_foo = np.array([0, 1, 5, 6])
+        expected_words0 = np.repeat(["fiveteen0", "sixteen0"], 2)
+        expected_words1 = np.repeat(["fiveteen1", "sixteen1"], 2)
+        expected_words2 = np.tile(["ten", "eleven"], 2)
+        data_frame = iris.pandas.as_data_frame(cube, add_aux_coords=True)
+        self.assertArrayEqual(data_frame.foo, expected_foo)
+        self.assertArrayEqual(data_frame.words0, expected_words0)
+        self.assertArrayEqual(data_frame.words1, expected_words1)
+        self.assertArrayEqual(data_frame.words2, expected_words2)
+
+    def test_multidim_aux(self):
+        cube = Cube(
+            np.arange(300, 312, 1).reshape([2, 2, 3]),
+            long_name="air_temperature",
+        )
+        dim0_coord = DimCoord([0, 10], long_name="longitude")
+        dim1_coord = DimCoord([25, 35], long_name="latitude")
+        dim2_coord = DimCoord([0, 100, 200], long_name="height")
+        aux0_coord = AuxCoord(
+            [[True, False], [False, False]], long_name="in_region"
+        )
+        cube.add_dim_coord(dim0_coord, 0)
+        cube.add_dim_coord(dim1_coord, 1)
+        cube.add_dim_coord(dim2_coord, 2)
+        cube.add_aux_coord(aux0_coord, data_dims=(0, 1))
+        expected_in_region = np.repeat([True, False, False, False], 3)
+        data_frame = iris.pandas.as_data_frame(cube, add_aux_coords=True)
+        self.assertArrayEqual(data_frame.in_region, expected_in_region)
+
+    def test_add_scalar_coord(self):
+        cube = Cube(np.array([[0, 1], [5, 6]]), long_name="foo")
+        scalar_coord = iris.coords.AuxCoord(
+            1, long_name="scalar_coord", units="no_unit"
+        )
+        cube.add_aux_coord(scalar_coord)
+        expected_scalar_coord = np.repeat(1, 4)
+        data_frame = iris.pandas.as_data_frame(cube, add_aux_coords=True)
+        self.assertArrayEqual(data_frame.scalar_coord, expected_scalar_coord)
+
+    def test_add_ancillary_variable(self):
+        cube = Cube(np.array([[0, 1], [5, 6]]), long_name="foo")
+        dim0_coord = DimCoord([0, 10], long_name="bar")
+        cube.add_dim_coord(dim0_coord, 0)
+        av = AncillaryVariable([10, 100], long_name="ancil_bar")
+        av2 = AncillaryVariable(
+            [1000], long_name="ancil_bar2"
+        )  # Scalar ancillary variable
+        cube.add_ancillary_variable(av, 0)
+        cube.add_ancillary_variable(av2)
+        expected_ancillary_variable = np.repeat([10, 100], 2)
+        expected_ancillary_variable2 = np.repeat([1000], 4)
+        data_frame = iris.pandas.as_data_frame(
+            cube, add_ancillary_variables=True
+        )
+        self.assertArrayEqual(
+            data_frame.ancil_bar, expected_ancillary_variable
+        )
+        self.assertArrayEqual(
+            data_frame.ancil_bar2, expected_ancillary_variable2
+        )
+
+    def test_add_cell_measures(self):
+        cube = Cube(np.array([[0, 1], [5, 6]]), long_name="foo")
+        dim0_coord = DimCoord([0, 10], long_name="bar")
+        cube.add_dim_coord(dim0_coord, 0)
+        cm = CellMeasure([10, 100], long_name="cell_measure")
+        cm2 = CellMeasure(
+            1e4, long_name="cell_measure2"
+        )  # Scalar cell measure
+        cube.add_cell_measure(cm, 0)
+        cube.add_cell_measure(cm2)
+        expected_cell_measure = np.repeat([10, 100], 2)
+        expected_cell_measure2 = np.repeat(1e4, 4)
+        data_frame = iris.pandas.as_data_frame(cube, add_cell_measures=True)
+        self.assertArrayEqual(data_frame.cell_measure, expected_cell_measure)
+        self.assertArrayEqual(data_frame.cell_measure2, expected_cell_measure2)
+
+    def test_instance_error(self):
+        with pytest.raises(TypeError):
+            _ = iris.pandas.as_data_frame(list())
+
+
 @skip_pandas
 @pytest.mark.filterwarnings(
     "ignore:.*as_cube has been deprecated.*:iris._deprecation.IrisDeprecation"
@@ -512,15 +884,37 @@ def test_copy_false(self):
 
 
 @skip_pandas
-class TestFutureAndDeprecation(tests.IrisTest):
-    def test_deprecation_warning(self):
+class TestFutureAndDeprecation:
+    def test_as_cube_deprecation_warning(self):
         data_frame = pandas.DataFrame([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
         with pytest.warns(
             IrisDeprecation, match="as_cube has been deprecated"
         ):
             _ = iris.pandas.as_cube(data_frame)
 
-    # Tests for FUTURE are expected when as_dataframe() is made n-dimensional.
+    def test_as_series_deprecation_warning(self):
+        cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo")
+        with pytest.warns(
+            IrisDeprecation, match="as_series has been deprecated"
+        ):
+            _ = iris.pandas.as_series(cube)
+
+    def test_as_dataframe_future_warning(self):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo"
+        )
+        with pytest.warns(
+            FutureWarning, match="You are using legacy 2-dimensional behaviour"
+        ):
+            _ = iris.pandas.as_data_frame(cube)
+
+    def test_as_dataframe_no_future_warning(self, activate_pandas_ndim):
+        cube = Cube(
+            np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo"
+        )
+        with warnings.catch_warnings():
+            warnings.simplefilter("error", FutureWarning)
+            _ = iris.pandas.as_data_frame(cube)
 
 
 @skip_pandas
diff --git a/lib/iris/tests/unit/tests/test_IrisTest.py b/lib/iris/tests/unit/tests/test_IrisTest.py
index 5725b59d40..10de2a7760 100644
--- a/lib/iris/tests/unit/tests/test_IrisTest.py
+++ b/lib/iris/tests/unit/tests/test_IrisTest.py
@@ -66,8 +66,7 @@ def test_different_mask_nonstrict(self):
             self._func(self.arr1, arr2, strict=False)
 
 
-@tests.iristest_timing_decorator
-class Test_assertMaskedArrayEqual(_MaskedArrayEquality, tests.IrisTest_nometa):
+class Test_assertMaskedArrayEqual(_MaskedArrayEquality, tests.IrisTest):
     @property
     def _func(self):
         return self.assertMaskedArrayEqual
@@ -114,10 +113,7 @@ def test_masked_nonmasked_same_emptymask(self):
         self.assertMaskedArrayEqual(arr1, arr2)
 
 
-@tests.iristest_timing_decorator
-class Test_assertMaskedArrayAlmostEqual(
-    _MaskedArrayEquality, tests.IrisTest_nometa
-):
+class Test_assertMaskedArrayAlmostEqual(_MaskedArrayEquality, tests.IrisTest):
     @property
     def _func(self):
         return self.assertMaskedArrayAlmostEqual