Skip to content

Commit

Permalink
Add ugrid dimension info and marker to cube repr (and html repr)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbdreyer committed Mar 20, 2020
1 parent fbdd5ec commit 8cefdf4
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 15 deletions.
84 changes: 75 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,28 @@ 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)
if ugrid_mesh.topology_dimension:
summary += "\n{pad:{width}}topology_dimension: {val}".format(
pad=" ",
width=indent,
val=ugrid_mesh.topology_dimension,
)
if ugrid_mesh.node_coordinates:
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
49 changes: 46 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,17 @@


class CubeUgrid(
namedtuple("CubeUgrid", ["cube_dim", "grid", "mesh_location"])
namedtuple(
"CubeUgrid",
[
"cube_dim",
"grid",
"mesh_location",
"topology_dimension",
"node_coordinates",
],
defaults=(None,) * 2,
)
):
"""
Object recording the unstructured grid dimension of a cube.
Expand All @@ -64,13 +74,28 @@ class CubeUgrid(
Which element of the mesh the cube is mapped to.
Can be 'face', 'edge' or 'node'. A 'volume' is not supported.
Optional:
* topology_dimension (int):
The highest dimensionality of the geometric elements in the mesh. Defaults to None.
* node_coordinates (string):
A list of the names of the spatial coordinates, used to geolocate the nodes. Defaults to None.
"""

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)
if self.topology_dimension:
result += '\n topology_dimension "{}" :'.format(
self.topology_dimension
)
if self.node_coordinates:
result += '\n node_coordinates "{}" :\n'.format(
" ".join(self.node_coordinates)
)
try:
mesh_str = str(self.grid.info)
except TypeError:
Expand All @@ -79,6 +104,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 +216,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"))
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

0 comments on commit 8cefdf4

Please sign in to comment.