The Mesh Data Model#
Important
This page is intended to summarise the essentials that Iris users need to know about meshes. For exhaustive details on UGRID itself: visit the official UGRID conventions site.
Evolution, not revolution#
Mesh support has been designed wherever possible to fit within the existing Iris model. Meshes concern only the spatial geography of data, and can optionally be limited to just the horizontal geography (e.g. X and Y). Other dimensions such as time or ensemble member (and often vertical levels) retain their familiar structured format.
The UGRID conventions themselves are designed as an addition to the existing CF conventions, which are at the core of Iris’ philosophy.
What’s Different?#
The mesh format represents data’s geography using an unstructured mesh. This has significant pros and cons when compared to a structured grid.
The Detail#
Structured Grids (the old world)#
Assigning data to locations using a structured grid is essentially an act of matching coordinate arrays to each dimension of the data array. The data can also be represented as an area (instead of a point) by including a bounds array for each coordinate array. Data on a structured grid. visualises an example.
Unstructured Meshes (the new world)#
A mesh is made up of different types of element:
0D |
|
The ‘core’ of the mesh. A point position in space, constructed from 2 or 3 coordinates (2D or 3D space). |
1D |
|
Constructed by connecting 2 nodes. |
2D |
|
Constructed by connecting 3 or more nodes. |
3D |
|
Constructed by connecting 4 or more nodes (which must each have 3 coordinates - 3D space). |
Every node in the mesh is defined by indexing the 1-dimensional X and Y (and
optionally Z) coordinate arrays (the node_coordinates
) - e.g.
(x[3], y[3])
gives the position of the fourth node. Note that this means
each node has its own coordinates, independent of every other node.
Any higher dimensional element - an edge/face/volume - is described by a
sequence of the indices of the nodes that make up that element. E.g. a
triangular face made from connecting the first, third and fourth nodes:
[0, 2, 3]
. These 1D sequences combine into a 2D array enumerating all
the elements of that type - edge/face/volume - called a connectivity.
E.g. we could make a mesh of 4 nodes, with 2 triangles described using this
face_node_connectivity
: [[0, 2, 3], [3, 2, 1]]
(note the shared nodes).
Note
More on Connectivities:
The element type described by a connectivity is known as its location;
edge
inedge_node_connectivity
.According to the UGRID conventions, the nodes in a face should be listed in “anti-clockwise order from above”.
Connectivities also exist to connect the higher dimensional elements, e.g.
face_edge_connectivity
. These are optional conveniences to speed up certain operations and will not be discussed here.
Important
Meshes are unstructured. The mesh elements - represented in the coordinate and connectivity arrays detailed above - are enumerated along a single unstructured dimension. An element’s position along this dimension has nothing to do with its spatial position.
A data variable associated with a mesh has a location of either node
,
edge
, face
or volume
. The data is stored in a 1D array with one
datum per element, matched to its element by matching the datum index with the
coordinate or connectivity index along the unstructured dimension. So for
an example data array called foo
:
foo[3]
would be at position (x[3], y[3])
if it were node-located, or at
faces[3]
if it were face-located. Data on an unstructured mesh visualises an
example of what is described above.
The mesh model also supports edges/faces/volumes having associated ‘centre’ coordinates - to allow point data to be assigned to these elements. ‘Centre’ is just a convenience term - the points can exist anywhere within their respective elements. See Data can be assigned to mesh edge/face/volume ‘centres’ for a visualised example.
Mesh Flexibility#
Above we have seen how one could replicate data on a structured grid using a mesh instead. But the utility of a mesh is the extra flexibility it offers. Here are the main examples:
Every node is completely independent - every one can have unique X andY (and Z) coordinate values. See Every mesh node is completely independent.
Faces and volumes can have variable node counts, i.e. different numbers of sides. This is achieved by masking the unused ‘slots’ in the connectivity array. See Mesh faces can have different node counts (using masking).
Data can be assigned to lines (edges) just as easily as points (nodes) or areas (faces). See Data can be assigned to mesh edges.
What does this mean?#
Meshes can represent much more varied spatial arrangements#
The highly specific way of recording position (geometry) and shape (topology) allows meshes to represent essentially any spatial arrangement of data. There are therefore many new applications that aren’t possible using a structured grid, including:
Mesh ‘payload’ is much larger than with structured grids#
Coordinates are recorded per-node, and connectivities are recorded per-element. This is opposed to a structured grid, where a single coordinate value is shared by every data point/area along that line.
For example: representing the surface of a cubed-sphere using a mesh leads to coordinates and connectivities being ~8 times larger than the data itself, as opposed to a small fraction of the data size when dividing a spherical surface using a structured grid of longitudes and latitudes.
This further increases the emphasis on lazy loading and processing of data using packages such as Dask.
Note
The large, 1D data arrays associated with meshes are a very different
shape to what Iris users and developers are used to. It is suspected
that optimal performance will need new chunking strategies, but at time
of writing (Jan 2022
) experience is still limited.
Spatial operations on mesh data are more complex#
Detail: Working with Mesh Data
Indexing a mesh data array cannot be used for:
Region selection
Neighbour identification
This is because - unlike with a structured data array - relative position in a mesh’s 1-dimensional data arrays has no relation to relative position in space. We must instead perform specialised operations using the information in the mesh’s connectivities, or by translating the mesh into a format designed for mesh analysis such as VTK.
Such calculations can still be optimised to avoid them slowing workflows, but the important take-away here is that adaptation is needed when working mesh data.
How Iris Represents This#
See also
Remember this is a prose summary. Precise documentation is at:
iris.experimental.ugrid
.
Note
At time of writing (Jan 2022
), neither 3D meshes nor 3D elements
(volumes) are supported.
The Basics#
The Iris Cube
has several new members:
- The
iris.experimental.ugrid.Mesh
that describes theCube
's horizontal geography.
These members will all be None
for a Cube
with no
associated Mesh
.
This Cube
's unstructured dimension has multiple attached
iris.experimental.ugrid.MeshCoord
s (one for each axis e.g.
x
/y
), which can be used to infer the points and bounds of any index on
the Cube
's unstructured dimension.
>>> print(edge_cube)
edge_data / (K) (-- : 6; height: 3)
Dimension coordinates:
height - x
Mesh coordinates:
latitude x -
longitude x -
Mesh:
name my_mesh
location edge
>>> print(edge_cube.location)
edge
>>> print(edge_cube.mesh_dim())
0
>>> print(edge_cube.mesh.summary(shorten=True))
<Mesh: 'my_mesh'>
The Detail#
How UGRID information is stored#
-
- The maximum dimensionality of shape (1D=edge, 2D=face) supported by this
Mesh
. Determines whichConnectivity
s are required/optional (see below). 1-3 collections of
iris.coords.AuxCoord
s:- Required:
node_coords
The nodes that are the basis for the mesh. - Optional:
edge_coords
,face_coords
For indicating the ‘centres’ of the edges/faces.
1 or more
iris.experimental.ugrid.Connectivity
s:- Required for 1D (edge) elements:
edge_node_connectivity
Define the edges by connecting nodes. - Required for 2D (face) elements:
face_node_connectivity
Define the faces by connecting nodes. Optional: any other connectivity type. See
iris.experimental.ugrid.mesh.Connectivity.UGRID_CF_ROLES
for the full list of types.
>>> print(edge_cube.mesh)
Mesh : 'my_mesh'
topology_dimension: 2
node
node_dimension: 'Mesh2d_node'
node coordinates
<AuxCoord: longitude / (degrees_east) [...] shape(5,)>
<AuxCoord: latitude / (degrees_north) [...] shape(5,)>
edge
edge_dimension: 'Mesh2d_edge'
edge_node_connectivity: <Connectivity: unknown / (unknown) [...] shape(6, 2)>
edge coordinates
<AuxCoord: longitude / (degrees_east) [...] shape(6,)>
<AuxCoord: latitude / (degrees_north) [...] shape(6,)>
face
face_dimension: 'Mesh2d_face'
face_node_connectivity: <Connectivity: unknown / (unknown) [...] shape(2, 4)>
face coordinates
<AuxCoord: longitude / (degrees_east) [...] shape(2,)>
<AuxCoord: latitude / (degrees_north) [...] shape(2,)>
long_name: 'my_mesh'
- Described in detail in MeshCoords.Stores the following information:
MeshCoords#
Links a Cube
to a Mesh
by
attaching to the Cube
's unstructured dimension, in the
same way that all Coord
s attach to
Cube
dimensions. This allows a single
Cube
to have a combination of unstructured and structured
dimensions (e.g. horizontal mesh plus vertical levels and a time series),
using the same logic for every dimension.
MeshCoord
s are instantiated using a given
Mesh
, location
(“node”/”edge”/”face”) and axis
. The process interprets the
Mesh
's
node_coords
and if appropriate the
edge_node_connectivity
/
face_node_connectivity
and
edge_coords
/
face_coords
to produce a Coord
points
and bounds
representation of all the Mesh
's
nodes/edges/faces for the given axis.
The method iris.experimental.ugrid.Mesh.to_MeshCoords()
is available to
create a MeshCoord
for
every axis represented by that Mesh
,
given only the location
argument
>>> for coord in edge_cube.coords(mesh_coords=True):
... print(coord)
MeshCoord : latitude / (degrees_north)
mesh: <Mesh: 'my_mesh'>
location: 'edge'
points: [3. , 1.5, 1.5, 1.5, 0. , 0. ]
bounds: [
[3., 3.],
[3., 0.],
[3., 0.],
[3., 0.],
[0., 0.],
[0., 0.]]
shape: (6,) bounds(6, 2)
dtype: float64
standard_name: 'latitude'
axis: 'y'
MeshCoord : longitude / (degrees_east)
mesh: <Mesh: 'my_mesh'>
location: 'edge'
points: [2.5, 0. , 5. , 6.5, 2.5, 6.5]
bounds: [
[0., 5.],
[0., 0.],
[5., 5.],
[5., 8.],
[0., 5.],
[5., 8.]]
shape: (6,) bounds(6, 2)
dtype: float64
standard_name: 'longitude'
axis: 'x'