pyTSEB 2.1.0__py3-none-any.whl
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.
- pyTSEB/MO_similarity.py +385 -0
- pyTSEB/PyTSEB.py +1666 -0
- pyTSEB/TSEB.py +3806 -0
- pyTSEB/TSEBConfigFileInterface.py +279 -0
- pyTSEB/TSEBIPythonInterface.py +1111 -0
- pyTSEB/__init__.py +0 -0
- pyTSEB/clumping_index.py +189 -0
- pyTSEB/dis_TSEB.py +642 -0
- pyTSEB/energy_combination_ET.py +1344 -0
- pyTSEB/meteo_utils.py +456 -0
- pyTSEB/net_radiation.py +640 -0
- pyTSEB/physiology.py +2234 -0
- pyTSEB/resistances.py +1093 -0
- pyTSEB/wind_profile.py +522 -0
- pytseb-2.1.0.dist-info/METADATA +207 -0
- pytseb-2.1.0.dist-info/RECORD +19 -0
- pytseb-2.1.0.dist-info/WHEEL +5 -0
- pytseb-2.1.0.dist-info/licenses/LICENSE +674 -0
- pytseb-2.1.0.dist-info/top_level.txt +1 -0
pyTSEB/PyTSEB.py
ADDED
|
@@ -0,0 +1,1666 @@
|
|
|
1
|
+
# This file is part PyTSEB, consisting of of high level pyTSEB scripting
|
|
2
|
+
# Copyright 2016 Hector Nieto and contributors listed in the README.md file.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Created on Thu Jan 7 16:37:45 2016
|
|
19
|
+
@author: Hector Nieto (hector.nieto@ica.csic.es)
|
|
20
|
+
|
|
21
|
+
DESCRIPTION
|
|
22
|
+
===========
|
|
23
|
+
This package contains the class object for configuring and running TSEB for both
|
|
24
|
+
an image with constant meteorology forcing and a time-series of tabulated data.
|
|
25
|
+
|
|
26
|
+
EXAMPLES
|
|
27
|
+
========
|
|
28
|
+
The easiest way to get a feeling of TSEB and its configuration is throuh the ipython/jupyter
|
|
29
|
+
notebooks.
|
|
30
|
+
|
|
31
|
+
Jupyter notebook pyTSEB GUI
|
|
32
|
+
---------------------------
|
|
33
|
+
To configure TSEB for processing a time series of tabulated data, type in a ipython terminal or a
|
|
34
|
+
jupyter notebook.
|
|
35
|
+
|
|
36
|
+
.. code-block:: ipython
|
|
37
|
+
|
|
38
|
+
from TSEB_IPython_Interface import TSEB_IPython_Interface # Import IPython TSEB interface
|
|
39
|
+
setup=TSEB_IPython_Interface() # Create the setup instance from the interface class object
|
|
40
|
+
setup.PointTimeSeriesWidget() # Launches the GUI
|
|
41
|
+
|
|
42
|
+
then to run pyTSEB.
|
|
43
|
+
|
|
44
|
+
.. code-block:: ipython
|
|
45
|
+
|
|
46
|
+
setup.GetDataTSEBWidgets(isImage = False) # Get the data from the widgets
|
|
47
|
+
setup.RunTSEB(isImage = False) # Run TSEB
|
|
48
|
+
|
|
49
|
+
Similarly, to configure and run TSEB for an image.
|
|
50
|
+
|
|
51
|
+
.. code-block:: ipython
|
|
52
|
+
|
|
53
|
+
from TSEB_IPython_Interface import TSEB_IPython_Interface # Import IPython TSEB interface
|
|
54
|
+
setup=TSEB_IPython_Interface() # Create the setup instance from the interface class object
|
|
55
|
+
setup.LocalImageWidget() # Launches the GUI
|
|
56
|
+
setup.GetDataTSEBWidgets(isImage = True) # Get the data from the widgets
|
|
57
|
+
setup.RunTSEB(isImage = True) # Run TSEB
|
|
58
|
+
|
|
59
|
+
Parsing directly a configuration file
|
|
60
|
+
-------------------------------------
|
|
61
|
+
You can also parse direcly into TSEB a configuration file previouly created.
|
|
62
|
+
|
|
63
|
+
>>> from TSEB_ConfigFile_Interface import TSEB_ConfigFile_Interface # Import Configuration File TSEB interface
|
|
64
|
+
>>> tseb=TSEB_ConfigFile_Interface()
|
|
65
|
+
>>> configData=tseb.parseInputConfig(configFile,isImage=True) # Read the data from the configuration file into a python dictionary
|
|
66
|
+
>>> tseb.GetDataTSEB(configData,isImage=True) # Parse the data from the dictionary to TSEB
|
|
67
|
+
>>> tseb.RunTSEB(isImage=True)
|
|
68
|
+
|
|
69
|
+
see the guidelines for input and configuration file preparation in :doc:`README_Notebooks`.
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
from os.path import join, splitext, dirname, basename, exists
|
|
74
|
+
from os import mkdir
|
|
75
|
+
from collections import OrderedDict
|
|
76
|
+
import math
|
|
77
|
+
|
|
78
|
+
from osgeo import gdal, ogr, osr
|
|
79
|
+
import numpy as np
|
|
80
|
+
import pandas as pd
|
|
81
|
+
from netCDF4 import Dataset
|
|
82
|
+
|
|
83
|
+
from . import TSEB
|
|
84
|
+
from . import meteo_utils as met
|
|
85
|
+
from . import net_radiation as rad
|
|
86
|
+
from . import resistances as res
|
|
87
|
+
from . import clumping_index as CI
|
|
88
|
+
from . import energy_combination_ET as pet
|
|
89
|
+
from . import dis_TSEB
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Constants for indicating whether model output field should be saved to file
|
|
93
|
+
S_N = 0 # Save Not
|
|
94
|
+
S_P = 1 # Save as Primary output
|
|
95
|
+
S_A = 2 # Save as Ancillary output
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PyTSEB(object):
|
|
99
|
+
|
|
100
|
+
def __init__(self, parameters):
|
|
101
|
+
self.p = parameters
|
|
102
|
+
|
|
103
|
+
# Model description parameters
|
|
104
|
+
self.model_type = self.p['model']
|
|
105
|
+
self.resistance_form = self.p['resistance_form']
|
|
106
|
+
self.res_params = {}
|
|
107
|
+
self.G_form = self.p['G_form']
|
|
108
|
+
self.water_stress = self.p['water_stress']
|
|
109
|
+
self.calc_daily_ET = False
|
|
110
|
+
|
|
111
|
+
def process_local_image(self):
|
|
112
|
+
''' Prepare input data and calculate energy fluxes for all the pixel in an image.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
None
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
in_data : dict
|
|
121
|
+
All the input data coming into the model.
|
|
122
|
+
out_data : dict
|
|
123
|
+
All the output data coming out of the model.
|
|
124
|
+
'''
|
|
125
|
+
|
|
126
|
+
# ======================================
|
|
127
|
+
# Process the input
|
|
128
|
+
|
|
129
|
+
# Create an input dictionary
|
|
130
|
+
in_data = dict()
|
|
131
|
+
temp_data = dict()
|
|
132
|
+
res_params = dict()
|
|
133
|
+
input_fields = self._get_input_structure()
|
|
134
|
+
|
|
135
|
+
# Get projection, geo transform of the input dataset, or its subset if specified.
|
|
136
|
+
# It is assumed that all the input rasters have exactly the same projection, dimensions and
|
|
137
|
+
# resolution.
|
|
138
|
+
try:
|
|
139
|
+
field = list(input_fields)[0]
|
|
140
|
+
fid = gdal.Open(self.p[field], gdal.GA_ReadOnly)
|
|
141
|
+
self.prj = fid.GetProjection()
|
|
142
|
+
self.geo = fid.GetGeoTransform()
|
|
143
|
+
dims = (fid.RasterYSize, fid.RasterXSize)
|
|
144
|
+
fid = None
|
|
145
|
+
self.subset = []
|
|
146
|
+
if "subset" in self.p:
|
|
147
|
+
self.subset, self.geo = self._get_subset(self.p["subset"], self.prj, self.geo)
|
|
148
|
+
if self.subset[3] <= 0 or self.subset[2] <= 0 or\
|
|
149
|
+
self.subset[0] >= dims[1] or self.subset[1] >= dims[0]:
|
|
150
|
+
print("ERROR: Requested subset does not intersect the data extent.")
|
|
151
|
+
return
|
|
152
|
+
if self.subset[1] + self.subset[3] > dims[0] or\
|
|
153
|
+
self.subset[0] + self.subset[2] > dims[1]:
|
|
154
|
+
print("WARNING: Requested subset extends beyond the data extent.")
|
|
155
|
+
self.subset[3] = min(self.subset[3], dims[0] - self.subset[1])
|
|
156
|
+
self.subset[2] = min(self.subset[2], dims[1] - self.subset[0])
|
|
157
|
+
dims = (self.subset[3], self.subset[2])
|
|
158
|
+
except KeyError:
|
|
159
|
+
print('Error reading ' + input_fields[field])
|
|
160
|
+
fid = None
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Process all input fields
|
|
164
|
+
for field in list(input_fields):
|
|
165
|
+
# Some fields might need special treatment
|
|
166
|
+
if field in ["lat", "lon", "stdlon", "DOY", "time"]:
|
|
167
|
+
success, temp_data[field] = self._set_param_array(field, dims)
|
|
168
|
+
elif field == "input_mask":
|
|
169
|
+
if self.p['input_mask'] == '0':
|
|
170
|
+
# Create mask from landcover array
|
|
171
|
+
mask = np.ones(dims, np.int32)
|
|
172
|
+
mask[np.logical_or.reduce((in_data['landcover'] == res.WATER,
|
|
173
|
+
in_data['landcover'] == res.URBAN,
|
|
174
|
+
in_data['landcover'] == res.SNOW))] = 0
|
|
175
|
+
success = True
|
|
176
|
+
else:
|
|
177
|
+
success, mask = self._set_param_array(field, dims)
|
|
178
|
+
elif field in ['KN_b', 'KN_c', 'KN_c_dash']:
|
|
179
|
+
success, res_params[field] = self._set_param_array(field, dims)
|
|
180
|
+
elif field == "G":
|
|
181
|
+
# Get the Soil Heat flux if G_form includes the option of
|
|
182
|
+
# Constant G or constant ratio of soil reaching radiation
|
|
183
|
+
if self.G_form[0][0] == TSEB.G_CONSTANT or self.G_form[0][0] == TSEB.G_RATIO:
|
|
184
|
+
success, self.G_form[1] = self._set_param_array(self.G_form[1], dims)
|
|
185
|
+
# Santanello and Friedls G
|
|
186
|
+
elif self.G_form[0][0] == TSEB.G_TIME_DIFF:
|
|
187
|
+
# Set the time in the G_form flag to compute the Santanello and
|
|
188
|
+
# Friedl G
|
|
189
|
+
self.G_form[1] = self._set_param_array("time", dims)[1]
|
|
190
|
+
elif field == 'S_dn_24':
|
|
191
|
+
success, in_data[field] = self._set_param_array(field, dims)
|
|
192
|
+
if success:
|
|
193
|
+
self.calc_daily_ET = True
|
|
194
|
+
else:
|
|
195
|
+
# Model specific fields which might need special treatment
|
|
196
|
+
success, inputs = self._set_special_model_input(field, dims)
|
|
197
|
+
if success:
|
|
198
|
+
in_data.update(inputs)
|
|
199
|
+
else:
|
|
200
|
+
success, in_data[field] = self._set_param_array(field, dims)
|
|
201
|
+
|
|
202
|
+
if not success:
|
|
203
|
+
# Some fields are optional is some circumstances or can be calculated if missing.
|
|
204
|
+
if field in ["SZA", "SAA"]:
|
|
205
|
+
print("Estimating missing %s parameter" % field)
|
|
206
|
+
try:
|
|
207
|
+
in_data['SZA'], in_data['SAA'] = met.calc_sun_angles(temp_data["lat"],
|
|
208
|
+
temp_data["lon"],
|
|
209
|
+
temp_data["stdlon"],
|
|
210
|
+
temp_data["DOY"],
|
|
211
|
+
temp_data["time"])
|
|
212
|
+
except KeyError as e:
|
|
213
|
+
print("ERROR: Cannot calculate or read {}. {} or parameter {} are missing."
|
|
214
|
+
.format(input_fields[field], field, e))
|
|
215
|
+
return
|
|
216
|
+
elif field == "p":
|
|
217
|
+
print("Estimating missing %s parameter" % field)
|
|
218
|
+
try:
|
|
219
|
+
in_data["p"] = met.calc_pressure(in_data["alt"])
|
|
220
|
+
except KeyError as e:
|
|
221
|
+
print("ERROR: Cannot calculate or read {}. {} or parameter {} are missing."
|
|
222
|
+
.format(input_fields[field], field, e))
|
|
223
|
+
return
|
|
224
|
+
elif field == "L_dn":
|
|
225
|
+
print("Estimating missing %s parameter" % field)
|
|
226
|
+
try:
|
|
227
|
+
in_data['L_dn'] = rad.calc_longwave_irradiance(in_data['ea'],
|
|
228
|
+
in_data['T_A1'],
|
|
229
|
+
in_data['p'],
|
|
230
|
+
in_data['z_T'])
|
|
231
|
+
except KeyError as e:
|
|
232
|
+
print("ERROR: Cannot calculate or read {}. {} or parameter {} are missing."
|
|
233
|
+
.format(input_fields[field], field, e))
|
|
234
|
+
return
|
|
235
|
+
elif (field in ['KN_b', 'KN_c', 'KN_c_dash']
|
|
236
|
+
and self.resistance_form != TSEB.KUSTAS_NORMAN_1999):
|
|
237
|
+
print("ERROR: Cannot read {}.".format(input_fields[field]))
|
|
238
|
+
return
|
|
239
|
+
elif field == "input_mask":
|
|
240
|
+
print("Please set input_mask=0 for processing the whole image.")
|
|
241
|
+
return
|
|
242
|
+
elif field == "S_dn_24":
|
|
243
|
+
print("Provide a valid S_dn_24 (Daily shortwave irradiance) "
|
|
244
|
+
"value if you want to estimate daily ET")
|
|
245
|
+
elif field in ["alt", "lat", "lon", "stdlon", "DOY", "time"]:
|
|
246
|
+
print("WARNING!: Non-critical parameter %s "
|
|
247
|
+
"is invalid or missing..."%field)
|
|
248
|
+
pass
|
|
249
|
+
else:
|
|
250
|
+
print('ERROR: file read {}'.format(field))
|
|
251
|
+
print('Please type a valid filename or a numeric value for '
|
|
252
|
+
.format(input_fields[field]))
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
temp_data = None
|
|
256
|
+
|
|
257
|
+
# ======================================
|
|
258
|
+
# Run the chosen model
|
|
259
|
+
|
|
260
|
+
out_data = self.run(in_data, mask)
|
|
261
|
+
|
|
262
|
+
# ======================================
|
|
263
|
+
# Save output files
|
|
264
|
+
|
|
265
|
+
# Output variables to be saved in images
|
|
266
|
+
all_fields = self._get_output_structure()
|
|
267
|
+
primary_fields = [field for field, save in all_fields.items() if save == S_P]
|
|
268
|
+
ancillary_fields = [field for field, save in all_fields.items() if save == S_A]
|
|
269
|
+
print(primary_fields)
|
|
270
|
+
outdir = dirname(self.p['output_file'])
|
|
271
|
+
if not exists(outdir):
|
|
272
|
+
mkdir(outdir)
|
|
273
|
+
self.write_raster_output(self.p['output_file'], out_data, primary_fields)
|
|
274
|
+
outputfile = splitext(self.p['output_file'])[0] + '_ancillary' + \
|
|
275
|
+
splitext(self.p['output_file'])[1]
|
|
276
|
+
self.write_raster_output(outputfile, out_data, ancillary_fields)
|
|
277
|
+
print('Saved Files')
|
|
278
|
+
|
|
279
|
+
return in_data, out_data
|
|
280
|
+
|
|
281
|
+
def process_point_series_array(self):
|
|
282
|
+
''' Prepare input data and calculate energy fluxes for all the dates in point time-series.
|
|
283
|
+
|
|
284
|
+
Parameters
|
|
285
|
+
----------
|
|
286
|
+
None
|
|
287
|
+
|
|
288
|
+
Returns
|
|
289
|
+
-------
|
|
290
|
+
in_data : dict
|
|
291
|
+
All the input data coming into the model.
|
|
292
|
+
out_data : dict
|
|
293
|
+
All the output data coming out of the model.
|
|
294
|
+
'''
|
|
295
|
+
|
|
296
|
+
def compose_date(
|
|
297
|
+
years,
|
|
298
|
+
months=1,
|
|
299
|
+
days=1,
|
|
300
|
+
weeks=None,
|
|
301
|
+
hours=None,
|
|
302
|
+
minutes=None,
|
|
303
|
+
seconds=None,
|
|
304
|
+
milliseconds=None,
|
|
305
|
+
microseconds=None,
|
|
306
|
+
nanoseconds=None):
|
|
307
|
+
''' Taken from http://stackoverflow.com/questions/34258892/converting-year-and-day-of-year-into-datetime-index-in-pandas'''
|
|
308
|
+
years = np.asarray(years) - 1970
|
|
309
|
+
months = np.asarray(months) - 1
|
|
310
|
+
days = np.asarray(days) - 1
|
|
311
|
+
types = ('<M8[Y]', '<m8[M]', '<m8[D]', '<m8[W]', '<m8[h]',
|
|
312
|
+
'<m8[m]', '<m8[s]', '<m8[ms]', '<m8[us]', '<m8[ns]')
|
|
313
|
+
vals = (years, months, days, weeks, hours, minutes, seconds,
|
|
314
|
+
milliseconds, microseconds, nanoseconds)
|
|
315
|
+
return sum(np.asarray(v, dtype=t) for t, v in zip(types, vals)
|
|
316
|
+
if v is not None)
|
|
317
|
+
|
|
318
|
+
# ======================================
|
|
319
|
+
# Process the input
|
|
320
|
+
|
|
321
|
+
# Read input data from CSV file
|
|
322
|
+
in_data = pd.read_csv(self.p['input_file'],
|
|
323
|
+
sep="\s+",
|
|
324
|
+
index_col=False)
|
|
325
|
+
in_data.index = compose_date(
|
|
326
|
+
years=in_data['year'],
|
|
327
|
+
days=in_data['DOY'],
|
|
328
|
+
hours=in_data['time'],
|
|
329
|
+
minutes=in_data['time'] % 1 * 60)
|
|
330
|
+
|
|
331
|
+
# Check if all the required columns are present
|
|
332
|
+
required_columns = self._get_required_data_columns()
|
|
333
|
+
missing = set(required_columns) - (set(in_data.columns))
|
|
334
|
+
if missing:
|
|
335
|
+
print('ERROR: ' + str(list(missing)) + ' not found in file ' + self.p['input_file'])
|
|
336
|
+
return None, None
|
|
337
|
+
|
|
338
|
+
# Fill in data fields which might not be in the input file
|
|
339
|
+
if 'SZA' not in in_data.columns:
|
|
340
|
+
sza, _ = met.calc_sun_angles(self.p['lat'], self.p['lon'],
|
|
341
|
+
self.p['stdlon'], in_data['DOY'], in_data['time'])
|
|
342
|
+
in_data['SZA'] = sza
|
|
343
|
+
if 'SAA' not in in_data.columns:
|
|
344
|
+
_, saa = met.calc_sun_angles(self.p['lat'], self.p['lon'],
|
|
345
|
+
self.p['stdlon'], in_data['DOY'], in_data['time'])
|
|
346
|
+
in_data['SAA'] = saa
|
|
347
|
+
if 'p' not in in_data.columns:
|
|
348
|
+
# Estimate barometric pressure from the altitude if not included in the table
|
|
349
|
+
in_data['p'] = met.calc_pressure(self.p['alt'])
|
|
350
|
+
if 'f_c' not in in_data.columns: # Fractional cover
|
|
351
|
+
in_data['f_c'] = self.p['f_c'] # Use default value
|
|
352
|
+
if 'w_C' not in in_data.columns: # Canopy width to height ratio
|
|
353
|
+
in_data['w_C'] = self.p['w_C'] # Use default value
|
|
354
|
+
if 'f_g' not in in_data.columns: # Green fraction
|
|
355
|
+
in_data['f_g'] = self.p['f_g'] # Use default value
|
|
356
|
+
if 'rho_vis_C' not in in_data.columns:
|
|
357
|
+
in_data['rho_vis_C'] = self.p['rho_vis_C']
|
|
358
|
+
if 'tau_vis_C' not in in_data.columns:
|
|
359
|
+
in_data['tau_vis_C'] = self.p['tau_vis_C']
|
|
360
|
+
if 'rho_nir_C' not in in_data.columns:
|
|
361
|
+
in_data['rho_nir_C'] = self.p['rho_nir_C']
|
|
362
|
+
if 'tau_nir_C' not in in_data.columns:
|
|
363
|
+
in_data['tau_nir_C'] = self.p['tau_nir_C']
|
|
364
|
+
if 'rho_vis_S' not in in_data.columns:
|
|
365
|
+
in_data['rho_vis_S'] = self.p['rho_vis_S']
|
|
366
|
+
if 'rho_nir_S' not in in_data.columns:
|
|
367
|
+
in_data['rho_nir_S'] = self.p['rho_nir_S']
|
|
368
|
+
if 'emis_C' not in in_data.columns:
|
|
369
|
+
in_data['emis_C'] = self.p['emis_C']
|
|
370
|
+
if 'emis_S' not in in_data.columns:
|
|
371
|
+
in_data['emis_S'] = self.p['emis_S']
|
|
372
|
+
|
|
373
|
+
# Fill in other data fields from the parameter file
|
|
374
|
+
in_data['landcover'] = self.p['landcover']
|
|
375
|
+
in_data['z_u'] = self.p['z_u']
|
|
376
|
+
in_data['z_T'] = self.p['z_T']
|
|
377
|
+
in_data['leaf_width'] = self.p['leaf_width']
|
|
378
|
+
in_data['z0_soil'] = self.p['z0_soil']
|
|
379
|
+
in_data['alpha_PT'] = self.p['alpha_PT']
|
|
380
|
+
in_data['x_LAD'] = self.p['x_LAD']
|
|
381
|
+
|
|
382
|
+
# Incoming long wave radiation
|
|
383
|
+
# If longwave irradiance was not provided then estimate it based on air
|
|
384
|
+
# temperature and humidity
|
|
385
|
+
if 'L_dn' not in in_data.columns:
|
|
386
|
+
in_data['L_dn'] = rad.calc_longwave_irradiance(in_data['ea'], in_data['T_A1'],
|
|
387
|
+
in_data['p'], in_data['z_T'])
|
|
388
|
+
|
|
389
|
+
# Get the Soil Heat flux if G_form includes the option of measured G
|
|
390
|
+
dims = in_data['LAI'].shape
|
|
391
|
+
if self.G_form[0][0] == TSEB.G_CONSTANT:
|
|
392
|
+
if 'G' in in_data.columns:
|
|
393
|
+
self.G_form[1] = in_data['G']
|
|
394
|
+
else:
|
|
395
|
+
self.G_form[1] = np.ones(dims) * self.G_form[1]
|
|
396
|
+
elif self.G_form[0][0] == TSEB.G_RATIO:
|
|
397
|
+
self.G_form[1] = np.ones(dims) * self.G_form[1]
|
|
398
|
+
elif self.G_form[0][0] == TSEB.G_TIME_DIFF:
|
|
399
|
+
# Set the time in the G_form flag to compute the Santanello and
|
|
400
|
+
# Friedl G
|
|
401
|
+
self.G_form[1] = in_data['time']
|
|
402
|
+
|
|
403
|
+
# Set the Kustas and Norman resistance parameters
|
|
404
|
+
if self.resistance_form == 0:
|
|
405
|
+
self.res_params['KN_b'] = np.ones(dims) * self.p['KN_b']
|
|
406
|
+
self.res_params['KN_c'] = np.ones(dims) * self.p['KN_c']
|
|
407
|
+
self.res_params['KN_C_dash'] = np.ones(dims) * self.p['KN_C_dash']
|
|
408
|
+
|
|
409
|
+
# ======================================
|
|
410
|
+
# Run the chosen model
|
|
411
|
+
|
|
412
|
+
out_data = self.run(in_data.to_records(index=False))
|
|
413
|
+
out_data = pd.DataFrame(data=out_data,
|
|
414
|
+
index=in_data.index)
|
|
415
|
+
|
|
416
|
+
# ======================================
|
|
417
|
+
# Save output file
|
|
418
|
+
|
|
419
|
+
# Output Headers
|
|
420
|
+
outputTxtFieldNames = [
|
|
421
|
+
'Year',
|
|
422
|
+
'DOY',
|
|
423
|
+
'Time',
|
|
424
|
+
'LAI',
|
|
425
|
+
'f_g',
|
|
426
|
+
'VZA',
|
|
427
|
+
'SZA',
|
|
428
|
+
'SAA',
|
|
429
|
+
'L_dn',
|
|
430
|
+
'Rn_model',
|
|
431
|
+
'Rn_sw_veg',
|
|
432
|
+
'Rn_sw_soil',
|
|
433
|
+
'Rn_lw_veg',
|
|
434
|
+
'Rn_lw_soil',
|
|
435
|
+
'T_C',
|
|
436
|
+
'T_S',
|
|
437
|
+
'T_AC',
|
|
438
|
+
'LE_model',
|
|
439
|
+
'H_model',
|
|
440
|
+
'LE_C',
|
|
441
|
+
'H_C',
|
|
442
|
+
'LE_S',
|
|
443
|
+
'H_S',
|
|
444
|
+
'G_model',
|
|
445
|
+
'R_S',
|
|
446
|
+
'R_x',
|
|
447
|
+
'R_A',
|
|
448
|
+
'u_friction',
|
|
449
|
+
'L',
|
|
450
|
+
'Skyl',
|
|
451
|
+
'z_0M',
|
|
452
|
+
'd_0',
|
|
453
|
+
'flag']
|
|
454
|
+
|
|
455
|
+
# Create the ouput directory if it doesn't exist
|
|
456
|
+
outdir = dirname(self.p['output_file'])
|
|
457
|
+
if not exists(outdir):
|
|
458
|
+
mkdir(outdir)
|
|
459
|
+
|
|
460
|
+
# Write the data
|
|
461
|
+
csvData = pd.concat([in_data[['year',
|
|
462
|
+
'DOY',
|
|
463
|
+
'time',
|
|
464
|
+
'LAI',
|
|
465
|
+
'f_g',
|
|
466
|
+
'VZA',
|
|
467
|
+
'SZA',
|
|
468
|
+
'SAA',
|
|
469
|
+
'L_dn']],
|
|
470
|
+
out_data[['R_n1',
|
|
471
|
+
'Sn_C1',
|
|
472
|
+
'Sn_S1',
|
|
473
|
+
'Ln_C1',
|
|
474
|
+
'Ln_S1',
|
|
475
|
+
'T_C1',
|
|
476
|
+
'T_S1',
|
|
477
|
+
'T_AC1',
|
|
478
|
+
'LE1',
|
|
479
|
+
'H1',
|
|
480
|
+
'LE_C1',
|
|
481
|
+
'H_C1',
|
|
482
|
+
'LE_S1',
|
|
483
|
+
'H_S1',
|
|
484
|
+
'G1',
|
|
485
|
+
'R_S1',
|
|
486
|
+
'R_x1',
|
|
487
|
+
'R_A1',
|
|
488
|
+
'u_friction',
|
|
489
|
+
'L',
|
|
490
|
+
'Skyl',
|
|
491
|
+
'z_0M',
|
|
492
|
+
'd_0',
|
|
493
|
+
'flag']]],
|
|
494
|
+
axis=1)
|
|
495
|
+
csvData.to_csv(
|
|
496
|
+
self.p['output_file'],
|
|
497
|
+
sep='\t',
|
|
498
|
+
index=False,
|
|
499
|
+
header=outputTxtFieldNames)
|
|
500
|
+
|
|
501
|
+
print('Done')
|
|
502
|
+
|
|
503
|
+
return in_data, out_data
|
|
504
|
+
|
|
505
|
+
def run(self, in_data, mask=None):
|
|
506
|
+
''' Execute the routines to calculate energy fluxes.
|
|
507
|
+
|
|
508
|
+
Parameters
|
|
509
|
+
----------
|
|
510
|
+
in_data : dict
|
|
511
|
+
The input data for the model.
|
|
512
|
+
mask : int array or None
|
|
513
|
+
If None then fluxes will be calculated for all input points. Otherwise, fluxes will be
|
|
514
|
+
calculated only for points for which mask is 1.
|
|
515
|
+
|
|
516
|
+
Returns
|
|
517
|
+
-------
|
|
518
|
+
out_data : dict
|
|
519
|
+
The output data from the model.
|
|
520
|
+
'''
|
|
521
|
+
|
|
522
|
+
print("Processing...")
|
|
523
|
+
|
|
524
|
+
model_params = {"calcG_params": [self.G_form[0], self.G_form[1]],
|
|
525
|
+
"resistance_form": [self.resistance_form, self.res_params]}
|
|
526
|
+
|
|
527
|
+
if mask is None:
|
|
528
|
+
mask = np.ones(in_data['LAI'].shape, np.int32)
|
|
529
|
+
|
|
530
|
+
# Create the output dictionary
|
|
531
|
+
out_data = dict()
|
|
532
|
+
for field in self._get_output_structure():
|
|
533
|
+
out_data[field] = np.zeros(in_data['LAI'].shape, np.float32) + np.nan
|
|
534
|
+
|
|
535
|
+
# Esimate diffuse and direct irradiance
|
|
536
|
+
difvis, difnir, fvis, fnir = rad.calc_difuse_ratio(
|
|
537
|
+
in_data['S_dn'], in_data['SZA'], press=in_data['p'])
|
|
538
|
+
out_data['fvis'] = fvis
|
|
539
|
+
out_data['fnir'] = fnir
|
|
540
|
+
out_data['Skyl'] = difvis * fvis + difnir * fnir
|
|
541
|
+
out_data['S_dn_dir'] = in_data['S_dn'] * (1.0 - out_data['Skyl'])
|
|
542
|
+
out_data['S_dn_dif'] = in_data['S_dn'] * out_data['Skyl']
|
|
543
|
+
|
|
544
|
+
# ======================================
|
|
545
|
+
# First process bare soil cases
|
|
546
|
+
|
|
547
|
+
noVegPixels = in_data['LAI'] <= 0
|
|
548
|
+
noVegPixels = np.logical_or.reduce(
|
|
549
|
+
(in_data['f_c'] <= 0.01,
|
|
550
|
+
in_data['LAI'] <= 0,
|
|
551
|
+
np.isnan(in_data['LAI'])))
|
|
552
|
+
# in_data['LAI'][noVegPixels] = 0
|
|
553
|
+
# in_data['f_c'][noVegPixels] = 0
|
|
554
|
+
i = np.array(np.logical_and(noVegPixels, mask == 1))
|
|
555
|
+
|
|
556
|
+
# Calculate roughness
|
|
557
|
+
out_data['z_0M'][i] = in_data['z0_soil'][i]
|
|
558
|
+
out_data['d_0'][i] = 0
|
|
559
|
+
|
|
560
|
+
# Net shortwave radition for bare soil
|
|
561
|
+
spectraGrdOSEB = out_data['fvis'] * \
|
|
562
|
+
in_data['rho_vis_S'] + out_data['fnir'] * in_data['rho_nir_S']
|
|
563
|
+
out_data['Sn_S1'][i] = (1. - spectraGrdOSEB[i]) * \
|
|
564
|
+
(out_data['S_dn_dir'][i] + out_data['S_dn_dif'][i])
|
|
565
|
+
|
|
566
|
+
# Other fluxes for bare soil
|
|
567
|
+
self._call_flux_model_soil(in_data, out_data, model_params, i)
|
|
568
|
+
|
|
569
|
+
# Set canopy fluxes to 0
|
|
570
|
+
out_data['Sn_C1'][i] = 0.0
|
|
571
|
+
out_data['Ln_C1'][i] = 0.0
|
|
572
|
+
out_data['LE_C1'][i] = 0.0
|
|
573
|
+
out_data['H_C1'][i] = 0.0
|
|
574
|
+
|
|
575
|
+
# ======================================
|
|
576
|
+
# Then process vegetated cases
|
|
577
|
+
|
|
578
|
+
i = np.array(np.logical_and(~noVegPixels, mask == 1))
|
|
579
|
+
|
|
580
|
+
# Calculate roughness
|
|
581
|
+
out_data['z_0M'][i], out_data['d_0'][i] = \
|
|
582
|
+
res.calc_roughness(in_data['LAI'][i],
|
|
583
|
+
in_data['h_C'][i],
|
|
584
|
+
w_C=in_data['w_C'][i],
|
|
585
|
+
landcover=in_data['landcover'][i],
|
|
586
|
+
f_c=in_data['f_c'][i])
|
|
587
|
+
|
|
588
|
+
# Net shortwave radiation for vegetation
|
|
589
|
+
F = np.zeros(in_data['LAI'].shape, np.float32)
|
|
590
|
+
F[i] = in_data['LAI'][i] / in_data['f_c'][i]
|
|
591
|
+
# Clumping index
|
|
592
|
+
omega0 = np.zeros(in_data['LAI'].shape, np.float32)
|
|
593
|
+
Omega = np.zeros(in_data['LAI'].shape, np.float32)
|
|
594
|
+
omega0[i] = CI.calc_omega0_Kustas(
|
|
595
|
+
in_data['LAI'][i],
|
|
596
|
+
in_data['f_c'][i],
|
|
597
|
+
x_LAD=in_data['x_LAD'][i],
|
|
598
|
+
isLAIeff=True)
|
|
599
|
+
if self.p['calc_row'][0] == 0: # randomly placed canopies
|
|
600
|
+
Omega[i] = CI.calc_omega_Kustas(
|
|
601
|
+
omega0[i], in_data['SZA'][i], w_C=in_data['w_C'][i])
|
|
602
|
+
elif self.p['calc_row'][0] == 1: # row crop canopies
|
|
603
|
+
Omega[i] = CI.calc_omega_rows(in_data['LAI'][i],
|
|
604
|
+
in_data['f_c'][i],
|
|
605
|
+
theta=in_data['SZA'][i],
|
|
606
|
+
psi=self.p['calc_row'][1] - in_data['SAA'][i],
|
|
607
|
+
w_c=in_data['w_C'][i],
|
|
608
|
+
x_lad=in_data['x_LAD'][i],
|
|
609
|
+
is_lai_eff=True)
|
|
610
|
+
|
|
611
|
+
else:
|
|
612
|
+
Omega[i] = CI.calc_omega_Kustas(
|
|
613
|
+
omega0[i], in_data['SZA'][i], w_C=in_data['w_C'][i])
|
|
614
|
+
LAI_eff = F * Omega
|
|
615
|
+
[out_data['Sn_C1'][i],
|
|
616
|
+
out_data['Sn_S1'][i]] = rad.calc_Sn_Campbell(in_data['LAI'][i],
|
|
617
|
+
in_data['SZA'][i],
|
|
618
|
+
out_data['S_dn_dir'][i],
|
|
619
|
+
out_data['S_dn_dif'][i],
|
|
620
|
+
out_data['fvis'][i],
|
|
621
|
+
out_data['fnir'][i],
|
|
622
|
+
in_data['rho_vis_C'][i],
|
|
623
|
+
in_data['tau_vis_C'][i],
|
|
624
|
+
in_data['rho_nir_C'][i],
|
|
625
|
+
in_data['tau_nir_C'][i],
|
|
626
|
+
in_data['rho_vis_S'][i],
|
|
627
|
+
in_data['rho_nir_S'][i],
|
|
628
|
+
x_LAD=in_data['x_LAD'][i],
|
|
629
|
+
LAI_eff=LAI_eff[i])
|
|
630
|
+
|
|
631
|
+
# Other fluxes for vegetation
|
|
632
|
+
self._call_flux_model_veg(in_data, out_data, model_params, i)
|
|
633
|
+
|
|
634
|
+
# Calculate the bulk fluxes
|
|
635
|
+
out_data['LE1'] = out_data['LE_C1'] + out_data['LE_S1']
|
|
636
|
+
out_data['LE_partition'] = out_data['LE_C1'] / out_data['LE1']
|
|
637
|
+
out_data['H1'] = out_data['H_C1'] + out_data['H_S1']
|
|
638
|
+
out_data['R_ns1'] = out_data['Sn_C1'] + out_data['Sn_S1']
|
|
639
|
+
out_data['R_nl1'] = out_data['Ln_C1'] + out_data['Ln_S1']
|
|
640
|
+
out_data['R_n1'] = out_data['R_ns1'] + out_data['R_nl1']
|
|
641
|
+
out_data['delta_R_n1'] = out_data['Sn_C1'] + out_data['Ln_C1']
|
|
642
|
+
|
|
643
|
+
if self.water_stress:
|
|
644
|
+
i = mask == 1
|
|
645
|
+
[_, _, _, _, _, _, out_data['LE_0'][i], _,
|
|
646
|
+
out_data['LE_C_0'][i], _, _, _, _, _, _, _, _, _, _] = \
|
|
647
|
+
pet.shuttleworth_wallace(
|
|
648
|
+
in_data['T_A1'][i],
|
|
649
|
+
in_data['u'][i],
|
|
650
|
+
in_data['ea'][i],
|
|
651
|
+
in_data['p'][i],
|
|
652
|
+
out_data['Sn_C1'][i],
|
|
653
|
+
out_data['Sn_S1'][i],
|
|
654
|
+
in_data['L_dn'][i],
|
|
655
|
+
np.maximum(in_data['LAI'][i], 0.01),
|
|
656
|
+
in_data['h_C'][i],
|
|
657
|
+
in_data['emis_C'][i],
|
|
658
|
+
in_data['emis_S'][i],
|
|
659
|
+
out_data['z_0M'][i],
|
|
660
|
+
out_data['d_0'][i],
|
|
661
|
+
in_data['z_u'][i],
|
|
662
|
+
in_data['z_T'][i],
|
|
663
|
+
f_c=in_data['f_c'][i],
|
|
664
|
+
w_C=in_data['w_C'][i],
|
|
665
|
+
leaf_width=in_data['leaf_width'][i],
|
|
666
|
+
z0_soil=in_data['z0_soil'][i],
|
|
667
|
+
x_LAD=in_data['x_LAD'][i],
|
|
668
|
+
Rst_min=self.p['Rst_min'],
|
|
669
|
+
R_ss=self.p['R_ss'],
|
|
670
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
671
|
+
model_params["calcG_params"][1][i]],
|
|
672
|
+
resistance_form=[model_params["resistance_form"][0],
|
|
673
|
+
{k: model_params["resistance_form"][1][k][i]
|
|
674
|
+
for k in model_params["resistance_form"][1]}])
|
|
675
|
+
|
|
676
|
+
out_data['CWSI'][i] = 1.0 - (out_data['LE_C1'][i] / out_data['LE_C_0'][i])
|
|
677
|
+
|
|
678
|
+
if self.calc_daily_ET:
|
|
679
|
+
out_data['ET_day'] = met.flux_2_evaporation(in_data['S_dn_24'] * out_data['LE1'] / in_data['S_dn'],
|
|
680
|
+
t_k=20+273.15,
|
|
681
|
+
time_domain=24)
|
|
682
|
+
|
|
683
|
+
print("Finished processing!")
|
|
684
|
+
return out_data
|
|
685
|
+
|
|
686
|
+
def _call_flux_model_veg(self, in_data, out_data, model_params, i):
|
|
687
|
+
''' Call a TSEB_PT model to calculate fluxes for data points containing vegetation.
|
|
688
|
+
|
|
689
|
+
Parameters
|
|
690
|
+
----------
|
|
691
|
+
in_data : dict
|
|
692
|
+
The input data for the model.
|
|
693
|
+
out_data : dict
|
|
694
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
695
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
696
|
+
data.
|
|
697
|
+
|
|
698
|
+
Returns
|
|
699
|
+
-------
|
|
700
|
+
None
|
|
701
|
+
'''
|
|
702
|
+
|
|
703
|
+
[out_data['flag'][i], out_data['T_S1'][i], out_data['T_C1'][i],
|
|
704
|
+
out_data['T_AC1'][i], out_data['Ln_S1'][i], out_data['Ln_C1'][i],
|
|
705
|
+
out_data['LE_C1'][i], out_data['H_C1'][i], out_data['LE_S1'][i],
|
|
706
|
+
out_data['H_S1'][i], out_data['G1'][i], out_data['R_S1'][i],
|
|
707
|
+
out_data['R_x1'][i], out_data['R_A1'][i], out_data['u_friction'][i],
|
|
708
|
+
out_data['L'][i], out_data['n_iterations'][i]] = TSEB.TSEB_PT(
|
|
709
|
+
in_data['T_R1'][i],
|
|
710
|
+
in_data['VZA'][i],
|
|
711
|
+
in_data['T_A1'][i],
|
|
712
|
+
in_data['u'][i],
|
|
713
|
+
in_data['ea'][i],
|
|
714
|
+
in_data['p'][i],
|
|
715
|
+
out_data['Sn_C1'][i],
|
|
716
|
+
out_data['Sn_S1'][i],
|
|
717
|
+
in_data['L_dn'][i],
|
|
718
|
+
in_data['LAI'][i],
|
|
719
|
+
in_data['h_C'][i],
|
|
720
|
+
in_data['emis_C'][i],
|
|
721
|
+
in_data['emis_S'][i],
|
|
722
|
+
out_data['z_0M'][i],
|
|
723
|
+
out_data['d_0'][i],
|
|
724
|
+
in_data['z_u'][i],
|
|
725
|
+
in_data['z_T'][i],
|
|
726
|
+
f_c=in_data['f_c'][i],
|
|
727
|
+
f_g=in_data['f_g'][i],
|
|
728
|
+
w_C=in_data['w_C'][i],
|
|
729
|
+
leaf_width=in_data['leaf_width'][i],
|
|
730
|
+
z0_soil=in_data['z0_soil'][i],
|
|
731
|
+
alpha_PT=in_data['alpha_PT'][i],
|
|
732
|
+
x_LAD=in_data['x_LAD'][i],
|
|
733
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
734
|
+
model_params["calcG_params"][1][i]],
|
|
735
|
+
resistance_form=[model_params["resistance_form"][0],
|
|
736
|
+
{k: model_params["resistance_form"][1][k][i]
|
|
737
|
+
for k in model_params["resistance_form"][1]}])
|
|
738
|
+
|
|
739
|
+
def _call_flux_model_soil(self, in_data, out_data, model_params, i):
|
|
740
|
+
''' Call a OSEB model to calculate soil fluxes for data points containing no vegetation.
|
|
741
|
+
|
|
742
|
+
Parameters
|
|
743
|
+
----------
|
|
744
|
+
in_data : dict
|
|
745
|
+
The input data for the model.
|
|
746
|
+
out_data : dict
|
|
747
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
748
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
749
|
+
data.
|
|
750
|
+
|
|
751
|
+
Returns
|
|
752
|
+
-------
|
|
753
|
+
None
|
|
754
|
+
'''
|
|
755
|
+
|
|
756
|
+
[out_data['flag'][i],
|
|
757
|
+
out_data['Ln_S1'][i],
|
|
758
|
+
out_data['LE_S1'][i],
|
|
759
|
+
out_data['H_S1'][i],
|
|
760
|
+
out_data['G1'][i],
|
|
761
|
+
out_data['R_A1'][i],
|
|
762
|
+
out_data['u_friction'][i],
|
|
763
|
+
out_data['L'][i],
|
|
764
|
+
out_data['n_iterations'][i]] = TSEB.OSEB(in_data['T_R1'][i],
|
|
765
|
+
in_data['T_A1'][i],
|
|
766
|
+
in_data['u'][i],
|
|
767
|
+
in_data['ea'][i],
|
|
768
|
+
in_data['p'][i],
|
|
769
|
+
out_data['Sn_S1'][i],
|
|
770
|
+
in_data['L_dn'][i],
|
|
771
|
+
in_data['emis_S'][i],
|
|
772
|
+
out_data['z_0M'][i],
|
|
773
|
+
out_data['d_0'][i],
|
|
774
|
+
in_data['z_u'][i],
|
|
775
|
+
in_data['z_T'][i],
|
|
776
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
777
|
+
model_params["calcG_params"][1][i]])
|
|
778
|
+
|
|
779
|
+
def _set_param_array(self, parameter, dims, band=1):
|
|
780
|
+
'''Set model input parameter as an array.
|
|
781
|
+
|
|
782
|
+
Parameters
|
|
783
|
+
----------
|
|
784
|
+
parameter : float or string
|
|
785
|
+
If float it should be the value of the parameter. If string of a number it is also
|
|
786
|
+
the value of the parameter. Otherwise it is the name of the parameter and the value, or
|
|
787
|
+
path to raster file which contains the values, is read from the parameters dictionary.
|
|
788
|
+
dims : int list
|
|
789
|
+
The dimensions of the output parameter array.
|
|
790
|
+
band : int (default = 1)
|
|
791
|
+
Band (in GDAL convention) of raster file to be read, if parameter is to be read from a
|
|
792
|
+
raster file.
|
|
793
|
+
|
|
794
|
+
Returns
|
|
795
|
+
-------
|
|
796
|
+
success : boolean
|
|
797
|
+
True is the parameter was succefully set, false otherwise.
|
|
798
|
+
array : float array
|
|
799
|
+
The set parameter array.
|
|
800
|
+
'''
|
|
801
|
+
|
|
802
|
+
success = True
|
|
803
|
+
array = None
|
|
804
|
+
|
|
805
|
+
# See if the parameter is a number
|
|
806
|
+
try:
|
|
807
|
+
array = np.zeros(dims, np.float32) + float(parameter)
|
|
808
|
+
return success, array
|
|
809
|
+
except ValueError:
|
|
810
|
+
pass
|
|
811
|
+
|
|
812
|
+
# Otherwise see if the parameter is a parameter name
|
|
813
|
+
try:
|
|
814
|
+
inputString = self.p[parameter]
|
|
815
|
+
except KeyError:
|
|
816
|
+
success = False
|
|
817
|
+
return success, array
|
|
818
|
+
# If it is then get the value of that parameter
|
|
819
|
+
try:
|
|
820
|
+
array = np.zeros(dims, np.float32) + float(inputString)
|
|
821
|
+
except ValueError:
|
|
822
|
+
try:
|
|
823
|
+
fid = gdal.Open(inputString, gdal.GA_ReadOnly)
|
|
824
|
+
if self.subset:
|
|
825
|
+
array = fid.GetRasterBand(band).ReadAsArray(self.subset[0],
|
|
826
|
+
self.subset[1],
|
|
827
|
+
self.subset[2],
|
|
828
|
+
self.subset[3]).astype(np.float32)
|
|
829
|
+
else:
|
|
830
|
+
array = fid.GetRasterBand(band).ReadAsArray().astype(np.float32)
|
|
831
|
+
except AttributeError:
|
|
832
|
+
print("%s image not present for parameter %s" % (inputString, parameter))
|
|
833
|
+
success = False
|
|
834
|
+
finally:
|
|
835
|
+
fid = None
|
|
836
|
+
|
|
837
|
+
return success, array
|
|
838
|
+
|
|
839
|
+
def write_raster_output(self, outfile, output, fields):
|
|
840
|
+
'''Write the specified arrays of a dictionary to a raster file.
|
|
841
|
+
|
|
842
|
+
Parameters
|
|
843
|
+
----------
|
|
844
|
+
outfile : string
|
|
845
|
+
Path to the output raster. If the path ends in ".nc" the output will be saved in a
|
|
846
|
+
netCDF file. If the path ends in ".vrt" then the outputs will be saved in a GDAL
|
|
847
|
+
virtual raster with the actual data saved as GeoTIFFs (one per field) in .data
|
|
848
|
+
sub-folder. Otherwise, the output will be saved as one GeoTIFF.
|
|
849
|
+
output : dict
|
|
850
|
+
The dictionary containing the output data arrays.
|
|
851
|
+
fields : string list
|
|
852
|
+
The list of output fields from the output dictionary to save to file.
|
|
853
|
+
|
|
854
|
+
Returns
|
|
855
|
+
-------
|
|
856
|
+
None
|
|
857
|
+
'''
|
|
858
|
+
|
|
859
|
+
# If the output file has .nc extension then save it as netCDF,
|
|
860
|
+
# otherwise assume that the output should be a GeoTIFF
|
|
861
|
+
ext = splitext(outfile)[1]
|
|
862
|
+
if ext.lower() == ".nc":
|
|
863
|
+
driver_name = "netCDF"
|
|
864
|
+
opt = ["FORMAT=NC4"]
|
|
865
|
+
opt = []
|
|
866
|
+
elif ext.lower() == ".vrt":
|
|
867
|
+
driver_name = "VRT"
|
|
868
|
+
opt = []
|
|
869
|
+
else:
|
|
870
|
+
driver_name = "COG"
|
|
871
|
+
opt = ['COMPRESS=DEFLATE', 'PREDICTOR=YES', 'BIGTIFF=IF_SAFER']
|
|
872
|
+
if driver_name in ["COG", "netCDF"]:
|
|
873
|
+
# Save the data using GDAL by first creating a MEM layer and later using Translate
|
|
874
|
+
rows, cols = np.shape(output['H1'])
|
|
875
|
+
driver = gdal.GetDriverByName("MEM")
|
|
876
|
+
nbands = len(fields)
|
|
877
|
+
ds = driver.Create("MEM", cols, rows, nbands, gdal.GDT_Float32)
|
|
878
|
+
ds.SetGeoTransform(self.geo)
|
|
879
|
+
ds.SetProjection(self.prj)
|
|
880
|
+
for i, field in enumerate(fields):
|
|
881
|
+
band = ds.GetRasterBand(i + 1)
|
|
882
|
+
band.WriteArray(output[field])
|
|
883
|
+
band.SetStatistics(*band.ComputeStatistics(0))
|
|
884
|
+
out_ds = gdal.Translate(outfile, ds, format=driver_name, creationOptions=opt,
|
|
885
|
+
noData=None)
|
|
886
|
+
# If GDAL drivers for other formats do not exist then default to GeoTiff
|
|
887
|
+
if out_ds is None:
|
|
888
|
+
print("Warning: Selected GDAL driver is not supported! Saving as GeoTiff!")
|
|
889
|
+
driver_name = "GTiff"
|
|
890
|
+
opt = ['COMPRESS=DEFLATE', 'PREDICTOR=1', 'BIGTIFF=IF_SAFER']
|
|
891
|
+
gdal.Translate(outfile, ds, format=driver_name, creationOptions=opt, noData=None)
|
|
892
|
+
out_ds = None
|
|
893
|
+
ds = None
|
|
894
|
+
# In case of netCDF format use netCDF4 module to assign proper names
|
|
895
|
+
# to variables (GDAL can't do this). Also it seems that GDAL has
|
|
896
|
+
# problems assigning projection to all the bands so fix that.
|
|
897
|
+
if driver_name == "netCDF":
|
|
898
|
+
ds = Dataset(outfile, 'a')
|
|
899
|
+
grid_mapping = ds["Band1"].grid_mapping
|
|
900
|
+
for i, field in enumerate(fields):
|
|
901
|
+
ds.renameVariable('Band{}'.format(i + 1), field)
|
|
902
|
+
ds[field].grid_mapping = grid_mapping
|
|
903
|
+
ds.close()
|
|
904
|
+
|
|
905
|
+
else:
|
|
906
|
+
# Save each individual oputput in a GeoTIFF file in .data directory using GDAL
|
|
907
|
+
out_dir = join(dirname(outfile),
|
|
908
|
+
splitext(basename(outfile))[0] + ".data")
|
|
909
|
+
if not exists(out_dir):
|
|
910
|
+
mkdir(out_dir)
|
|
911
|
+
out_files = []
|
|
912
|
+
rows, cols = np.shape(output['H1'])
|
|
913
|
+
outfile_tif = (splitext(basename(outfile))[0]).replace("_ancillary", "")
|
|
914
|
+
for i, field in enumerate(fields):
|
|
915
|
+
driver = gdal.GetDriverByName("MEM")
|
|
916
|
+
out_path = join(out_dir, f"{outfile_tif}_{field}.tif")
|
|
917
|
+
ds = driver.Create("MEM", cols, rows, 1, gdal.GDT_Float32)
|
|
918
|
+
ds.SetGeoTransform(self.geo)
|
|
919
|
+
ds.SetProjection(self.prj)
|
|
920
|
+
band = ds.GetRasterBand(1)
|
|
921
|
+
band.WriteArray(output[field])
|
|
922
|
+
opt = ['COMPRESS=DEFLATE', 'PREDICTOR=YES', 'BIGTIFF=IF_SAFER']
|
|
923
|
+
out_ds = gdal.Translate(out_path, ds, format="COG", creationOptions=opt,
|
|
924
|
+
noData=None, stats=True)
|
|
925
|
+
# If GDAL drivers for other formats do not exist then default to GeoTiff
|
|
926
|
+
if out_ds is None:
|
|
927
|
+
opt = ['COMPRESS=DEFLATE', 'PREDICTOR=1', 'BIGTIFF=IF_SAFER']
|
|
928
|
+
gdal.Translate(out_path, ds, format="GTiff", creationOptions=opt, noData=None,
|
|
929
|
+
stats=True)
|
|
930
|
+
out_ds = None
|
|
931
|
+
ds = None
|
|
932
|
+
out_files.extend([out_path])
|
|
933
|
+
|
|
934
|
+
# Create the Virtual Raster Table
|
|
935
|
+
out_vrt = out_dir.replace('.data', '.vrt')
|
|
936
|
+
print(out_files)
|
|
937
|
+
gdal.BuildVRT(out_vrt, out_files, separate=True)
|
|
938
|
+
|
|
939
|
+
def _get_output_structure(self):
|
|
940
|
+
''' Output fields' names for TSEB model.
|
|
941
|
+
|
|
942
|
+
Parameters
|
|
943
|
+
----------
|
|
944
|
+
None
|
|
945
|
+
|
|
946
|
+
Returns
|
|
947
|
+
-------
|
|
948
|
+
output_structure: ordered dict
|
|
949
|
+
Names of the output fields as keys and instructions on whether the output
|
|
950
|
+
should be saved to file as values.
|
|
951
|
+
'''
|
|
952
|
+
|
|
953
|
+
output_structure = OrderedDict([
|
|
954
|
+
# Energy fluxes
|
|
955
|
+
('R_n1', S_P), # net radiation reaching the surface at time t1
|
|
956
|
+
('R_ns1', S_A), # net shortwave radiation reaching the surface at time t1
|
|
957
|
+
('R_nl1', S_A), # net longwave radiation reaching the surface at time t1
|
|
958
|
+
('delta_R_n1', S_A), # net radiation divergence in the canopy at time t1
|
|
959
|
+
('Sn_S1', S_N), # Shortwave radiation reaching the soil at time t1
|
|
960
|
+
('Sn_C1', S_N), # Shortwave radiation intercepted by the canopy at time t1
|
|
961
|
+
('Ln_S1', S_N), # Longwave radiation reaching the soil at time t1
|
|
962
|
+
('Ln_C1', S_N), # Longwave radiation intercepted by the canopy at time t1
|
|
963
|
+
('H_C1', S_A), # canopy sensible heat flux (W/m^2) at time t1
|
|
964
|
+
('H_S1', S_N), # soil sensible heat flux (W/m^2) at time t1
|
|
965
|
+
('H1', S_P), # total sensible heat flux (W/m^2) at time t1
|
|
966
|
+
('LE_C1', S_A), # canopy latent heat flux (W/m^2) at time t1
|
|
967
|
+
('LE_S1', S_N), # soil latent heat flux (W/m^2) at time t1
|
|
968
|
+
('LE1', S_P), # total latent heat flux (W/m^2) at time t1
|
|
969
|
+
('LE_partition', S_A), # Latent Heat Flux Partition (LEc/LE) at time t1
|
|
970
|
+
('G1', S_P), # ground heat flux (W/m^2) at time t1
|
|
971
|
+
# temperatures (might not be accurate)
|
|
972
|
+
('T_C1', S_A), # canopy temperature at time t1 (deg C)
|
|
973
|
+
('T_S1', S_A), # soil temperature at time t1 (deg C)
|
|
974
|
+
('T_AC1', S_N), # air temperature at the canopy interface at time t1 (deg C)
|
|
975
|
+
# resistances
|
|
976
|
+
# resistance to heat transport in the surface layer (s/m) at time t1
|
|
977
|
+
('R_A1', S_A),
|
|
978
|
+
# resistance to heat transport in the canopy surface layer (s/m) at time t1
|
|
979
|
+
('R_x1', S_A),
|
|
980
|
+
# resistance to heat transport from the soil surface (s/m) at time t1 fluxes
|
|
981
|
+
('R_S1', S_A),
|
|
982
|
+
# miscaleneous
|
|
983
|
+
('albedo1', S_N), # surface albedo (Rs_out/Rs_in)
|
|
984
|
+
('omega0', S_N), # nadir view vegetation clumping factor
|
|
985
|
+
('alpha', S_N), # the priestly Taylor factor
|
|
986
|
+
('Ri', S_N), # Richardson number at time t1
|
|
987
|
+
('L', S_A), # Monin Obukhov Length at time t1
|
|
988
|
+
('u_friction', S_A), # Friction velocity
|
|
989
|
+
('theta_s1', S_N), # Sun zenith angle at time t1
|
|
990
|
+
('F', S_N), # Leaf Area Index
|
|
991
|
+
('z_0M', S_N), # Aerodynamic roughness length for momentum trasport (m)
|
|
992
|
+
('d_0', S_N), # Zero-plane displacement height (m)
|
|
993
|
+
('Skyl', S_N),
|
|
994
|
+
('flag', S_A), # Quality flag
|
|
995
|
+
('n_iterations', S_N)]) # Number of iterations before model converged to stable value
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
if self.calc_daily_ET:
|
|
999
|
+
output_structure['ET_day'] = S_P
|
|
1000
|
+
|
|
1001
|
+
if self.water_stress:
|
|
1002
|
+
output_structure['LE_0'] = S_A
|
|
1003
|
+
output_structure['LE_C_0'] = S_A
|
|
1004
|
+
output_structure['CWSI'] = S_P
|
|
1005
|
+
|
|
1006
|
+
return output_structure
|
|
1007
|
+
|
|
1008
|
+
def _get_input_structure(self):
|
|
1009
|
+
''' Input fields' names for TSEB_PT model. Only relevant for image processing mode.
|
|
1010
|
+
|
|
1011
|
+
Parameters
|
|
1012
|
+
----------
|
|
1013
|
+
None
|
|
1014
|
+
|
|
1015
|
+
Returns
|
|
1016
|
+
-------
|
|
1017
|
+
input_fields: string ordered dict
|
|
1018
|
+
Names (keys) and descriptions (values) of TSEB_PT input fields.
|
|
1019
|
+
'''
|
|
1020
|
+
|
|
1021
|
+
input_fields = OrderedDict([
|
|
1022
|
+
# General parameters
|
|
1023
|
+
("T_R1", "Land Surface Temperature"),
|
|
1024
|
+
("LAI", "Leaf Area Index"),
|
|
1025
|
+
("VZA", "View Zenith Angle for LST"),
|
|
1026
|
+
("landcover", "Landcover"),
|
|
1027
|
+
("input_mask", "Input Mask"),
|
|
1028
|
+
# Vegetation parameters
|
|
1029
|
+
("f_c", "Fractional Cover"),
|
|
1030
|
+
("h_C", "Canopy Height"),
|
|
1031
|
+
("w_C", "Canopy Width Ratio"),
|
|
1032
|
+
("f_g", "Green Vegetation Fraction"),
|
|
1033
|
+
("leaf_width", "Leaf Width"),
|
|
1034
|
+
("x_LAD", "Leaf Angle Distribution"),
|
|
1035
|
+
("alpha_PT", "Initial Priestley-Taylor Alpha Value"),
|
|
1036
|
+
# Spectral Properties
|
|
1037
|
+
("rho_vis_C", "Leaf PAR Reflectance"),
|
|
1038
|
+
("tau_vis_C", "Leaf PAR Transmitance"),
|
|
1039
|
+
("rho_nir_C", "Leaf NIR Reflectance"),
|
|
1040
|
+
("tau_nir_C", "Leaf NIR Transmitance"),
|
|
1041
|
+
("rho_vis_S", "Soil PAR Reflectance"),
|
|
1042
|
+
("rho_nir_S", "Soil NIR Reflectance"),
|
|
1043
|
+
("emis_C", "Leaf Emissivity"),
|
|
1044
|
+
("emis_S", "Soil Emissivity"),
|
|
1045
|
+
# Illumination conditions
|
|
1046
|
+
("lat", "Latitude"),
|
|
1047
|
+
("lon", "Longitude"),
|
|
1048
|
+
("stdlon", "Standard Longitude"),
|
|
1049
|
+
("time", "Observation Time for LST"),
|
|
1050
|
+
("DOY", "Observation Day Of Year for LST"),
|
|
1051
|
+
("SZA", "Sun Zenith Angle"),
|
|
1052
|
+
("SAA", "Sun Azimuth Angle"),
|
|
1053
|
+
# Meteorological parameters
|
|
1054
|
+
("T_A1", "Air temperature"),
|
|
1055
|
+
("u", "Wind Speed"),
|
|
1056
|
+
("ea", "Vapour Pressure"),
|
|
1057
|
+
("alt", "Altitude"),
|
|
1058
|
+
("p", "Pressure"),
|
|
1059
|
+
("S_dn", "Shortwave Irradiance"),
|
|
1060
|
+
("z_T", "Air Temperature Height"),
|
|
1061
|
+
("z_u", "Wind Speed Height"),
|
|
1062
|
+
("z0_soil", "Soil Roughness"),
|
|
1063
|
+
("L_dn", "Longwave Irradiance"),
|
|
1064
|
+
# Resistance parameters
|
|
1065
|
+
("KN_b", "Kustas and Norman Resistance Parameter b"),
|
|
1066
|
+
("KN_c", "Kustas and Norman Resistance Parameter c"),
|
|
1067
|
+
("KN_C_dash", "Kustas and Norman Resistance Parameter c-dash"),
|
|
1068
|
+
# Soil heat flux parameter
|
|
1069
|
+
("G", "Soil Heat Flux Parameter"),
|
|
1070
|
+
('S_dn_24', 'Daily shortwave irradiance')])
|
|
1071
|
+
|
|
1072
|
+
return input_fields
|
|
1073
|
+
|
|
1074
|
+
def _set_special_model_input(self, field, dims):
|
|
1075
|
+
''' Special processing for setting certain input fields. Only relevant for image processing
|
|
1076
|
+
mode.
|
|
1077
|
+
|
|
1078
|
+
Parameters
|
|
1079
|
+
----------
|
|
1080
|
+
field : string
|
|
1081
|
+
The name of the input field for which the special processing is needed.
|
|
1082
|
+
dims : int list
|
|
1083
|
+
The dimensions of the output parameter array.
|
|
1084
|
+
|
|
1085
|
+
Returns
|
|
1086
|
+
-------
|
|
1087
|
+
success : boolean
|
|
1088
|
+
True is the parameter was succefully set, false otherwise.
|
|
1089
|
+
inputs : dict
|
|
1090
|
+
Dictionary with keys holding input field name and value holding the input value.
|
|
1091
|
+
'''
|
|
1092
|
+
return False, None
|
|
1093
|
+
|
|
1094
|
+
def _get_required_data_columns(self):
|
|
1095
|
+
''' Input columns' names required in an input asci table for TSEB_PT model. Only relevant
|
|
1096
|
+
for point time-series processing mode.
|
|
1097
|
+
|
|
1098
|
+
Parameters
|
|
1099
|
+
----------
|
|
1100
|
+
None
|
|
1101
|
+
|
|
1102
|
+
Returns
|
|
1103
|
+
-------
|
|
1104
|
+
required_columns : string tuple
|
|
1105
|
+
Names of the required input columns.
|
|
1106
|
+
'''
|
|
1107
|
+
|
|
1108
|
+
required_columns = ('year',
|
|
1109
|
+
'DOY',
|
|
1110
|
+
'time',
|
|
1111
|
+
'T_R1',
|
|
1112
|
+
'VZA',
|
|
1113
|
+
'T_A1',
|
|
1114
|
+
'u',
|
|
1115
|
+
'ea',
|
|
1116
|
+
'S_dn',
|
|
1117
|
+
'LAI',
|
|
1118
|
+
'h_C')
|
|
1119
|
+
return required_columns
|
|
1120
|
+
|
|
1121
|
+
def _get_subset(self, roi_shape, raster_proj_wkt, raster_geo_transform):
|
|
1122
|
+
|
|
1123
|
+
# Find extent of ROI in roiShape projection
|
|
1124
|
+
roi = ogr.Open(roi_shape)
|
|
1125
|
+
roi_layer = roi.GetLayer()
|
|
1126
|
+
roi_extent = roi_layer.GetExtent()
|
|
1127
|
+
|
|
1128
|
+
# Convert the extent to raster projection
|
|
1129
|
+
roi_proj = roi_layer.GetSpatialRef()
|
|
1130
|
+
raster_proj = osr.SpatialReference()
|
|
1131
|
+
raster_proj.ImportFromWkt(raster_proj_wkt)
|
|
1132
|
+
try:
|
|
1133
|
+
# For GDAL 3 indicate the "legacy" axis order
|
|
1134
|
+
# https://gdal.org/tutorials/osr_api_tut.html#crs-and-axis-order
|
|
1135
|
+
# https://github.com/OSGeo/gdal/issues/1546
|
|
1136
|
+
roi_proj = roi_proj.Clone()
|
|
1137
|
+
roi_proj.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)
|
|
1138
|
+
raster_proj = raster_proj.Clone()
|
|
1139
|
+
raster_proj.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)
|
|
1140
|
+
except AttributeError:
|
|
1141
|
+
# For GDAL 2 do nothing
|
|
1142
|
+
pass
|
|
1143
|
+
transform = osr.CoordinateTransformation(roi_proj, raster_proj)
|
|
1144
|
+
point_UL = ogr.CreateGeometryFromWkt("POINT ({} {})"
|
|
1145
|
+
.format(min(roi_extent[0], roi_extent[1]),
|
|
1146
|
+
max(roi_extent[2], roi_extent[3])))
|
|
1147
|
+
point_UL.Transform(transform)
|
|
1148
|
+
point_UL = point_UL.GetPoint()
|
|
1149
|
+
point_LR = ogr.CreateGeometryFromWkt("POINT ({} {})"
|
|
1150
|
+
.format(max(roi_extent[0], roi_extent[1]),
|
|
1151
|
+
min(roi_extent[2], roi_extent[3])))
|
|
1152
|
+
point_LR.Transform(transform)
|
|
1153
|
+
point_LR = point_LR.GetPoint()
|
|
1154
|
+
|
|
1155
|
+
# Get pixel location of this extent
|
|
1156
|
+
ulX = raster_geo_transform[0]
|
|
1157
|
+
ulY = raster_geo_transform[3]
|
|
1158
|
+
pixel_size = raster_geo_transform[1]
|
|
1159
|
+
pixel_UL = [max(int(math.floor((ulY - point_UL[1]) / pixel_size)), 0),
|
|
1160
|
+
max(int(math.floor((point_UL[0] - ulX) / pixel_size)), 0)]
|
|
1161
|
+
pixel_LR = [int(round((ulY - point_LR[1]) / pixel_size)),
|
|
1162
|
+
int(round((point_LR[0] - ulX) / pixel_size))]
|
|
1163
|
+
|
|
1164
|
+
# Get projected extent
|
|
1165
|
+
point_proj_UL = (ulX + pixel_UL[1] * pixel_size, ulY - pixel_UL[0] * pixel_size)
|
|
1166
|
+
|
|
1167
|
+
# Convert to xoff, yoff, xcount, ycount as required by GDAL ReadAsArray()
|
|
1168
|
+
subset_pix = [pixel_UL[1], pixel_UL[0],
|
|
1169
|
+
pixel_LR[1] - pixel_UL[1], pixel_LR[0] - pixel_UL[0]]
|
|
1170
|
+
|
|
1171
|
+
# Get the geo transform of the subset
|
|
1172
|
+
subset_geo_transform = [point_proj_UL[0], pixel_size, raster_geo_transform[2],
|
|
1173
|
+
point_proj_UL[1], raster_geo_transform[4], -pixel_size]
|
|
1174
|
+
|
|
1175
|
+
return subset_pix, subset_geo_transform
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
class PyDTD(PyTSEB):
|
|
1179
|
+
|
|
1180
|
+
def __init__(self, parameters):
|
|
1181
|
+
super().__init__(parameters)
|
|
1182
|
+
|
|
1183
|
+
def _get_input_structure(self):
|
|
1184
|
+
''' Input fields' names for DTD model. Only relevant for image processing mode.
|
|
1185
|
+
|
|
1186
|
+
Parameters
|
|
1187
|
+
----------
|
|
1188
|
+
None
|
|
1189
|
+
|
|
1190
|
+
Returns
|
|
1191
|
+
-------
|
|
1192
|
+
outputStructure: string ordered dict
|
|
1193
|
+
Names (keys) and descriptions (values) of DTD input fields.
|
|
1194
|
+
'''
|
|
1195
|
+
|
|
1196
|
+
input_fields = super()._get_input_structure()
|
|
1197
|
+
input_fields["T_R0"] = "Early Morning Land Surface Temperature"
|
|
1198
|
+
input_fields["T_A0"] = "Early Morning Air Temperature"
|
|
1199
|
+
return input_fields
|
|
1200
|
+
|
|
1201
|
+
def _get_required_data_columns(self):
|
|
1202
|
+
''' Input columns' names required in an input asci table for TSEB_PT model. Only relevant
|
|
1203
|
+
for point time-series processing mode.
|
|
1204
|
+
|
|
1205
|
+
Parameters
|
|
1206
|
+
----------
|
|
1207
|
+
None
|
|
1208
|
+
|
|
1209
|
+
Returns
|
|
1210
|
+
-------
|
|
1211
|
+
required_columns : string tuple
|
|
1212
|
+
Names of the required input columns.
|
|
1213
|
+
'''
|
|
1214
|
+
|
|
1215
|
+
required_columns = ('year',
|
|
1216
|
+
'DOY',
|
|
1217
|
+
'time',
|
|
1218
|
+
'T_R0',
|
|
1219
|
+
'T_R1',
|
|
1220
|
+
'VZA',
|
|
1221
|
+
'T_A0',
|
|
1222
|
+
'T_A1',
|
|
1223
|
+
'u',
|
|
1224
|
+
'ea',
|
|
1225
|
+
'S_dn',
|
|
1226
|
+
'LAI',
|
|
1227
|
+
'h_C')
|
|
1228
|
+
|
|
1229
|
+
return required_columns
|
|
1230
|
+
|
|
1231
|
+
def _call_flux_model_veg(self, in_data, out_data, model_params, i):
|
|
1232
|
+
''' Call a DTD model to calculate fluxes for data points containing vegetation.
|
|
1233
|
+
|
|
1234
|
+
Parameters
|
|
1235
|
+
----------
|
|
1236
|
+
in_data : dict
|
|
1237
|
+
The input data for the model.
|
|
1238
|
+
out_data : dict
|
|
1239
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
1240
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
1241
|
+
data.
|
|
1242
|
+
|
|
1243
|
+
Returns
|
|
1244
|
+
-------
|
|
1245
|
+
None
|
|
1246
|
+
'''
|
|
1247
|
+
|
|
1248
|
+
[out_data['flag'][i], out_data['T_S1'][i], out_data['T_C1'][i],
|
|
1249
|
+
out_data['T_AC1'][i], out_data['Ln_S1'][i], out_data['Ln_C1'][i],
|
|
1250
|
+
out_data['LE_C1'][i], out_data['H_C1'][i], out_data['LE_S1'][i],
|
|
1251
|
+
out_data['H_S1'][i], out_data['G1'][i], out_data['R_S1'][i],
|
|
1252
|
+
out_data['R_x1'][i], out_data['R_A1'][i], out_data['u_friction'][i],
|
|
1253
|
+
out_data['L'][i], out_data['Ri'], out_data['n_iterations'][i]] = TSEB.DTD(
|
|
1254
|
+
in_data['T_R0'][i],
|
|
1255
|
+
in_data['T_R1'][i],
|
|
1256
|
+
in_data['VZA'][i],
|
|
1257
|
+
in_data['T_A0'][i],
|
|
1258
|
+
in_data['T_A1'][i],
|
|
1259
|
+
in_data['u'][i],
|
|
1260
|
+
in_data['ea'][i],
|
|
1261
|
+
in_data['p'][i],
|
|
1262
|
+
out_data['Sn_C1'][i],
|
|
1263
|
+
out_data['Sn_S1'][i],
|
|
1264
|
+
in_data['L_dn'][i],
|
|
1265
|
+
in_data['LAI'][i],
|
|
1266
|
+
in_data['h_C'][i],
|
|
1267
|
+
in_data['emis_C'][i],
|
|
1268
|
+
in_data['emis_S'][i],
|
|
1269
|
+
out_data['z_0M'][i],
|
|
1270
|
+
out_data['d_0'][i],
|
|
1271
|
+
in_data['z_u'][i],
|
|
1272
|
+
in_data['z_T'][i],
|
|
1273
|
+
f_c=in_data['f_c'][i],
|
|
1274
|
+
w_C=in_data['w_C'][i],
|
|
1275
|
+
f_g=in_data['f_g'][i],
|
|
1276
|
+
leaf_width=in_data['leaf_width'][i],
|
|
1277
|
+
z0_soil=in_data['z0_soil'][i],
|
|
1278
|
+
alpha_PT=in_data['alpha_PT'][i],
|
|
1279
|
+
x_LAD=in_data['x_LAD'][i],
|
|
1280
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
1281
|
+
model_params["calcG_params"][1][i]],
|
|
1282
|
+
resistance_form=[model_params["resistance_form"][0],
|
|
1283
|
+
{k: model_params["resistance_form"][1][k][i]
|
|
1284
|
+
for k in model_params["resistance_form"][1]}])
|
|
1285
|
+
|
|
1286
|
+
def _call_flux_model_soil(self, in_data, out_data, model_params, i):
|
|
1287
|
+
''' Call a OSEB model to calculate soil fluxes for data points containing no vegetation.
|
|
1288
|
+
|
|
1289
|
+
Parameters
|
|
1290
|
+
----------
|
|
1291
|
+
in_data : dict
|
|
1292
|
+
The input data for the model.
|
|
1293
|
+
out_data : dict
|
|
1294
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
1295
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
1296
|
+
data.
|
|
1297
|
+
|
|
1298
|
+
Returns
|
|
1299
|
+
-------
|
|
1300
|
+
None
|
|
1301
|
+
'''
|
|
1302
|
+
|
|
1303
|
+
[out_data['flag'][i],
|
|
1304
|
+
out_data['Ln_S1'][i],
|
|
1305
|
+
out_data['LE_S1'][i],
|
|
1306
|
+
out_data['H_S1'][i],
|
|
1307
|
+
out_data['G1'][i],
|
|
1308
|
+
out_data['R_A1'][i],
|
|
1309
|
+
out_data['u_friction'][i],
|
|
1310
|
+
out_data['L'][i],
|
|
1311
|
+
out_data['n_iterations'][i]] = TSEB.OSEB(in_data['T_R1'][i],
|
|
1312
|
+
in_data['T_A1'][i],
|
|
1313
|
+
in_data['u'][i],
|
|
1314
|
+
in_data['ea'][i],
|
|
1315
|
+
in_data['p'][i],
|
|
1316
|
+
out_data['Sn_S1'][i],
|
|
1317
|
+
in_data['L_dn'][i],
|
|
1318
|
+
in_data['emis_S'][i],
|
|
1319
|
+
out_data['z_0M'][i],
|
|
1320
|
+
out_data['d_0'][i],
|
|
1321
|
+
in_data['z_u'][i],
|
|
1322
|
+
in_data['z_T'][i],
|
|
1323
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
1324
|
+
model_params["calcG_params"][1][i]],
|
|
1325
|
+
T0_K=(in_data['T_R0'][i], in_data['T_A0'][i]))
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
class PyTSEB2T(PyTSEB):
|
|
1329
|
+
|
|
1330
|
+
def __init__(self, parameters):
|
|
1331
|
+
super().__init__(parameters)
|
|
1332
|
+
|
|
1333
|
+
def _get_input_structure(self):
|
|
1334
|
+
''' Input fields' names for TSEB_2T model. Only relevant for image processing mode.
|
|
1335
|
+
|
|
1336
|
+
Parameters
|
|
1337
|
+
----------
|
|
1338
|
+
None
|
|
1339
|
+
|
|
1340
|
+
Returns
|
|
1341
|
+
-------
|
|
1342
|
+
outputStructure: string ordered dict
|
|
1343
|
+
Names (keys) and descriptions (values) of TSEB_2T input fields.
|
|
1344
|
+
'''
|
|
1345
|
+
|
|
1346
|
+
input_fields = super()._get_input_structure()
|
|
1347
|
+
del input_fields["T_R1"]
|
|
1348
|
+
input_fields["T_C"] = "Canopy Temperature"
|
|
1349
|
+
input_fields["T_S"] = "Soil Temperature"
|
|
1350
|
+
return input_fields
|
|
1351
|
+
|
|
1352
|
+
def _set_special_model_input(self, field, dims):
|
|
1353
|
+
''' Special processing for setting certain input fields. Only relevant for image processing
|
|
1354
|
+
mode.
|
|
1355
|
+
|
|
1356
|
+
Parameters
|
|
1357
|
+
----------
|
|
1358
|
+
field : string
|
|
1359
|
+
The name of the input field for which the special processing is needed.
|
|
1360
|
+
dims : int list
|
|
1361
|
+
The dimensions of the output parameter array.
|
|
1362
|
+
|
|
1363
|
+
Returns
|
|
1364
|
+
-------
|
|
1365
|
+
success : boolean
|
|
1366
|
+
True is the parameter was succefully set, false otherwise.
|
|
1367
|
+
array : float array
|
|
1368
|
+
The set parameter array.
|
|
1369
|
+
'''
|
|
1370
|
+
|
|
1371
|
+
if field == "T_C":
|
|
1372
|
+
success, val = self._set_param_array("T_R1", dims)
|
|
1373
|
+
inputs = {field: val}
|
|
1374
|
+
elif field == "T_S":
|
|
1375
|
+
success, val = self._set_param_array("T_R1", dims, band=2)
|
|
1376
|
+
inputs = {field: val}
|
|
1377
|
+
else:
|
|
1378
|
+
success = False
|
|
1379
|
+
inputs = None
|
|
1380
|
+
return success, inputs
|
|
1381
|
+
|
|
1382
|
+
def _get_required_data_columns(self):
|
|
1383
|
+
''' Input columns' names required in an input asci table for TSEB_PT model. Only relevant
|
|
1384
|
+
for point time-series processing mode.
|
|
1385
|
+
|
|
1386
|
+
Parameters
|
|
1387
|
+
----------
|
|
1388
|
+
None
|
|
1389
|
+
|
|
1390
|
+
Returns
|
|
1391
|
+
-------
|
|
1392
|
+
required_columns : string tuple
|
|
1393
|
+
Names of the required input columns.
|
|
1394
|
+
'''
|
|
1395
|
+
|
|
1396
|
+
required_columns = ('year',
|
|
1397
|
+
'DOY',
|
|
1398
|
+
'time',
|
|
1399
|
+
'T_C',
|
|
1400
|
+
'T_S',
|
|
1401
|
+
'T_A1',
|
|
1402
|
+
'u',
|
|
1403
|
+
'ea',
|
|
1404
|
+
'S_dn',
|
|
1405
|
+
'LAI',
|
|
1406
|
+
'h_C')
|
|
1407
|
+
return required_columns
|
|
1408
|
+
|
|
1409
|
+
def _call_flux_model_veg(self, in_data, out_data, model_params, i):
|
|
1410
|
+
''' Call a TSEB_2T model to calculate fluxes for data points containing vegetation.
|
|
1411
|
+
|
|
1412
|
+
Parameters
|
|
1413
|
+
----------
|
|
1414
|
+
in_data : dict
|
|
1415
|
+
The input data for the model.
|
|
1416
|
+
out_data : dict
|
|
1417
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
1418
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
1419
|
+
data.
|
|
1420
|
+
|
|
1421
|
+
Returns
|
|
1422
|
+
-------
|
|
1423
|
+
None
|
|
1424
|
+
'''
|
|
1425
|
+
|
|
1426
|
+
[out_data['flag'][i], out_data['T_AC1'][i], out_data['Ln_S1'][i],
|
|
1427
|
+
out_data['Ln_C1'][i], out_data['LE_C1'][i], out_data['H_C1'][i],
|
|
1428
|
+
out_data['LE_S1'][i], out_data['H_S1'][i], out_data['G1'][i],
|
|
1429
|
+
out_data['R_S1'][i], out_data['R_x1'][i], out_data['R_A1'][i],
|
|
1430
|
+
out_data['u_friction'][i], out_data['L'][i], out_data['n_iterations'][i]] = TSEB.TSEB_2T(
|
|
1431
|
+
in_data['T_C'][i],
|
|
1432
|
+
in_data['T_S'][i],
|
|
1433
|
+
in_data['T_A1'][i],
|
|
1434
|
+
in_data['u'][i],
|
|
1435
|
+
in_data['ea'][i],
|
|
1436
|
+
in_data['p'][i],
|
|
1437
|
+
out_data['Sn_C1'][i],
|
|
1438
|
+
out_data['Sn_S1'][i],
|
|
1439
|
+
in_data['L_dn'][i],
|
|
1440
|
+
in_data['LAI'][i],
|
|
1441
|
+
in_data['h_C'][i],
|
|
1442
|
+
in_data['emis_C'][i],
|
|
1443
|
+
in_data['emis_S'][i],
|
|
1444
|
+
out_data['z_0M'][i],
|
|
1445
|
+
out_data['d_0'][i],
|
|
1446
|
+
in_data['z_u'][i],
|
|
1447
|
+
in_data['z_T'][i],
|
|
1448
|
+
f_c=in_data['f_c'][i],
|
|
1449
|
+
f_g=in_data['f_g'][i],
|
|
1450
|
+
w_C=in_data['w_C'][i],
|
|
1451
|
+
leaf_width=in_data['leaf_width'][i],
|
|
1452
|
+
z0_soil=in_data['z0_soil'][i],
|
|
1453
|
+
alpha_PT=in_data['alpha_PT'][i],
|
|
1454
|
+
x_LAD=in_data['x_LAD'][i],
|
|
1455
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
1456
|
+
model_params["calcG_params"][1][i]],
|
|
1457
|
+
resistance_form=[model_params["resistance_form"][0],
|
|
1458
|
+
{k: model_params["resistance_form"][1][k][i]
|
|
1459
|
+
for k in model_params["resistance_form"][1]}])
|
|
1460
|
+
|
|
1461
|
+
def _call_flux_model_soil(self, in_data, out_data, model_params, i):
|
|
1462
|
+
''' Call a OSEB model to calculate soil fluxes for data points containing no vegetation.
|
|
1463
|
+
|
|
1464
|
+
Parameters
|
|
1465
|
+
----------
|
|
1466
|
+
in_data : dict
|
|
1467
|
+
The input data for the model.
|
|
1468
|
+
out_data : dict
|
|
1469
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
1470
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
1471
|
+
data.
|
|
1472
|
+
|
|
1473
|
+
Returns
|
|
1474
|
+
-------
|
|
1475
|
+
None
|
|
1476
|
+
'''
|
|
1477
|
+
|
|
1478
|
+
[out_data['flag'][i],
|
|
1479
|
+
out_data['Ln_S1'][i],
|
|
1480
|
+
out_data['LE_S1'][i],
|
|
1481
|
+
out_data['H_S1'][i],
|
|
1482
|
+
out_data['G1'][i],
|
|
1483
|
+
out_data['R_A1'][i],
|
|
1484
|
+
out_data['u_friction'][i],
|
|
1485
|
+
out_data['L'][i],
|
|
1486
|
+
out_data['n_iterations'][i]] = TSEB.OSEB(in_data['T_S'][i],
|
|
1487
|
+
in_data['T_A1'][i],
|
|
1488
|
+
in_data['u'][i],
|
|
1489
|
+
in_data['ea'][i],
|
|
1490
|
+
in_data['p'][i],
|
|
1491
|
+
out_data['Sn_S1'][i],
|
|
1492
|
+
in_data['L_dn'][i],
|
|
1493
|
+
in_data['emis_S'][i],
|
|
1494
|
+
out_data['z_0M'][i],
|
|
1495
|
+
out_data['d_0'][i],
|
|
1496
|
+
in_data['z_u'][i],
|
|
1497
|
+
in_data['z_T'][i],
|
|
1498
|
+
calcG_params=[model_params["calcG_params"][0],
|
|
1499
|
+
model_params["calcG_params"][1][i]])
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
class PydisTSEB(PyTSEB):
|
|
1503
|
+
|
|
1504
|
+
def __init__(self, parameters):
|
|
1505
|
+
|
|
1506
|
+
super().__init__(parameters)
|
|
1507
|
+
# Method for ensuring consistency between low- and high-resolution fluxes
|
|
1508
|
+
self.flux_LR_method = self.p["flux_LR_method"]
|
|
1509
|
+
# Correct LST (if True) or air temperature (if false) during disaggregation.
|
|
1510
|
+
self.correct_LST = self.p["correct_LST"]
|
|
1511
|
+
|
|
1512
|
+
def _get_input_structure(self):
|
|
1513
|
+
''' Input fields' names for disTSEB model. Only relevant for image processing mode.
|
|
1514
|
+
|
|
1515
|
+
Parameters
|
|
1516
|
+
----------
|
|
1517
|
+
None
|
|
1518
|
+
|
|
1519
|
+
Returns
|
|
1520
|
+
-------
|
|
1521
|
+
outputStructure: string ordered dict
|
|
1522
|
+
Names (keys) and descriptions (values) of disTSEB input fields.
|
|
1523
|
+
'''
|
|
1524
|
+
|
|
1525
|
+
input_fields = super()._get_input_structure()
|
|
1526
|
+
input_fields["flux_LR"] = "pyTSEB Low Resolution Flux date"
|
|
1527
|
+
input_fields["flux_LR_ancillary"] = "pyTSEB Low Resolution ancillary Flux data"
|
|
1528
|
+
|
|
1529
|
+
return input_fields
|
|
1530
|
+
|
|
1531
|
+
def _get_output_structure(self):
|
|
1532
|
+
''' Input fields' names for disTSEB model. Only relevant for image processing mode.
|
|
1533
|
+
|
|
1534
|
+
Parameters
|
|
1535
|
+
----------
|
|
1536
|
+
None
|
|
1537
|
+
|
|
1538
|
+
Returns
|
|
1539
|
+
-------
|
|
1540
|
+
outputStructure: string ordered dict
|
|
1541
|
+
Names (keys) and descriptions (values) of disTSEB input fields.
|
|
1542
|
+
'''
|
|
1543
|
+
|
|
1544
|
+
output_structure = super()._get_output_structure()
|
|
1545
|
+
output_structure["T_offset"] = S_A
|
|
1546
|
+
output_structure["T_offset_orig"] = S_A
|
|
1547
|
+
output_structure["counter"] = S_A
|
|
1548
|
+
return output_structure
|
|
1549
|
+
|
|
1550
|
+
def _set_special_model_input(self, field, dims):
|
|
1551
|
+
''' Special processing for setting certain input fields. Only relevant for image processing
|
|
1552
|
+
mode.
|
|
1553
|
+
|
|
1554
|
+
Parameters
|
|
1555
|
+
----------
|
|
1556
|
+
field : string
|
|
1557
|
+
The name of the input field for which the special processing is needed.
|
|
1558
|
+
dims : int list
|
|
1559
|
+
The dimensions of the output parameter array.
|
|
1560
|
+
|
|
1561
|
+
Returns
|
|
1562
|
+
-------
|
|
1563
|
+
success : boolean
|
|
1564
|
+
True is the parameter was succefully set, false otherwise.
|
|
1565
|
+
array : float array
|
|
1566
|
+
The set parameter array.
|
|
1567
|
+
'''
|
|
1568
|
+
if field in ["flux_LR", "flux_LR_ancillary"]:
|
|
1569
|
+
# Low resolution data in case disaggregation is to be used.
|
|
1570
|
+
inputs = {}
|
|
1571
|
+
fid = gdal.Open(self.p[field], gdal.GA_ReadOnly)
|
|
1572
|
+
if fid is None:
|
|
1573
|
+
print("ERROR: Low resolution data for disaggregation is not avaiable.")
|
|
1574
|
+
return False, None
|
|
1575
|
+
prj_LR = fid.GetProjection()
|
|
1576
|
+
geo_LR = fid.GetGeoTransform()
|
|
1577
|
+
subset = []
|
|
1578
|
+
if "subset" in self.p:
|
|
1579
|
+
subset, geo_LR = self._get_subset(self.p["subset"], prj_LR, geo_LR)
|
|
1580
|
+
inputs[field] = fid.GetRasterBand(1).ReadAsArray(subset[0],
|
|
1581
|
+
subset[1],
|
|
1582
|
+
subset[2],
|
|
1583
|
+
subset[3]).astype(np.float32)
|
|
1584
|
+
else:
|
|
1585
|
+
inputs[field] = fid.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
|
1586
|
+
inputs['scale'] = [geo_LR, prj_LR, self.geo, self.prj]
|
|
1587
|
+
success = True
|
|
1588
|
+
else:
|
|
1589
|
+
success = False
|
|
1590
|
+
inputs = None
|
|
1591
|
+
|
|
1592
|
+
return success, inputs
|
|
1593
|
+
|
|
1594
|
+
def _call_flux_model_soil(self, in_data, out_data, model_params, i):
|
|
1595
|
+
return
|
|
1596
|
+
|
|
1597
|
+
def _call_flux_model_veg(self, in_data, out_data, model_params, i):
|
|
1598
|
+
''' Call a dis_TSEB model to calculate fluxes
|
|
1599
|
+
|
|
1600
|
+
Parameters
|
|
1601
|
+
----------
|
|
1602
|
+
in_data : dict
|
|
1603
|
+
The input data for the model.
|
|
1604
|
+
out_data : dict
|
|
1605
|
+
Dict containing the output data from the model which will be updated. It also contains
|
|
1606
|
+
previusly calculated shortwave radiation and roughness values which are used as input
|
|
1607
|
+
data.
|
|
1608
|
+
|
|
1609
|
+
Returns
|
|
1610
|
+
-------
|
|
1611
|
+
None
|
|
1612
|
+
'''
|
|
1613
|
+
|
|
1614
|
+
print('Running dis TSEB for the whole image')
|
|
1615
|
+
|
|
1616
|
+
[out_data['flag'],
|
|
1617
|
+
out_data['T_S1'],
|
|
1618
|
+
out_data['T_C1'],
|
|
1619
|
+
out_data['T_AC1'],
|
|
1620
|
+
out_data['Ln_S1'],
|
|
1621
|
+
out_data['Ln_C1'],
|
|
1622
|
+
out_data['LE_C1'],
|
|
1623
|
+
out_data['H_C1'],
|
|
1624
|
+
out_data['LE_S1'],
|
|
1625
|
+
out_data['H_S1'],
|
|
1626
|
+
out_data['G1'],
|
|
1627
|
+
out_data['R_S1'],
|
|
1628
|
+
out_data['R_x1'],
|
|
1629
|
+
out_data['R_A1'],
|
|
1630
|
+
out_data['u_friction'],
|
|
1631
|
+
out_data['L'],
|
|
1632
|
+
out_data['n_iterations'],
|
|
1633
|
+
out_data['T_offset'],
|
|
1634
|
+
out_data['counter'],
|
|
1635
|
+
out_data['T_offset_orig']] = dis_TSEB.dis_TSEB(
|
|
1636
|
+
in_data['flux_LR'],
|
|
1637
|
+
in_data['scale'],
|
|
1638
|
+
in_data['T_R1'],
|
|
1639
|
+
in_data['VZA'],
|
|
1640
|
+
in_data['T_A1'],
|
|
1641
|
+
in_data['u'],
|
|
1642
|
+
in_data['ea'],
|
|
1643
|
+
in_data['p'],
|
|
1644
|
+
out_data['Sn_C1'],
|
|
1645
|
+
out_data['Sn_S1'],
|
|
1646
|
+
in_data['L_dn'],
|
|
1647
|
+
in_data['LAI'],
|
|
1648
|
+
in_data['h_C'],
|
|
1649
|
+
in_data['emis_C'],
|
|
1650
|
+
in_data['emis_S'],
|
|
1651
|
+
out_data['z_0M'],
|
|
1652
|
+
out_data['d_0'],
|
|
1653
|
+
in_data['z_u'],
|
|
1654
|
+
in_data['z_T'],
|
|
1655
|
+
UseL=in_data['flux_LR_ancillary'],
|
|
1656
|
+
f_c=in_data['f_c'],
|
|
1657
|
+
f_g=in_data['f_g'],
|
|
1658
|
+
w_C=in_data['w_C'],
|
|
1659
|
+
leaf_width=in_data['leaf_width'],
|
|
1660
|
+
z0_soil=in_data['z0_soil'],
|
|
1661
|
+
alpha_PT=in_data['alpha_PT'],
|
|
1662
|
+
x_LAD=in_data['x_LAD'],
|
|
1663
|
+
calcG_params=model_params["calcG_params"],
|
|
1664
|
+
resistance_form=model_params["resistance_form"],
|
|
1665
|
+
flux_LR_method=self.flux_LR_method,
|
|
1666
|
+
correct_LST=self.correct_LST)
|