PM-JPL 1.4.0__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.

@@ -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
@@ -0,0 +1,4 @@
1
+ from os.path import join, abspath, dirname
2
+
3
+ from importlib.metadata import version
4
+ __version__ = version("PM-JPL")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PM-JPL
3
- Version: 1.4.0
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1 +0,0 @@
1
- from .downscaling import *
@@ -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
-