PM-JPL 1.4.1__py3-none-any.whl → 1.5.1__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/PMJPL.py +37 -26
- PMJPL/__init__.py +1 -6
- PMJPL/calculate_gamma.py +128 -0
- PMJPL/canopy_aerodynamic_resistance.py +151 -18
- PMJPL/canopy_conductance.py +70 -14
- PMJPL/constants.py +1 -1
- PMJPL/correctance_factor.py +56 -7
- PMJPL/process_daily_ET_table.py +40 -0
- PMJPL/version.py +4 -0
- {pm_jpl-1.4.1.dist-info → pm_jpl-1.5.1.dist-info}/METADATA +8 -2
- pm_jpl-1.5.1.dist-info/RECORD +24 -0
- PMJPL/downscaling/__init__.py +0 -1
- PMJPL/downscaling/downscaling.py +0 -271
- PMJPL/downscaling/linear_downscale.py +0 -71
- PMJPL/evapotranspiration_conversion/__init__.py +0 -1
- PMJPL/evapotranspiration_conversion/evapotranspiration_conversion.py +0 -82
- PMJPL/fwet.py +0 -21
- PMJPL/meteorology_conversion/__init__.py +0 -1
- PMJPL/meteorology_conversion/meteorology_conversion.py +0 -123
- PMJPL/penman_monteith/__init__.py +0 -1
- PMJPL/penman_monteith/penman_monteith.py +0 -19
- PMJPL/priestley_taylor/__init__.py +0 -1
- PMJPL/priestley_taylor/priestley_taylor.py +0 -27
- PMJPL/santanello/__init__.py +0 -1
- PMJPL/santanello/santanello.py +0 -46
- PMJPL/vegetation_conversion/__init__.py +0 -1
- PMJPL/vegetation_conversion/vegetation_conversion.py +0 -47
- PMJPL/version.txt +0 -1
- pm_jpl-1.4.1.dist-info/RECORD +0 -38
- {pm_jpl-1.4.1.dist-info → pm_jpl-1.5.1.dist-info}/WHEEL +0 -0
- {pm_jpl-1.4.1.dist-info → pm_jpl-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {pm_jpl-1.4.1.dist-info → pm_jpl-1.5.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from sun_angles import SHA_deg_from_DOY_lat, daylight_from_SHA, sunrise_from_SHA
|
|
4
|
+
from verma_net_radiation import daily_Rn_integration_verma
|
|
5
|
+
from daily_evapotranspiration_upscaling import daily_ET_from_daily_LE
|
|
6
|
+
|
|
7
|
+
from meteorology_conversion import celcius_to_kelvin
|
|
8
|
+
|
|
9
|
+
def process_daily_ET_table(input_df: pd.DataFrame) -> pd.DataFrame:
|
|
10
|
+
hour_of_day = input_df.hour_of_day
|
|
11
|
+
DOY = input_df.doy
|
|
12
|
+
lat = input_df.lat
|
|
13
|
+
LE = input_df.LE
|
|
14
|
+
Rn = input_df.Rn
|
|
15
|
+
EF = LE / Rn
|
|
16
|
+
|
|
17
|
+
SHA_deg = SHA_deg_from_DOY_lat(DOY=DOY, latitude=lat)
|
|
18
|
+
sunrise_hour = sunrise_from_SHA(SHA_deg)
|
|
19
|
+
daylight_hours = daylight_from_SHA(SHA_deg)
|
|
20
|
+
|
|
21
|
+
Rn_daylight = daily_Rn_integration_verma(
|
|
22
|
+
Rn=Rn,
|
|
23
|
+
hour_of_day=hour_of_day,
|
|
24
|
+
DOY=DOY,
|
|
25
|
+
lat=lat,
|
|
26
|
+
sunrise_hour=sunrise_hour,
|
|
27
|
+
daylight_hours=daylight_hours
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
LE_daylight = EF * Rn_daylight
|
|
31
|
+
ET = daily_ET_from_daily_LE(LE_daylight, daylight_hours)
|
|
32
|
+
|
|
33
|
+
output_df = input_df.copy()
|
|
34
|
+
output_df["EF"] = EF
|
|
35
|
+
output_df["sunrise_hour"] = sunrise_hour
|
|
36
|
+
output_df["daylight_hours"] = daylight_hours
|
|
37
|
+
output_df["Rn_daylight"] = Rn_daylight
|
|
38
|
+
output_df["ET"] = ET
|
|
39
|
+
|
|
40
|
+
return output_df
|
PMJPL/version.py
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PM-JPL
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: JPL implementation of the MOD16 evapotranspiration algorithm for high resolution instantaneous remote sensing imagery
|
|
5
5
|
Author-email: Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>, Qiaozhen Mu <qiaozhen@ntsg.umt.edu>, Maosheng Zhao <zhao@ntsg.umt.edu>, "Steven W. Running" <swr@ntsg.umt.edu>, "Claire S. Villanueva-Weeks" <claire.s.villanueva-weeks@jpl.gov>
|
|
6
6
|
Project-URL: Homepage, https://github.com/JPL-Evapotranspiration-Algorithms/PM-JPL
|
|
@@ -9,18 +9,24 @@ Classifier: Operating System :: OS Independent
|
|
|
9
9
|
Requires-Python: >=3.10
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
+
Requires-Dist: carlson-fractional-vegetation-cover
|
|
13
|
+
Requires-Dist: carlson-leaf-area-index
|
|
12
14
|
Requires-Dist: check-distribution
|
|
15
|
+
Requires-Dist: daily-evapotranspiration-upscaling
|
|
13
16
|
Requires-Dist: ECOv002-CMR>=1.0.5
|
|
14
17
|
Requires-Dist: ECOv002-granules>=1.0.3
|
|
15
18
|
Requires-Dist: ECOv003-granules
|
|
16
19
|
Requires-Dist: GEOS5FP>=1.1.1
|
|
17
20
|
Requires-Dist: MCD12C1_2019_v006
|
|
21
|
+
Requires-Dist: meteorology-conversion
|
|
18
22
|
Requires-Dist: NASADEM
|
|
19
23
|
Requires-Dist: numpy
|
|
20
24
|
Requires-Dist: pandas
|
|
25
|
+
Requires-Dist: priestley-taylor
|
|
26
|
+
Requires-Dist: PTJPL>=1.5.1
|
|
21
27
|
Requires-Dist: rasters>=1.4.6
|
|
22
28
|
Requires-Dist: SEBAL-soil-heat-flux
|
|
23
|
-
Requires-Dist: sun-angles
|
|
29
|
+
Requires-Dist: sun-angles>=1.3.0
|
|
24
30
|
Requires-Dist: verma-net-radiation
|
|
25
31
|
Provides-Extra: dev
|
|
26
32
|
Requires-Dist: build; extra == "dev"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
PMJPL/PMJPL.py,sha256=Hi2zYpOF0Fk2VUbZuQ31uYDYFx1W92t5zxyGcy3E-h8,16079
|
|
2
|
+
PMJPL/VPD_factor.py,sha256=ezHMnnMPGNBt6ZTyiwsTEyMr8fwre6JDsHQea68P7T0,970
|
|
3
|
+
PMJPL/__init__.py,sha256=jke4us8XYaXqU4fL97WYb7Ot1kdD4hV_2NyqFchUh6Q,91
|
|
4
|
+
PMJPL/calculate_gamma.py,sha256=cB7pnbhJM4nArjmtg-gvGR6aj1NvXMOlG2RNZ8oLPFs,5907
|
|
5
|
+
PMJPL/canopy_aerodynamic_resistance.py,sha256=eU5x8riEROyynsMiWDHR_sDCeDexsWDlhuU69tUvC2s,8490
|
|
6
|
+
PMJPL/canopy_conductance.py,sha256=ow7Egm_2wu2Zu5WxkESaUABf7UQA0ldmyPe5Wf4FQ2Y,4814
|
|
7
|
+
PMJPL/constants.py,sha256=T5Da1IzPGM6_E6tBIm2s3CXtwUYP0XXYCHyiPuaLknw,786
|
|
8
|
+
PMJPL/correctance_factor.py,sha256=bVsu23oeTJoeMe-SIsH6cdFyATsLhgwvQUz79yf6kcQ,3181
|
|
9
|
+
PMJPL/interception.py,sha256=2kDqMefH_kzMO9mLVY9BPFSoJE4K0pkQCXwrSi2CR9c,1605
|
|
10
|
+
PMJPL/mod16.csv,sha256=1aVfqLOAJwYuZPaScyqbk4yacObPgp8Q-IPzJm0yReY,847
|
|
11
|
+
PMJPL/parameters.py,sha256=kM26BUDMi-N03Gi2PqQi48kYMKakCB3ReiRMUGrvRQU,1974
|
|
12
|
+
PMJPL/potential_soil_evaporation.py,sha256=O8bHGM4ARqSXJDHu6IWneSEy5qScenotGR3uawijGWg,1829
|
|
13
|
+
PMJPL/process_daily_ET_table.py,sha256=dbKp8LaTXnWaj8gOLueX_AvQpvVVb9OWfz5RqC-kYrc,1197
|
|
14
|
+
PMJPL/soil_moisture_constraint.py,sha256=URUGsE1_p2P1dVQ8qKRcKb93hahYWtAmYqoassRu-PI,667
|
|
15
|
+
PMJPL/tmin_factor.py,sha256=UEeET10ErR2rUr1ViRN8UZmoCACxeeiFAReZXr9Kts4,1595
|
|
16
|
+
PMJPL/transpiration.py,sha256=lhRm2A0yxrlJxDw2he-k3PhE7G-sBsVvAMKo5fmh7wU,1998
|
|
17
|
+
PMJPL/version.py,sha256=7e7k9a2pQYZR4Fyhl6UDY8NQ1lFi5ChR0Zk9Mpl45BU,115
|
|
18
|
+
PMJPL/wet_canopy_resistance.py,sha256=Eape5EBJuoP-IRvD0SWHJKCJWhzm03vcG72OZVYqt_Q,879
|
|
19
|
+
PMJPL/wet_soil_evaporation.py,sha256=mw9JG1oXfVdSWnVRLP2SPd53mSRK96b8k3E1zf0ci7A,1440
|
|
20
|
+
pm_jpl-1.5.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
21
|
+
pm_jpl-1.5.1.dist-info/METADATA,sha256=-QtcUy-InQc4tDHH58GFGte0PAvrz5hlnoIbVu0EdI4,4391
|
|
22
|
+
pm_jpl-1.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
pm_jpl-1.5.1.dist-info/top_level.txt,sha256=YaAFwdYHUxfUW08hiuFquUEdDGrsYn1duuMMjJKh0Ws,6
|
|
24
|
+
pm_jpl-1.5.1.dist-info/RECORD,,
|
PMJPL/downscaling/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .downscaling import *
|
PMJPL/downscaling/downscaling.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from dateutil import parser
|
|
3
|
-
import numpy as np
|
|
4
|
-
from rasters import Raster, RasterGeometry, RasterGrid
|
|
5
|
-
import rasters as rt
|
|
6
|
-
|
|
7
|
-
DEFAULT_UPSAMPLING = "average"
|
|
8
|
-
DEFAULT_DOWNSAMPLING = "linear"
|
|
9
|
-
|
|
10
|
-
def bias_correct(
|
|
11
|
-
coarse_image: Raster,
|
|
12
|
-
fine_image: Raster,
|
|
13
|
-
upsampling: str = "average",
|
|
14
|
-
downsampling: str = "linear",
|
|
15
|
-
return_bias: bool = False):
|
|
16
|
-
fine_geometry = fine_image.geometry
|
|
17
|
-
coarse_geometry = coarse_image.geometry
|
|
18
|
-
upsampled = fine_image.to_geometry(coarse_geometry, resampling=upsampling)
|
|
19
|
-
bias_coarse = upsampled - coarse_image
|
|
20
|
-
bias_fine = bias_coarse.to_geometry(fine_geometry, resampling=downsampling)
|
|
21
|
-
bias_corrected_fine = fine_image - bias_fine
|
|
22
|
-
|
|
23
|
-
if return_bias:
|
|
24
|
-
return bias_corrected_fine, bias_fine
|
|
25
|
-
else:
|
|
26
|
-
return bias_corrected_fine
|
|
27
|
-
|
|
28
|
-
def linear_downscale(
|
|
29
|
-
coarse_image: Raster,
|
|
30
|
-
fine_image: Raster,
|
|
31
|
-
upsampling: str = "average",
|
|
32
|
-
downsampling: str = "linear",
|
|
33
|
-
use_gap_filling: bool = False,
|
|
34
|
-
apply_scale: bool = True,
|
|
35
|
-
apply_bias: bool = True,
|
|
36
|
-
return_scale_and_bias: bool = False) -> Raster:
|
|
37
|
-
if upsampling is None:
|
|
38
|
-
upsampling = DEFAULT_UPSAMPLING
|
|
39
|
-
|
|
40
|
-
if downsampling is None:
|
|
41
|
-
downsampling = DEFAULT_DOWNSAMPLING
|
|
42
|
-
|
|
43
|
-
coarse_geometry = coarse_image.geometry
|
|
44
|
-
fine_geometry = fine_image.geometry
|
|
45
|
-
upsampled = fine_image.to_geometry(coarse_geometry, resampling=upsampling)
|
|
46
|
-
|
|
47
|
-
if apply_scale:
|
|
48
|
-
scale_coarse = coarse_image / upsampled
|
|
49
|
-
scale_coarse = rt.where(coarse_image == 0, 0, scale_coarse)
|
|
50
|
-
scale_coarse = rt.where(upsampled == 0, 0, scale_coarse)
|
|
51
|
-
scale_fine = scale_coarse.to_geometry(fine_geometry, resampling=downsampling)
|
|
52
|
-
scale_corrected_fine = fine_image * scale_fine
|
|
53
|
-
fine_image = scale_corrected_fine
|
|
54
|
-
else:
|
|
55
|
-
scale_fine = fine_image * 0 + 1
|
|
56
|
-
|
|
57
|
-
if apply_bias:
|
|
58
|
-
upsampled = fine_image.to_geometry(coarse_geometry, resampling=upsampling)
|
|
59
|
-
bias_coarse = upsampled - coarse_image
|
|
60
|
-
bias_fine = bias_coarse.to_geometry(fine_geometry, resampling=downsampling)
|
|
61
|
-
bias_corrected_fine = fine_image - bias_fine
|
|
62
|
-
fine_image = bias_corrected_fine
|
|
63
|
-
else:
|
|
64
|
-
bias_fine = fine_image * 0
|
|
65
|
-
|
|
66
|
-
if use_gap_filling:
|
|
67
|
-
gap_fill = coarse_image.to_geometry(fine_geometry, resampling=downsampling)
|
|
68
|
-
fine_image = fine_image.fill(gap_fill)
|
|
69
|
-
|
|
70
|
-
if return_scale_and_bias:
|
|
71
|
-
fine_image["scale"] = scale_fine
|
|
72
|
-
fine_image["bias"] = bias_fine
|
|
73
|
-
|
|
74
|
-
return fine_image
|
|
75
|
-
|
|
76
|
-
def NDVI_to_FVC(NDVI: Raster) -> Raster:
|
|
77
|
-
NDVIv = 0.52 # +- 0.03
|
|
78
|
-
NDVIs = 0.04 # +- 0.03
|
|
79
|
-
FVC = rt.clip((NDVI - NDVIs) / (NDVIv - NDVIs), 0, 1)
|
|
80
|
-
|
|
81
|
-
return FVC
|
|
82
|
-
|
|
83
|
-
def downscale_air_temperature(
|
|
84
|
-
time_UTC: datetime,
|
|
85
|
-
Ta_K_coarse: Raster,
|
|
86
|
-
ST_K: Raster,
|
|
87
|
-
water: Raster = None,
|
|
88
|
-
fine_geometry: RasterGeometry = None,
|
|
89
|
-
coarse_geometry: RasterGeometry = None,
|
|
90
|
-
resampling: str = None,
|
|
91
|
-
upsampling: str = None,
|
|
92
|
-
downsampling: str = None,
|
|
93
|
-
apply_scale: bool = True,
|
|
94
|
-
apply_bias: bool = True,
|
|
95
|
-
return_scale_and_bias: bool = False) -> Raster:
|
|
96
|
-
"""
|
|
97
|
-
near-surface air temperature (Ta) in Kelvin
|
|
98
|
-
:param time_UTC: date/time in UTC
|
|
99
|
-
:param geometry: optional target geometry
|
|
100
|
-
:param resampling: optional sampling method for resampling to target geometry
|
|
101
|
-
:return: raster of Ta
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
if isinstance(time_UTC, str):
|
|
105
|
-
time_UTC = parser.parse(time_UTC)
|
|
106
|
-
|
|
107
|
-
if fine_geometry is None:
|
|
108
|
-
fine_geometry = ST_K.geometry
|
|
109
|
-
|
|
110
|
-
if coarse_geometry is None:
|
|
111
|
-
coarse_geometry = Ta_K_coarse.geometry
|
|
112
|
-
|
|
113
|
-
ST_K_water = None
|
|
114
|
-
|
|
115
|
-
if water is not None:
|
|
116
|
-
ST_K_water = rt.where(water, ST_K, np.nan)
|
|
117
|
-
ST_K = rt.where(water, np.nan, ST_K)
|
|
118
|
-
|
|
119
|
-
scale = None
|
|
120
|
-
bias = None
|
|
121
|
-
|
|
122
|
-
Ta_K = linear_downscale(
|
|
123
|
-
coarse_image=Ta_K_coarse,
|
|
124
|
-
fine_image=ST_K,
|
|
125
|
-
upsampling=upsampling,
|
|
126
|
-
downsampling=downsampling,
|
|
127
|
-
apply_scale=apply_scale,
|
|
128
|
-
apply_bias=apply_bias,
|
|
129
|
-
return_scale_and_bias=return_scale_and_bias
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
if water is not None:
|
|
133
|
-
Ta_K_water = linear_downscale(
|
|
134
|
-
coarse_image=Ta_K_coarse,
|
|
135
|
-
fine_image=ST_K_water,
|
|
136
|
-
upsampling=upsampling,
|
|
137
|
-
downsampling=downsampling,
|
|
138
|
-
apply_scale=apply_scale,
|
|
139
|
-
apply_bias=apply_bias,
|
|
140
|
-
return_scale_and_bias=False
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
Ta_K = rt.where(water, Ta_K_water, Ta_K)
|
|
144
|
-
|
|
145
|
-
Ta_K.filenames = Ta_K_coarse.filenames
|
|
146
|
-
|
|
147
|
-
return Ta_K
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def downscale_soil_moisture(
|
|
151
|
-
time_UTC: datetime,
|
|
152
|
-
fine_geometry: RasterGrid,
|
|
153
|
-
coarse_geometry: RasterGrid,
|
|
154
|
-
SM_coarse: Raster,
|
|
155
|
-
SM_resampled: Raster,
|
|
156
|
-
ST_fine: Raster,
|
|
157
|
-
NDVI_fine: Raster,
|
|
158
|
-
water: Raster,
|
|
159
|
-
fvlim=0.5,
|
|
160
|
-
a=0.5,
|
|
161
|
-
smoothing="linear") -> Raster:
|
|
162
|
-
fine = fine_geometry
|
|
163
|
-
ST_fine = ST_fine.mask(~water)
|
|
164
|
-
NDVI_fine = NDVI_fine.mask(~water)
|
|
165
|
-
FVC_fine = NDVI_to_FVC(NDVI_fine)
|
|
166
|
-
soil_fine = FVC_fine < fvlim
|
|
167
|
-
Tmin_coarse = ST_fine.to_geometry(coarse_geometry, resampling="min")
|
|
168
|
-
Tmax_coarse = ST_fine.to_geometry(coarse_geometry, resampling="max")
|
|
169
|
-
Ts_fine = ST_fine.mask(soil_fine)
|
|
170
|
-
Tsmin_coarse = Ts_fine.to_geometry(coarse_geometry, resampling="min").fill(Tmin_coarse)
|
|
171
|
-
Tsmax_coarse = Ts_fine.to_geometry(coarse_geometry, resampling="max").fill(Tmax_coarse)
|
|
172
|
-
ST_coarse = ST_fine.to_geometry(coarse_geometry, resampling="average")
|
|
173
|
-
SEE_coarse = (Tsmax_coarse - ST_coarse) / rt.clip(Tsmax_coarse - Tsmin_coarse, 1, None)
|
|
174
|
-
SM_SEE_proportion = (SM_coarse / SEE_coarse).to_geometry(fine, resampling=smoothing)
|
|
175
|
-
Tsmax_fine = Tsmax_coarse.to_geometry(fine_geometry, resampling=smoothing)
|
|
176
|
-
Tsrange_fine = (Tsmax_coarse - Tsmin_coarse).to_geometry(fine, resampling=smoothing)
|
|
177
|
-
SEE_fine = (Tsmax_fine - ST_fine) / rt.clip(Tsrange_fine, 1, None)
|
|
178
|
-
|
|
179
|
-
SEE_mean = SEE_coarse.to_geometry(fine, resampling=smoothing)
|
|
180
|
-
SM_fine = rt.clip(SM_resampled + a * SM_SEE_proportion * (SEE_fine - SEE_mean), 0, 1)
|
|
181
|
-
SM_fine = SM_fine.mask(~water)
|
|
182
|
-
|
|
183
|
-
return SM_fine
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def downscale_vapor_pressure_deficit(
|
|
187
|
-
time_UTC: datetime,
|
|
188
|
-
VPD_Pa_coarse: Raster,
|
|
189
|
-
ST_K: Raster,
|
|
190
|
-
fine_geometry: RasterGeometry = None,
|
|
191
|
-
coarse_geometry: RasterGeometry = None,
|
|
192
|
-
resampling: str = None,
|
|
193
|
-
upsampling: str = None,
|
|
194
|
-
downsampling: str = None,
|
|
195
|
-
return_scale_and_bias: bool = False) -> Raster:
|
|
196
|
-
if upsampling is None:
|
|
197
|
-
upsampling = "average"
|
|
198
|
-
|
|
199
|
-
if downsampling is None:
|
|
200
|
-
downsampling = "linear"
|
|
201
|
-
|
|
202
|
-
if fine_geometry is None:
|
|
203
|
-
fine_geometry = ST_K.geometry
|
|
204
|
-
|
|
205
|
-
if coarse_geometry is None:
|
|
206
|
-
coarse_geometry = VPD_Pa_coarse.geometry
|
|
207
|
-
|
|
208
|
-
return linear_downscale(
|
|
209
|
-
coarse_image=VPD_Pa_coarse,
|
|
210
|
-
fine_image=ST_K,
|
|
211
|
-
upsampling=upsampling,
|
|
212
|
-
downsampling=downsampling,
|
|
213
|
-
return_scale_and_bias=return_scale_and_bias
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def downscale_relative_humidity(
|
|
218
|
-
time_UTC: datetime,
|
|
219
|
-
RH_coarse: Raster,
|
|
220
|
-
SM,
|
|
221
|
-
ST_K,
|
|
222
|
-
VPD_kPa,
|
|
223
|
-
water: Raster = None,
|
|
224
|
-
fine_geometry: RasterGeometry = None,
|
|
225
|
-
coarse_geometry: RasterGeometry = None,
|
|
226
|
-
resampling: str = None,
|
|
227
|
-
upsampling: str = None,
|
|
228
|
-
downsampling: str = None) -> Raster:
|
|
229
|
-
if upsampling is None:
|
|
230
|
-
upsampling = "average"
|
|
231
|
-
|
|
232
|
-
if downsampling is None:
|
|
233
|
-
downsampling = "linear"
|
|
234
|
-
|
|
235
|
-
if fine_geometry is None:
|
|
236
|
-
fine_geometry = SM.geometry
|
|
237
|
-
|
|
238
|
-
if coarse_geometry is None:
|
|
239
|
-
coarse_geometry = RH_coarse.geometry
|
|
240
|
-
|
|
241
|
-
bias_fine = None
|
|
242
|
-
|
|
243
|
-
RH_estimate_fine = SM ** (1 / VPD_kPa)
|
|
244
|
-
|
|
245
|
-
RH = bias_correct(
|
|
246
|
-
coarse_image=RH_coarse,
|
|
247
|
-
fine_image=RH_estimate_fine,
|
|
248
|
-
upsampling=upsampling,
|
|
249
|
-
downsampling=downsampling,
|
|
250
|
-
return_bias=False
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
if water is not None:
|
|
254
|
-
ST_K_water = rt.where(water, ST_K, np.nan)
|
|
255
|
-
RH_coarse_complement = 1 - RH_coarse
|
|
256
|
-
RH_complement_water = linear_downscale(
|
|
257
|
-
coarse_image=RH_coarse_complement,
|
|
258
|
-
fine_image=ST_K_water,
|
|
259
|
-
upsampling=upsampling,
|
|
260
|
-
downsampling=downsampling,
|
|
261
|
-
apply_bias=True,
|
|
262
|
-
return_scale_and_bias=False
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
RH_water = 1 - RH_complement_water
|
|
266
|
-
RH = rt.where(water, RH_water, RH)
|
|
267
|
-
|
|
268
|
-
RH = rt.clip(RH, 0, 1)
|
|
269
|
-
|
|
270
|
-
return RH
|
|
271
|
-
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
from rasters import Raster
|
|
2
|
-
import rasters as rt
|
|
3
|
-
|
|
4
|
-
DEFAULT_UPSAMPLING = "average"
|
|
5
|
-
DEFAULT_DOWNSAMPLING = "linear"
|
|
6
|
-
|
|
7
|
-
def bias_correct(
|
|
8
|
-
coarse_image: Raster,
|
|
9
|
-
fine_image: Raster,
|
|
10
|
-
upsampling: str = "average",
|
|
11
|
-
downsampling: str = "linear",
|
|
12
|
-
return_bias: bool = False):
|
|
13
|
-
fine_geometry = fine_image.geometry
|
|
14
|
-
coarse_geometry = coarse_image.geometry
|
|
15
|
-
upsampled = fine_image.to_geometry(coarse_geometry, resampling=upsampling)
|
|
16
|
-
bias_coarse = upsampled - coarse_image
|
|
17
|
-
bias_fine = bias_coarse.to_geometry(fine_geometry, resampling=downsampling)
|
|
18
|
-
bias_corrected_fine = fine_image - bias_fine
|
|
19
|
-
|
|
20
|
-
if return_bias:
|
|
21
|
-
return bias_corrected_fine, bias_fine
|
|
22
|
-
else:
|
|
23
|
-
return bias_corrected_fine
|
|
24
|
-
|
|
25
|
-
def linear_downscale(
|
|
26
|
-
coarse_image: Raster,
|
|
27
|
-
fine_image: Raster,
|
|
28
|
-
upsampling: str = "average",
|
|
29
|
-
downsampling: str = "cubic",
|
|
30
|
-
use_gap_filling: bool = False,
|
|
31
|
-
apply_scale: bool = True,
|
|
32
|
-
apply_bias: bool = True,
|
|
33
|
-
return_scale_and_bias: bool = False) -> Raster:
|
|
34
|
-
if upsampling is None:
|
|
35
|
-
upsampling = DEFAULT_UPSAMPLING
|
|
36
|
-
|
|
37
|
-
if downsampling is None:
|
|
38
|
-
downsampling = DEFAULT_DOWNSAMPLING
|
|
39
|
-
|
|
40
|
-
coarse_geometry = coarse_image.geometry
|
|
41
|
-
fine_geometry = fine_image.geometry
|
|
42
|
-
upsampled = fine_image.to_geometry(coarse_geometry, resampling=upsampling)
|
|
43
|
-
|
|
44
|
-
if apply_scale:
|
|
45
|
-
scale_coarse = coarse_image / upsampled
|
|
46
|
-
scale_coarse = rt.where(coarse_image == 0, 0, scale_coarse)
|
|
47
|
-
scale_coarse = rt.where(upsampled == 0, 0, scale_coarse)
|
|
48
|
-
scale_fine = scale_coarse.to_geometry(fine_geometry, resampling=downsampling)
|
|
49
|
-
scale_corrected_fine = fine_image * scale_fine
|
|
50
|
-
fine_image = scale_corrected_fine
|
|
51
|
-
else:
|
|
52
|
-
scale_fine = fine_image * 0 + 1
|
|
53
|
-
|
|
54
|
-
if apply_bias:
|
|
55
|
-
upsampled = fine_image.to_geometry(coarse_geometry, resampling=upsampling)
|
|
56
|
-
bias_coarse = upsampled - coarse_image
|
|
57
|
-
bias_fine = bias_coarse.to_geometry(fine_geometry, resampling=downsampling)
|
|
58
|
-
bias_corrected_fine = fine_image - bias_fine
|
|
59
|
-
fine_image = bias_corrected_fine
|
|
60
|
-
else:
|
|
61
|
-
bias_fine = fine_image * 0
|
|
62
|
-
|
|
63
|
-
if use_gap_filling:
|
|
64
|
-
gap_fill = coarse_image.to_geometry(fine_geometry, resampling=downsampling)
|
|
65
|
-
fine_image = fine_image.fill(gap_fill)
|
|
66
|
-
|
|
67
|
-
if return_scale_and_bias:
|
|
68
|
-
fine_image["scale"] = scale_fine
|
|
69
|
-
fine_image["bias"] = bias_fine
|
|
70
|
-
|
|
71
|
-
return fine_image
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .evapotranspiration_conversion import *
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
from sun_angles import SHA_deg_from_DOY_lat, daylight_from_SHA, sunrise_from_SHA
|
|
3
|
-
|
|
4
|
-
import rasters as rt
|
|
5
|
-
from rasters import Raster
|
|
6
|
-
import numpy as np
|
|
7
|
-
import pandas as pd
|
|
8
|
-
|
|
9
|
-
from verma_net_radiation import daily_Rn_integration_verma
|
|
10
|
-
|
|
11
|
-
from ..meteorology_conversion.meteorology_conversion import celcius_to_kelvin
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# latent heat of vaporization for water at 20 Celsius in Joules per kilogram
|
|
15
|
-
LAMBDA_JKG_WATER_20C = 2450000.0
|
|
16
|
-
|
|
17
|
-
def lambda_Jkg_from_Ta_K(Ta_K: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
18
|
-
# Calculate the latent heat of vaporization (J kg-1)
|
|
19
|
-
return (2.501 - 0.002361 * (Ta_K - 273.15)) * 1e6
|
|
20
|
-
|
|
21
|
-
def lambda_Jkg_from_Ta_C(Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
22
|
-
Ta_K = celcius_to_kelvin(Ta_C)
|
|
23
|
-
lambda_Jkg = lambda_Jkg_from_Ta_K(Ta_K)
|
|
24
|
-
|
|
25
|
-
return lambda_Jkg
|
|
26
|
-
|
|
27
|
-
def daily_ET_from_daily_LE(
|
|
28
|
-
LE_daylight: Union[Raster, np.ndarray],
|
|
29
|
-
daylight_hours: Union[Raster, np.ndarray],
|
|
30
|
-
lambda_Jkg: float = LAMBDA_JKG_WATER_20C) -> Union[Raster, np.ndarray]:
|
|
31
|
-
"""
|
|
32
|
-
Calculate daily evapotranspiration (ET) from daily latent heat flux (LE).
|
|
33
|
-
|
|
34
|
-
Parameters:
|
|
35
|
-
LE_daily (Union[Raster, np.ndarray]): Daily latent heat flux.
|
|
36
|
-
daylight_hours (Union[Raster, np.ndarray]): Length of day in hours.
|
|
37
|
-
latent_vaporization (float, optional): Latent heat of vaporization. Defaults to LATENT_VAPORIZATION.
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
Union[Raster, np.ndarray]: Daily evapotranspiration in kilograms.
|
|
41
|
-
"""
|
|
42
|
-
# convert length of day in hours to seconds
|
|
43
|
-
daylight_seconds = daylight_hours * 3600.0
|
|
44
|
-
|
|
45
|
-
# factor seconds out of watts to get joules and divide by latent heat of vaporization to get kilograms
|
|
46
|
-
ET_daily_kg = rt.clip(LE_daylight * daylight_seconds / LAMBDA_JKG_WATER_20C, 0.0, None)
|
|
47
|
-
|
|
48
|
-
return ET_daily_kg
|
|
49
|
-
|
|
50
|
-
def process_daily_ET_table(input_df: pd.DataFrame) -> pd.DataFrame:
|
|
51
|
-
hour_of_day = input_df.hour_of_day
|
|
52
|
-
DOY = input_df.doy
|
|
53
|
-
lat = input_df.lat
|
|
54
|
-
LE = input_df.LE
|
|
55
|
-
Rn = input_df.Rn
|
|
56
|
-
EF = LE / Rn
|
|
57
|
-
|
|
58
|
-
SHA_deg = SHA_deg_from_DOY_lat(DOY=DOY, latitude=lat)
|
|
59
|
-
sunrise_hour = sunrise_from_SHA(SHA_deg)
|
|
60
|
-
daylight_hours = daylight_from_SHA(SHA_deg)
|
|
61
|
-
|
|
62
|
-
Rn_daylight = daily_Rn_integration_verma(
|
|
63
|
-
Rn=Rn,
|
|
64
|
-
hour_of_day=hour_of_day,
|
|
65
|
-
DOY=DOY,
|
|
66
|
-
lat=lat,
|
|
67
|
-
sunrise_hour=sunrise_hour,
|
|
68
|
-
daylight_hours=daylight_hours
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
LE_daylight = EF * Rn_daylight
|
|
72
|
-
ET = daily_ET_from_daily_LE(LE_daylight, daylight_hours)
|
|
73
|
-
|
|
74
|
-
output_df = input_df.copy()
|
|
75
|
-
output_df["EF"] = EF
|
|
76
|
-
output_df["sunrise_hour"] = sunrise_hour
|
|
77
|
-
output_df["daylight_hours"] = daylight_hours
|
|
78
|
-
output_df["Rn_daylight"] = Rn_daylight
|
|
79
|
-
output_df["ET"] = ET
|
|
80
|
-
|
|
81
|
-
return output_df
|
|
82
|
-
|
PMJPL/fwet.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from typing import Union
|
|
3
|
-
from rasters import Raster
|
|
4
|
-
|
|
5
|
-
from .constants import RH_THRESHOLD, MIN_FWET
|
|
6
|
-
|
|
7
|
-
def calculate_fwet(
|
|
8
|
-
RH: Union[Raster, np.ndarray],
|
|
9
|
-
RH_threshold: float = RH_THRESHOLD,
|
|
10
|
-
min_fwet: float = MIN_FWET) -> Union[Raster, np.ndarray]:
|
|
11
|
-
"""
|
|
12
|
-
calculates relative surface wetness
|
|
13
|
-
:param RH: relative humdity from 0.0 to 1.0
|
|
14
|
-
:return: relative surface wetness from 0.0 to 1.0
|
|
15
|
-
"""
|
|
16
|
-
fwet = np.float32(np.clip(RH ** 4.0, min_fwet, None))
|
|
17
|
-
|
|
18
|
-
if RH_threshold is not None:
|
|
19
|
-
fwet = np.where(RH < RH_threshold, min_fwet, fwet)
|
|
20
|
-
|
|
21
|
-
return fwet
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .meteorology_conversion import *
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
import numpy as np
|
|
3
|
-
import rasters as rt
|
|
4
|
-
from rasters import Raster
|
|
5
|
-
|
|
6
|
-
# gas constant for dry air in joules per kilogram per kelvin
|
|
7
|
-
RD = 286.9
|
|
8
|
-
|
|
9
|
-
# gas constant for moist air in joules per kilogram per kelvin
|
|
10
|
-
RW = 461.5
|
|
11
|
-
|
|
12
|
-
# specific heat of water vapor in joules per kilogram per kelvin
|
|
13
|
-
CPW = 1846.0
|
|
14
|
-
|
|
15
|
-
# specific heat of dry air in joules per kilogram per kelvin
|
|
16
|
-
CPD = 1005.0
|
|
17
|
-
|
|
18
|
-
def kelvin_to_celsius(T_K: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
19
|
-
"""
|
|
20
|
-
convert temperature in kelvin to celsius.
|
|
21
|
-
:param T_K: temperature in kelvin
|
|
22
|
-
:return: temperature in celsius
|
|
23
|
-
"""
|
|
24
|
-
return T_K - 273.15
|
|
25
|
-
|
|
26
|
-
def celcius_to_kelvin(T_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
27
|
-
"""
|
|
28
|
-
convert temperature in celsius to kelvin.
|
|
29
|
-
:param T_C: temperature in celsius
|
|
30
|
-
:return: temperature in kelvin
|
|
31
|
-
"""
|
|
32
|
-
return T_C + 273.15
|
|
33
|
-
|
|
34
|
-
def calculate_specific_humidity(
|
|
35
|
-
Ea_Pa: Union[Raster, np.ndarray],
|
|
36
|
-
Ps_Pa: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
37
|
-
"""
|
|
38
|
-
Calculate the specific humidity of air as a ratio of kilograms of water to kilograms of air.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
Ea_Pa (Union[Raster, np.ndarray]): Actual water vapor pressure in Pascal.
|
|
42
|
-
surface_pressure_Pa (Union[Raster, np.ndarray]): Surface pressure in Pascal.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
Union[Raster, np.ndarray]: Specific humidity in kilograms of water per kilograms of air.
|
|
46
|
-
"""
|
|
47
|
-
return ((0.622 * Ea_Pa) / (Ps_Pa - (0.387 * Ea_Pa)))
|
|
48
|
-
|
|
49
|
-
def calculate_specific_heat(specific_humidity: Union[Raster, np.ndarray]):
|
|
50
|
-
# calculate specific heat capacity of the air (Cp)
|
|
51
|
-
# in joules per kilogram per kelvin
|
|
52
|
-
# from specific heat of water vapor (CPW)
|
|
53
|
-
# and specific heat of dry air (CPD)
|
|
54
|
-
Cp_Jkg = specific_humidity * CPW + (1 - specific_humidity) * CPD
|
|
55
|
-
|
|
56
|
-
return Cp_Jkg
|
|
57
|
-
|
|
58
|
-
def calculate_air_density(
|
|
59
|
-
surface_pressure_Pa: Union[Raster, np.ndarray],
|
|
60
|
-
Ta_K: Union[Raster, np.ndarray],
|
|
61
|
-
specific_humidity: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
62
|
-
"""
|
|
63
|
-
Calculate air density.
|
|
64
|
-
|
|
65
|
-
Parameters:
|
|
66
|
-
surface_pressure_Pa (Union[Raster, np.ndarray]): Surface pressure in Pascal.
|
|
67
|
-
Ta_K (Union[Raster, np.ndarray]): Air temperature in Kelvin.
|
|
68
|
-
specific_humidity (Union[Raster, np.ndarray]): Specific humidity.
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
Union[Raster, np.ndarray]: Air density in kilograms per cubic meter.
|
|
72
|
-
"""
|
|
73
|
-
# numerator: Pa(N / m ^ 2 = kg * m / s ^ 2); denominator: J / kg / K * K)
|
|
74
|
-
rhoD = surface_pressure_Pa / (RD * Ta_K)
|
|
75
|
-
|
|
76
|
-
# calculate air density (rho) in kilograms per cubic meter
|
|
77
|
-
rho = rhoD * ((1.0 + specific_humidity) / (1.0 + specific_humidity * (RW / RD)))
|
|
78
|
-
|
|
79
|
-
return rho
|
|
80
|
-
|
|
81
|
-
def SVP_kPa_from_Ta_C(Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
82
|
-
"""
|
|
83
|
-
Calculate the saturation vapor pressure in kiloPascal (kPa) from air temperature in Celsius.
|
|
84
|
-
|
|
85
|
-
Parameters:
|
|
86
|
-
Ta_C (Union[Raster, np.ndarray]): Air temperature in Celsius.
|
|
87
|
-
|
|
88
|
-
Returns:
|
|
89
|
-
Union[Raster, np.ndarray]: Saturation vapor pressure in kPa.
|
|
90
|
-
|
|
91
|
-
"""
|
|
92
|
-
SVP_kPa = np.clip(0.611 * np.exp((Ta_C * 17.27) / (Ta_C + 237.7)), 1, None)
|
|
93
|
-
|
|
94
|
-
return SVP_kPa
|
|
95
|
-
|
|
96
|
-
def SVP_Pa_from_Ta_C(Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
97
|
-
"""
|
|
98
|
-
Calculate the saturation vapor pressure in Pascal (Pa) from the air temperature in Celsius (Ta_C).
|
|
99
|
-
|
|
100
|
-
Parameters:
|
|
101
|
-
Ta_C (Union[Raster, np.ndarray]): Air temperature in Celsius.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
Union[Raster, np.ndarray]: Saturation vapor pressure in Pascal (Pa).
|
|
105
|
-
"""
|
|
106
|
-
return SVP_kPa_from_Ta_C(Ta_C) * 1000
|
|
107
|
-
|
|
108
|
-
def calculate_surface_pressure(elevation_m: Union[Raster, np.ndarray], Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
|
|
109
|
-
"""
|
|
110
|
-
Calculate surface pressure using elevation and air temperature.
|
|
111
|
-
|
|
112
|
-
Parameters:
|
|
113
|
-
elevation_m (Union[Raster, np.ndarray]): Elevation in meters.
|
|
114
|
-
Ta_K (Union[Raster, np.ndarray]): Air temperature in Kelvin.
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
Union[Raster, np.ndarray]: Surface pressure in Pascal (Pa).
|
|
118
|
-
"""
|
|
119
|
-
Ta_K = kelvin_to_celsius(Ta_C)
|
|
120
|
-
Ps_Pa = 101325.0 * (1.0 - 0.0065 * elevation_m / Ta_K) ** (9.807 / (0.0065 * 287.0)) # [Pa]
|
|
121
|
-
|
|
122
|
-
return Ps_Pa
|
|
123
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .penman_monteith import *
|