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.
Figure 1 Data on a structured grid.#
1D coordinate arrays (pink circles) are combined to construct a structured grid of points (pink crosses). 2D bounds arrays (blue circles) can also be used to describe the 1D boundaries (blue lines) at either side of each rank of points; each point therefore having four bounds (x+y, upper+lower), together describing a quadrilateral area around that point. Data from the 2D data array (orange circles) can be assigned to these point locations (orange diamonds) or area locations (orange quads) by matching the relative positions in the data array to the relative spatial positions - see the black outlined shapes as examples of this in action.
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.
Figure 2 Data on an unstructured mesh#
1D coordinate arrays (pink circles) describe node positions in space (pink crosses). A 2D connectivity array (blue circles) describes faces by connecting four nodes - by referencing their indices - into a face outline (blue outlines on the map). Data from the 1D data array (orange circles) can be assigned to these node locations (orange diamonds) or face locations (orange quads) by matching the indices in the data array to the indices in the coordinate arrays (for nodes) or connectivity array (for faces). See the black outlined shapes as examples of index matching in action, and the black stippled shapes to demonstrate that relative array position confers no relative spatial information.
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.
Figure 3 Data can be assigned to mesh edge/face/volume ‘centres’#
1D node coordinate arrays (pink circles) describe node positions in space (pink crosses). A 2D connectivity array (blue circles) describes faces by connecting four nodes into a face outline (blue outlines on the map). Further 1D face coordinate arrays (pink circles) describe a ‘centre’ point position (pink stars) for each face enumerated in the connectivity array.
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.
Figure 4 Every mesh node is completely independent#
The same array shape and structure used to describe the node positions (pink crosses) in a regular grid (left-hand maps) is equally able to describe any position for these nodes (e.g. the right-hand maps), simply by changing the array values. The quadrilateral faces (blue outlines) can therefore be given any quadrilateral shape by re-positioning their constituent nodes.
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).
Figure 5 Mesh faces can have different node counts (using masking)#
The 2D connectivity array (blue circles) describes faces by connecting nodes (pink crosses) to make up a face (blue outlines). The faces can use different numbers of nodes by shaping the connectivity array to accommodate the face with the most nodes, then masking unused node ‘slots’ (black circles) for faces with fewer nodes than the maximum.
Data can be assigned to lines (edges) just as easily as points (nodes) or areas (faces). See Data can be assigned to mesh edges.
Figure 6 Data can be assigned to mesh edges#
The 2D connectivity array (blue circles) describes edges by connecting 2 nodes (pink crosses) to make up an edge (blue lines). Data can be assigned to the edges (orange lines) by matching the indices of the 1D data array (not shown) to the indices in the connectivity array.
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.mesh
.
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.mesh.MeshXY
that describes theCube
's horizontal geography.
These members will all be None
for a Cube
with no
associated MeshXY
.
This Cube
's unstructured dimension has multiple attached
iris.mesh.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))
<MeshXY: 'my_mesh'>
The Detail#
How UGRID information is stored#
-
- The maximum dimensionality of shape (1D=edge, 2D=face) supported by this
MeshXY
. 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.mesh.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.mesh.Connectivity.UGRID_CF_ROLES
for the full list of types.
>>> print(edge_cube.mesh)
MeshXY : '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'
MeshCoords#
Links a Cube
to a MeshXY
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
MeshXY
, location
(“node”/”edge”/”face”) and axis
. The process interprets the
MeshXY
'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 MeshXY
's
nodes/edges/faces for the given axis.
The method iris.mesh.MeshXY.to_MeshCoords()
is available to
create a MeshCoord
for
every axis represented by that MeshXY
,
given only the location
argument
>>> for coord in edge_cube.coords(mesh_coords=True):
... print(coord)
MeshCoord : latitude / (degrees_north)
mesh: <MeshXY: '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: <MeshXY: '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'