Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 71 additions & 9 deletions lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -2233,6 +2233,11 @@ def summary(self, shorten=False, name_padding=35):
versus length and optionally relevant coordinate information.

"""
try:
ugrid_mesh = self.ugrid
except AttributeError:
ugrid_mesh = None

# Create a set to contain the axis names for each data dimension.
dim_names = [set() for dim in range(len(self.shape))]

Expand All @@ -2241,10 +2246,16 @@ def summary(self, shorten=False, name_padding=35):
for dim in range(len(self.shape)):
dim_coords = self.coords(contains_dimension=dim, dim_coords=True)
if dim_coords:
dim_names[dim].add(dim_coords[0].name())
dim_name = dim_coords[0].name()
else:
dim_names[dim].add("-- ")
dim_name = "-- "

if ugrid_mesh:
# Identify the unstructured dimension with an `*`.
if dim == ugrid_mesh.cube_dim:
dim_name = "*" + dim_name

dim_names[dim].add(dim_name)
# Convert axes sets to lists and sort.
dim_names = [sorted(names, key=sorted_axes) for names in dim_names]

Expand Down Expand Up @@ -2336,6 +2347,7 @@ def vector_summary(
max_line_offset,
cell_measures=None,
ancillary_variables=None,
ugrid_mesh=None,
):
"""
Generates a list of suitably aligned strings containing coord
Expand All @@ -2351,6 +2363,10 @@ def vector_summary(
cell_measures = []
if ancillary_variables is None:
ancillary_variables = []
if ugrid_mesh is None:
ugrid_mesh = []
else:
ugrid_mesh = [ugrid_mesh]
vector_summary = []
vectors = []

Expand All @@ -2366,7 +2382,10 @@ def vector_summary(
# Generate basic textual summary for each vector coordinate
# - WITHOUT dimension markers.
for dim_meta in (
vector_coords + cell_measures + ancillary_variables
vector_coords
+ cell_measures
+ ancillary_variables
+ ugrid_mesh
):
vector_summary.append(
"%*s%s"
Expand Down Expand Up @@ -2427,17 +2446,42 @@ def vector_summary(
)
vector_summary[index] += line
vectors = vectors + ancillary_variables
# Interleave any extra lines that are needed to distinguish
# the coordinates.
vector_summary = self._summary_extra(
vectors, vector_summary, extra_indent
)
if ugrid_mesh:
# Generate full textual summary for the ugrid mesh - WITH
# dimension markers.
# Note: Only one ugrid_mesh, which maps along single
# dimension, should exist.
index = 0
dims = [ugrid_mesh[index].cube_dim]
for dim in range(len(self.shape)):
width = alignment[dim] - len(vector_summary[index])
char = "x" if dim in dims else "-"
line = "{pad:{width}}{char}".format(
pad=" ", width=width, char=char
)
vector_summary[index] += line

if vector_coords:
# Interleave any extra lines that are needed to distinguish
# the coordinates.
# TODO: This should also be done for cell measures and
# ancillary variables.
vector_summary = self._summary_extra(
vectors, vector_summary, extra_indent
)

return vector_summary, cube_header

# Calculate the maximum line offset.
max_line_offset = 0
for coord in all_coords:
dimension_metadata_to_check = (
list(all_coords)
+ vector_cell_measures
+ vector_ancillary_variables
)
if ugrid_mesh:
dimension_metadata_to_check += [ugrid_mesh]
for coord in dimension_metadata_to_check:
max_line_offset = max(
max_line_offset,
len(
Expand Down Expand Up @@ -2500,6 +2544,24 @@ def vector_summary(
summary += "\n Ancillary variables:\n"
summary += "\n".join(ancillary_variable_summary)

#
# Generate summary of ugrid mesh object.
#
if ugrid_mesh:
ugrid_mesh_summary, cube_header = vector_summary(
[], cube_header, max_line_offset, ugrid_mesh=ugrid_mesh,
)
summary += "\n ugrid information:\n"
summary += "\n".join(ugrid_mesh_summary)
summary += "\n{pad:{width}}topology_dimension: {val}".format(
pad=" ", width=indent, val=ugrid_mesh.topology_dimension,
)
summary += "\n{pad:{width}}node_coordinates: {val}".format(
pad=" ",
width=indent,
val=" ".join(ugrid_mesh.node_coordinates),
)

#
# Generate textual summary of cube scalar coordinates.
#
Expand Down
20 changes: 17 additions & 3 deletions lib/iris/experimental/representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(self, cube):
"Derived coordinates:": None,
"Cell measures:": None,
"Ancillary variables:": None,
"ugrid information:": None,
"Scalar coordinates:": None,
"Scalar cell measures:": None,
"Attributes:": None,
Expand All @@ -102,6 +103,7 @@ def __init__(self, cube):
"Derived coordinates:",
"Cell measures:",
"Ancillary variables:",
"ugrid information:",
]

self.two_cell_headers = ["Scalar coordinates:", "Attributes:"]
Expand All @@ -123,6 +125,11 @@ def _get_dim_names(self):
Note: borrows from `cube.summary`.

"""
try:
ugrid_mesh = self.cube.ugrid
except AttributeError:
ugrid_mesh = None

# Create a set to contain the axis names for each data dimension.
dim_names = list(range(len(self.cube.shape)))

Expand All @@ -133,9 +140,16 @@ def _get_dim_names(self):
contains_dimension=dim, dim_coords=True
)
if dim_coords:
dim_names[dim] = dim_coords[0].name()
dim_name = dim_coords[0].name()
else:
dim_names[dim] = "--"
dim_name = "--"

if ugrid_mesh:
# Identify the unstructured dimension with an `*`.
if dim == ugrid_mesh.cube_dim:
dim_name = "*" + dim_name

dim_names[dim] = dim_name
return dim_names

def _dim_names(self):
Expand Down Expand Up @@ -285,7 +299,7 @@ def _make_content(self):
for line in v:
# Add every other row in the sub-heading.
if k in self.dim_desc_coords:
body = re.findall(r"[\w-]+", line)
body = re.findall(r"[\w\.-]+", line)
title = body.pop(0)
colspan = 0
elif k in self.two_cell_headers:
Expand Down
45 changes: 42 additions & 3 deletions lib/iris/fileformats/ugrid_cf_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@


class CubeUgrid(
namedtuple("CubeUgrid", ["cube_dim", "grid", "mesh_location"])
namedtuple(
"CubeUgrid",
[
"cube_dim",
"grid",
"mesh_location",
"topology_dimension",
"node_coordinates",
],
)
):
"""
Object recording the unstructured grid dimension of a cube.
Expand All @@ -64,13 +73,25 @@ class CubeUgrid(
Which element of the mesh the cube is mapped to.
Can be 'face', 'edge' or 'node'. A 'volume' is not supported.

* topology_dimension (int):
The highest dimensionality of the geometric elements in the mesh.

* node_coordinates (set):
A set of the names of the spatial coordinates, used to geolocate the nodes.

"""

def __str__(self):
result = "Cube unstructured-grid dimension:"
result += "\n cube dimension = {}".format(self.cube_dim)
result += '\n mesh_location = "{}"'.format(self.mesh_location)
result += '\n mesh "{}" :\n'.format(self.grid.mesh_name)
result += '\n mesh "{}" :'.format(self.grid.mesh_name)
result += '\n topology_dimension "{}" :'.format(
self.topology_dimension
)
result += '\n node_coordinates "{}" :\n'.format(
" ".join(self.node_coordinates)
)
try:
mesh_str = str(self.grid.info)
except TypeError:
Expand All @@ -79,6 +100,9 @@ def __str__(self):
result += "\n"
return result

def name(self):
return ".".join([self.grid.mesh_name, self.mesh_location])


class UGridCFReader:
"""
Expand Down Expand Up @@ -188,8 +212,23 @@ def complete_ugrid_cube(self, cube):
raise ValueError(msg.format(meshes_info))
if meshes_info:
i_dim, (mesh, mesh_location) = meshes_info[0]
mesh_var = self.dataset.variables[mesh.mesh_name]

topology_dimension = mesh_var.getncattr("topology_dimension")
node_coordinates = []
for node_var_name in mesh_var.getncattr("node_coordinates").split(
" "
):
node_var = self.dataset.variables[node_var_name]
node_coordinates.append(node_var.getncattr("standard_name"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help feeling we should be applying the same here as in Iris --> standard_name or long_name or var_name.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was a bit unsure about this bit. I'll do the different naming as you suggest.

node_coordinates = set(node_coordinates)

cube.ugrid = CubeUgrid(
cube_dim=i_dim, grid=mesh, mesh_location=mesh_location
cube_dim=i_dim,
grid=mesh,
mesh_location=mesh_location,
topology_dimension=topology_dimension,
node_coordinates=node_coordinates,
)
else:
# Add an empty 'cube.ugrid' to all cubes otherwise.
Expand Down
4 changes: 4 additions & 0 deletions lib/iris/tests/integration/ugrid_cf_reader/test_ugrid_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def test_basic_load(self):
self.assertIsInstance(cubegrid, CubeUgrid)
self.assertEqual(cubegrid.cube_dim, 2)
self.assertEqual(cubegrid.mesh_location, "node")
self.assertEqual(cubegrid.topology_dimension, 2)
self.assertEqual(
cubegrid.node_coordinates, set(["latitude", "longitude"])
)

# Check cube.ugrid.grid : a gridded Grid type.
ugrid = cubegrid.grid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,11 @@ def setUp(self):
self.representer._get_bits(self.representer._get_lines())

def test_population(self):
for v in self.representer.str_headings.values():
self.assertIsNotNone(v)
for k, v in self.representer.str_headings.items():
if k == "ugrid information:":
self.assertIsNone(v)
else:
self.assertIsNotNone(v)

def test_headings__dimcoords(self):
contents = self.representer.str_headings["Dimension coordinates:"]
Expand Down