22import hdf5storage
33import numpy as np
44import xarray as xr
5+ from . import utils
56from . import variable_conversion as vc
67
78
@@ -29,40 +30,44 @@ def _open_multifile_datasets(paths, lat, lon, lat_key='lat', lon_key='lon'):
2930 return ds
3031
3132
32- def _read_lambda_coef (lambda_directory , lat , lon ):
33+ def _read_lambda_coef (lambda_directory , lat , lon , depth_indices ):
3334 """Internal function that reads the lambda coefficient files and returns the data
3435 of interest in a dictionary.
3536
3637 Args:
3738 lambda_directory (Path): Path to the directory which contains the lambda data.
3839 lat (float): Latitude of the site of interest (in degrees North)
3940 lon (float): Longitude of the site of interest (in degrees East)
41+ depth_indices (list): List of which indices (0 - 7) should be selected from the
42+ lambda variable dataset.
4043
4144 Returns:
4245 dict: Dictionary containing the lambda coefficient data.
4346 """
47+ if not np .all ([d in range (0 , 8 ) for d in depth_indices ]):
48+ raise ValueError ("Incorrect depth indices provided. Indices range from 0 to 7" )
49+
4450 lambda_files = sorted (lambda_directory .glob ("lambda_l*.nc" ))
4551
4652 ds = _open_multifile_datasets (lambda_files , lat , lon )
4753
4854 # which depth indices the STEMMUS_SCOPE model expects
49- indices = [0 , 2 , 4 , 5 , 6 , 7 ]
50- coef_lambda = ds ['lambda' ].isel (depth = indices ).values
51-
52- lambda_matfile = {'Coef_Lamda' : coef_lambda }
55+ ds = ds .sortby ("depth" ) # make sure that the depths are sorted in increasing order
56+ coef_lambda = ds ['lambda' ].isel (depth = depth_indices ).values
5357
54- return lambda_matfile
58+ return { 'Coef_Lamda' : coef_lambda }
5559
5660
57- def _read_soil_composition (soil_data_path , lat , lon ):
61+ def _read_soil_composition (soil_data_path , lat , lon , depth_indices ):
5862 """Internal function that reads the soil composition files and returns them in a
5963 dictionary.
6064
6165 Args:
6266 soil_data_path (Path): Path to the directory which contains the soil data.
6367 lat (float): Latitude of the site of interest (in degrees North)
6468 lon (float): Longitude of the site of interest (in degrees East)
65-
69+ depth_indices (list): List of which indices (0 - 7) should be selected from the
70+ soil composition dataset.
6671 Returns:
6772 dict: Dictionary containing the soil composition data.
6873 """
@@ -73,30 +78,29 @@ def _read_soil_composition(soil_data_path, lat, lon):
7378
7479 ds = _open_multifile_datasets (soil_comp_paths , lat , lon )
7580
76- depths_indices = [ 0 , 2 , 4 , 5 , 6 , 7 ]
77- ds = ds . isel ( depth = depths_indices )
81+ if not np . all ([ d in range ( 0 , 8 ) for d in depth_indices ]):
82+ raise ValueError ( "Incorrect depth indices provided. Indices range from 0 to 7" )
7883
79- clay_fraction = vc .percent_to_fraction (ds ['CLAY' ].values )
80- sand_fraction = vc .percent_to_fraction (ds ['SAND' ].values )
81- organic_fraction = vc .hundredth_percent_to_fraction (ds ['OC' ].values )
84+ ds = ds .sortby ("depth" ) # make sure that the depths are sorted in increasing order
85+ ds = ds .isel (depth = depth_indices )
8286
83- soil_comp_matfiledata = {
84- 'FOC' : clay_fraction ,
85- 'FOS' : sand_fraction ,
86- 'MSOC' : organic_fraction ,
87- }
87+ clay_fraction = ds ['CLAY' ].values / 100 # convert % to fraction
88+ sand_fraction = ds ['SAND' ].values / 100 # convert % to fraction
89+ organic_fraction = ds ['OC' ].values / 10000 # convert from 1/100th % to fraction.
8890
89- return soil_comp_matfiledata
91+ return { 'FOC' : clay_fraction , 'FOS' : sand_fraction , 'MSOC' : organic_fraction }
9092
9193
92- def _read_hydraulic_parameters (soil_data_path , lat , lon ):
94+ def _read_hydraulic_parameters (soil_data_path , lat , lon , depths ):
9395 """Internal function that reads the soil hydraulic parameters from the Schaap
9496 dataset and returns them in a dictionary.
9597
9698 Args:
9799 soil_data_path (Path): Path to the directory which contains the soil data.
98100 lat (float): Latitude of the site of interest (in degrees North)
99101 lon (float): Longitude of the site of interest (in degrees East)
102+ depths (list): List of depths which should be selected from the dataset. The
103+ valid depths are: 0, 5, 15, 30, 60, 100 and 200 cm.
100104
101105 Returns:
102106 dict: Dictionary containing the hydraulic parameters.
@@ -106,31 +110,31 @@ def _read_hydraulic_parameters(soil_data_path, lat, lon):
106110 ptf_files , lat , lon , lat_key = 'latitude' , lon_key = 'longitude'
107111 )
108112
109- depths = [0 , 5 , 30 , 60 , 100 , 200 ]
113+ valid_depths = [0 , 5 , 15 , 30 , 60 , 100 , 200 ]
114+ if not np .all ([d in valid_depths for d in depths ]):
115+ raise ValueError ("Incorrect depth value(s) provided. Available depths are"
116+ f"{ valid_depths } " )
110117
111- alpha = np .zeros (len (depths ))
112- Ks = np .zeros (len (depths ))
113- theta_s = np .zeros (len (depths ))
114- theta_r = np .zeros (len (depths ))
115- coef_n = np .zeros (len (depths ))
118+ schaap_vars = ['alpha' , 'Ks' , 'thetas' , 'thetar' , 'n' ]
119+ schaap_data = {key : np .zeros (len (depths )) for key in schaap_vars }
116120
117121 for i , depth in enumerate (depths ):
118- alpha [ i ] = ds [ f"alpha_ { depth } cm" ]
119- Ks [ i ] = ds [f"Ks_ { depth } cm" ]
120- theta_s [ i ] = ds [ f"thetas_ { depth } cm" ]
121- theta_r [ i ] = ds [ f"thetar_ { depth } cm" ]
122- coef_n [ i ] = ds [ f"n_ { depth } cm" ]
122+ for var in schaap_vars :
123+ schaap_data [ var ][ i ] = ds [f"{ var } _ { depth } cm" ]
124+
125+ fieldmc = vc . field_moisture_content ( schaap_data [ 'thetar' ], schaap_data [ 'thetas' ],
126+ schaap_data [ 'alpha' ], schaap_data [ 'n' ])
123127
124128 hydraulic_matfiledata = {
125- 'SaturatedMC' : theta_s ,
126- 'ResidualMC' : theta_r ,
127- 'Coefficient_n' : coef_n ,
128- 'Coefficient_Alpha' : alpha ,
129- 'porosity' : theta_s ,
130- 'Ks0' : Ks [0 ],
131- 'SaturatedK' : vc . per_day_to_per_second ( Ks ),
132- 'fieldMC' : vc . field_moisture_content ( theta_r , theta_s , alpha , coef_n ) ,
133- 'theta_s0' : theta_s [0 ]
129+ 'SaturatedMC' : schaap_data [ 'thetas' ] ,
130+ 'ResidualMC' : schaap_data [ 'thetar' ] ,
131+ 'Coefficient_n' : schaap_data [ 'n' ] ,
132+ 'Coefficient_Alpha' : schaap_data [ ' alpha' ] ,
133+ 'porosity' : schaap_data [ 'thetas' ] ,
134+ 'Ks0' : schaap_data [ 'Ks' ] [0 ],
135+ 'SaturatedK' : schaap_data [ 'Ks' ] / ( 24 * 3600 ), # convert 1/day -> 1/s
136+ 'fieldMC' : fieldmc ,
137+ 'theta_s0' : schaap_data [ 'thetas' ] [0 ]
134138 }
135139
136140 return hydraulic_matfiledata
@@ -149,14 +153,13 @@ def _read_surface_data(soil_data_path, lat, lon):
149153 dict: Dictionary containing the `fmax` value (maximum fractional saturated area)
150154 """
151155 ds = xr .open_dataset (soil_data_path / 'surfdata.nc' )
156+ lat , lon = utils .convert_to_lsm_coordinates (lat , lon )
152157 ds = ds .sel (
153- lsmlat = vc . lat_to_lsmlat ( lat ) , lsmlon = vc . lon_to_lsmlon ( lon ) )
158+ lsmlat = lat , lsmlon = lon )
154159
155160 fmax = ds ['FMAX' ].values
156161
157- surface_matfiledata = {'fmax' : fmax }
158-
159- return surface_matfiledata
162+ return {'fmax' : fmax }
160163
161164
162165def _collect_soil_data (soil_data_path , lat , lon ):
@@ -171,35 +174,34 @@ def _collect_soil_data(soil_data_path, lat, lon):
171174 Returns:
172175 dict: Dictionary containing all the processed soil property data.
173176 """
177+ lambda_directory = soil_data_path / "lambda"
174178
175- matfiledata = {}
176-
177- lambda_directory = soil_data_path / 'lambda'
179+ schaap_depths = [0 , 5 , 30 , 60 , 100 , 200 ]
180+ depth_indices = [0 , 2 , 4 , 5 , 6 , 7 ]
178181
179- matfiledata .update (_read_lambda_coef (lambda_directory , lat , lon ))
180- matfiledata .update (_read_hydraulic_parameters (soil_data_path , lat , lon ))
181- matfiledata .update (_read_soil_composition (soil_data_path , lat , lon ))
182+ matfiledata = _read_lambda_coef (lambda_directory , lat , lon , depth_indices )
183+ matfiledata .update (_read_hydraulic_parameters (soil_data_path , lat , lon ,
184+ schaap_depths ))
185+ matfiledata .update (_read_soil_composition (soil_data_path , lat , lon , depth_indices ))
182186 matfiledata .update (_read_surface_data (soil_data_path , lat , lon ))
183187
184188 return matfiledata
185189
186190
187- def _retrieve_latlon (plumber2_file ):
188- """Retrieves the latitude and longitude coordinates from the PLUMBER2 dataset file,
189- to allow for the
191+ def _retrieve_latlon (file ):
192+ """Retrieves the latitude and longitude coordinates from the dataset file.
190193
191194 Args:
192- plumber2_file (Path): Full path to the netCDF file containing the site latitude
195+ file (Path): Full path to the netCDF file containing the site latitude
193196 and longitude
194197
195198 Returns:
196199 tuple(float, float): Tuple containing the latitude and longitude values.
197200 Latitude in degrees N, longitude in degrees E.
198201 """
199- ds = xr .open_dataset (plumber2_file )
200- ds = ds .squeeze ()
201- lat = float (ds .latitude .values )
202- lon = float (ds .longitude .values )
202+ ds = xr .open_dataset (file )
203+ lon = ds .longitude .values .flatten ()
204+ lat = ds .latitude .values .flatten ()
203205 return lat , lon
204206
205207
@@ -215,15 +217,16 @@ def prepare_soil_data(soil_data_dir, matfile_path, run_config):
215217 run_config (dict): Dictionary containing the configuration for the current
216218 STEMMUS_SCOPE run.
217219 """
218- forcing_file = Path (run_config ['ForcingPath' ]) / run_config ['ForcingFileName' ]
219- lat , lon = _retrieve_latlon (forcing_file )
220+ forcing_file = Path (run_config ["ForcingPath" ]) / run_config ["ForcingFileName" ]
220221
221- # Ensure pathlib Paths are used
222- soil_data_path = Path (soil_data_dir )
223- matfile_path = Path (matfile_path )
222+ # Data missing at ID-Pag site. See github.com/EcoExtreML/STEMMUS_SCOPE/issues/77
223+ if run_config ["ForcingFileName" ].startswith ("ID" ):
224+ lat , lon = - 1. , 112.
225+ else :
226+ lat , lon = _retrieve_latlon (forcing_file )
224227
225- matfiledata = _collect_soil_data (soil_data_path , lat , lon )
228+ matfiledata = _collect_soil_data (Path ( soil_data_dir ) , lat , lon )
226229
227230 hdf5storage .savemat (
228- matfile_path / ' soil_parameters.mat' , mdict = matfiledata , appendmat = False ,
231+ Path ( matfile_path ) / " soil_parameters.mat" , mdict = matfiledata , appendmat = False ,
229232 )
0 commit comments