#!/usr/bin/env python
"""
Get variables from or print information of a netcdf file
This module was written by Matthias Cuntz while at Department of
Computational Hydrosystems, Helmholtz Centre for Environmental
Research - UFZ, Leipzig, Germany, and continued while at Institut
National de Recherche pour l'Agriculture, l'Alimentation et
l'Environnement (INRAE), Nancy, France.
:copyright: Copyright 2009-2022 Matthias Cuntz, Stephan Thober, see AUTHORS.rst for details.
:license: MIT License, see LICENSE for details.
.. moduleauthor:: Matthias Cuntz
The following functions are provided
.. autosummary::
infonetcdf
ncinfo
readnetcdf
ncread
History
* Written Jul 2009 by
Matthias Cuntz (mc (at) macu (dot) de)
* Removed quiet keyword, Jun 2012, Matthias Cuntz
* Ported to Python 3, Feb 2013, Matthias Cuntz
* Wrapper functions netcdfread, ncread, readnc, Oct 2013, Matthias Cuntz
* overwrite keyword, Apr 2014, Stephan Thober
* dims keyword, May 2014, Stephan Thober
* attributes keyword, Jun 2016, Stephan Thober
* Restrict overwrite to files with only one variables,
Aug 2016, Stephan Thober
* Do not count dimension variables in variable count for overwrite,
Oct 2016, Matthias Cuntz
* Remove wrappers netcdfread and readnc, Mar 2022, Matthias Cuntz
* Remove reform keyword, Mar 2022, Matthias Cuntz
* Put all info abilities into separate routine infonetcdf
with wrapper function ncinfo, Mar 2022, Matthias Cuntz
* Invert functions and wrapper functions, Feb 2023, Matthias Cuntz
"""
__all__ = ['infonetcdf', 'ncinfo',
'readnetcdf', 'ncread']
[docs]def ncinfo(ncfile,
var='', code=-1, dims=False, shape=False, attributes=False,
variables=False, codes=False,
long_names=False, units=False,
sort=False):
"""
Get information on variables in a netcdf file
Parameters
----------
ncfile : str
netCDF file name
var : str, optional
Variable name, only relevant if *dims* or *attributes* are True
*var* takes precedence over *code*.
code : int, optional
Variable code such as in files coming from GRIB,
only relevant if *dims* or *attributes* are True.
*var* takes precedence over *code*.
dims : bool, optional
Get tuple of dimension names for the variable with name *var*
or number *code*.
shape : bool, optional
Get shape of the variable with name *var* or number *code*.
attributes : bool, optional
Get dictionary of all attributes of variable with name *var* or number
*code*, or all file attributes of *ncfile* if *var* and *code* are not
given
variables : bool, optional
Get list of variables in *ncfile*
codes : bool, optional
Get list of variable attributes *code*
Missing codes will be filled with -1.
long_names : bool, optional
Get list of variable attributes *long_name*
Missing long_names will be filled with ''.
units : bool, optional
Get list of variable attributes *units*.
Missing units will be filled with ''.
sort : bool, optional
Sort output of *variables*, *codes*, *units*, and *long_names*
with variable name as the sort key
Returns
-------
tuple of variable dimension names,
tuple of variable dimensions,
list of variable names, codes, units, long_names, or
dictionary of attributes
Examples
--------
Get variable names
>>> ncfile = 'test_readnetcdf.nc'
>>> print([ str(i) for i in ncinfo(ncfile, variables=True) ])
['x', 'y', 'is1', 'is2']
>>> print([ str(i)
... for i in ncinfo(ncfile, variables=True, sort=True) ])
['is1', 'is2', 'x', 'y']
Get codes
>>> print(ncinfo(ncfile, codes=True))
[-1, -1, 128, 129]
>>> print(ncinfo(ncfile, codes=True, sort=True))
[128, 129, -1, -1]
Get special attributes units and long_names
>>> print([ str(i) for i in ncinfo(ncfile, units=True) ])
['xx', 'yy', 'arbitrary', 'arbitrary']
>>> print([ str(i) for i in ncinfo(ncfile, units=True, sort=True) ])
['arbitrary', 'arbitrary', 'xx', 'yy']
>>> print([ str(i) for i in ncinfo(ncfile, long_names=True) ])
['x-axis', 'y-axis', 'all ones', 'all twos']
>>> print([ str(i)
... for i in ncinfo(ncfile, long_names=True, sort=True) ])
['all ones', 'all twos', 'x-axis', 'y-axis']
Get dims
>>> print([ str(i) for i in ncinfo(ncfile, var='is1', dims=True) ])
['y', 'x']
Get shape
>>> print(ncinfo(ncfile, var='is1', shape=True))
(2, 4)
Get attributes
>>> t1 = ncinfo(ncfile, var='is1', attributes=True)
>>> print([ str(i) for i in sorted(t1) ])
['code', 'long_name', 'units']
"""
import netCDF4 as nc
f = nc.Dataset(ncfile, 'r')
# Variables
vvars = list(f.variables.keys())
nvars = len(vvars)
# Sort and get sort indices
if sort:
# index
ivars = sorted(range(nvars), key=vvars.__getitem__)
# sorted variables
svars = [ vvars[i] for i in ivars ]
else:
svars = vvars
ivars = list(range(nvars))
# variables
if variables:
f.close()
return svars
# code
if codes:
cods = [-1]*nvars
for i, v in enumerate(svars):
attr = f.variables[v].ncattrs()
if 'code' in attr:
cods[i] = getattr(f.variables[v], 'code')
f.close()
return cods
# long_name
if long_names:
lnames = [''] * nvars
for i, v in enumerate(svars):
attr = f.variables[v].ncattrs()
if 'long_name' in attr:
lnames[i] = getattr(f.variables[v], 'long_name')
f.close()
return lnames
# units
if units:
unts = [''] * nvars
for i, v in enumerate(svars):
attr = f.variables[v].ncattrs()
if 'units' in attr:
unts[i] = getattr(f.variables[v], 'units')
f.close()
return unts
# dims
if dims:
if (not var) and (code == -1):
raise ValueError('var or code has to be given for'
' inquiring dimensions.')
if var:
if var not in svars:
f.close()
raise ValueError(f'Variable {var} not in file {ncfile}.')
dimensions = f.variables[var].dimensions
f.close()
return dimensions
if code > -1:
cods = [-1] * nvars
for i, v in enumerate(svars):
attr = f.variables[v].ncattrs()
if 'code' in attr:
cods[i] = getattr(f.variables[v], 'code')
if code not in cods:
f.close()
raise ValueError(f'Code {code} not in file {ncfile}.')
var = svars[cods.index(code)]
dimensions = f.variables[var].dimensions
f.close()
return dimensions
# shape
if shape:
if (not var) and (code == -1):
raise ValueError('var or code has to be given for'
' inquiring its shape.')
if var:
if var not in svars:
f.close()
raise ValueError(f'Variable {var} not in file {ncfile}.')
shape = f.variables[var].shape
f.close()
return shape
if code > -1:
cods = [-1] * nvars
for i, v in enumerate(svars):
attr = f.variables[v].ncattrs()
if 'code' in attr:
cods[i] = getattr(f.variables[v], 'code')
if code not in cods:
f.close()
raise ValueError(f'Code {code} not in file {ncfile}.')
var = svars[cods.index(code)]
shape = f.variables[var].shape
f.close()
return shape
# attributes
if attributes:
# file attributes
if (not var) and (code == -1):
attrs = dict()
attr = f.ncattrs()
for a in attr:
attrs[a] = getattr(f, a)
f.close()
return attrs
# variables attributes
if var:
if var not in svars:
f.close()
raise ValueError(f'Variable {var} not in file {ncfile}.')
attrs = dict()
attr = f.variables[var].ncattrs()
for a in attr:
attrs[a] = getattr(f.variables[var], a)
f.close()
return attrs
if code > -1:
cods = [-1] * nvars
for i, v in enumerate(svars):
attr = f.variables[v].ncattrs()
if 'code' in attr:
cods[i] = getattr(f.variables[v], 'code')
if code not in cods:
f.close()
raise ValueError(f'Code {code} not in file {ncfile}.')
var = svars[cods.index(code)]
attrs = dict()
attr = f.variables[var].ncattrs()
for a in attr:
attrs[a] = getattr(f.variables[var], a)
f.close()
return attrs
# no keyword
f.close()
return
[docs]def infonetcdf(*args, **kwargs):
"""
Wrapper for :func:`ncinfo`
"""
return ncinfo(*args, **kwargs)
[docs]def ncread(ncfile, var='', code=-1, squeeze=False,
pointer=False, overwrite=False):
"""
Gets variables of a netcdf file
Parameters
----------
ncfile : str
netCDF file name
variables : bool, optional
Get list of variables in *ncfile*
codes : bool, optional
Get list of variable attributes *code*
Missing codes will be filled with -1.
squeeze : bool, optional
Squeeze output array, i.e. remove dimensions of size 1.
pointer : bool, optional
Return pointers to the open file and to the variable if True,
i.e. without actually reading the variable.
The file will be open in read-only 'r' mode.
*overwrite* precedes over *pointer*.
overwrite : bool, optional
Return pointers to the open file and to the variable if True,
where the file is opened in append 'a' mode allowing to modify
the variable. ``ncread`` allows *overwrite* only if the file
contains a single variable (without the dimension variables).
*overwrite* precedes over *pointer*.
Returns
-------
numpy array of the variable with name *var* or number *code*, or
(file pointer, variable pointer)
Examples
--------
Read variable or code
>>> ncfile = 'test_readnetcdf.nc'
>>> print(ncread(ncfile, var='is1'))
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
>>> print(ncread(ncfile, code=129))
[[2. 2. 2. 2.]
[2. 2. 2. 2.]]
Just get file handle so that read is done later at indexing
useful for example to inquire remote netcdf files first
>>> fh, var = ncread(ncfile, var='is1', pointer=True)
>>> print(var.shape)
(2, 4)
>>> print(var[:])
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
>>> fh.close()
Change a variable in a file
>>> ncfile = 'test_readnetcdf1.nc'
>>> print(ncread(ncfile, var='is1'))
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
>>> fh, var = ncread(ncfile, var='is1', overwrite=True)
>>> var[:] *= 2.
>>> fh.close()
>>> print(ncread(ncfile, var='is1'))
[[2. 2. 2. 2.]
[2. 2. 2. 2.]]
>>> fh, var = ncread(ncfile, var='is1', overwrite=True)
>>> var[:] *= 0.5
>>> fh.close()
"""
import netCDF4 as nc
if (not var) and (code == -1):
raise ValueError('var or code has to be given.')
vvars = ncinfo(ncfile, variables=True)
if var:
if var not in vvars:
raise ValueError(f'Variable {var} not in file {ncfile}.')
else:
cods = ncinfo(ncfile, codes=True)
if code not in cods:
raise ValueError(f'Code {code} not in file {ncfile}.')
var = vvars[cods.index(code)]
fmode = 'r'
if overwrite:
fmode = 'a'
# check that only one variable
dims = ncinfo(ncfile, var=var, dims=True)
vvars1 = [ v for v in vvars if v not in dims ]
if len(vvars1) > 1:
raise ValueError('overwrite is only allowed on files with a single'
' variable (without dimension variables).')
f = nc.Dataset(ncfile, fmode)
v = f.variables[var]
if pointer or overwrite:
return f, v
v = v[:]
if squeeze:
v = v.squeeze()
f.close()
return v
[docs]def readnetcdf(*args, **kwargs):
"""
Wrapper for :func:`ncread`
"""
return ncread(*args, **kwargs)
if __name__ == '__main__':
import doctest
doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)