from warnings import warn
from collections import OrderedDict
import numpy as np
[docs]class PseudoNetCDFVariable(np.ndarray):
"""
PseudoNetCDFVariable presents the Scientific.IO.NetCDF.NetCDFVariable
interface, but unlike that type, provides a contructor for variables
that could be used without adding it to the parent file
"""
__array_priority__ = 10000000.
[docs] def xarray(self, iscoord=False):
"""
Experimental function
Returns
-------
out : xarray.DataArray
object with dimensions and coordinates
dims : string are set by self.get_coords()
coords : are set by self.get_coords()
Notes
-----
When data is 2-d, the dimensions
"""
import xarray as xr
myname = self._name
if iscoord:
coordd = {}
else:
coordd = dict([(k, self.get_coord(k))
for k in self.get_coord_names() if k != myname])
attrs = dict([(k, getattr(self, k)) for k in self.ncattrs()])
return xr.DataArray(self[:], dims=self.dimensions, coords=coordd,
attrs=attrs)
def get_coord_names(self):
dims = list(self.dimensions)
ndims = len(dims)
coords = getattr(self, 'coordinates', '').split()
ncoords = len(coords)
maxdims = max(ndims, ncoords)
coords = ((ndims * [None]) + coords)[-maxdims:]
dims = (dims + ncoords * [None])[:maxdims]
coords = [dimn if coordn is None else coordn for dimn,
coordn in zip(dims, coords)]
return coords
def get_coord(self, coordn):
if coordn in self._parent.variables:
v = self._parent.variables[coordn]
if len(v.dimensions) > 1 and coordn in v.dimensions:
warn(coordn + ' has dimensions ' + str(v.dimensions) +
'; so it will use a standard linear coordinate. ' +
'Consider manually adding coords')
coordi = list(self.dimensions).index(coordn)
coordv = np.arange(self.shape[coordi])
else:
coordv = (v.dimensions, v.xarray(iscoord=True))
else:
coordi = list(self.dimensions).index(coordn)
coordv = np.arange(self.shape[coordi])
return coordv
def get_coords(self):
coords = self.get_coord_names()
coordsd = {}
for coordn in coords:
coordsd[coordn] = self.get_coord(coordn)
return coordsd
def __repr__(self):
out = str(self)
return object.__repr__(self).replace(' at ', '\n' + out + ' at ')
def __str__(self):
namekeys = ['_name', 'name', 'standard_name', 'long_name']
if hasattr(self, '_name'):
var_name = getattr(self, '_name').strip()
else:
for nck in self.ncattrs():
if 'name' in nck:
namekeys.append(nck)
for nck in namekeys:
if hasattr(self, nck):
var_name = getattr(self, nck).strip()
break
else:
var_name = 'unknown'
var_type = dict(float32='float',
float64='double',
int32='integer',
uint32='integer',
int64='long',
bool='bool',
string8='char',
string80='char').get(self.dtype.name, self.dtype.name)
out = ""
indent = " "
startindent = ""
out += (
startindent + 1 * indent +
("%s %s%s; // shape: %s\n" % (var_type, var_name,
str(self.dimensions)
.replace('u\'', '')
.replace('\'', '')
.replace(',)', ')'),
str(self.shape))))
for prop_name in self.ncattrs():
prop = getattr(self, prop_name)
out += startindent + 2 * indent + \
("%s:%s = %s ;\n" %
(var_name, prop_name, repr(prop).replace("'", '"')))
out += 'array: '
out += self.array().__str__()
return out
[docs] def array(self):
"""
Returns
-------
out : numpy.ndarray
parent type view object
"""
return self.view(type=np.ndarray)
def __setattr__(self, k, v):
"""
Set attributes (aka properties) and identify user-defined attributes.
"""
if k[:1] != '_' and \
k not in ('dimensions', 'typecode'):
if k not in self._ncattrs:
self._ncattrs += (k, )
object.__setattr__(self, k, v)
def __delattr__(self, k):
if k in self._ncattrs:
self._ncattrs = tuple([k_ for k_ in self._ncattrs if k_ != k])
object.__delattr__(self, k)
def setncattr(self, k, v):
return setattr(self, k, v)
def getncattr(self, k):
if k in self._ncattrs:
return getattr(self, k)
else:
raise AttributeError(f'{k} Attribute not found')
def setncatts(self, attdict):
for pk, pv in attdict.items():
setattr(self, pk, pv)
def getncatts(self):
outd = OrderedDict()
for pk in self.ncattrs():
outd[pk] = getattr(self, pk)
return outd
[docs] def ncattrs(self):
"""
Returns
-------
ncattrs : tuple of strings
attributes that have been user defined
"""
return self._ncattrs
def __new__(subtype, parent, name, typecode, dimensions, **kwds):
"""
Creates a variable using the dimensions as defined in
the parent object
Parameters
----------
parent : PseudoNetCDFFile
an object with a dimensions variable
name: string
name for variable
typecode: string or numpy.dtype
numpy style typecode
dimensions: tuple of strings
dimension names to be used from parent
**kwds: keywords
Dictionary of keywords to be added as properties to the variable.
The keyword 'values' is a special case that will be used as the
starting values of the array
"""
if 'values' in kwds.keys():
result = kwds.pop('values')
typecode = result.dtype.char
# Adding easy default dimension object;
# Avoiding PseudoNetCDFFile due to recursive dependence
if parent is None:
from ._files import PseudoNetCDFFile
parent = PseudoNetCDFFile()
for dk, dl in zip(dimensions, result.shape):
parent.createDimension(dk, dl)
else:
shape = []
for d in dimensions:
dim = parent.dimensions[d]
# Adding support for netCDF3 dimension objects
if not isinstance(dim, int):
dim = len(dim)
shape.append(dim)
result = np.zeros(shape, typecode)
result = result[...].view(subtype)
result.typecode = lambda: typecode
result.dimensions = tuple(dimensions)
result._ncattrs = ()
result._parent = parent
result._name = name
for k, v in kwds.items():
setattr(result, k, v)
return result
def __array_finalize__(self, obj):
if obj is None:
return
_parent = getattr(obj, '_parent', object())
object.__setattr__(self, '_parent', _parent)
_name = getattr(obj, '_name', 'unknown')
object.__setattr__(self, '_name', _name)
ntypecode = getattr(obj, 'typecode', lambda: self.dtype.char)
object.__setattr__(self, 'typecode', ntypecode)
ndimensions = getattr(obj, 'dimensions', lambda: self.dtype.char)
object.__setattr__(self, 'dimensions', ndimensions)
nncattrs = getattr(obj, '_ncattrs', getattr(self, '_ncattrs', ()))
object.__setattr__(self, '_ncattrs', nncattrs)
if hasattr(obj, '_ncattrs'):
for k in nncattrs:
if not hasattr(self, k):
object.__setattr__(self, k, getattr(obj, k))
[docs] def swapaxes(self, a1, a2):
out = np.ndarray.swapaxes(self, a1, a2)
newdims = list(self.dimensions)
newdims[a1] = self.dimensions[a2]
newdims[a2] = self.dimensions[a1]
out.dimensions = tuple(newdims)
return out
[docs] def getValue(self):
"""
Returns
-------
item : scalar
value
"""
return self.item()
[docs] def assignValue(self, value):
"""
Parameters
----------
value : scalar
assign value to scalar variable
"""
self.itemset(value)
[docs]class PseudoNetCDFMaskedVariable(PseudoNetCDFVariable, np.ma.MaskedArray):
__array_priority__ = 1000000000.
def __new__(subtype, parent, name, typecode='f', dimensions=(), **kwds):
"""
Creates a variable using the dimensions as defined in
the parent object
parent: PseudoNetCDFFile
an object with a dimensions variable
name: string
name for variable
typecode: string or numpy.dtype
numpy style typecode
dimensions: tuple of strings
dimension names to be used from parent
**kwds: dictionary
keywords to be added as properties to the variable. The keyword
'values' is a special case that will be used as the starting
values of the array
"""
if 'values' in kwds.keys():
result = kwds.pop('values')
else:
shape = []
for d in dimensions:
dim = parent.dimensions[d]
# Adding support for netCDF3 dimension objects
if not isinstance(dim, int):
dim = len(dim)
shape.append(dim)
fill_value = kwds.get('fill_value', None)
if fill_value is None:
for pk in ('fill_value', 'missing_value', '_FillValue'):
fill_value = kwds.get(pk, None)
if fill_value is not None:
break
result = np.ma.zeros(shape, dtype='S1' if typecode ==
'c' else typecode,
fill_value=fill_value)
result = result.view(subtype)
result._ncattrs = ()
result._parent = parent
result._name = name
result.typecode = lambda: typecode
result.dimensions = tuple(dimensions)
for k, v in kwds.items():
setattr(result, k, v)
return result
[docs] def array(self):
"""
Returns
-------
out : numpy.ma.masked_array
parent type view object
"""
return self.view(type=np.ma.masked_array)
def __array_finalize__(self, obj):
np.ma.MaskedArray.__array_finalize__(self, obj)
def _update_from(self, obj):
dt = self.dtype.char
self.typecode = getattr(
obj, 'typecode', lambda: ('c' if dt == 'S' else dt))
self.dimensions = getattr(
obj, 'dimensions', getattr(self, 'dimensions', ()))
self._ncattrs = getattr(obj, '_ncattrs', getattr(self, '_ncattrs', ()))
self._fill_value = getattr(
obj, '_fill_value', getattr(self, '_fill_value', -999))
if hasattr(obj, '_ncattrs'):
for k in obj._ncattrs:
if k in ('fill_value',):
continue
setattr(self, k, getattr(obj, k))
np.ma.MaskedArray._update_from(self, obj)
def __getitem__(self, item):
out = np.ma.MaskedArray.__getitem__(self, item)
try:
out._fill_value = self._fill_value
except Exception:
pass
out = out.view(PseudoNetCDFMaskedVariable)
if np.isscalar(out):
return out
if hasattr(self, 'dimensions'):
out.dimensions = self.dimensions
if hasattr(self, '_ncattrs'):
for k in self._ncattrs:
setattr(out, k, getattr(self, k))
else:
self._ncattrs = ()
return out
def __setattr__(self, k, v):
"""
Set attributes (aka properties) and identify user-defined attributes.
"""
if k[:1] != '_' and \
k not in ('dimensions', 'typecode', 'mask'):
if k not in self._ncattrs:
self._ncattrs += (k, )
np.ma.MaskedArray.__setattr__(self, k, v)
def __delattr__(self, k):
if k in self._ncattrs:
self._ncattrs = tuple([k_ for k_ in self._ncattrs if k_ != k])
object.__delattr__(self, k)
[docs] def swapaxes(self, a1, a2):
out = np.ma.masked_array.swapaxes(self, a1, a2)
newdims = list(self.dimensions)
newdims[a1] = self.dimensions[a2]
newdims[a2] = self.dimensions[a1]
out.dimensions = tuple(newdims)
return out
[docs] def ncattrs(self):
"""
Returns
-------
ncattrs : tuple of strings
attributes that have been user defined
"""
return self._ncattrs
[docs] def getValue(self):
"""
Returns
-------
out : scalar
value
"""
return self.item()
[docs] def assignValue(self, value):
"""
Parameters
----------
value : scalar
value to assign to scalar variable
"""
self.itemset(value)
[docs]def PseudoIOAPIVariable(parent, name, typecode, dimensions, **kwds):
"""
Creates a variable using the dimensions as defined in
the parent object
Parameters
----------
parent : PseudoNetCDFFile
an object with a dimensions variable
name: string
name for variable
typecode: string or numpy.dtype
numpy style typecode
dimensions: tuple of strings
dimension names to be used from parent
**kwds : dictionary
units: default = none
long_name: default = name
var_desc: default = name
"""
retval = PseudoNetCDFVariable(parent, name, typecode, dimensions, **kwds)
if 'units' not in kwds:
warn('IOAPI variables must have units; %s has been initialized ' +
'with "None" units')
retval.units = 'None'
if 'long_name' not in kwds:
retval.long_name = name.ljust(16)
if 'var_desc' not in kwds:
retval.var_desc = name.ljust(80)
return retval