PM-JPL 1.2.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.
Potentially problematic release.
This version of PM-JPL might be problematic. Click here for more details.
- PMJPL/MCD12C1/MCD12C1.py +10 -0
- PMJPL/MCD12C1/__init__.py +1 -0
- PMJPL/PMJPL.py +406 -0
- PMJPL/SEBAL/SEBAL.py +45 -0
- PMJPL/SEBAL/__init__.py +1 -0
- PMJPL/VPD_factor.py +26 -0
- PMJPL/__init__.py +9 -0
- PMJPL/canopy_aerodynamic_resistance.py +31 -0
- PMJPL/canopy_conductance.py +36 -0
- PMJPL/constants.py +34 -0
- PMJPL/correctance_factor.py +17 -0
- PMJPL/downscaling/__init__.py +1 -0
- PMJPL/downscaling/downscaling.py +271 -0
- PMJPL/downscaling/linear_downscale.py +71 -0
- PMJPL/evapotranspiration_conversion/__init__.py +1 -0
- PMJPL/evapotranspiration_conversion/evapotranspiration_conversion.py +80 -0
- PMJPL/fwet.py +21 -0
- PMJPL/interception.py +41 -0
- PMJPL/meteorology_conversion/__init__.py +1 -0
- PMJPL/meteorology_conversion/meteorology_conversion.py +123 -0
- PMJPL/parameters.py +41 -0
- PMJPL/penman_monteith/__init__.py +1 -0
- PMJPL/penman_monteith/penman_monteith.py +20 -0
- PMJPL/potential_soil_evaporation.py +48 -0
- PMJPL/priestley_taylor/__init__.py +1 -0
- PMJPL/priestley_taylor/priestley_taylor.py +27 -0
- PMJPL/santanello/__init__.py +1 -0
- PMJPL/santanello/santanello.py +46 -0
- PMJPL/soil_heat_flux/__init__.py +1 -0
- PMJPL/soil_heat_flux/soil_heat_flux.py +62 -0
- PMJPL/soil_moisture_constraint.py +19 -0
- PMJPL/tmin_factor.py +45 -0
- PMJPL/transpiration.py +44 -0
- PMJPL/vegetation_conversion/__init__.py +1 -0
- PMJPL/vegetation_conversion/vegetation_conversion.py +47 -0
- PMJPL/verma_net_radiation/__init__.py +1 -0
- PMJPL/verma_net_radiation/verma_net_radiation.py +108 -0
- PMJPL/wet_canopy_resistance.py +21 -0
- PMJPL/wet_soil_evaporation.py +36 -0
- pm_jpl-1.2.0.dist-info/METADATA +84 -0
- pm_jpl-1.2.0.dist-info/RECORD +44 -0
- pm_jpl-1.2.0.dist-info/WHEEL +5 -0
- pm_jpl-1.2.0.dist-info/licenses/LICENSE +201 -0
- pm_jpl-1.2.0.dist-info/top_level.txt +1 -0
PMJPL/MCD12C1/MCD12C1.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from os.path import join, abspath, dirname
|
|
2
|
+
import numpy as np
|
|
3
|
+
import rasters as rt
|
|
4
|
+
from rasters import Raster, RasterGeometry
|
|
5
|
+
|
|
6
|
+
def load_MCD12C1_IGBP(geometry: RasterGeometry = None) -> Raster:
|
|
7
|
+
filename = join(abspath(dirname(__file__)), "MCD12C1.A2019001.006.2020220162300.tif")
|
|
8
|
+
image = rt.Raster.open(filename, geometry=geometry, resampling="nearest")
|
|
9
|
+
|
|
10
|
+
return image
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .MCD12C1 import *
|
PMJPL/PMJPL.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MOD16 model of evapotranspiration
|
|
3
|
+
|
|
4
|
+
This implementation follows the MOD16 Version 1.5 Collection 6 algorithm described in the MOD16 user's guide.
|
|
5
|
+
https://landweb.nascom.nasa.gov/QA_WWW/forPage/user_guide/MOD16UsersGuide2016.pdf
|
|
6
|
+
|
|
7
|
+
Developed by Gregory Halverson in the Jet Propulsion Laboratory Year-Round Internship Program (Columbus Technologies and Services), in coordination with the ECOSTRESS mission and master's thesis studies at California State University, Northridge.
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Callable, Dict, Union
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from os.path import join, abspath, dirname, expanduser
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
from numpy import where, nan, exp, array, isnan, logical_and, clip, float32
|
|
17
|
+
import warnings
|
|
18
|
+
|
|
19
|
+
import rasters as rt
|
|
20
|
+
|
|
21
|
+
from rasters import Raster, RasterGrid, RasterGeometry
|
|
22
|
+
|
|
23
|
+
from .MCD12C1.MCD12C1 import load_MCD12C1_IGBP
|
|
24
|
+
from .parameters import MOD16_parameter_from_IGBP
|
|
25
|
+
from .evapotranspiration_conversion.evapotranspiration_conversion import lambda_Jkg_from_Ta_C
|
|
26
|
+
from .meteorology_conversion.meteorology_conversion import SVP_Pa_from_Ta_C, calculate_specific_heat, calculate_surface_pressure, celcius_to_kelvin
|
|
27
|
+
from .penman_monteith.penman_monteith import calculate_gamma
|
|
28
|
+
from .priestley_taylor.priestley_taylor import delta_Pa_from_Ta_C, delta_kPa_from_Ta_C
|
|
29
|
+
|
|
30
|
+
from .soil_heat_flux import calculate_soil_heat_flux
|
|
31
|
+
from .evapotranspiration_conversion import daily_ET_from_daily_LE
|
|
32
|
+
from .meteorology_conversion import kelvin_to_celsius, calculate_specific_humidity, calculate_air_density
|
|
33
|
+
from .vegetation_conversion.vegetation_conversion import FVC_from_NDVI, LAI_from_NDVI
|
|
34
|
+
|
|
35
|
+
from .constants import *
|
|
36
|
+
|
|
37
|
+
from .fwet import calculate_fwet
|
|
38
|
+
from .soil_moisture_constraint import calculate_fSM
|
|
39
|
+
from .tmin_factor import calculate_tmin_factor
|
|
40
|
+
from .correctance_factor import calculate_rcorr
|
|
41
|
+
from .VPD_factor import calculate_VPD_factor
|
|
42
|
+
|
|
43
|
+
from .canopy_conductance import calculate_canopy_conductance
|
|
44
|
+
|
|
45
|
+
from .wet_canopy_resistance import calculate_wet_canopy_resistance
|
|
46
|
+
from .canopy_aerodynamic_resistance import calculate_rtotc
|
|
47
|
+
|
|
48
|
+
from .wet_soil_evaporation import calculate_wet_soil_evaporation
|
|
49
|
+
from .potential_soil_evaporation import calculate_potential_soil_evaporation
|
|
50
|
+
from .interception import calculate_interception
|
|
51
|
+
from .transpiration import calculate_transpiration
|
|
52
|
+
|
|
53
|
+
__author__ = 'Qiaozhen Mu, Maosheng Zhao, Steven W. Running, Gregory Halverson'
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
DEFAULT_WORKING_DIRECTORY = "."
|
|
58
|
+
DEFAULT_MOD16_INTERMEDIATE = "MOD16_intermediate"
|
|
59
|
+
|
|
60
|
+
DEFAULT_OUTPUT_VARIABLES = [
|
|
61
|
+
'LEi',
|
|
62
|
+
'LEc',
|
|
63
|
+
'LEs',
|
|
64
|
+
'LE',
|
|
65
|
+
'LE_daily',
|
|
66
|
+
'ET_daily_kg'
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
def PMJPL(
|
|
70
|
+
Rn: Union[Raster, np.ndarray],
|
|
71
|
+
G: Union[Raster, np.ndarray],
|
|
72
|
+
NDVI: Union[Raster, np.ndarray],
|
|
73
|
+
Ta_C: Union[Raster, np.ndarray],
|
|
74
|
+
Tmin_C: Union[Raster, np.ndarray],
|
|
75
|
+
RH: Union[Raster, np.ndarray],
|
|
76
|
+
IGBP: Union[Raster, np.ndarray],
|
|
77
|
+
Ps_Pa: Union[Raster, np.ndarray] = None,
|
|
78
|
+
elevation_m: Union[Raster, np.ndarray] = None,
|
|
79
|
+
delta_Pa: Union[Raster, np.ndarray] = None,
|
|
80
|
+
gamma_Jkg: Union[Raster, np.ndarray, float] = None) -> Dict[str, Raster]:
|
|
81
|
+
results = {}
|
|
82
|
+
|
|
83
|
+
# calculate leaf area index if it's not given
|
|
84
|
+
if LAI is None:
|
|
85
|
+
# calculate leaf area index from NDVI
|
|
86
|
+
LAI = LAI_from_NDVI(NDVI)
|
|
87
|
+
|
|
88
|
+
# calculate fraction of vegetation cover if it's not given
|
|
89
|
+
if FVC is None:
|
|
90
|
+
# calculate fraction of vegetation cover from NDVI
|
|
91
|
+
FVC = FVC_from_NDVI(NDVI)
|
|
92
|
+
|
|
93
|
+
# calculate surface air pressure if it's not given
|
|
94
|
+
if Ps_Pa is None:
|
|
95
|
+
# calculate surface air pressure is Pascal
|
|
96
|
+
Ps_Pa = calculate_surface_pressure(elevation_m=elevation_m, Ta_C=Ta_C)
|
|
97
|
+
|
|
98
|
+
# calculate Penman-Monteith/Priestley-Taylor delta term if it's not given
|
|
99
|
+
if delta_Pa is None:
|
|
100
|
+
# calculate Penman-Monteith/Priestley-Taylor delta term in Pascal per degree Celsius
|
|
101
|
+
delta_Pa = delta_Pa_from_Ta_C(Ta_C)
|
|
102
|
+
|
|
103
|
+
# calculate latent heat of vaporization if it's not given
|
|
104
|
+
if lambda_Jkg is None:
|
|
105
|
+
# calculate latent heat of vaporization in Joules per kilogram
|
|
106
|
+
lambda_Jkg = lambda_Jkg_from_Ta_C(Ta_C)
|
|
107
|
+
|
|
108
|
+
logger.info("calculating PM-MOD meteorology")
|
|
109
|
+
|
|
110
|
+
# calculate air temperature in Kelvin
|
|
111
|
+
Ta_K = celcius_to_kelvin(Ta_C)
|
|
112
|
+
|
|
113
|
+
# calculate saturation vapor pressure in Pascal from air temperature in Celsius
|
|
114
|
+
SVP_Pa = SVP_Pa_from_Ta_C(Ta_C)
|
|
115
|
+
|
|
116
|
+
# calculate vapor pressure in Pascal from releative humidity and saturation vapor pressure
|
|
117
|
+
Ea_Pa = RH * SVP_Pa
|
|
118
|
+
|
|
119
|
+
# specific humidity of air
|
|
120
|
+
# as a ratio of kilograms of water to kilograms of air and water
|
|
121
|
+
# from surface pressure and actual water vapor pressure
|
|
122
|
+
specific_humidity = calculate_specific_humidity(Ea_Pa, Ps_Pa)
|
|
123
|
+
results['specific_humidity'] = specific_humidity
|
|
124
|
+
|
|
125
|
+
# calculate air density (rho) in kilograms per cubic meter
|
|
126
|
+
rho_kgm3 = calculate_air_density(Ps_Pa, Ta_K, specific_humidity)
|
|
127
|
+
results["rho_kgm3"] = rho_kgm3
|
|
128
|
+
|
|
129
|
+
# calculate specific heat capacity of the air (Cp)
|
|
130
|
+
# in joules per kilogram per kelvin
|
|
131
|
+
# from specific heat of water vapor (CPW)
|
|
132
|
+
# and specific heat of dry air (CPD)
|
|
133
|
+
Cp_Jkg = calculate_specific_heat(specific_humidity)
|
|
134
|
+
results["Cp"] = Cp_Jkg
|
|
135
|
+
|
|
136
|
+
# calculate delta term if it's not given
|
|
137
|
+
if delta_Pa is None:
|
|
138
|
+
# slope of saturation vapor pressure curve in Pascal per degree
|
|
139
|
+
delta_Pa = delta_Pa_from_Ta_C(Ta_C)
|
|
140
|
+
|
|
141
|
+
results["delta_Pa"] = delta_Pa
|
|
142
|
+
|
|
143
|
+
# calculate gamma term if it's not given
|
|
144
|
+
if gamma_Jkg is None:
|
|
145
|
+
# calculate psychrometric gamma in Joules per kilogram
|
|
146
|
+
gamma_Jkg = calculate_gamma(
|
|
147
|
+
Ta_C=Ta_C,
|
|
148
|
+
Ps_Pa=Ps_Pa,
|
|
149
|
+
Cp_Jkg=Cp_Jkg
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# vapor pressure deficit in Pascal
|
|
153
|
+
VPD_Pa = rt.clip(SVP_Pa - Ea_Pa, 0.0, None)
|
|
154
|
+
|
|
155
|
+
# calculate relative surface wetness (fwet)
|
|
156
|
+
# from relative humidity
|
|
157
|
+
fwet = calculate_fwet(RH)
|
|
158
|
+
results['fwet'] = fwet
|
|
159
|
+
|
|
160
|
+
logger.info("calculating PM-MOD resistances")
|
|
161
|
+
|
|
162
|
+
# query leaf conductance to sensible heat (gl_sh) in seconds per meter
|
|
163
|
+
gl_sh = MOD16_parameter_from_IGBP(
|
|
164
|
+
variable="gl_sh",
|
|
165
|
+
IGBP=IGBP
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
results['gl_sh'] = gl_sh
|
|
169
|
+
|
|
170
|
+
# calculate wet canopy resistance to sensible heat (rhc) in seconds per meter
|
|
171
|
+
# from leaf conductance to sensible heat (gl_sh), LAI, and relative surface wetness (fwet)
|
|
172
|
+
rhc = calculate_wet_canopy_resistance(gl_sh, LAI, fwet)
|
|
173
|
+
results['rhc'] = rhc
|
|
174
|
+
|
|
175
|
+
# calculate resistance to radiative heat transfer through air (rrc)
|
|
176
|
+
rrc = float32(rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3.0))
|
|
177
|
+
results['rrc'] = rrc
|
|
178
|
+
|
|
179
|
+
# calculate aerodynamic resistance (rhrc)
|
|
180
|
+
# in seconds per meter
|
|
181
|
+
# from wet canopy resistance to sensible heat
|
|
182
|
+
# and resistance to radiative heat transfer through air
|
|
183
|
+
rhrc = float32((rhc * rrc) / (rhc + rrc))
|
|
184
|
+
results['rhrc'] = rhrc
|
|
185
|
+
|
|
186
|
+
# calculate leaf conductance to evaporated water vapor (gl_e_wv)
|
|
187
|
+
gl_e_wv = MOD16_parameter_from_IGBP(
|
|
188
|
+
variable="gl_e_wv",
|
|
189
|
+
IGBP=IGBP
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
results['gl_e_wv'] = gl_e_wv
|
|
193
|
+
|
|
194
|
+
rvc = calculate_wet_canopy_resistance(gl_e_wv, LAI, fwet)
|
|
195
|
+
results['rvc'] = rvc
|
|
196
|
+
|
|
197
|
+
# caluclate available radiation to the canopy (Ac)
|
|
198
|
+
# in watts per square meter
|
|
199
|
+
# this is the same as net radiation to the canopy in PT-JPL
|
|
200
|
+
Ac = Rn * FVC
|
|
201
|
+
results['Ac'] = Ac
|
|
202
|
+
|
|
203
|
+
# calculate wet latent heat flux (LEi)
|
|
204
|
+
# in watts per square meter
|
|
205
|
+
LEi = calculate_interception(
|
|
206
|
+
delta_Pa=delta_Pa,
|
|
207
|
+
Ac=Ac,
|
|
208
|
+
rho=rho_kgm3,
|
|
209
|
+
Cp=Cp_Jkg,
|
|
210
|
+
VPD_Pa=VPD_Pa,
|
|
211
|
+
FVC=FVC,
|
|
212
|
+
rhrc=rhrc,
|
|
213
|
+
fwet=fwet,
|
|
214
|
+
rvc=rvc,
|
|
215
|
+
gamma_Jkg=gamma_Jkg
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
results['LEi'] = LEi
|
|
219
|
+
|
|
220
|
+
# calculate correctance factor (rcorr)
|
|
221
|
+
# for stomatal and cuticular conductances
|
|
222
|
+
# from surface pressure and air temperature
|
|
223
|
+
rcorr = calculate_rcorr(Ps_Pa, Ta_K)
|
|
224
|
+
results['rcorr'] = rcorr
|
|
225
|
+
|
|
226
|
+
# query biome-specific mean potential stomatal conductance per unit leaf area
|
|
227
|
+
CL = MOD16_parameter_from_IGBP(
|
|
228
|
+
variable="CL",
|
|
229
|
+
IGBP=IGBP
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
results['CL'] = CL
|
|
233
|
+
|
|
234
|
+
# query open minimum temperature by land-cover
|
|
235
|
+
tmin_open = MOD16_parameter_from_IGBP(
|
|
236
|
+
variable="tmin_open",
|
|
237
|
+
IGBP=IGBP
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
results['tmin_open'] = tmin_open
|
|
241
|
+
|
|
242
|
+
# query closed minimum temperature by land-cover
|
|
243
|
+
tmin_close = MOD16_parameter_from_IGBP(
|
|
244
|
+
variable="tmin_close",
|
|
245
|
+
IGBP=IGBP
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
results['tmin_close'] = tmin_close
|
|
249
|
+
|
|
250
|
+
# calculate minimum temperature factor for stomatal conductance
|
|
251
|
+
mTmin = calculate_tmin_factor(Tmin_C, tmin_open, tmin_close, IGBP)
|
|
252
|
+
results['mTmin'] = mTmin
|
|
253
|
+
|
|
254
|
+
# query open vapor pressure deficit by land-cover
|
|
255
|
+
VPD_open = MOD16_parameter_from_IGBP(
|
|
256
|
+
variable="VPD_open",
|
|
257
|
+
IGBP=IGBP
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
results['vpd_open'] = VPD_open
|
|
261
|
+
|
|
262
|
+
# query closed vapor pressure deficit by land-cover
|
|
263
|
+
VPD_close = MOD16_parameter_from_IGBP(
|
|
264
|
+
variable="VPD_close",
|
|
265
|
+
IGBP=IGBP
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
results['vpd_close'] = VPD_close
|
|
269
|
+
|
|
270
|
+
# calculate vapor pressure deficit factor for stomatal conductance
|
|
271
|
+
mVPD = calculate_VPD_factor(VPD_open, VPD_close, VPD_Pa)
|
|
272
|
+
results['mVPD'] = mVPD
|
|
273
|
+
|
|
274
|
+
# calculate stomatal conductance (gs1)
|
|
275
|
+
gs1 = CL * mTmin * mVPD * rcorr
|
|
276
|
+
results['gs1'] = gs1
|
|
277
|
+
|
|
278
|
+
# correct cuticular conductance constant to leaf cuticular conductance (Gcu) using correction factor (rcorr)
|
|
279
|
+
Gcu = CUTICULAR_CONDUCTANCE * rcorr
|
|
280
|
+
results['Gcu'] = Gcu
|
|
281
|
+
|
|
282
|
+
# calculate canopy conductance
|
|
283
|
+
# equivalent to g_canopy
|
|
284
|
+
Cc = calculate_canopy_conductance(LAI, fwet, gl_sh, gs1, Gcu)
|
|
285
|
+
results['Cc'] = Cc
|
|
286
|
+
|
|
287
|
+
# calculate surface resistance to evapotranspiration as inverse of canopy conductance (Cc)
|
|
288
|
+
rs = rt.clip(1.0 / Cc, 0.0, MAX_RESISTANCE)
|
|
289
|
+
results['rs'] = rs
|
|
290
|
+
|
|
291
|
+
# calculate convective heat transfer as inverse of leaf conductance to sensible heat (gl_sh)
|
|
292
|
+
rh = 1.0 / gl_sh
|
|
293
|
+
results['rh'] = rs
|
|
294
|
+
|
|
295
|
+
# calculate radiative heat transfer (rr)
|
|
296
|
+
rr = rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3)
|
|
297
|
+
results['rr'] = rr
|
|
298
|
+
|
|
299
|
+
# calculate parallel resistance (ra)
|
|
300
|
+
# MOD16 user guide is not clear about what to call this
|
|
301
|
+
ra = (rh * rr) / (rh + rr)
|
|
302
|
+
results["ra"] = ra
|
|
303
|
+
|
|
304
|
+
# calculate transpiration
|
|
305
|
+
LEc = calculate_transpiration(
|
|
306
|
+
delta_Pa=delta_Pa,
|
|
307
|
+
Ac=Ac,
|
|
308
|
+
rho=rho_kgm3,
|
|
309
|
+
Cp_Jkg=Cp_Jkg,
|
|
310
|
+
VPD_Pa=VPD_Pa,
|
|
311
|
+
FVC=FVC,
|
|
312
|
+
ra=ra,
|
|
313
|
+
fwet=fwet,
|
|
314
|
+
rs=rs,
|
|
315
|
+
gamma_Jkg=gamma_Jkg
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
results['LEc'] = LEc
|
|
319
|
+
|
|
320
|
+
# soil evaporation
|
|
321
|
+
|
|
322
|
+
# query aerodynamic resistant constraints from land-cover
|
|
323
|
+
rbl_max = MOD16_parameter_from_IGBP(
|
|
324
|
+
variable="rbl_max",
|
|
325
|
+
IGBP=IGBP
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
results['rbl_max'] = rbl_max
|
|
329
|
+
|
|
330
|
+
rbl_min = MOD16_parameter_from_IGBP(
|
|
331
|
+
variable="rbl_min",
|
|
332
|
+
IGBP=IGBP
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
results['rbl_min'] = rbl_min
|
|
336
|
+
|
|
337
|
+
rtotc = calculate_rtotc(VPD_Pa, VPD_open, VPD_close, rbl_max, rbl_min)
|
|
338
|
+
results['rtotc'] = rtotc
|
|
339
|
+
|
|
340
|
+
# calculate total aerodynamic resistance
|
|
341
|
+
# by applying correction to total canopy resistance
|
|
342
|
+
rtot = rcorr * rtotc
|
|
343
|
+
results['rtot'] = rtot
|
|
344
|
+
|
|
345
|
+
# calculate resistance to radiative heat transfer through air
|
|
346
|
+
rrs = float32(rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3))
|
|
347
|
+
results['rrs'] = rrs
|
|
348
|
+
|
|
349
|
+
# calculate aerodynamic resistance at the soil surface
|
|
350
|
+
ras = (rtot * rrs) / (rtot + rrs)
|
|
351
|
+
results['ras'] = ras
|
|
352
|
+
|
|
353
|
+
# calculate available radiation at the soil
|
|
354
|
+
Asoil = rt.clip((1.0 - FVC) * Rn - G, 0.0, None)
|
|
355
|
+
results['Asoil'] = Asoil
|
|
356
|
+
|
|
357
|
+
# separate wet soil evaporation and potential soil evaporation
|
|
358
|
+
|
|
359
|
+
# calculate wet soil evaporation
|
|
360
|
+
LE_soil_wet = calculate_wet_soil_evaporation(
|
|
361
|
+
delta_Pa=delta_Pa,
|
|
362
|
+
Asoil=Asoil,
|
|
363
|
+
rho=rho_kgm3,
|
|
364
|
+
Cp_Jkg=Cp_Jkg,
|
|
365
|
+
FVC=FVC,
|
|
366
|
+
VPD_Pa=VPD_Pa,
|
|
367
|
+
ras=ras,
|
|
368
|
+
fwet=fwet,
|
|
369
|
+
rtot=rtot,
|
|
370
|
+
gamma_Jkg=gamma_Jkg
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
results['LE_soil_wet'] = LE_soil_wet
|
|
374
|
+
|
|
375
|
+
# calculate potential soil evaporation
|
|
376
|
+
LE_soil_pot = calculate_potential_soil_evaporation(
|
|
377
|
+
delta_Pa=delta_Pa,
|
|
378
|
+
Asoil=Asoil,
|
|
379
|
+
rho=rho_kgm3,
|
|
380
|
+
Cp_Jkg=Cp_Jkg,
|
|
381
|
+
FVC=FVC,
|
|
382
|
+
VPD_Pa=VPD_Pa,
|
|
383
|
+
ras=ras,
|
|
384
|
+
fwet=fwet,
|
|
385
|
+
rtot=rtot,
|
|
386
|
+
gamma_Jkg=gamma_Jkg
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
results['LE_soil_pot'] = LE_soil_pot
|
|
390
|
+
|
|
391
|
+
# calculate soil moisture constraint
|
|
392
|
+
fSM = calculate_fSM(RH, VPD_Pa)
|
|
393
|
+
results['fSM'] = fSM
|
|
394
|
+
|
|
395
|
+
# calculate soil evaporation
|
|
396
|
+
LEs = rt.clip(LE_soil_wet + LE_soil_pot * fSM, 0.0, None)
|
|
397
|
+
|
|
398
|
+
# fill soil evaporation with zero
|
|
399
|
+
LEs = rt.where(isnan(LEs), 0.0, LEs)
|
|
400
|
+
results['LEs'] = LEs
|
|
401
|
+
|
|
402
|
+
# sum partitions into total latent heat flux
|
|
403
|
+
LE = rt.clip(LEi + LEc + LEs, 0.0, Rn)
|
|
404
|
+
results['LE'] = LE
|
|
405
|
+
|
|
406
|
+
return results
|
PMJPL/SEBAL/SEBAL.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import rasters as rt
|
|
4
|
+
|
|
5
|
+
def calculate_soil_heat_flux(Rn: np.ndarray, ST_C: np.ndarray, NDVI: np.ndarray, albedo: np.ndarray) -> np.ndarray:
|
|
6
|
+
"""
|
|
7
|
+
This function calculates the soil heat flux (G) in the Surface Energy Balance Algorithm for Land (SEBAL) model.
|
|
8
|
+
The formula used in the function is a simplification of the more complex relationship between these variables in the energy balance at the surface.
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
Rn (np.ndarray): Net radiation at the surface.
|
|
12
|
+
ST_C (np.ndarray): Surface temperature in Celsius.
|
|
13
|
+
NDVI (np.ndarray): Normalized Difference Vegetation Index, indicating the presence and condition of vegetation.
|
|
14
|
+
albedo (np.ndarray): Measure of the diffuse reflection of solar radiation.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
np.ndarray: The soil heat flux (G), a key component in the energy balance.
|
|
18
|
+
|
|
19
|
+
Reference:
|
|
20
|
+
"Evapotranspiration Estimation Based on Remote Sensing and the SEBAL Model in the Bosten Lake Basin of China" [^1^][1]
|
|
21
|
+
"""
|
|
22
|
+
# Empirical coefficients used in the calculation
|
|
23
|
+
coeff1 = 0.0038
|
|
24
|
+
coeff2 = 0.0074
|
|
25
|
+
|
|
26
|
+
# Vegetation cover correction factor
|
|
27
|
+
NDVI_correction = 1 - 0.98 * NDVI ** 4
|
|
28
|
+
|
|
29
|
+
# Calculation of the soil heat flux (G)
|
|
30
|
+
G = Rn * ST_C * (coeff1 + coeff2 * albedo) * NDVI_correction
|
|
31
|
+
|
|
32
|
+
G = rt.clip(G, 0, None)
|
|
33
|
+
|
|
34
|
+
return G
|
|
35
|
+
|
|
36
|
+
def process_SEBAL_G_table(input_df: pd.DataFrame) -> pd.DataFrame:
|
|
37
|
+
Rn = input_df.Rn
|
|
38
|
+
ST_C = input_df.ST_C
|
|
39
|
+
NDVI = input_df.NDVI
|
|
40
|
+
albedo = input_df.albedo
|
|
41
|
+
G = calculate_soil_heat_flux(Rn=Rn, ST_C=ST_C, NDVI=NDVI, albedo=albedo)
|
|
42
|
+
output_df = input_df.copy()
|
|
43
|
+
output_df["G"] = G
|
|
44
|
+
|
|
45
|
+
return output_df
|
PMJPL/SEBAL/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .SEBAL import *
|
PMJPL/VPD_factor.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import numpy as np
|
|
3
|
+
import rasters as rt
|
|
4
|
+
from rasters import Raster
|
|
5
|
+
|
|
6
|
+
def calculate_VPD_factor(
|
|
7
|
+
VPD_open: Union[Raster, np.ndarray],
|
|
8
|
+
VPD_close: Union[Raster, np.ndarray],
|
|
9
|
+
VPD: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
10
|
+
"""
|
|
11
|
+
Calculate the VPD factor based on the open and closed vapor pressure deficit
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
VPD_open (Union[Raster, np.ndarray]): open vapor pressure deficit
|
|
15
|
+
VPD_close (Union[Raster, np.ndarray]): closed vapor pressure deficit
|
|
16
|
+
VPD (Union[Raster, np.ndarray]): vapor pressure deficit
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Union[Raster, np.ndarray]: VPD factor
|
|
20
|
+
"""
|
|
21
|
+
# calculate VPD factor using queried open and closed VPD
|
|
22
|
+
mVPD = rt.where(VPD <= VPD_open, 1.0, np.nan)
|
|
23
|
+
mVPD = rt.where(rt.logical_and(VPD_open < VPD, VPD < VPD_close), (VPD_close - VPD) / (VPD_close - VPD_open), mVPD)
|
|
24
|
+
mVPD = rt.where(VPD >= VPD_close, 0.0, mVPD)
|
|
25
|
+
|
|
26
|
+
return mVPD
|
PMJPL/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import numpy as np
|
|
3
|
+
import rasters as rt
|
|
4
|
+
from rasters import Raster
|
|
5
|
+
|
|
6
|
+
def calculate_rtotc(
|
|
7
|
+
VPD: Union[Raster, np.ndarray],
|
|
8
|
+
vpd_open: Union[Raster, np.ndarray],
|
|
9
|
+
vpd_close: Union[Raster, np.ndarray],
|
|
10
|
+
rbl_max: Union[Raster, np.ndarray],
|
|
11
|
+
rbl_min: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
12
|
+
"""
|
|
13
|
+
calculates total aerodynamic resistance to the canopy
|
|
14
|
+
from vapor pressure deficit and biome-specific constraints.
|
|
15
|
+
:param VPD: vapor pressure deficit in Pascal
|
|
16
|
+
:param vpd_open: vapor pressure deficit when stomata are open in Pascal
|
|
17
|
+
:param vpd_close: vapor pressure deficit when stomata are closed in Pascal
|
|
18
|
+
:param rbl_max: maximum boundary layer resistance in seconds per meter
|
|
19
|
+
:param rbl_min: minimum boundary layer resistance in seconds per meter
|
|
20
|
+
:return: aerodynamic resistance to the canopy in seconds per meter
|
|
21
|
+
"""
|
|
22
|
+
rtotc = rt.where(VPD <= vpd_open, rbl_max, np.nan)
|
|
23
|
+
rtotc = rt.where(VPD >= vpd_close, rbl_min, rtotc)
|
|
24
|
+
|
|
25
|
+
rtotc = rt.where(
|
|
26
|
+
rt.logical_and(vpd_open < VPD, VPD < vpd_close),
|
|
27
|
+
rbl_min + (rbl_max - rbl_min) * (vpd_close - VPD) / (vpd_close - vpd_open),
|
|
28
|
+
rtotc
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return rtotc
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import Union
|
|
3
|
+
from .constants import MAX_RESISTANCE
|
|
4
|
+
import rasters as rt
|
|
5
|
+
from rasters import Raster
|
|
6
|
+
|
|
7
|
+
def calculate_canopy_conductance(
|
|
8
|
+
LAI: Union[Raster, np.ndarray],
|
|
9
|
+
fwet: Union[Raster, np.ndarray],
|
|
10
|
+
gl_sh: Union[Raster, np.ndarray],
|
|
11
|
+
gs1: Union[Raster, np.ndarray],
|
|
12
|
+
Gcu: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
13
|
+
"""
|
|
14
|
+
calculate canopy conductance (Cc)
|
|
15
|
+
Canopy conductance (Cc) to transpired water vapor per unit LAI is derived from stomatal
|
|
16
|
+
and cuticular conductances in parallel with each other, and both in series with leaf boundary layer
|
|
17
|
+
conductance (Thornton, 1998; Running & Kimball, 2005). In the case of plant transpiration,
|
|
18
|
+
surface conductance is equivalent to the canopy conductance (Cc), and hence surface resistance
|
|
19
|
+
(rs) is the inverse of canopy conductance (Cc).
|
|
20
|
+
:param LAI: leaf-area index
|
|
21
|
+
:param fwet: relative surface wetness
|
|
22
|
+
:param gl_sh: leaf boundary layer conductance
|
|
23
|
+
:param gs1: stomatal conductance
|
|
24
|
+
:param Gcu: cuticular conductance
|
|
25
|
+
:return: canopy conductance
|
|
26
|
+
"""
|
|
27
|
+
# noinspection PyTypeChecker
|
|
28
|
+
Cc = rt.where(
|
|
29
|
+
rt.logical_and(LAI > 0.0, (1.0 - fwet) > 0.0),
|
|
30
|
+
gl_sh * (gs1 + Gcu) / (gs1 + gl_sh + Gcu) * LAI * (1.0 - fwet),
|
|
31
|
+
0.0
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
Cc = rt.clip(Cc, 1.0 / MAX_RESISTANCE, None)
|
|
35
|
+
|
|
36
|
+
return Cc
|
PMJPL/constants.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .priestley_taylor import GAMMA_PA
|
|
2
|
+
|
|
3
|
+
# TODO need to defend picking arbitrary maximum to avoid extreme values
|
|
4
|
+
MIN_RESISTANCE = 0.0
|
|
5
|
+
MAX_RESISTANCE = 2000.0
|
|
6
|
+
|
|
7
|
+
# gas constant for dry air in joules per kilogram per kelvin
|
|
8
|
+
RD = 286.9
|
|
9
|
+
|
|
10
|
+
# gas constant for moist air in joules per kilogram per kelvin
|
|
11
|
+
RW = 461.5
|
|
12
|
+
|
|
13
|
+
# specific heat of water vapor in joules per kilogram per kelvin
|
|
14
|
+
CPW = 1846.0
|
|
15
|
+
|
|
16
|
+
# specific heat of dry air in joules per kilogram per kelvin
|
|
17
|
+
CPD = 1005.0
|
|
18
|
+
|
|
19
|
+
# psychrometric constant in Pascal per kelvin
|
|
20
|
+
# GAMMA = 67.0
|
|
21
|
+
|
|
22
|
+
# Stefan Boltzmann constant
|
|
23
|
+
SIGMA = 5.678e-8
|
|
24
|
+
|
|
25
|
+
# cuticular conductance in meters per second
|
|
26
|
+
CUTICULAR_CONDUCTANCE = 0.00001
|
|
27
|
+
|
|
28
|
+
# VPD factor in Pascal for soil moisture constraint
|
|
29
|
+
# MOD16 uses 200, but PT-JPL uses 1000
|
|
30
|
+
BETA = 200
|
|
31
|
+
|
|
32
|
+
RH_THRESHOLD = 0.7
|
|
33
|
+
|
|
34
|
+
MIN_FWET = 0.0001
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import numpy as np
|
|
3
|
+
import rasters as rt
|
|
4
|
+
from rasters import Raster
|
|
5
|
+
|
|
6
|
+
def calculate_rcorr(
|
|
7
|
+
Ps_Pa: Union[Raster, np.ndarray],
|
|
8
|
+
Ta_K: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
9
|
+
"""
|
|
10
|
+
calculates correctance factor (rcorr)
|
|
11
|
+
for stomatal and cuticular conductances
|
|
12
|
+
from surface pressure and air temperature.
|
|
13
|
+
:param Ps_Pa: surface pressure in Pascal
|
|
14
|
+
:param Ta_K: near-surface air temperature in kelvin
|
|
15
|
+
:return: correctance factor (rcorr)
|
|
16
|
+
"""
|
|
17
|
+
return 1.0 / ((101300.0 / Ps_Pa) * (Ta_K / 293.15) ** 1.75)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .downscaling import *
|