diff --git a/docs/src/further_topics/ugrid/images/fesom_mesh.png b/docs/src/further_topics/ugrid/images/fesom_mesh.png new file mode 100644 index 0000000000..283899a94b Binary files /dev/null and b/docs/src/further_topics/ugrid/images/fesom_mesh.png differ diff --git a/docs/src/further_topics/ugrid/images/smc_mesh.png b/docs/src/further_topics/ugrid/images/smc_mesh.png new file mode 100644 index 0000000000..8c5a9d86eb Binary files /dev/null and b/docs/src/further_topics/ugrid/images/smc_mesh.png differ diff --git a/docs/src/further_topics/ugrid/index.rst b/docs/src/further_topics/ugrid/index.rst index 81ba24428a..c45fd271a2 100644 --- a/docs/src/further_topics/ugrid/index.rst +++ b/docs/src/further_topics/ugrid/index.rst @@ -38,6 +38,7 @@ Read on to find out more... * :doc:`data_model` - learn why the mesh experience is so different. * :doc:`partner_packages` - meet some optional dependencies that provide powerful mesh operations. * :doc:`operations` - experience how your workflows will look when written for mesh data. +* :doc:`other_meshes` - check out some examples of converting various mesh formats into Iris' mesh format. .. Need an actual TOC to get Sphinx working properly, but have hidden it in @@ -50,5 +51,6 @@ Read on to find out more... data_model partner_packages operations + other_meshes __ CF-UGRID_ diff --git a/docs/src/further_topics/ugrid/other_meshes.rst b/docs/src/further_topics/ugrid/other_meshes.rst new file mode 100644 index 0000000000..e6f477624e --- /dev/null +++ b/docs/src/further_topics/ugrid/other_meshes.rst @@ -0,0 +1,225 @@ +.. _other_meshes: + +Converting Other Mesh Formats +***************************** + +Iris' Mesh Data Model is based primarily on the CF-UGRID conventions (see +:doc:`data_model`), but other mesh formats can be converted to fit into this +model, **enabling use of Iris' specialised mesh support**. Below are some +examples demonstrating how this works for various mesh formats. + +.. contents:: + :local: + +`FESOM 1.4`_ Voronoi Polygons +----------------------------- +.. figure:: images/fesom_mesh.png + :width: 300 + :alt: Sample of FESOM mesh voronoi polygons, with variable numbers of sides. + +A FESOM mesh encoded in a NetCDF file includes: + +* X+Y point coordinates +* X+Y corners coordinates of the Voronoi Polygons around these points - + represented as the bounds of the coordinates + +To represent the Voronoi Polygons as faces, the corner coordinates will be used +as the **nodes** when creating the Iris +:class:`~iris.experimental.ugrid.mesh.Mesh`. + +.. dropdown:: :opticon:`code` + + .. code-block:: python + + >>> import iris + >>> from iris.experimental.ugrid import Mesh + + + >>> temperature_cube = iris.load_cube("my_file.nc", "sea_surface_temperature") + >>> print(temperature_cube) + sea_surface_temperature / (degC) (time: 12; -- : 126859) + Dimension coordinates: + time x - + Auxiliary coordinates: + latitude - x + longitude - x + Cell methods: + mean where sea area + mean time + Attributes: + grid 'FESOM 1.4 (unstructured grid in the horizontal with 126859 wet nodes;... + ... + + >>> print(temperature_cube.coord("longitude")) + AuxCoord : longitude / (degrees) + points: + bounds: + shape: (126859,) bounds(126859, 18) + dtype: float64 + standard_name: 'longitude' + var_name: 'lon' + + # Use a Mesh to represent the Cube's horizontal geography, by replacing + # the existing face AuxCoords with new MeshCoords. + >>> fesom_mesh = Mesh.from_coords(temperature_cube.coord('longitude'), + ... temperature_cube.coord('latitude')) + >>> for new_coord in fesom_mesh.to_MeshCoords("face"): + ... old_coord = temperature_cube.coord(new_coord.name()) + ... unstructured_dim, = old_coord.cube_dims(temperature_cube) + ... temperature_cube.remove_coord(old_coord) + ... temperature_cube.add_aux_coord(new_coord, unstructured_dim) + + >>> print(temperature_cube) + sea_surface_temperature / (degC) (time: 12; -- : 126859) + Dimension coordinates: + time x - + Mesh coordinates: + latitude - x + longitude - x + Cell methods: + mean where sea area + mean time + Attributes: + grid 'FESOM 1.4 (unstructured grid in the horizontal with 126859 wet nodes;... + ... + + >>> print(temperature_cube.mesh) + Mesh : 'unknown' + topology_dimension: 2 + node + node_dimension: 'Mesh2d_node' + node coordinates + shape(2283462,)> + shape(2283462,)> + face + face_dimension: 'Mesh2d_face' + face_node_connectivity: shape(126859, 18)> + face coordinates + shape(126859,)> + shape(126859,)> + +`WAVEWATCH III`_ Spherical Multi-Cell (SMC) WAVE Quad Grid +---------------------------------------------------------- +.. figure:: images/smc_mesh.png + :width: 300 + :alt: Sample of an SMC mesh, with decreasing quad sizes at the coastlines. + +An SMC grid encoded in a NetCDF file includes: + +* X+Y face centre coordinates +* X+Y base face sizes +* X+Y face size factors + +From this information we can derive face corner coordinates, which will be used +as the **nodes** when creating the Iris +:class:`~iris.experimental.ugrid.mesh.Mesh`. + + +.. dropdown:: :opticon:`code` + + .. code-block:: python + + >>> import iris + >>> from iris.experimental.ugrid import Mesh + >>> import numpy as np + + + >>> wave_cube = iris.load_cube("my_file.nc", "sea_surface_wave_significant_height") + >>> print(wave_cube) + sea_surface_wave_significant_height / (m) (time: 7; -- : 666328) + Dimension coordinates: + time x - + Auxiliary coordinates: + forecast_period x - + latitude - x + latitude cell size factor - x + longitude - x + longitude cell size factor - x + Scalar coordinates: + forecast_reference_time 2021-12-05 00:00:00 + Attributes: + SIN4 namelist parameter BETAMAX 1.39 + SMC_grid_type 'seapoint' + WAVEWATCH_III_switches 'NOGRB SHRD PR2 UNO SMC FLX0 LN1 ST4 NL1 BT1 DB1 TR0 BS0 IC0 IS0 REF0 WNT1... + WAVEWATCH_III_version_number '7.13' + altitude_resolution 'n/a' + area 'Global wave model GS512L4EUK' + base_lat_size 0.029296871 + base_lon_size 0.043945305 + ... + + >>> faces_x = wave_cube.coord("longitude") + >>> faces_y = wave_cube.coord("latitude") + >>> face_size_factor_x = wave_cube.coord("longitude cell size factor") + >>> face_size_factor_y = wave_cube.coord("latitude cell size factor") + >>> base_x_size = wave_cube.attributes["base_lon_size"] + >>> base_y_size = wave_cube.attributes["base_lat_size"] + + # Calculate face corners from face centres and face size factors. + >>> face_centres_x = faces_x.points + >>> face_centres_y = faces_y.points + >>> face_size_x = face_size_factor_x.points * base_x_size + >>> face_size_y = face_size_factor_y.points * base_y_size + + >>> x_mins = (face_centres_x - 0.5 * face_size_x).reshape(-1, 1) + >>> x_maxs = (face_centres_x + 0.5 * face_size_x).reshape(-1, 1) + >>> y_mins = (face_centres_y - 0.5 * face_size_y).reshape(-1, 1) + >>> y_maxs = (face_centres_y + 0.5 * face_size_y).reshape(-1, 1) + + >>> face_corners_x = np.hstack([x_mins, x_maxs, x_maxs, x_mins]) + >>> face_corners_y = np.hstack([y_mins, y_mins, y_maxs, y_maxs]) + + # Add face corners as coordinate bounds. + >>> faces_x.bounds = face_corners_x + >>> faces_y.bounds = face_corners_y + + # Use a Mesh to represent the Cube's horizontal geography, by replacing + # the existing face AuxCoords with new MeshCoords. + >>> smc_mesh = Mesh.from_coords(faces_x, faces_y) + >>> for new_coord in smc_mesh.to_MeshCoords("face"): + ... old_coord = wave_cube.coord(new_coord.name()) + ... unstructured_dim, = old_coord.cube_dims(wave_cube) + ... wave_cube.remove_coord(old_coord) + ... wave_cube.add_aux_coord(new_coord, unstructured_dim) + + >>> print(wave_cube) + sea_surface_wave_significant_height / (m) (time: 7; -- : 666328) + Dimension coordinates: + time x - + Mesh coordinates: + latitude - x + longitude - x + Auxiliary coordinates: + forecast_period x - + latitude cell size factor - x + longitude cell size factor - x + Scalar coordinates: + forecast_reference_time 2021-12-05 00:00:00 + Attributes: + SIN4 namelist parameter BETAMAX 1.39 + SMC_grid_type 'seapoint' + WAVEWATCH_III_switches 'NOGRB SHRD PR2 UNO SMC FLX0 LN1 ST4 NL1 BT1 DB1 TR0 BS0 IC0 IS0 REF0 WNT1... + WAVEWATCH_III_version_number '7.13' + altitude_resolution 'n/a' + area 'Global wave model GS512L4EUK' + base_lat_size 0.029296871 + base_lon_size 0.043945305 + ... + + >>> print(wave_cube.mesh) + Mesh : 'unknown' + topology_dimension: 2 + node + node_dimension: 'Mesh2d_node' + node coordinates + + + face + face_dimension: 'Mesh2d_face' + face_node_connectivity: + face coordinates + + + +.. _WAVEWATCH III: https://github.com/NOAA-EMC/WW3 +.. _FESOM 1.4: https://fesom.de/models/fesom14/ diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 6ed6764cfa..25308831d3 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -160,6 +160,9 @@ This document explains the changes made to Iris for this release numpydoc strings and fixed some API documentation rendering. See :ref:`docstrings`. (:issue:`4657`, :pull:`4689`) +# `@trexfeathers`_ added a page with examples of converting various mesh + formats into the Iris Mesh Data Model. (:pull:`4739`) + 💼 Internal ===========