From 52c180972c9d70fa7195d1981a894ed709e05503 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Tue, 30 Mar 2021 08:43:47 -0500 Subject: [PATCH 1/6] Add mesh schema objects. --- gridded/pyugrid/ugrid.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/gridded/pyugrid/ugrid.py b/gridded/pyugrid/ugrid.py index be6ec1c..6a871b0 100644 --- a/gridded/pyugrid/ugrid.py +++ b/gridded/pyugrid/ugrid.py @@ -19,9 +19,11 @@ from __future__ import (absolute_import, division, print_function) import hashlib +from itertools import chain from collections import OrderedDict import numpy as np +import netCDF4 import gridded.pyugrid.read_netcdf as read_netcdf from gridded.pyugrid.util import point_in_tri @@ -1146,3 +1148,63 @@ def _save_variables(self, nclocal, variables): # Add the extra attributes. for att_name, att_value in var.attributes.items(): setattr(data_var, att_name, att_value) + + +class Mesh(dict): + REQUIRED = ('cf_role', 'topology_dimension', 'node_coordinates') + def __init__(self): + super().__init__() + self['cf_role'] = 'mesh_topology' + self['data_model'] = 'NETCDF4' + + + # populate required keys and fill with None + self.update(dict.fromkeys(self.REQUIRED, None)) + self['dimension'] = {} + self['variables'] = {} + + @classmethod + def from_nc(cls, ncfile): + rv = cls() + + rv['data_model'] = ncfile.data_model + + # find the first mesh variable + # What to do when error condition exists? + mesh = ncfile.get_variables_by_attributes(cf_role="mesh_topology")[0] + rv['name'] = mesh.name + + # Ensure that the required attributes get copied (or raise an error) + for k in set(chain(rv.REQUIRED, mesh.ncattrs())): + rv[k] = getattr(mesh, k) + + rv['dimensions'] = dimensions = {} + rv['variables'] = variables = {} + + for x in ncfile.dimensions.values(): + dimensions[x.name] = Dimension.from_ncdimension(x) + + for x in ncfile.variables.values(): + variables[x.name] = Variable.from_ncvariable(x) + + return rv + +class Dimension(dict): + @classmethod + def from_ncdimension(cls, dimension): + assert isinstance(dimension, netCDF4.Dimension) + rv = cls() + rv['name'] = dimension.name + rv['size'] = dimension.size + return rv + +class Variable(dict): + @classmethod + def from_ncvariable(cls, variable): + assert isinstance(variable, netCDF4.Variable) + rv = cls() + rv['name'] = variable.name + rv['dimension'] = variable.dimension + rv['dtype'] = variable.dtype + rv['attrs'] = dict((v, getattr(variable, v)) for v in variable.ncattrs()) + return rv From 807093583926e79b434560973f2a00f74b4d3da3 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Tue, 30 Mar 2021 08:45:39 -0500 Subject: [PATCH 2/6] Reuse dimension and variable mappings. --- gridded/pyugrid/ugrid.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gridded/pyugrid/ugrid.py b/gridded/pyugrid/ugrid.py index 6a871b0..d2f1d1f 100644 --- a/gridded/pyugrid/ugrid.py +++ b/gridded/pyugrid/ugrid.py @@ -1173,13 +1173,14 @@ def from_nc(cls, ncfile): # What to do when error condition exists? mesh = ncfile.get_variables_by_attributes(cf_role="mesh_topology")[0] rv['name'] = mesh.name - + # Ensure that the required attributes get copied (or raise an error) for k in set(chain(rv.REQUIRED, mesh.ncattrs())): rv[k] = getattr(mesh, k) - rv['dimensions'] = dimensions = {} - rv['variables'] = variables = {} + + dimensions = rv['dimensions'] + variables = rv['variables'] for x in ncfile.dimensions.values(): dimensions[x.name] = Dimension.from_ncdimension(x) From 35f6990d60413306ce2e130c7ee468b4297d7118 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Wed, 31 Mar 2021 19:50:40 -0500 Subject: [PATCH 3/6] Init dictionary with values. --- gridded/pyugrid/ugrid.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gridded/pyugrid/ugrid.py b/gridded/pyugrid/ugrid.py index d2f1d1f..8f92f31 100644 --- a/gridded/pyugrid/ugrid.py +++ b/gridded/pyugrid/ugrid.py @@ -1154,6 +1154,9 @@ class Mesh(dict): REQUIRED = ('cf_role', 'topology_dimension', 'node_coordinates') def __init__(self): super().__init__() + self.update(dict.fromkeys(self.REQUIRED, None)) + self['name'] = name + self['data_model'] = 'NETCDF4' self['cf_role'] = 'mesh_topology' self['data_model'] = 'NETCDF4' @@ -1191,6 +1194,9 @@ def from_nc(cls, ncfile): return rv class Dimension(dict): + def __init__(self, **kwargs): + super().__init__(**kwargs) + @classmethod def from_ncdimension(cls, dimension): assert isinstance(dimension, netCDF4.Dimension) @@ -1200,6 +1206,9 @@ def from_ncdimension(cls, dimension): return rv class Variable(dict): + def __init__(self, **kwargs): + super().__init__(**kwargs) + @classmethod def from_ncvariable(cls, variable): assert isinstance(variable, netCDF4.Variable) From e1e277d41a1d44b5602a466abcb03da075e9b8e1 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Wed, 31 Mar 2021 19:51:51 -0500 Subject: [PATCH 4/6] Updates to Mesh classes. --- gridded/pyugrid/ugrid.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/gridded/pyugrid/ugrid.py b/gridded/pyugrid/ugrid.py index 8f92f31..5643beb 100644 --- a/gridded/pyugrid/ugrid.py +++ b/gridded/pyugrid/ugrid.py @@ -1152,17 +1152,14 @@ def _save_variables(self, nclocal, variables): class Mesh(dict): REQUIRED = ('cf_role', 'topology_dimension', 'node_coordinates') - def __init__(self): + FILTER = ('data_model', 'dimensions', 'variables', 'name') + def __init__(self, name=None): super().__init__() self.update(dict.fromkeys(self.REQUIRED, None)) self['name'] = name self['data_model'] = 'NETCDF4' self['cf_role'] = 'mesh_topology' - self['data_model'] = 'NETCDF4' - - # populate required keys and fill with None - self.update(dict.fromkeys(self.REQUIRED, None)) self['dimension'] = {} self['variables'] = {} @@ -1193,6 +1190,29 @@ def from_nc(cls, ncfile): return rv + def add_dimension(self, name, size): + dim = Dimension(name=name, size=size) + self['dimensions'][name] = dim + + def add_variable(self, name, dtype, dimension): + v = Variable(name=name, dtype=dtype, dimension=dimension) + self['variables'][name] = v + + def get_attribute(self, attr): + for var in self.keys() - self.FILTER: + value = self[var] + if attr == value or (isinstance(value, list) and attr in value): + return var + + def get_values(self, attr, mesh): + var = self.get_attribute(attr) + + values = getattr(mesh, var) + if isinstance(self[var], list): + index = self[var].index(attr) + return values[:, index] + return values + class Dimension(dict): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -1200,9 +1220,7 @@ def __init__(self, **kwargs): @classmethod def from_ncdimension(cls, dimension): assert isinstance(dimension, netCDF4.Dimension) - rv = cls() - rv['name'] = dimension.name - rv['size'] = dimension.size + rv = cls(name=dimension.name, size=dimension.size) return rv class Variable(dict): From da8111ef726dcc6c657207ebe783868a37fec5ee Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Thu, 1 Apr 2021 12:18:17 -0500 Subject: [PATCH 5/6] Add node_coordinates alias --- gridded/pyugrid/ugrid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gridded/pyugrid/ugrid.py b/gridded/pyugrid/ugrid.py index 5643beb..32577df 100644 --- a/gridded/pyugrid/ugrid.py +++ b/gridded/pyugrid/ugrid.py @@ -237,6 +237,8 @@ def num_vertices(self): def nodes(self): return self._nodes + node_coordinates = nodes + @property def node_lon(self): return self._nodes[:, 0] From a6a5e020c650e6cbe5756f93b56ca7cc496bb5b7 Mon Sep 17 00:00:00 2001 From: Ryan Grout Date: Thu, 1 Apr 2021 12:25:10 -0500 Subject: [PATCH 6/6] factor out key filter --- gridded/pyugrid/ugrid.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gridded/pyugrid/ugrid.py b/gridded/pyugrid/ugrid.py index 32577df..0bf9cca 100644 --- a/gridded/pyugrid/ugrid.py +++ b/gridded/pyugrid/ugrid.py @@ -1200,15 +1200,21 @@ def add_variable(self, name, dtype, dimension): v = Variable(name=name, dtype=dtype, dimension=dimension) self['variables'][name] = v + def _filtered_keys(self): + return self.keys() - self.FILTER + def get_attribute(self, attr): - for var in self.keys() - self.FILTER: + for var in self._filtered_keys(): value = self[var] if attr == value or (isinstance(value, list) and attr in value): return var def get_values(self, attr, mesh): - var = self.get_attribute(attr) - + if attr in self._filtered_keys(): + var = attr + else: + var = self.get_attribute(attr) + values = getattr(mesh, var) if isinstance(self[var], list): index = self[var].index(attr)