pyPRMS 0.9.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. pyprms-0.9.7/LICENSE.md +21 -0
  2. pyprms-0.9.7/PKG-INFO +67 -0
  3. pyprms-0.9.7/README.md +30 -0
  4. pyprms-0.9.7/pyPRMS/Exceptions_custom.py +31 -0
  5. pyprms-0.9.7/pyPRMS/__init__.py +52 -0
  6. pyprms-0.9.7/pyPRMS/cbh/Cbh.py +431 -0
  7. pyprms-0.9.7/pyPRMS/cbh/CbhAscii.py +458 -0
  8. pyprms-0.9.7/pyPRMS/cbh/CbhNetcdf.py +199 -0
  9. pyprms-0.9.7/pyPRMS/cbh/__init__.py +3 -0
  10. pyprms-0.9.7/pyPRMS/constants.py +131 -0
  11. pyprms-0.9.7/pyPRMS/control/Control.py +362 -0
  12. pyprms-0.9.7/pyPRMS/control/ControlFile.py +161 -0
  13. pyprms-0.9.7/pyPRMS/control/ControlVariable.py +208 -0
  14. pyprms-0.9.7/pyPRMS/control/__init__.py +3 -0
  15. pyprms-0.9.7/pyPRMS/dimensions/Dimension.py +154 -0
  16. pyprms-0.9.7/pyPRMS/dimensions/Dimensions.py +256 -0
  17. pyprms-0.9.7/pyPRMS/dimensions/__init__.py +2 -0
  18. pyprms-0.9.7/pyPRMS/input/DataFile.py +354 -0
  19. pyprms-0.9.7/pyPRMS/input/InputVariable.py +61 -0
  20. pyprms-0.9.7/pyPRMS/input/__init__.py +0 -0
  21. pyprms-0.9.7/pyPRMS/metadata/__init__.py +1 -0
  22. pyprms-0.9.7/pyPRMS/metadata/metadata.py +430 -0
  23. pyprms-0.9.7/pyPRMS/parameters/ParamDb.py +73 -0
  24. pyprms-0.9.7/pyPRMS/parameters/Parameter.py +624 -0
  25. pyprms-0.9.7/pyPRMS/parameters/ParameterFile.py +190 -0
  26. pyprms-0.9.7/pyPRMS/parameters/ParameterNetCDF.py +74 -0
  27. pyprms-0.9.7/pyPRMS/parameters/ParameterSet.py +96 -0
  28. pyprms-0.9.7/pyPRMS/parameters/Parameters.py +1506 -0
  29. pyprms-0.9.7/pyPRMS/parameters/__init__.py +5 -0
  30. pyprms-0.9.7/pyPRMS/plot_helpers.py +305 -0
  31. pyprms-0.9.7/pyPRMS/prms_helpers.py +235 -0
  32. pyprms-0.9.7/pyPRMS/py.typed +0 -0
  33. pyprms-0.9.7/pyPRMS/summary/OutputCSV.py +64 -0
  34. pyprms-0.9.7/pyPRMS/summary/OutputVariable.py +163 -0
  35. pyprms-0.9.7/pyPRMS/summary/OutputVariables.py +227 -0
  36. pyprms-0.9.7/pyPRMS/summary/__init__.py +2 -0
  37. pyprms-0.9.7/pyPRMS/utilities/__init__.py +0 -0
  38. pyprms-0.9.7/pyPRMS/utilities/convert_cbh.py +107 -0
  39. pyprms-0.9.7/pyPRMS/utilities/convert_model_output.py +91 -0
  40. pyprms-0.9.7/pyPRMS/utilities/convert_params.py +60 -0
  41. pyprms-0.9.7/pyPRMS/version.py +13 -0
  42. pyprms-0.9.7/pyPRMS/xml/cbh.xml +163 -0
  43. pyprms-0.9.7/pyPRMS/xml/control.xml +1447 -0
  44. pyprms-0.9.7/pyPRMS/xml/dimensions.xml +311 -0
  45. pyprms-0.9.7/pyPRMS/xml/modules.xml +251 -0
  46. pyprms-0.9.7/pyPRMS/xml/parameters.xml +6932 -0
  47. pyprms-0.9.7/pyPRMS/xml/time_series_input.xml +198 -0
  48. pyprms-0.9.7/pyPRMS/xml/variables.xml +8173 -0
  49. pyprms-0.9.7/pyPRMS.egg-info/PKG-INFO +67 -0
  50. pyprms-0.9.7/pyPRMS.egg-info/SOURCES.txt +55 -0
  51. pyprms-0.9.7/pyPRMS.egg-info/dependency_links.txt +1 -0
  52. pyprms-0.9.7/pyPRMS.egg-info/entry_points.txt +3 -0
  53. pyprms-0.9.7/pyPRMS.egg-info/not-zip-safe +1 -0
  54. pyprms-0.9.7/pyPRMS.egg-info/requires.txt +24 -0
  55. pyprms-0.9.7/pyPRMS.egg-info/top_level.txt +1 -0
  56. pyprms-0.9.7/pyproject.toml +61 -0
  57. pyprms-0.9.7/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Parker Norton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyprms-0.9.7/PKG-INFO ADDED
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.2
2
+ Name: pyPRMS
3
+ Version: 0.9.7
4
+ Summary: Python library for Precipitation-Runoff Modeling System
5
+ Author-email: Parker Norton <pnorton@usgs.gov>
6
+ Maintainer-email: Parker Norton <pnorton@usgs.gov>
7
+ Project-URL: Repository, https://github.com/EC-USGS/pyPRMS.git
8
+ Keywords: PRMS,NHM,streamflow,hydrology,surface
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: <3.13,>=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE.md
14
+ Requires-Dist: cartopy
15
+ Requires-Dist: dask
16
+ Requires-Dist: fsspec
17
+ Requires-Dist: geopandas
18
+ Requires-Dist: matplotlib
19
+ Requires-Dist: netCDF4
20
+ Requires-Dist: networkx>=2
21
+ Requires-Dist: numpy
22
+ Requires-Dist: pandas
23
+ Requires-Dist: pyproj
24
+ Requires-Dist: rich
25
+ Requires-Dist: shapely
26
+ Requires-Dist: sphinx
27
+ Requires-Dist: sphinx-autodoc-typehints
28
+ Requires-Dist: sphinx-book-theme
29
+ Requires-Dist: xarray
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy; extra == "dev"
32
+ Requires-Dist: mypy_extensions; extra == "dev"
33
+ Requires-Dist: pytest; extra == "dev"
34
+ Requires-Dist: pytest-cov; extra == "dev"
35
+ Requires-Dist: pytest-xdist; extra == "dev"
36
+ Requires-Dist: pytest-datafiles; extra == "dev"
37
+
38
+ pyPRMS
39
+ ======
40
+ [![ci-badge](https://github.com/EC-USGS/pyPRMS/actions/workflows/ci.yaml/badge.svg?branch=development)](https://github.com/EC-USGS/pyPRMS/actions?query=workflows%3CI)
41
+
42
+ A Python library for working with the Precipitation-Runoff Modeling System (PRMS)
43
+
44
+ ----
45
+
46
+ The pyPRMS library provides a set of python interfaces to read, modify, and write various files used by and for PRMS.
47
+
48
+ pip install
49
+ -----------
50
+ The library can be installed using pip. The development branch contains the most
51
+ up-to-date version of the software.
52
+
53
+ To install from development branch use the following command::
54
+
55
+ pip install git+https://github.com/EC-USGS/pyPRMS.git
56
+
57
+ To update from the development branch type::
58
+
59
+ pip install git+https://github.com/EC-USGS/pyPRMS.git --upgrade
60
+
61
+ To uninstall pyPRMS type::
62
+
63
+ pip uninstall pyPRMS
64
+
65
+
66
+
67
+
pyprms-0.9.7/README.md ADDED
@@ -0,0 +1,30 @@
1
+ pyPRMS
2
+ ======
3
+ [![ci-badge](https://github.com/EC-USGS/pyPRMS/actions/workflows/ci.yaml/badge.svg?branch=development)](https://github.com/EC-USGS/pyPRMS/actions?query=workflows%3CI)
4
+
5
+ A Python library for working with the Precipitation-Runoff Modeling System (PRMS)
6
+
7
+ ----
8
+
9
+ The pyPRMS library provides a set of python interfaces to read, modify, and write various files used by and for PRMS.
10
+
11
+ pip install
12
+ -----------
13
+ The library can be installed using pip. The development branch contains the most
14
+ up-to-date version of the software.
15
+
16
+ To install from development branch use the following command::
17
+
18
+ pip install git+https://github.com/EC-USGS/pyPRMS.git
19
+
20
+ To update from the development branch type::
21
+
22
+ pip install git+https://github.com/EC-USGS/pyPRMS.git --upgrade
23
+
24
+ To uninstall pyPRMS type::
25
+
26
+ pip uninstall pyPRMS
27
+
28
+
29
+
30
+
@@ -0,0 +1,31 @@
1
+
2
+ class ConcatError(Exception):
3
+ """Concatenation error"""
4
+ pass
5
+
6
+
7
+ class ParameterError(Exception):
8
+ def __init__(self, err_args):
9
+ Exception.__init__(self, err_args)
10
+ self.errArgs = err_args
11
+
12
+ class ParameterExistsError(Exception):
13
+ def __init__(self, err_args):
14
+ Exception.__init__(self, err_args)
15
+ self.errArgs = err_args
16
+
17
+ class ParameterNotValidError(Exception):
18
+ def __init__(self, err_args):
19
+ Exception.__init__(self, err_args)
20
+ self.errArgs = err_args
21
+
22
+ class FixedDimensionError(Exception):
23
+ """Raised when attempting to modify a fixed dimension"""
24
+ def __init__(self, err_args):
25
+ Exception.__init__(self, err_args)
26
+ self.errArgs = err_args
27
+
28
+ class ControlError(Exception):
29
+ def __init__(self, err_args):
30
+ Exception.__init__(self, err_args)
31
+ self.errArgs = err_args
@@ -0,0 +1,52 @@
1
+
2
+ from . import constants
3
+ from . import Exceptions_custom
4
+ from . import plot_helpers
5
+ from . import prms_helpers
6
+ from .control.Control import Control
7
+ from .control.ControlVariable import ControlVariable
8
+ from .control.ControlFile import ControlFile
9
+ from .dimensions.Dimension import Dimension
10
+ from .dimensions.Dimensions import Dimensions, ParamDimensions
11
+ from .metadata.metadata import MetaData
12
+ from .summary.OutputVariables import OutputVariables
13
+ from .summary.OutputVariable import OutputVariable
14
+ from .parameters.Parameter import Parameter
15
+ from .parameters.Parameters import Parameters
16
+ from .parameters.ParameterFile import ParameterFile
17
+ from .parameters.ParamDb import ParamDb
18
+ from .parameters.ParameterNetCDF import ParameterNetCDF
19
+ from .cbh.Cbh import Cbh
20
+ from .cbh.CbhAscii import CbhAscii
21
+ from .cbh.CbhNetcdf import CbhNetcdf
22
+ from pyPRMS.input.DataFile import DataFile
23
+
24
+
25
+ from .version import __author__, __author_email__, __version__
26
+
27
+ __all__ = ['constants',
28
+ 'Exceptions_custom',
29
+ 'plot_helpers',
30
+ 'prms_helpers',
31
+ 'control',
32
+ 'dimensions',
33
+ 'parameters',
34
+ 'cbh',
35
+ 'Cbh',
36
+ 'CbhAscii',
37
+ 'CbhNetcdf',
38
+ 'Control',
39
+ 'ControlFile',
40
+ 'ControlVariable',
41
+ 'Dimension',
42
+ 'Dimensions',
43
+ 'MetaData',
44
+ 'OutputVariables',
45
+ 'OutputVariable',
46
+ 'ParamDimensions',
47
+ 'Parameter',
48
+ 'Parameters',
49
+ 'ParameterFile',
50
+ 'ParamDb',
51
+ 'ParameterNetCDF',
52
+ 'DataFile']
@@ -0,0 +1,431 @@
1
+ import fsspec # type: ignore
2
+ import numpy as np
3
+ import pandas as pd # type: ignore
4
+ import netCDF4 as nc # type: ignore
5
+ import xarray as xr # type: ignore
6
+
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional, Sequence, Union
9
+
10
+ from rich.console import Console
11
+ from rich import pretty
12
+
13
+ from ..control.Control import Control
14
+ from ..constants import MetaDataType, NEW_PTYPE_TO_DTYPE
15
+
16
+ pretty.install()
17
+ con = Console(force_jupyter=False)
18
+
19
+ __author__ = 'Parker Norton (pnorton@usgs.gov)'
20
+
21
+ CBH_VARNAMES = ['prcp', 'tmin', 'tmax']
22
+ CBH_INDEX_COLS = [0, 1, 2, 3, 4, 5]
23
+ TS_FORMAT = '%Y %m %d %H %M %S' # 1915 1 13 0 0 0
24
+ NA_VALS_DEFAULT = ('-99.0', '-999.0', 'NaN', 'inf')
25
+ DATA_SEP = '####'
26
+
27
+ var_crosswalk: Dict[str, str] = dict(tmax='tmax_hru',
28
+ T2MAX='tmax_hru',
29
+ tmin='tmin_hru',
30
+ T2MIN='tmin_hru',
31
+ precip='hru_ppt',
32
+ prcp='hru_ppt',
33
+ RAIN='hru_ppt',
34
+ rhavg='humidity_hru')
35
+ temp_units = {0: 'degree_fahrenheit', 1: 'degree_celsius'}
36
+ precip_units = {0: 'inch', 1: 'mm'}
37
+
38
+
39
+ class Cbh(object):
40
+ """Climate-By-HRU (CBH) files for PRMS."""
41
+
42
+ def __init__(self, src_path: Union[str, Path, List[Union[str, Path]]],
43
+ metadata: MetaDataType,
44
+ engine: Optional[str] = 'ascii',
45
+ control: Optional[Control] = None):
46
+ """
47
+ :param src_path: List of paths to CBH files
48
+ :param metadata: Metadata dictionary for Climate-by-HRU variables
49
+ :param engine: Engine to use for reading CBH files (one of netcdf, zarr, or ascii)
50
+ :param control: Control object for PRMS model containing configuration information
51
+ """
52
+
53
+ self.has_nhm_id = False
54
+ self.metadata = metadata['cbh']
55
+ self.__var_map = {}
56
+ self.__var_src: Dict[str, str] = {}
57
+
58
+ if isinstance(src_path, str):
59
+ src_path = Path(src_path)
60
+
61
+ if isinstance(src_path, list):
62
+ self.__src_path = [Path(ff).resolve() for ff in src_path]
63
+ else:
64
+ if '*' in src_path.name:
65
+ # wildcard character in filename so glob the path
66
+ self.__src_path = list(src_path.parent.glob(src_path.name))
67
+ else:
68
+ self.__src_path = [src_path.resolve()]
69
+
70
+ con.print(f'CBH files: {self.__src_path}')
71
+
72
+ assert self.__src_path is not None
73
+
74
+ match engine:
75
+ case 'netcdf':
76
+ ds = xr.open_mfdataset(self.__src_path, chunks={}, combine='by_coords',
77
+ data_vars='minimal', decode_cf=True, engine='netcdf4',
78
+ parallel=True)
79
+ case 'zarr':
80
+ if len(self.__src_path) > 1:
81
+ con.print('[red]ERROR[/]: Zarr engine does not support reading multiple files')
82
+ elif not self.__src_path[0].is_dir():
83
+ con.print('[red]ERROR[/]: Zarr engine requires a directory of files')
84
+ else:
85
+ ds = xr.open_zarr(self.__src_path[0], consolidated=True)
86
+ case 'ascii':
87
+ if control is None:
88
+ ds = self._cbh_to_xarray(self.__src_path) # type: ignore
89
+ else:
90
+ # When a control object is specified, the src_path indicates
91
+ # the model directory and the *_day variables are read to get
92
+ # candidate CBH files.
93
+ cbh_file_vars = dict(albedo_day='albedo_hru',
94
+ cloud_cover_day='cloud_cover_cbh',
95
+ humidity_day='humidity_hru',
96
+ potet_day='potet',
97
+ precip_day='hru_ppt',
98
+ swrad_day='swrad',
99
+ tmax_day='tmax_hru',
100
+ tmin_day='tmin_hru',
101
+ transp_day='transp_on',
102
+ windspeed_day='windspeed_hru')
103
+
104
+ cbh_files = []
105
+ # cbh_vars = []
106
+ for ctl_var, prms_var in cbh_file_vars.items():
107
+ cfile = control.get(ctl_var).values
108
+ assert type(cfile) is str
109
+
110
+ if (self.__src_path[0] / cfile).exists():
111
+ con.print(f'[green]INFO[/]: Found {cfile}')
112
+ cbh_files.append(self.__src_path[0] / cfile)
113
+ # cbh_vars.append(prms_var)
114
+
115
+ ds = self._cbh_to_xarray(cbh_files) # type: ignore # , variables=cbh_vars)
116
+
117
+ if 'nhm_id' in ds.data_vars:
118
+ # dataset has nhm_id variable so use it as the nhru dimension
119
+ ds = ds.assign_coords(nhru=ds.nhm_id)
120
+ self.has_nhm_id = True
121
+
122
+ for cvar in ds.data_vars:
123
+ self.__var_map[str(cvar)] = var_crosswalk.get(str(cvar), str(cvar))
124
+
125
+ self.__dataset = ds
126
+
127
+ @property
128
+ def data(self) -> xr.Dataset:
129
+ """Returns the CBH dataset.
130
+
131
+ :returns: xarray dataset
132
+ """
133
+
134
+ return self.__dataset
135
+
136
+ @property
137
+ def var_map(self) -> Dict[str, str]:
138
+ """Return variable-to-prms_variable mapping."""
139
+
140
+ return self.__var_map
141
+
142
+ @property
143
+ def var_src(self) -> Dict[str, str]:
144
+ """Return variable to source-file mapping."""
145
+
146
+ return self.__var_src
147
+
148
+ def set_nhm_id(self, nhm_ids: np.ndarray):
149
+ """Add the model nhm_id parameter as a coordinate variable.
150
+
151
+ :param nhm_ids: array of nhm_id values
152
+ """
153
+
154
+ # Returns ValueError if the nhm_ids size does not match the dataset dimension size
155
+ if not self.has_nhm_id:
156
+ # Only add nhm_id if it doesn't already exist
157
+ self.__dataset['nhm_id'] = ('nhru', nhm_ids)
158
+ self.__dataset['nhm_id'].attrs['long_name'] = 'Global model Hydrologic Response Unit ID (HRU)'
159
+ self.__dataset = self.__dataset.assign_coords(nhru=self.__dataset.nhm_id)
160
+ self.has_nhm_id = True
161
+
162
+ def write_ascii(self, filename: Union[str, Path],
163
+ variable: str,
164
+ time_slice: Optional[Union[list, slice]] = None,
165
+ hru_ids: Optional[Union[list, np.ndarray]] = None):
166
+ """Write CBH data for selected variable to PRMS ASCII-formatted file.
167
+
168
+ :param filename: Climate-by-HRU filename
169
+ :param variable: CBH variable to write
170
+ :param time_slice: time slice to write
171
+ :param hru_ids: list or array of HRU IDs (local IDs if has_nhm_id is false) to write
172
+ """
173
+
174
+ if hru_ids is None:
175
+ # Return all HRUs if hru_ids is not provided
176
+ hru_ids = self.__dataset['nhru'].values
177
+
178
+ if time_slice is None:
179
+ # Return all time steps if time_slice is not provided
180
+ time_slice = slice(self.__dataset['time'][0].values,
181
+ self.__dataset['time'][-1].values)
182
+
183
+ if isinstance(time_slice, list):
184
+ time_slice = slice(time_slice[0], time_slice[-1])
185
+
186
+ # For out_order the first six columns contain the time information and
187
+ # are always output for the cbh files
188
+ # out_order: List[Union[int, str]] = [kk for kk in self.__nhm_hrus]
189
+ out_order = [kk for kk in hru_ids]
190
+ for cc in ['second', 'minute', 'hour', 'day', 'month', 'year']:
191
+ out_order.insert(0, cc)
192
+
193
+ # variable = var_crosswalk.get(variable, variable)
194
+
195
+ if variable in self.__dataset.data_vars:
196
+ ds = self.__dataset[variable].sel(nhru=hru_ids, time=time_slice).to_pandas()
197
+
198
+ # Add time information as columns
199
+ ds['year'] = ds.index.year
200
+ ds['month'] = ds.index.month
201
+ ds['day'] = ds.index.day
202
+ ds['hour'] = 0
203
+ ds['minute'] = 0
204
+ ds['second'] = 0
205
+
206
+ out_cbh = open(filename, 'w')
207
+ out_cbh.write('Written by Bandit\n')
208
+ out_cbh.write(f'{var_crosswalk.get(variable, variable)} {len(hru_ids)}\n')
209
+ out_cbh.write('########################################\n')
210
+ ds.to_csv(out_cbh, columns=out_order, na_rep='-999', float_format='%0.2f',
211
+ sep=' ', index=False, header=False, lineterminator='\n', encoding=None,
212
+ chunksize=10)
213
+ out_cbh.close()
214
+ else:
215
+ print(f'WARNING: {variable} does not exist in source CBH files..skipping')
216
+
217
+ def write_netcdf(self, filename: Union[str, Path],
218
+ variables: Optional[List[str]] = None,
219
+ global_attrs: Optional[Dict] = None,
220
+ time_slice: Optional[Union[list, slice]] = None,
221
+ hru_ids: Optional[Union[list, np.ndarray]] = None):
222
+ """Write CBH variables to netCDF file.
223
+
224
+ :param filename: name of netCDF output file
225
+ :param variables: list of CBH variables to write
226
+ :param global_attrs: optional dictionary of global attributes to include in netcdf file
227
+ :param time_slice: time slice to write
228
+ :param hru_ids: list or array of HRU IDs (local IDs if has_nhm_id is false) to write
229
+ """
230
+
231
+ if hru_ids is None:
232
+ # Return all HRUs if hru_ids is not provided
233
+ hru_ids = self.__dataset['nhru'].values
234
+
235
+ if time_slice is None:
236
+ # Return all time steps if time_slice is not provided
237
+ time_slice = slice(self.__dataset['time'][0].values,
238
+ self.__dataset['time'][-1].values)
239
+
240
+ if isinstance(time_slice, list):
241
+ time_slice = slice(time_slice[0], time_slice[-1])
242
+
243
+ ds = self.__dataset.sel(nhru=hru_ids, time=time_slice)
244
+ # ds = ds.sel(time=slice(self.__stdate, self.__endate), nhru=self.__nhm_hrus)
245
+
246
+ if variables is None:
247
+ pass
248
+ elif isinstance(variables, list):
249
+ ds = ds[variables]
250
+
251
+ # Remove _FillValue from coordinate variables
252
+ for vv in list(ds.coords):
253
+ ds[vv].encoding.update({'_FillValue': None})
254
+
255
+ if 'crs' in ds.variables:
256
+ # ds['crs'] = self.__dataset['crs']
257
+ ds['crs'].encoding.update({'_FillValue': None,
258
+ 'contiguous': True})
259
+
260
+ ds['time'].attrs['standard_name'] = 'time'
261
+ ds['time'].attrs['long_name'] = 'time'
262
+
263
+ # Add nhm_id variable which will be the global NHM IDs
264
+ ds['nhm_id'] = ds['nhru']
265
+ ds['nhm_id'].attrs['long_name'] = 'Global model Hydrologic Response Unit ID (HRU)'
266
+
267
+ if not self.has_nhm_id:
268
+ ds['nhm_id'].attrs['note'] = 'Locally generated ID; not the same as NHM global ID'
269
+
270
+ # Change the nhru coordinate variable values to reflect the local model HRU IDs
271
+ ds = ds.assign_coords(nhru=np.arange(1, ds.nhru.values.size+1, dtype=ds.nhru.dtype))
272
+ ds['nhru'].attrs['long_name'] = 'Local model Hydrologic Response Unit ID (HRU)'
273
+ ds['nhru'].attrs['cf_role'] = 'timeseries_id'
274
+
275
+ # Add/update global attributes
276
+ ds.attrs['Description'] = 'Climate-by-HRU'
277
+
278
+ if global_attrs is not None:
279
+ for kk, vv in global_attrs.items():
280
+ ds.attrs[kk] = vv
281
+
282
+ encoding = {}
283
+
284
+ for cvar in ds.variables:
285
+ if ds[cvar].ndim > 1:
286
+ encoding[cvar] = dict(_FillValue=ds[cvar].encoding['_FillValue'],
287
+ compression='zlib',
288
+ complevel=2,
289
+ fletcher32=True)
290
+ else:
291
+ encoding[cvar] = dict(_FillValue=None,
292
+ contiguous=True)
293
+
294
+ ds.load().to_netcdf(filename, engine='netcdf4', format='NETCDF4', encoding=encoding)
295
+
296
+ def _cbh_to_xarray(self, filename: Union[str, Path, List[Union[str, Path]]]) -> xr.Dataset:
297
+ # variables: Optional[List[str]] = None) -> xr.Dataset:
298
+ """Convert ASCII CBH file(s) to xarray
299
+
300
+ :param filename: list of CBH filepaths or a single CBH filename
301
+ :returns: xarray dataset of CBH data
302
+ """
303
+
304
+ var_meta = dict(time=dict(standard_name='time', long_name='time'),
305
+ nhru=dict(standard_name='nhru', long_name='Local model Hydrologic Response Unit ID (HRU)'))
306
+
307
+ var_enc = {'float32': dict(_FillValue=nc.default_fillvals['f4']),
308
+ 'float64': dict(_FillValue=nc.default_fillvals['f8']),
309
+ 'int32': dict(_FillValue=nc.default_fillvals['i4']),
310
+ 'int64': dict(_FillValue=nc.default_fillvals['i8'])}
311
+
312
+ if isinstance(filename, str):
313
+ filename = Path(filename)
314
+
315
+ if not isinstance(filename, list):
316
+ filename = [filename]
317
+
318
+ df = []
319
+ read_ok = True
320
+
321
+ for idx, cfile in enumerate(filename):
322
+ assert isinstance(cfile, Path)
323
+
324
+ # First get the header info which has the variable name and number of HRUs
325
+ with open(cfile, 'r') as fhdl:
326
+ # First line is a descriptive header
327
+ header = fhdl.readline().rstrip()
328
+
329
+ # Next line has the variable name (not checked by PRMS) and number of dimensions
330
+ var_name, ndims = fhdl.readline().rstrip().split()
331
+ ndims = int(ndims) # type: ignore
332
+
333
+ self.__var_src[var_name] = cfile.name
334
+ # if variables is not None:
335
+ # # Override the variable name when a list of variables has been provided
336
+ # var_name = variables[idx]
337
+ # else:
338
+ # var_name = var_crosswalk.get(var_name, var_name)
339
+
340
+ line = fhdl.readline().rstrip()
341
+
342
+ if line[0:len(DATA_SEP)] != DATA_SEP:
343
+ if line.split()[0] == 'orad':
344
+ # This happens when orad_flag == 1
345
+ con.print(f'[red]ERROR[/]: Two variables in CBH file ({var_name}, orad). Data will not be read.')
346
+ else:
347
+ con.print(f'[red]ERROR[/]: Unknown extra line: {line}.\n Data will not be read')
348
+
349
+ read_ok = False
350
+
351
+ if read_ok:
352
+ df.append(pd.DataFrame(self._read_ascii_file(cfile).stack()))
353
+ df[-1].index.rename(['time', 'nhru'], inplace=True)
354
+ df[-1].rename(columns={0: var_name}, inplace=True)
355
+
356
+ ds = xr.merge([cdf.to_xarray() for cdf in df])
357
+
358
+ # Apply metadata to variables
359
+ for cvar in ds.variables:
360
+ cvar_x = var_crosswalk.get(str(cvar), str(cvar))
361
+
362
+ if cvar_x in self.metadata:
363
+ # if cvar in self.metadata or cvar in self.__var_map:
364
+ # cattrs = self.metadata[cvar]
365
+ cattrs = self.metadata[cvar_x]
366
+ # con.print(f' cattrs: {cattrs}')
367
+
368
+ ds[cvar] = ds[cvar].astype(NEW_PTYPE_TO_DTYPE[cattrs['datatype']])
369
+
370
+ ds[cvar].attrs['long_name'] = cattrs['description']
371
+
372
+ if cattrs['units'] == 'temp_units':
373
+ # For now just default to degrees_fahrenheit
374
+ ds[cvar].attrs['units'] = temp_units[0]
375
+ elif cattrs['units'] == 'precip_units':
376
+ # For now just default to inches
377
+ ds[cvar].attrs['units'] = precip_units[0]
378
+ else:
379
+ ds[cvar].attrs['units'] = cattrs['units']
380
+
381
+ # Set the fill value
382
+ ds[cvar].encoding.update(var_enc[cattrs['datatype']])
383
+
384
+ if cvar in var_meta:
385
+ for cattr, cval in var_meta[cvar].items(): # type: ignore
386
+ ds[cvar].attrs[cattr] = cval
387
+
388
+ # if cvar in var_enc:
389
+ # ds[cvar].encoding.update(var_enc[cvar]) # type: ignore
390
+
391
+ return ds
392
+
393
+ @staticmethod
394
+ def _read_ascii_file(filename: Union[str, Path],
395
+ columns: Optional[List] = None) -> pd.DataFrame:
396
+ """Reads a single ASCII CBH file.
397
+
398
+ :param filename: name of the CBH file
399
+ :param columns: columns to read
400
+ :returns: dataframe of CBH variable
401
+ """
402
+
403
+ if isinstance(filename, str):
404
+ filename = Path(filename)
405
+
406
+ # Written by Bandit
407
+ # prcp 14
408
+ # ########################################
409
+ # 1980 1 1 0
410
+
411
+ # Columns 0-5 always represent date/time information
412
+ time_col_names = {0: 'year', 1: 'month', 2: 'day', 3: 'hour', 4: 'minute', 5: 'second'}
413
+
414
+ df = pd.read_csv(filename, sep=' ', skipinitialspace=True,
415
+ skiprows=3, engine='python',
416
+ # skiprows=3, engine='c', memory_map=True,
417
+ header=None, na_values=NA_VALS_DEFAULT,
418
+ usecols=columns)
419
+
420
+ # Rename columns with time information
421
+ df.rename(columns=time_col_names, inplace=True)
422
+
423
+ # Rename columns with local model indices
424
+ ren_dict = {k + 6: k + 1 for k in range(len(df.columns))}
425
+ df.rename(columns=ren_dict, inplace=True)
426
+
427
+ df['time'] = pd.to_datetime(df[time_col_names.values()], yearfirst=True)
428
+ df.drop(columns=time_col_names.values(), inplace=True)
429
+ df.set_index('time', inplace=True)
430
+
431
+ return df