AliceTt commited on
Commit
98a2104
·
1 Parent(s): 599ecdc

addded some docs for indicators

Browse files
docs/agro_indicators.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from pvlib.location import lookup_altitude
3
+ from agroclimatic_indicators.pyeto import fao
4
+
5
+ def et0(irradiance, T, Tmax, Tmin, RHmin, RHmax, WS, JJulien, latitude, longitude):
6
+ """
7
+ Calculate the daily reference evapotranspiration [ml/day] w.r.t. Penman-Monteith formula.
8
+
9
+ Parameters
10
+ ----------
11
+ irradiance : array_like
12
+ Daily global horizontal irradiance [MJ/m2/day].
13
+
14
+ T : array_like
15
+ Mean daily air temperature at 2 m height [deg Celsius].
16
+
17
+ Tmax : array_like
18
+ Maximum air temperature at 2 m height [deg Celsius].
19
+
20
+ Tmin : array_like
21
+ Minimum air temperature at 2 m height [deg Celsius].
22
+
23
+ RHmin : array_like
24
+ Minimum daily relative humidity [%].
25
+
26
+ RHmax : array_like
27
+ Maximum daily relative humidity [%].
28
+
29
+ WS : array_like
30
+ Wind speed at 10 m height [m s-1].
31
+
32
+ JJulien : array_like
33
+ Julian day.
34
+
35
+ latitude : array_like
36
+ Latitude in °.
37
+
38
+ longitude : array_like
39
+ Longitude in °.
40
+
41
+ Returns
42
+ -------
43
+ array_like
44
+ Reference evapotranspiration (ETo) from a hypothetical grass reference surface [mm day-1].
45
+ """
46
+
47
+ latRad = (latitude * np.pi) / 180
48
+ ### VPD, SVP
49
+
50
+ svp_tmax = fao.svp_from_t(Tmax)
51
+ svp_tmin = fao.svp_from_t(Tmin)
52
+ svp = fao.svp_from_t(T)
53
+ avp = fao.avp_from_rhmin_rhmax(svp_tmin, svp_tmax, RHmin, RHmax)
54
+ delta_svp = fao.delta_svp(T)
55
+
56
+ ### IR
57
+
58
+ #sol_rad = irradiance * 36 / 10000 #Conversion W/m2 en MJ/m2/day
59
+ sol_rad = irradiance
60
+
61
+ ### Radiation Nette
62
+
63
+ sol_dec = fao.sol_dec(JJulien)
64
+ sunset_hour_angle = fao.sunset_hour_angle(latRad, sol_dec)
65
+ inv_dist_earth_sun = fao.inv_rel_dist_earth_sun(JJulien)
66
+
67
+ et_rad = fao.et_rad(latRad, sol_dec, sunset_hour_angle, inv_dist_earth_sun)
68
+
69
+ T_kelvin = T + 273.15
70
+ Tmin_kelvin = Tmin + 273.15
71
+ Tmax_kelvin = Tmax + 273.15
72
+
73
+ altitude = np.array(lookup_altitude(latitude, longitude))
74
+
75
+ cs_rad = fao.cs_rad(altitude, et_rad)
76
+
77
+ net_out_lw_rad = fao.net_out_lw_rad(Tmin_kelvin, Tmax_kelvin, sol_rad, cs_rad, avp)
78
+
79
+ net_in_sol_rad = fao.net_in_sol_rad(sol_rad, 0.2)
80
+
81
+ net_rad = fao.net_rad(net_in_sol_rad, net_out_lw_rad)
82
+
83
+ atm_pressure = fao.atm_pressure(altitude)
84
+
85
+ psy = fao.psy_const_of_psychrometer(2, atm_pressure)
86
+
87
+ ws_2m = fao.wind_speed_2m(WS, 10)
88
+
89
+ et0 = fao.fao56_penman_monteith(net_rad, T_kelvin, ws_2m, svp, avp, delta_svp, psy)
90
+
91
+ return et0
92
+
93
+
94
+ def gdd(Tmin, Tmax, Tbase):
95
+ """
96
+ Calculate the Growing Degree Days [Degrees Celsius].
97
+
98
+ Parameters
99
+ ----------
100
+ Tmax : float or numpy array
101
+ Maximum daily air temperature [deg Celsius].
102
+
103
+ Tmin : float or numpy array
104
+ Minimum daily air temperature [deg Celsius].
105
+
106
+ Tbase : float or numpy array
107
+ Base crop temperature (corresponding to zero vegetation) [deg Celsius].
108
+
109
+ Returns
110
+ -------
111
+ float
112
+ Growing Degree Days [deg Celsius].
113
+ """
114
+
115
+ return ((Tmax + Tmin) / 2) - Tbase
116
+
117
+
118
+ def gelif(Tmin, Tfrost):
119
+ """
120
+ Define if the day is a frosting day.
121
+
122
+ Parameters
123
+ ----------
124
+ Tmin : float or numpy array
125
+ Minimum daily air temperature [deg Celsius].
126
+
127
+ Tfrost : float
128
+ Crop frost temperature (depends on the crop and phenophase) [deg Celsius].
129
+
130
+ Returns
131
+ -------
132
+ bool
133
+ True if it's a frosting day, else False.
134
+ """
135
+
136
+ if Tmin > Tfrost:
137
+ is_forst = False
138
+ elif Tmin <= Tfrost:
139
+ is_forst = True
140
+
141
+ return is_forst
142
+
143
+
144
+ def vpd(T, RH):
145
+ """
146
+ Compute deficit vapor pressure.
147
+
148
+ Parameters
149
+ ----------
150
+ T : float or numpy array
151
+ Mean timestep air temperature [deg Celsius].
152
+
153
+ RH : float or numpy array
154
+ Mean timestep relative humidity [unitless %].
155
+
156
+ Returns
157
+ -------
158
+ float or numpy array
159
+ Vapor pressure deficit (VPD).
160
+ """
161
+
162
+ VPS = 0.6108 * np.exp((17.27 * T) / (T + 237.3))
163
+ VPD = VPS * (1 - RH / 100)
164
+
165
+ return VPD
docs/animal_indicators.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ def hli(irradiance : float, air_temperature : float, RH : float, wind_speed : float):
4
+ """
5
+ Compute Heat Load Index (HLI), a thermal stress indicator for animals.
6
+
7
+ Parameters
8
+ ----------
9
+ irradiance : float
10
+ Solar radiation [W.m-2]
11
+
12
+ air_temperature : float
13
+ Air temperature [°C]
14
+
15
+ RH : float
16
+ Relative humidity [%]
17
+
18
+ wind_speed : float
19
+ Wind speed [m.s-1]
20
+
21
+ Returns
22
+ -------
23
+ float
24
+ Heat Load Index value.
25
+ """
26
+
27
+ BGT = 0.01498*irradiance + 1.184*air_temperature - 0.0789*RH - 2.739
28
+ HLI = np.where(BGT >= 25, 1.55*BGT- 0.5*wind_speed + np.exp(2.4 - wind_speed)+8.62 + 0.38*RH, 1.30*BGT - wind_speed+10.66 + 0.28*RH)
29
+
30
+ return HLI
docs/climatic_indicators.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def frost_bool(Tmin,Tfrost):
2
+
3
+ """
4
+ Test if the day is a frost day (minimum daily temperature below frost threshold).
5
+
6
+ Parameters
7
+ ----------
8
+ Tmin : float
9
+ Minimum daily air temperature (deg Celsius).
10
+
11
+ Tfrost : float
12
+ Frost temperature (regular or strong frost) (deg Celsius).
13
+
14
+ Returns
15
+ -------
16
+ bool
17
+ True if it's a frost day, else False.
18
+ """
19
+
20
+ return Tmin <= Tfrost
21
+
22
+
23
+
24
+
25
+ def scorch_bool(Tmax,Tscorch):
26
+
27
+ """
28
+ Test if the day is a scorching day (jour échaudant) (maximum daily temperature above scorch threshold).
29
+
30
+ Parameters
31
+ ----------
32
+ Tmax : float
33
+ Maximum daily air temperature (deg Celsius).
34
+
35
+ Tscorch : float
36
+ Temperature threshold above which the day is considered scorching (crop-dependent) (deg Celsius).
37
+
38
+ Returns
39
+ -------
40
+ bool
41
+ True if it's a scorching day, else False.
42
+ """
43
+
44
+ return Tmax >= Tscorch
45
+
46
+
47
+
48
+ def thermalstress_bool(Tmax, Tstress):
49
+
50
+ """
51
+ Define if the day is a source of thermal stress (maximum daily temperature above stress threshold).
52
+
53
+ Parameters
54
+ ----------
55
+ Tmax : float
56
+ Maximum daily air temperature (deg Celsius).
57
+
58
+ Tstress : float
59
+ Temperature threshold above which the day is considered stressful (deg Celsius).
60
+
61
+ Returns
62
+ -------
63
+ bool
64
+ True if the day is a source of thermal stress, else False.
65
+ """
66
+ return Tmax >= Tstress
67
+
68
+
69
+
70
+ def summerday_bool(Tmax):
71
+ """
72
+ Define if the day is a summer day (maximum daily temperature above 25°C).
73
+
74
+ Parameters
75
+ ----------
76
+ Tmax : float
77
+ Maximum daily air temperature (deg Celsius).
78
+
79
+ Returns
80
+ -------
81
+ bool
82
+ True if it's a summer day, else False.
83
+ """
84
+
85
+ return Tmax >= 25
86
+
87
+
88
+ def tropicalnight_bool(Tmin):
89
+
90
+ """
91
+ Define if night is a tropical night (min night temperature above 20°).
92
+
93
+ Parameters
94
+ ----------
95
+ Tmin : float
96
+ Min Night Air temperature (degrees Celsius).
97
+
98
+ Returns
99
+ -------
100
+ bool
101
+ True if tropical night, else False.
102
+ """
103
+
104
+ return Tmin >= 20
105
+
106
+
docs/compute.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from pvlib.solarposition import sun_rise_set_transit_spa
4
+ from agroclimatic_indicators import (
5
+ agro_indicators,
6
+ animal_indicators,
7
+ climatic_indicators,
8
+ )
9
+
10
+
11
+ ## Compute Agronomics
12
+ def compute_vpd(df: pd.DataFrame):
13
+ """
14
+ Compute VPD.
15
+
16
+ Parameters
17
+ ----------
18
+ df : DataFrame
19
+ The input dataframe containing sensor data.
20
+
21
+ Returns
22
+ -------
23
+ arraylike
24
+ VPD at df's timestep
25
+ """
26
+ return agro_indicators.vpd(df.air_temperature, df.relative_humidity)
27
+
28
+
29
+
30
+ def compute_et0(
31
+ df: pd.DataFrame,
32
+ latitude: float,
33
+ longitude: float
34
+ ):
35
+ """
36
+ Compute reference evapotranspiration.
37
+
38
+ Parameters
39
+ ----------
40
+ df : DataFrame
41
+ The input dataframe containing sensor data.
42
+
43
+ latitude : float
44
+ Latitude of the location.
45
+ longitude : float
46
+ Longitude of the location
47
+
48
+ Returns
49
+ -------
50
+ arraylike
51
+ Daily reference evapotranspiration.
52
+ """
53
+
54
+ irradiance = (
55
+ (df.photon_flux_density.resample("1h").mean() / 2.1).resample("1d").sum()
56
+ )
57
+ T = df.air_temperature.resample("1d").mean()
58
+ Tmin = df.air_temperature.resample("1d").min()
59
+ Tmax = df.air_temperature.resample("1d").max()
60
+ RHmin = df.relative_humidity.resample("1d").min()
61
+ RHmax = df.relative_humidity.resample("1d").max()
62
+ WS = df.wind_speed.resample("1d").mean()
63
+ JJulien = np.unique(df.index.day_of_year)
64
+
65
+ l = [
66
+ agro_indicators.et0(
67
+ irradiance.iloc[i],
68
+ T.iloc[i],
69
+ Tmax.iloc[i],
70
+ Tmin.iloc[i],
71
+ RHmin.iloc[i],
72
+ RHmax.iloc[i],
73
+ WS.iloc[i],
74
+ JJulien[i],
75
+ latitude,
76
+ np.array([longitude]),
77
+ )
78
+ for i in range(len(JJulien))
79
+ ]
80
+
81
+ if len(JJulien) == 1:
82
+ et0 = l[0]
83
+ else:
84
+ et0 = l
85
+
86
+ return et0
87
+
88
+
89
+ ## Compute Climatics
90
+
91
+
92
+ def compute_frostday(df: pd.DataFrame):
93
+ """
94
+ Define if day is a frost day (min temperature below 0°C).
95
+
96
+ Parameters
97
+ ----------
98
+ df : DataFrame
99
+ Air sensors data.
100
+
101
+ Returns
102
+ -------
103
+ bool
104
+ True if day is a frost day, else False.
105
+ """
106
+
107
+ T = df.air_temperature.resample("1d").min()
108
+
109
+ ind = climatic_indicators.frost_bool(T, 0)
110
+ if ind.shape[0] == 1:
111
+ ind = ind.iloc[0]
112
+ return ind
113
+
114
+
115
+ def compute_strongfrostday(df: pd.DataFrame):
116
+ """
117
+ Define if day is a strong frost day (min temperature below -3°C).
118
+
119
+ Parameters
120
+ ----------
121
+ df : DataFrame
122
+ The input dataframe containing temperature data.
123
+
124
+ Returns
125
+ -------
126
+ bool
127
+ True if day is a strong frost day, else False.
128
+ """
129
+
130
+ T = df.air_temperature.resample("1d").min()
131
+
132
+ ind = climatic_indicators.frost_bool(T, -3)
133
+ if ind.shape[0] == 1:
134
+ ind = ind.iloc[0]
135
+
136
+ return ind
137
+
138
+
139
+ def compute_thermalstressday(df: pd.DataFrame, stress_threshold: float = 35):
140
+ """
141
+ Define if daily temperature is a source of thermal stress (max temperature above stress threshold).
142
+
143
+ Parameters
144
+ ----------
145
+ df : DataFrame
146
+ The input dataframe containing air temperature data.
147
+
148
+ stress_threshold : float
149
+ Threshold temperature of stress (degrees Celsius).
150
+
151
+ Returns
152
+ -------
153
+ bool
154
+ True if day is a day with thermal stress, else False.
155
+ """
156
+
157
+ T = df.air_temperature.resample("1d").max()
158
+
159
+ ind = climatic_indicators.thermalstress_bool(T, stress_threshold)
160
+ if ind.shape[0] == 1:
161
+ ind = ind.iloc[0]
162
+
163
+ return ind
164
+
165
+
166
+ def compute_summerday(df: pd.DataFrame):
167
+ """
168
+ Define if day is a summer day (max temperature above 25°C).
169
+
170
+ Parameters
171
+ ----------
172
+ df : DataFrame
173
+ The input dataframe containing air temperature data.
174
+
175
+ Returns
176
+ -------
177
+ bool
178
+ True if day is a summer day, else False.
179
+ """
180
+ T = df.air_temperature.resample("1d").max()
181
+
182
+ ind = climatic_indicators.summerday_bool(T)
183
+ if ind.shape[0] == 1:
184
+ ind = ind.iloc[0]
185
+
186
+ return ind
187
+
188
+
189
+ def compute_scorchday(df: pd.DataFrame, scorch_threshold: float = 25):
190
+ """
191
+ Define if day is a scorching day (jour échaudant) (max temperature above scorch threshold).
192
+
193
+ Parameters
194
+ ----------
195
+ df : DataFrame
196
+ The input dataframe containing air temperature data.
197
+
198
+ scorch_threshold : float
199
+ Temperature threshold above which the day is considered scorching (degrees Celsius).
200
+
201
+ Returns
202
+ -------
203
+ bool
204
+ True if day is a scorching day, else False.
205
+ """
206
+ T = df.air_temperature.resample("1d").max()
207
+
208
+ ind = climatic_indicators.scorch_bool(T, scorch_threshold)
209
+
210
+ if ind.shape[0] == 1:
211
+ ind = ind.iloc[0]
212
+
213
+ return ind
214
+
215
+
216
+ def compute_tropicalnight(df: pd.DataFrame, latitude: float, longitude: float):
217
+ """
218
+ Define if night is a tropical night (min temperature above 20°C).
219
+
220
+ Parameters
221
+ ----------
222
+ df : DataFrame
223
+ The input dataframe containing air temperature data.
224
+
225
+ latitude : float
226
+ Latitude of the location.
227
+
228
+ longitude : float
229
+ Longitude of the location.
230
+
231
+ Returns
232
+ -------
233
+ bool
234
+ True if night is a tropical night, else False.
235
+ """
236
+ if len(df) == 0:
237
+ return None
238
+
239
+ df = sun_rise_set_transit_spa(
240
+ times=df.index, latitude=latitude, longitude=longitude
241
+ ).merge(df["air_temperature"], left_index=True, right_index=True)
242
+ df = df.loc[(df.index < df["sunrise"]) | (df.index > df["sunset"])]
243
+
244
+ df_minnight = df.resample("24h", offset="12h")["air_temperature"].min()
245
+
246
+ tropicalnight = climatic_indicators.tropicalnight_bool(df_minnight)
247
+
248
+ if tropicalnight.shape[0] == 1:
249
+ tropicalnight = tropicalnight.iloc[0]
250
+
251
+ return tropicalnight
252
+
253
+
254
+ def compute_mintempnight(df: pd.DataFrame, latitude: float, longitude: float):
255
+ """
256
+ Return minimal night temperature.
257
+
258
+ Parameters
259
+ ----------
260
+ df : DataFrame
261
+ The input dataframe containing air temperature data.
262
+
263
+ latitude : float
264
+ Latitude of the location.
265
+
266
+ longitude : float
267
+ Longitude of the location.
268
+
269
+ Returns
270
+ -------
271
+ float
272
+ Min night temperature (degrees Celsius).
273
+ """
274
+ if len(df) == 0:
275
+ return None
276
+
277
+ df = sun_rise_set_transit_spa(df.index, latitude, longitude).merge(
278
+ df["air_temperature"], left_index=True, right_index=True
279
+ )
280
+ df = df.loc[(df.index < df["sunrise"]) | (df.index > df["sunset"])]
281
+
282
+ df_minnight = df.resample("24h", offset="12h")["air_temperature"].min()
283
+
284
+ if df_minnight.shape[0] == 1:
285
+ df_minnight = df_minnight.iloc[0]
286
+
287
+ return df_minnight
288
+
289
+
290
+ def compute_hli(
291
+ df: pd.DataFrame,
292
+ ):
293
+ """
294
+ Computes HLI (heat load index).
295
+
296
+ Parameters
297
+ ----------
298
+ df : DataFrame
299
+ The input dataframe containing air temperature data.
300
+
301
+ Returns
302
+ -------
303
+ float
304
+ HLI value.
305
+ """
306
+
307
+ ind = animal_indicators.hli(
308
+ irradiance=df.photon_flux_density,
309
+ air_temperature=df.air_temperature,
310
+ RH=df.relative_humidity,
311
+ wind_speed=df.wind_speed,
312
+ )
313
+
314
+ if ind.size == 1:
315
+ df_ind = float(ind)
316
+ else:
317
+ df_ind = pd.Series(ind, index=df.index)
318
+
319
+ return df_ind
docs/pyeto/__init__.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .convert import (
2
+ celsius2kelvin,
3
+ kelvin2celsius,
4
+ deg2rad,
5
+ rad2deg,
6
+ )
7
+
8
+ from .fao import (
9
+ atm_pressure,
10
+ avp_from_tmin,
11
+ avp_from_rhmin_rhmax,
12
+ avp_from_rhmax,
13
+ avp_from_rhmean,
14
+ avp_from_tdew,
15
+ avp_from_twet_tdry,
16
+ cs_rad,
17
+ daily_mean_t,
18
+ daylight_hours,
19
+ delta_svp,
20
+ energy2evap,
21
+ et_rad,
22
+ fao56_penman_monteith,
23
+ hargreaves,
24
+ inv_rel_dist_earth_sun,
25
+ mean_svp,
26
+ monthly_soil_heat_flux,
27
+ monthly_soil_heat_flux2,
28
+ net_in_sol_rad,
29
+ net_out_lw_rad,
30
+ net_rad,
31
+ psy_const,
32
+ psy_const_of_psychrometer,
33
+ rh_from_avp_svp,
34
+ SOLAR_CONSTANT,
35
+ sol_dec,
36
+ sol_rad_from_sun_hours,
37
+ sol_rad_from_t,
38
+ sol_rad_island,
39
+ STEFAN_BOLTZMANN_CONSTANT,
40
+ sunset_hour_angle,
41
+ svp_from_t,
42
+ wind_speed_2m,
43
+ )
44
+
45
+ from .thornthwaite import (
46
+ thornthwaite,
47
+ monthly_mean_daylight_hours,
48
+ )
49
+
50
+ __all__ = [
51
+ # Unit conversions
52
+ "celsius2kelvin",
53
+ "deg2rad",
54
+ "kelvin2celsius",
55
+ "rad2deg",
56
+ # FAO equations
57
+ "atm_pressure",
58
+ "avp_from_tmin",
59
+ "avp_from_rhmin_rhmax",
60
+ "avp_from_rhmax",
61
+ "avp_from_rhmean",
62
+ "avp_from_tdew",
63
+ "avp_from_twet_tdry",
64
+ "cs_rad",
65
+ "daily_mean_t",
66
+ "daylight_hours",
67
+ "delta_svp",
68
+ "energy2evap",
69
+ "et_rad",
70
+ "fao56_penman_monteith",
71
+ "hargreaves",
72
+ "inv_rel_dist_earth_sun",
73
+ "mean_svp",
74
+ "monthly_soil_heat_flux",
75
+ "monthly_soil_heat_flux2",
76
+ "net_in_sol_rad",
77
+ "net_out_lw_rad",
78
+ "net_rad",
79
+ "psy_const",
80
+ "psy_const_of_psychrometer",
81
+ "rh_from_avp_svp",
82
+ "SOLAR_CONSTANT",
83
+ "sol_dec",
84
+ "sol_rad_from_sun_hours",
85
+ "sol_rad_from_t",
86
+ "sol_rad_island",
87
+ "STEFAN_BOLTZMANN_CONSTANT",
88
+ "sunset_hour_angle",
89
+ "svp_from_t",
90
+ "wind_speed_2m",
91
+ # Thornthwaite method
92
+ "thornthwaite",
93
+ "monthly_mean_daylight_hours",
94
+ ]
docs/pyeto/_check.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Internal validation functions.
3
+
4
+ :copyright: (c) 2015 by Mark Richards.
5
+ :license: BSD 3-Clause, see LICENSE.txt for more details.
6
+ """
7
+ from .convert import deg2rad
8
+ import numpy as np
9
+
10
+ # Internal constants
11
+ # Latitude
12
+ _MINLAT_RADIANS = deg2rad(-90.0)
13
+ _MAXLAT_RADIANS = deg2rad(90.0)
14
+
15
+ # Solar declination
16
+ _MINSOLDEC_RADIANS = deg2rad(-23.5)
17
+ _MAXSOLDEC_RADIANS = deg2rad(23.5)
18
+
19
+ # Sunset hour angle
20
+ _MINSHA_RADIANS = 0.0
21
+ _MAXSHA_RADIANS = deg2rad(180)
22
+
23
+
24
+ def check_day_hours(hours, arg_name):
25
+ """
26
+ Check that *hours* is in the range 1 to 24.
27
+ """
28
+ if not np.all((0 <= hours) & (hours <= 24)):
29
+ raise ValueError("{0} should be in range 0-24: {1!r}".format(arg_name, hours))
30
+
31
+
32
+ def check_doy(doy):
33
+ """
34
+ Check day of the year is valid.
35
+ """
36
+ if not np.all((1 <= doy) & (doy <= 366)):
37
+ raise ValueError(
38
+ "Day of the year (doy) must be in range 1-366."
39
+ )
40
+
41
+
42
+ def check_latitude_rad(latitude):
43
+ if not np.all((_MINLAT_RADIANS <= latitude) & (latitude <= _MAXLAT_RADIANS)):
44
+ raise ValueError(
45
+ "latitude outside valid range {0!r} to {1!r} rad: {2!r}".format(
46
+ _MINLAT_RADIANS, _MAXLAT_RADIANS, latitude
47
+ )
48
+ )
49
+
50
+
51
+ def check_sol_dec_rad(sd):
52
+ """
53
+ Solar declination can vary between -23.5 and +23.5 degrees.
54
+
55
+ See http://mypages.iit.edu/~maslanka/SolarGeo.pdf
56
+ """
57
+ if not np.all((_MINSOLDEC_RADIANS <= sd) & (sd <= _MAXSOLDEC_RADIANS)):
58
+ raise ValueError(
59
+ "solar declination outside valid range {0!r} to {1!r} rad: {2!r}".format(
60
+ _MINSOLDEC_RADIANS, _MAXSOLDEC_RADIANS, sd
61
+ )
62
+ )
63
+
64
+
65
+ def check_sunset_hour_angle_rad(sha):
66
+ """
67
+ Sunset hour angle has the range 0 to 180 degrees.
68
+
69
+ See http://mypages.iit.edu/~maslanka/SolarGeo.pdf
70
+ """
71
+ if not np.all((_MINSHA_RADIANS <= sha) & (sha <= _MAXSHA_RADIANS)):
72
+ raise ValueError(
73
+ "sunset hour angle outside valid range {0!r} to {1!r} rad: {2!r}".format(
74
+ _MINSHA_RADIANS, _MAXSHA_RADIANS, sha
75
+ )
76
+ )
docs/pyeto/convert.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unit conversion functions.
3
+
4
+ :copyright: (c) 2015 by Mark Richards.
5
+ :license: BSD 3-Clause, see LICENSE.txt for more details.
6
+ """
7
+
8
+ import numpy as np
9
+
10
+
11
+ def celsius2kelvin(celsius):
12
+ """
13
+ Convert temperature in degrees Celsius to degrees Kelvin.
14
+
15
+ :param celsius: Degrees Celsius
16
+ :return: Degrees Kelvin
17
+ :rtype: float
18
+ """
19
+ return celsius + 273.15
20
+
21
+
22
+ def kelvin2celsius(kelvin):
23
+ """
24
+ Convert temperature in degrees Kelvin to degrees Celsius.
25
+
26
+ :param kelvin: Degrees Kelvin
27
+ :return: Degrees Celsius
28
+ :rtype: float
29
+ """
30
+ return kelvin - 273.15
31
+
32
+
33
+ def deg2rad(degrees):
34
+ """
35
+ Convert angular degrees to radians
36
+
37
+ :param degrees: Value in degrees to be converted.
38
+ :return: Value in radians
39
+ :rtype: float
40
+ """
41
+ return degrees * (np.pi / 180.0)
42
+
43
+
44
+ def rad2deg(radians):
45
+ """
46
+ Convert radians to angular degrees
47
+
48
+ :param radians: Value in radians to be converted.
49
+ :return: Value in angular degrees
50
+ :rtype: float
51
+ """
52
+ return radians * (180.0 / np.pi)
docs/pyeto/fao.py ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Library of functions for estimating reference evapotransporation (ETo) for
3
+ a grass reference crop using the FAO-56 Penman-Monteith and Hargreaves
4
+ equations. The library includes numerous functions for estimating missing
5
+ meteorological data.
6
+
7
+ :copyright: (c) 2015 by Mark Richards.
8
+ :license: BSD 3-Clause, see LICENSE.txt for more details.
9
+ """
10
+
11
+ import numpy as np
12
+
13
+ from ._check import (
14
+ check_day_hours as _check_day_hours,
15
+ check_doy as _check_doy,
16
+ check_latitude_rad as _check_latitude_rad,
17
+ check_sol_dec_rad as _check_sol_dec_rad,
18
+ check_sunset_hour_angle_rad as _check_sunset_hour_angle_rad,
19
+ )
20
+
21
+ #: Solar constant [ MJ m-2 min-1]
22
+ SOLAR_CONSTANT = 0.0820
23
+
24
+ # Stefan Boltzmann constant [MJ K-4 m-2 day-1]
25
+ STEFAN_BOLTZMANN_CONSTANT = 0.000000004903 #
26
+ """Stefan Boltzmann constant [MJ K-4 m-2 day-1]"""
27
+
28
+
29
+ def atm_pressure(altitude):
30
+ """
31
+ Estimate atmospheric pressure from altitude.
32
+
33
+ Calculated using a simplification of the ideal gas law, assuming 20 degrees
34
+ Celsius for a standard atmosphere. Based on equation 7, page 62 in Allen
35
+ et al (1998).
36
+
37
+ :param altitude: Elevation/altitude above sea level [m]
38
+ :return: atmospheric pressure [kPa]
39
+ :rtype: float
40
+ """
41
+ tmp = (293.0 - (0.0065 * altitude)) / 293.0
42
+ return np.power(tmp, 5.26) * 101.3
43
+
44
+
45
+ def avp_from_tmin(tmin):
46
+ """
47
+ Estimate actual vapour pressure (*ea*) from minimum temperature.
48
+
49
+ This method is to be used where humidity data are lacking or are of
50
+ questionable quality. The method assumes that the dewpoint temperature
51
+ is approximately equal to the minimum temperature (*tmin*), i.e. the
52
+ air is saturated with water vapour at *tmin*.
53
+
54
+ **Note**: This assumption may not hold in arid/semi-arid areas.
55
+ In these areas it may be better to subtract 2 deg C from the
56
+ minimum temperature (see Annex 6 in FAO paper).
57
+
58
+ Based on equation 48 in Allen et al (1998).
59
+
60
+ :param tmin: Daily minimum temperature [deg C]
61
+ :return: Actual vapour pressure [kPa]
62
+ :rtype: float
63
+ """
64
+ return 0.611 * np.exp((17.27 * tmin) / (tmin + 237.3))
65
+
66
+
67
+ def avp_from_rhmin_rhmax(svp_tmin, svp_tmax, rh_min, rh_max):
68
+ """
69
+ Estimate actual vapour pressure (*ea*) from saturation vapour pressure and
70
+ relative humidity.
71
+
72
+ Based on FAO equation 17 in Allen et al (1998).
73
+
74
+ :param svp_tmin: Saturation vapour pressure at daily minimum temperature
75
+ [kPa]. Can be estimated using ``svp_from_t()``.
76
+ :param svp_tmax: Saturation vapour pressure at daily maximum temperature
77
+ [kPa]. Can be estimated using ``svp_from_t()``.
78
+ :param rh_min: Minimum relative humidity [%]
79
+ :param rh_max: Maximum relative humidity [%]
80
+ :return: Actual vapour pressure [kPa]
81
+ :rtype: float
82
+ """
83
+ tmp1 = svp_tmin * (rh_max / 100.0)
84
+ tmp2 = svp_tmax * (rh_min / 100.0)
85
+ return (tmp1 + tmp2) / 2.0
86
+
87
+
88
+ def avp_from_rhmax(svp_tmin, rh_max):
89
+ """
90
+ Estimate actual vapour pressure (*e*a) from saturation vapour pressure at
91
+ daily minimum temperature and maximum relative humidity
92
+
93
+ Based on FAO equation 18 in Allen et al (1998).
94
+
95
+ :param svp_tmin: Saturation vapour pressure at daily minimum temperature
96
+ [kPa]. Can be estimated using ``svp_from_t()``.
97
+ :param rh_max: Maximum relative humidity [%]
98
+ :return: Actual vapour pressure [kPa]
99
+ :rtype: float
100
+ """
101
+ return svp_tmin * (rh_max / 100.0)
102
+
103
+
104
+ def avp_from_rhmean(svp_tmin, svp_tmax, rh_mean):
105
+ """
106
+ Estimate actual vapour pressure (*ea*) from saturation vapour pressure at
107
+ daily minimum and maximum temperature, and mean relative humidity.
108
+
109
+ Based on FAO equation 19 in Allen et al (1998).
110
+
111
+ :param svp_tmin: Saturation vapour pressure at daily minimum temperature
112
+ [kPa]. Can be estimated using ``svp_from_t()``.
113
+ :param svp_tmax: Saturation vapour pressure at daily maximum temperature
114
+ [kPa]. Can be estimated using ``svp_from_t()``.
115
+ :param rh_mean: Mean relative humidity [%] (average of RH min and RH max).
116
+ :return: Actual vapour pressure [kPa]
117
+ :rtype: float
118
+ """
119
+ return (rh_mean / 100.0) * ((svp_tmax + svp_tmin) / 2.0)
120
+
121
+
122
+ def avp_from_tdew(tdew):
123
+ """
124
+ Estimate actual vapour pressure (*ea*) from dewpoint temperature.
125
+
126
+ Based on equation 14 in Allen et al (1998). As the dewpoint temperature is
127
+ the temperature to which air needs to be cooled to make it saturated, the
128
+ actual vapour pressure is the saturation vapour pressure at the dewpoint
129
+ temperature.
130
+
131
+ This method is preferable to calculating vapour pressure from
132
+ minimum temperature.
133
+
134
+ :param tdew: Dewpoint temperature [deg C]
135
+ :return: Actual vapour pressure [kPa]
136
+ :rtype: float
137
+ """
138
+ return 0.6108 * np.exp((17.27 * tdew) / (tdew + 237.3))
139
+
140
+
141
+ def avp_from_twet_tdry(twet, tdry, svp_twet, psy_const):
142
+ """
143
+ Estimate actual vapour pressure (*ea*) from wet and dry bulb temperature.
144
+
145
+ Based on equation 15 in Allen et al (1998). As the dewpoint temperature
146
+ is the temperature to which air needs to be cooled to make it saturated, the
147
+ actual vapour pressure is the saturation vapour pressure at the dewpoint
148
+ temperature.
149
+
150
+ This method is preferable to calculating vapour pressure from
151
+ minimum temperature.
152
+
153
+ Values for the psychrometric constant of the psychrometer (*psy_const*)
154
+ can be calculated using ``psyc_const_of_psychrometer()``.
155
+
156
+ :param twet: Wet bulb temperature [deg C]
157
+ :param tdry: Dry bulb temperature [deg C]
158
+ :param svp_twet: Saturated vapour pressure at the wet bulb temperature
159
+ [kPa]. Can be estimated using ``svp_from_t()``.
160
+ :param psy_const: Psychrometric constant of the pyschrometer [kPa deg C-1].
161
+ Can be estimated using ``psy_const()`` or
162
+ ``psy_const_of_psychrometer()``.
163
+ :return: Actual vapour pressure [kPa]
164
+ :rtype: float
165
+ """
166
+ return svp_twet - (psy_const * (tdry - twet))
167
+
168
+
169
+ def cs_rad(altitude, et_rad):
170
+ """
171
+ Estimate clear sky radiation from altitude and extraterrestrial radiation.
172
+
173
+ Based on equation 37 in Allen et al (1998) which is recommended when
174
+ calibrated Angstrom values are not available.
175
+
176
+ :param altitude: Elevation above sea level [m]
177
+ :param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
178
+ estimated using ``et_rad()``.
179
+ :return: Clear sky radiation [MJ m-2 day-1]
180
+ :rtype: float
181
+ """
182
+ return (0.00002 * altitude + 0.75) * et_rad
183
+
184
+
185
+ def daily_mean_t(tmin, tmax):
186
+ """
187
+ Estimate mean daily temperature from the daily minimum and maximum
188
+ temperatures.
189
+
190
+ :param tmin: Minimum daily temperature [deg C]
191
+ :param tmax: Maximum daily temperature [deg C]
192
+ :return: Mean daily temperature [deg C]
193
+ :rtype: float
194
+ """
195
+ return (tmax + tmin) / 2.0
196
+
197
+
198
+ def daylight_hours(sha):
199
+ """
200
+ Calculate daylight hours from sunset hour angle.
201
+
202
+ Based on FAO equation 34 in Allen et al (1998).
203
+
204
+ :param sha: Sunset hour angle [rad]. Can be calculated using
205
+ ``sunset_hour_angle()``.
206
+ :return: Daylight hours.
207
+ :rtype: float
208
+ """
209
+ _check_sunset_hour_angle_rad(sha)
210
+ return (24.0 / np.pi) * sha
211
+
212
+
213
+ def delta_svp(t):
214
+ """
215
+ Estimate the slope of the saturation vapour pressure curve at a given
216
+ temperature.
217
+
218
+ Based on equation 13 in Allen et al (1998). If using in the Penman-Monteith
219
+ *t* should be the mean air temperature.
220
+
221
+ :param t: Air temperature [deg C]. Use mean air temperature for use in
222
+ Penman-Monteith.
223
+ :return: Saturation vapour pressure [kPa degC-1]
224
+ :rtype: float
225
+ """
226
+ tmp = 4098 * (0.6108 * np.exp((17.27 * t) / (t + 237.3)))
227
+ return tmp / np.power((t + 237.3), 2)
228
+
229
+
230
+ def energy2evap(energy):
231
+ """
232
+ Convert energy (e.g. radiation energy) in MJ m-2 day-1 to the equivalent
233
+ evaporation, assuming a grass reference crop.
234
+
235
+ Energy is converted to equivalent evaporation using a conversion
236
+ factor equal to the inverse of the latent heat of vapourisation
237
+ (1 / lambda = 0.408).
238
+
239
+ Based on FAO equation 20 in Allen et al (1998).
240
+
241
+ :param energy: Energy e.g. radiation or heat flux [MJ m-2 day-1].
242
+ :return: Equivalent evaporation [mm day-1].
243
+ :rtype: float
244
+ """
245
+ return 0.408 * energy
246
+
247
+
248
+ def et_rad(latitude, sol_dec, sha, ird):
249
+ """
250
+ Estimate daily extraterrestrial radiation (*Ra*, 'top of the atmosphere
251
+ radiation').
252
+
253
+ Based on equation 21 in Allen et al (1998). If monthly mean radiation is
254
+ required make sure *sol_dec*. *sha* and *irl* have been calculated using
255
+ the day of the year that corresponds to the middle of the month.
256
+
257
+ **Note**: From Allen et al (1998): "For the winter months in latitudes
258
+ greater than 55 degrees (N or S), the equations have limited validity.
259
+ Reference should be made to the Smithsonian Tables to assess possible
260
+ deviations."
261
+
262
+ :param latitude: Latitude [radians]
263
+ :param sol_dec: Solar declination [radians]. Can be calculated using
264
+ ``sol_dec()``.
265
+ :param sha: Sunset hour angle [radians]. Can be calculated using
266
+ ``sunset_hour_angle()``.
267
+ :param ird: Inverse relative distance earth-sun [dimensionless]. Can be
268
+ calculated using ``inv_rel_dist_earth_sun()``.
269
+ :return: Daily extraterrestrial radiation [MJ m-2 day-1]
270
+ :rtype: float
271
+ """
272
+ _check_latitude_rad(latitude)
273
+ _check_sol_dec_rad(sol_dec)
274
+ _check_sunset_hour_angle_rad(sha)
275
+
276
+ tmp1 = (24.0 * 60.0) / np.pi
277
+ tmp2 = sha * np.sin(latitude) * np.sin(sol_dec)
278
+ tmp3 = np.cos(latitude) * np.cos(sol_dec) * np.sin(sha)
279
+ return tmp1 * SOLAR_CONSTANT * ird * (tmp2 + tmp3)
280
+
281
+
282
+ def fao56_penman_monteith(net_rad, t, ws, svp, avp, delta_svp, psy, shf=0.0):
283
+ """
284
+ Estimate reference evapotranspiration (ETo) from a hypothetical
285
+ short grass reference surface using the FAO-56 Penman-Monteith equation.
286
+
287
+ Based on equation 6 in Allen et al (1998).
288
+
289
+ :param net_rad: Net radiation at crop surface [MJ m-2 day-1]. If
290
+ necessary this can be estimated using ``net_rad()``.
291
+ :param t: Air temperature at 2 m height [deg Kelvin].
292
+ :param ws: Wind speed at 2 m height [m s-1]. If not measured at 2m,
293
+ convert using ``wind_speed_at_2m()``.
294
+ :param svp: Saturation vapour pressure [kPa]. Can be estimated using
295
+ ``svp_from_t()''.
296
+ :param avp: Actual vapour pressure [kPa]. Can be estimated using a range
297
+ of functions with names beginning with 'avp_from'.
298
+ :param delta_svp: Slope of saturation vapour pressure curve [kPa degC-1].
299
+ Can be estimated using ``delta_svp()``.
300
+ :param psy: Psychrometric constant [kPa deg C]. Can be estimatred using
301
+ ``psy_const_of_psychrometer()`` or ``psy_const()``.
302
+ :param shf: Soil heat flux (G) [MJ m-2 day-1] (default is 0.0, which is
303
+ reasonable for a daily or 10-day time steps). For monthly time steps
304
+ *shf* can be estimated using ``monthly_soil_heat_flux()`` or
305
+ ``monthly_soil_heat_flux2()``.
306
+ :return: Reference evapotranspiration (ETo) from a hypothetical
307
+ grass reference surface [mm day-1].
308
+ :rtype: float
309
+ """
310
+ a1 = (0.408 * (net_rad - shf) * delta_svp /
311
+ (delta_svp + (psy * (1 + 0.34 * ws))))
312
+ a2 = (900 * ws / t * (svp - avp) * psy /
313
+ (delta_svp + (psy * (1 + 0.34 * ws))))
314
+ return a1 + a2
315
+
316
+
317
+ def hargreaves(tmin, tmax, tmean, et_rad):
318
+ """
319
+ Estimate reference evapotranspiration over grass (ETo) using the Hargreaves
320
+ equation.
321
+
322
+ Generally, when solar radiation data, relative humidity data
323
+ and/or wind speed data are missing, it is better to estimate them using
324
+ the functions available in this module, and then calculate ETo
325
+ the FAO Penman-Monteith equation. However, as an alternative, ETo can be
326
+ estimated using the Hargreaves ETo equation.
327
+
328
+ Based on equation 52 in Allen et al (1998).
329
+
330
+ :param tmin: Minimum daily temperature [deg C]
331
+ :param tmax: Maximum daily temperature [deg C]
332
+ :param tmean: Mean daily temperature [deg C]. If emasurements not
333
+ available it can be estimated as (*tmin* + *tmax*) / 2.
334
+ :param et_rad: Extraterrestrial radiation (Ra) [MJ m-2 day-1]. Can be
335
+ estimated using ``et_rad()``.
336
+ :return: Reference evapotranspiration over grass (ETo) [mm day-1]
337
+ :rtype: float
338
+ """
339
+ # Note, multiplied by 0.408 to convert extraterrestrial radiation could
340
+ # be given in MJ m-2 day-1 rather than as equivalent evaporation in
341
+ # mm day-1
342
+ return 0.0023 * (tmean + 17.8) * (tmax - tmin) ** 0.5 * 0.408 * et_rad
343
+
344
+
345
+ def inv_rel_dist_earth_sun(day_of_year):
346
+ """
347
+ Calculate the inverse relative distance between earth and sun from
348
+ day of the year.
349
+
350
+ Based on FAO equation 23 in Allen et al (1998).
351
+
352
+ :param day_of_year: Day of the year [1 to 366]
353
+ :return: Inverse relative distance between earth and the sun
354
+ :rtype: float
355
+ """
356
+ _check_doy(day_of_year)
357
+ return 1 + (0.033 * np.cos((2.0 * np.pi / 365.0) * day_of_year))
358
+
359
+
360
+ def mean_svp(tmin, tmax):
361
+ """
362
+ Estimate mean saturation vapour pressure, *es* [kPa] from minimum and
363
+ maximum temperature.
364
+
365
+ Based on equations 11 and 12 in Allen et al (1998).
366
+
367
+ Mean saturation vapour pressure is calculated as the mean of the
368
+ saturation vapour pressure at tmax (maximum temperature) and tmin
369
+ (minimum temperature).
370
+
371
+ :param tmin: Minimum temperature [deg C]
372
+ :param tmax: Maximum temperature [deg C]
373
+ :return: Mean saturation vapour pressure (*es*) [kPa]
374
+ :rtype: float
375
+ """
376
+ return (svp_from_t(tmin) + svp_from_t(tmax)) / 2.0
377
+
378
+
379
+ def monthly_soil_heat_flux(t_month_prev, t_month_next):
380
+ """
381
+ Estimate monthly soil heat flux (Gmonth) from the mean air temperature of
382
+ the previous and next month, assuming a grass crop.
383
+
384
+ Based on equation 43 in Allen et al (1998). If the air temperature of the
385
+ next month is not known use ``monthly_soil_heat_flux2()`` instead. The
386
+ resulting heat flux can be converted to equivalent evaporation [mm day-1]
387
+ using ``energy2evap()``.
388
+
389
+ :param t_month_prev: Mean air temperature of the previous month
390
+ [deg Celsius]
391
+ :param t_month2_next: Mean air temperature of the next month [deg Celsius]
392
+ :return: Monthly soil heat flux (Gmonth) [MJ m-2 day-1]
393
+ :rtype: float
394
+ """
395
+ return 0.07 * (t_month_next - t_month_prev)
396
+
397
+
398
+ def monthly_soil_heat_flux2(t_month_prev, t_month_cur):
399
+ """
400
+ Estimate monthly soil heat flux (Gmonth) [MJ m-2 day-1] from the mean
401
+ air temperature of the previous and current month, assuming a grass crop.
402
+
403
+ Based on equation 44 in Allen et al (1998). If the air temperature of the
404
+ next month is available, use ``monthly_soil_heat_flux()`` instead. The
405
+ resulting heat flux can be converted to equivalent evaporation [mm day-1]
406
+ using ``energy2evap()``.
407
+
408
+ Arguments:
409
+ :param t_month_prev: Mean air temperature of the previous month
410
+ [deg Celsius]
411
+ :param t_month_cur: Mean air temperature of the current month [deg Celsius]
412
+ :return: Monthly soil heat flux (Gmonth) [MJ m-2 day-1]
413
+ :rtype: float
414
+ """
415
+ return 0.14 * (t_month_cur - t_month_prev)
416
+
417
+
418
+ def net_in_sol_rad(sol_rad, albedo=0.23):
419
+ """
420
+ Calculate net incoming solar (or shortwave) radiation from gross
421
+ incoming solar radiation, assuming a grass reference crop.
422
+
423
+ Net incoming solar radiation is the net shortwave radiation resulting
424
+ from the balance between incoming and reflected solar radiation. The
425
+ output can be converted to equivalent evaporation [mm day-1] using
426
+ ``energy2evap()``.
427
+
428
+ Based on FAO equation 38 in Allen et al (1998).
429
+
430
+ :param sol_rad: Gross incoming solar radiation [MJ m-2 day-1]. If
431
+ necessary this can be estimated using functions whose name
432
+ begins with 'sol_rad_from'.
433
+ :param albedo: Albedo of the crop as the proportion of gross incoming solar
434
+ radiation that is reflected by the surface. Default value is 0.23,
435
+ which is the value used by the FAO for a short grass reference crop.
436
+ Albedo can be as high as 0.95 for freshly fallen snow and as low as
437
+ 0.05 for wet bare soil. A green vegetation over has an albedo of
438
+ about 0.20-0.25 (Allen et al, 1998).
439
+ :return: Net incoming solar (or shortwave) radiation [MJ m-2 day-1].
440
+ :rtype: float
441
+ """
442
+ return (1 - albedo) * sol_rad
443
+
444
+
445
+ def net_out_lw_rad(tmin, tmax, sol_rad, cs_rad, avp):
446
+ """
447
+ Estimate net outgoing longwave radiation.
448
+
449
+ This is the net longwave energy (net energy flux) leaving the
450
+ earth's surface. It is proportional to the absolute temperature of
451
+ the surface raised to the fourth power according to the Stefan-Boltzmann
452
+ law. However, water vapour, clouds, carbon dioxide and dust are absorbers
453
+ and emitters of longwave radiation. This function corrects the Stefan-
454
+ Boltzmann law for humidity (using actual vapor pressure) and cloudiness
455
+ (using solar radiation and clear sky radiation). The concentrations of all
456
+ other absorbers are assumed to be constant.
457
+
458
+ The output can be converted to equivalent evaporation [mm day-1] using
459
+ ``energy2evap()``.
460
+
461
+ Based on FAO equation 39 in Allen et al (1998).
462
+
463
+ :param tmin: Absolute daily minimum temperature [degrees Kelvin]
464
+ :param tmax: Absolute daily maximum temperature [degrees Kelvin]
465
+ :param sol_rad: Solar radiation [MJ m-2 day-1]. If necessary this can be
466
+ estimated using ``sol+rad()``.
467
+ :param cs_rad: Clear sky radiation [MJ m-2 day-1]. Can be estimated using
468
+ ``cs_rad()``.
469
+ :param avp: Actual vapour pressure [kPa]. Can be estimated using functions
470
+ with names beginning with 'avp_from'.
471
+ :return: Net outgoing longwave radiation [MJ m-2 day-1]
472
+ :rtype: float
473
+ """
474
+ tmp1 = (STEFAN_BOLTZMANN_CONSTANT *
475
+ ((np.power(tmax, 4) + np.power(tmin, 4)) / 2))
476
+ tmp2 = (0.34 - (0.14 * np.sqrt(avp)))
477
+ tmp3 = 1.35 * (sol_rad / cs_rad) - 0.35
478
+ return tmp1 * tmp2 * tmp3
479
+
480
+
481
+ def net_rad(ni_sw_rad, no_lw_rad):
482
+ """
483
+ Calculate daily net radiation at the crop surface, assuming a grass
484
+ reference crop.
485
+
486
+ Net radiation is the difference between the incoming net shortwave (or
487
+ solar) radiation and the outgoing net longwave radiation. Output can be
488
+ converted to equivalent evaporation [mm day-1] using ``energy2evap()``.
489
+
490
+ Based on equation 40 in Allen et al (1998).
491
+
492
+ :param ni_sw_rad: Net incoming shortwave radiation [MJ m-2 day-1]. Can be
493
+ estimated using ``net_in_sol_rad()``.
494
+ :param no_lw_rad: Net outgoing longwave radiation [MJ m-2 day-1]. Can be
495
+ estimated using ``net_out_lw_rad()``.
496
+ :return: Daily net radiation [MJ m-2 day-1].
497
+ :rtype: float
498
+ """
499
+ return ni_sw_rad - no_lw_rad
500
+
501
+
502
+ def psy_const(atmos_pres):
503
+ """
504
+ Calculate the psychrometric constant.
505
+
506
+ This method assumes that the air is saturated with water vapour at the
507
+ minimum daily temperature. This assumption may not hold in arid areas.
508
+
509
+ Based on equation 8, page 95 in Allen et al (1998).
510
+
511
+ :param atmos_pres: Atmospheric pressure [kPa]. Can be estimated using
512
+ ``atm_pressure()``.
513
+ :return: Psychrometric constant [kPa degC-1].
514
+ :rtype: float
515
+ """
516
+ return 0.000665 * atmos_pres
517
+
518
+
519
+ def psy_const_of_psychrometer(psychrometer, atmos_pres):
520
+ """
521
+ Calculate the psychrometric constant for different types of
522
+ psychrometer at a given atmospheric pressure.
523
+
524
+ Based on FAO equation 16 in Allen et al (1998).
525
+
526
+ :param psychrometer: Integer between 1 and 3 which denotes type of
527
+ psychrometer:
528
+ 1. ventilated (Asmann or aspirated type) psychrometer with
529
+ an air movement of approximately 5 m/s
530
+ 2. natural ventilated psychrometer with an air movement
531
+ of approximately 1 m/s
532
+ 3. non ventilated psychrometer installed indoors
533
+ :param atmos_pres: Atmospheric pressure [kPa]. Can be estimated using
534
+ ``atm_pressure()``.
535
+ :return: Psychrometric constant [kPa degC-1].
536
+ :rtype: float
537
+ """
538
+ # Select coefficient based on type of ventilation of the wet bulb
539
+ if psychrometer == 1:
540
+ psy_coeff = 0.000662
541
+ elif psychrometer == 2:
542
+ psy_coeff = 0.000800
543
+ elif psychrometer == 3:
544
+ psy_coeff = 0.001200
545
+ else:
546
+ raise ValueError(
547
+ 'psychrometer should be in range 1 to 3: {0!r}'.format(psychrometer))
548
+
549
+ return psy_coeff * atmos_pres
550
+
551
+
552
+ def rh_from_avp_svp(avp, svp):
553
+ """
554
+ Calculate relative humidity as the ratio of actual vapour pressure
555
+ to saturation vapour pressure at the same temperature.
556
+
557
+ See Allen et al (1998), page 67 for details.
558
+
559
+ :param avp: Actual vapour pressure [units do not matter so long as they
560
+ are the same as for *svp*]. Can be estimated using functions whose
561
+ name begins with 'avp_from'.
562
+ :param svp: Saturated vapour pressure [units do not matter so long as they
563
+ are the same as for *avp*]. Can be estimated using ``svp_from_t()``.
564
+ :return: Relative humidity [%].
565
+ :rtype: float
566
+ """
567
+ return 100.0 * avp / svp
568
+
569
+
570
+ def sol_dec(day_of_year):
571
+ """
572
+ Calculate solar declination from day of the year.
573
+
574
+ Based on FAO equation 24 in Allen et al (1998).
575
+
576
+ :param day_of_year: Day of year integer between 1 and 365 or 366).
577
+ :return: solar declination [radians]
578
+ :rtype: float
579
+ """
580
+ _check_doy(day_of_year)
581
+ return 0.409 * np.sin(((2.0 * np.pi / 365.0) * day_of_year - 1.39))
582
+
583
+
584
+ def sol_rad_from_sun_hours(daylight_hours, sunshine_hours, et_rad):
585
+ """
586
+ Calculate incoming solar (or shortwave) radiation, *Rs* (radiation hitting
587
+ a horizontal plane after scattering by the atmosphere) from relative
588
+ sunshine duration.
589
+
590
+ If measured radiation data are not available this method is preferable
591
+ to calculating solar radiation from temperature. If a monthly mean is
592
+ required then divide the monthly number of sunshine hours by number of
593
+ days in the month and ensure that *et_rad* and *daylight_hours* was
594
+ calculated using the day of the year that corresponds to the middle of
595
+ the month.
596
+
597
+ Based on equations 34 and 35 in Allen et al (1998).
598
+
599
+ :param dl_hours: Number of daylight hours [hours]. Can be calculated
600
+ using ``daylight_hours()``.
601
+ :param sunshine_hours: Sunshine duration [hours].
602
+ :param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
603
+ estimated using ``et_rad()``.
604
+ :return: Incoming solar (or shortwave) radiation [MJ m-2 day-1]
605
+ :rtype: float
606
+ """
607
+ _check_day_hours(sunshine_hours, 'sun_hours')
608
+ _check_day_hours(daylight_hours, 'daylight_hours')
609
+
610
+ # 0.5 and 0.25 are default values of regression constants (Angstrom values)
611
+ # recommended by FAO when calibrated values are unavailable.
612
+ return (0.5 * sunshine_hours / daylight_hours + 0.25) * et_rad
613
+
614
+
615
+ def sol_rad_from_t(et_rad, cs_rad, tmin, tmax, coastal):
616
+ """
617
+ Estimate incoming solar (or shortwave) radiation, *Rs*, (radiation hitting
618
+ a horizontal plane after scattering by the atmosphere) from min and max
619
+ temperature together with an empirical adjustment coefficient for
620
+ 'interior' and 'coastal' regions.
621
+
622
+ The formula is based on equation 50 in Allen et al (1998) which is the
623
+ Hargreaves radiation formula (Hargreaves and Samani, 1982, 1985). This
624
+ method should be used only when solar radiation or sunshine hours data are
625
+ not available. It is only recommended for locations where it is not
626
+ possible to use radiation data from a regional station (either because
627
+ climate conditions are heterogeneous or data are lacking).
628
+
629
+ **NOTE**: this method is not suitable for island locations due to the
630
+ moderating effects of the surrounding water.
631
+
632
+ :param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
633
+ estimated using ``et_rad()``.
634
+ :param cs_rad: Clear sky radiation [MJ m-2 day-1]. Can be estimated
635
+ using ``cs_rad()``.
636
+ :param tmin: Daily minimum temperature [deg C].
637
+ :param tmax: Daily maximum temperature [deg C].
638
+ :param coastal: ``True`` if site is a coastal location, situated on or
639
+ adjacent to coast of a large land mass and where air masses are
640
+ influenced by a nearby water body, ``False`` if interior location
641
+ where land mass dominates and air masses are not strongly influenced
642
+ by a large water body.
643
+ :return: Incoming solar (or shortwave) radiation (Rs) [MJ m-2 day-1].
644
+ :rtype: float
645
+ """
646
+ # Determine value of adjustment coefficient [deg C-0.5] for
647
+ # coastal/interior locations
648
+ if coastal:
649
+ adj = 0.19
650
+ else:
651
+ adj = 0.16
652
+
653
+ sol_rad = adj * np.sqrt(tmax - tmin) * et_rad
654
+
655
+ # The solar radiation value is constrained by the clear sky radiation
656
+ return np.minimum(sol_rad, cs_rad)
657
+
658
+
659
+ def sol_rad_island(et_rad):
660
+ """
661
+ Estimate incoming solar (or shortwave) radiation, *Rs* (radiation hitting
662
+ a horizontal plane after scattering by the atmosphere) for an island
663
+ location.
664
+
665
+ An island is defined as a land mass with width perpendicular to the
666
+ coastline <= 20 km. Use this method only if radiation data from
667
+ elsewhere on the island is not available.
668
+
669
+ **NOTE**: This method is only applicable for low altitudes (0-100 m)
670
+ and monthly calculations.
671
+
672
+ Based on FAO equation 51 in Allen et al (1998).
673
+
674
+ :param et_rad: Extraterrestrial radiation [MJ m-2 day-1]. Can be
675
+ estimated using ``et_rad()``.
676
+ :return: Incoming solar (or shortwave) radiation [MJ m-2 day-1].
677
+ :rtype: float
678
+ """
679
+ return (0.7 * et_rad) - 4.0
680
+
681
+
682
+ def sunset_hour_angle(latitude, sol_dec):
683
+ """
684
+ Calculate sunset hour angle (*Ws*) from latitude and solar
685
+ declination.
686
+
687
+ Based on FAO equation 25 in Allen et al (1998).
688
+
689
+ :param latitude: Latitude [radians]. Note: *latitude* should be negative
690
+ if it in the southern hemisphere, positive if in the northern
691
+ hemisphere.
692
+ :param sol_dec: Solar declination [radians]. Can be calculated using
693
+ ``sol_dec()``.
694
+ :return: Sunset hour angle [radians].
695
+ :rtype: float
696
+ """
697
+ _check_latitude_rad(latitude)
698
+ _check_sol_dec_rad(sol_dec)
699
+
700
+ cos_sha = -np.tan(latitude) * np.tan(sol_dec)
701
+ # If tmp is >= 1 there is no sunset, i.e. 24 hours of daylight
702
+ # If tmp is <= 1 there is no sunrise, i.e. 24 hours of darkness
703
+ # See http://www.itacanet.org/the-sun-as-a-source-of-energy/
704
+ # part-3-calculating-solar-angles/
705
+ # Domain of acos is -1 <= x <= 1 radians (this is not mentioned in FAO-56!)
706
+ return np.arccos(np.minimum(np.maximum(cos_sha, -1.0), 1.0))
707
+
708
+
709
+ def svp_from_t(t):
710
+ """
711
+ Estimate saturation vapour pressure (*es*) from air temperature.
712
+
713
+ Based on equations 11 and 12 in Allen et al (1998).
714
+
715
+ :param t: Temperature [deg C]
716
+ :return: Saturation vapour pressure [kPa]
717
+ :rtype: float
718
+ """
719
+ return 0.6108 * np.exp((17.27 * t) / (t + 237.3))
720
+
721
+
722
+ def wind_speed_2m(ws, z):
723
+ """
724
+ Convert wind speed measured at different heights above the soil
725
+ surface to wind speed at 2 m above the surface, assuming a short grass
726
+ surface.
727
+
728
+ Based on FAO equation 47 in Allen et al (1998).
729
+
730
+ :param ws: Measured wind speed [m s-1]
731
+ :param z: Height of wind measurement above ground surface [m]
732
+ :return: Wind speed at 2 m above the surface [m s-1]
733
+ :rtype: float
734
+ """
735
+ return ws * (4.87 / np.log((67.8 * z) - 5.42))
docs/pyeto/thornthwaite.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Calculate potential evapotranspiration using the Thornthwaite (1948 method)
3
+
4
+ :copyright: (c) 2015 by Mark Richards.
5
+ :license: BSD 3-Clause, see LICENSE.txt for more details.
6
+
7
+ References
8
+ ----------
9
+ Thornthwaite CW (1948) An approach toward a rational classification of
10
+ climate. Geographical Review, 38, 55-94.
11
+ """
12
+
13
+ import calendar
14
+
15
+ from . import fao
16
+ from ._check import check_latitude_rad as _check_latitude_rad
17
+
18
+ _MONTHDAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
19
+ _LEAP_MONTHDAYS = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
20
+
21
+
22
+ def thornthwaite(monthly_t, monthly_mean_dlh, year=None):
23
+ """
24
+ Estimate monthly potential evapotranspiration (PET) using the
25
+ Thornthwaite (1948) method.
26
+
27
+ Thornthwaite equation:
28
+
29
+ *PET* = 1.6 (*L*/12) (*N*/30) (10*Ta* / *I*)***a*
30
+
31
+ where:
32
+
33
+ * *Ta* is the mean daily air temperature [deg C, if negative use 0] of the
34
+ month being calculated
35
+ * *N* is the number of days in the month being calculated
36
+ * *L* is the mean day length [hours] of the month being calculated
37
+ * *a* = (6.75 x 10-7)*I***3 - (7.71 x 10-5)*I***2 + (1.792 x 10-2)*I* + 0.49239
38
+ * *I* is a heat index which depends on the 12 monthly mean temperatures and
39
+ is calculated as the sum of (*Tai* / 5)**1.514 for each month, where
40
+ Tai is the air temperature for each month in the year
41
+
42
+ :param monthly_t: Iterable containing mean daily air temperature for each
43
+ month of the year [deg C].
44
+ :param monthly_mean_dlh: Iterable containing mean daily daylight
45
+ hours for each month of the year (hours]. These can be calculated
46
+ using ``monthly_mean_daylight_hours()``.
47
+ :param year: Year for which PET is required. The only effect of year is
48
+ to change the number of days in February to 29 if it is a leap year.
49
+ If it is left as the default (None), then the year is assumed not to
50
+ be a leap year.
51
+ :return: Estimated monthly potential evaporation of each month of the year
52
+ [mm/month]
53
+ :rtype: List of floats
54
+ """
55
+ if len(monthly_t) != 12:
56
+ raise ValueError(
57
+ 'monthly_t should be length 12 but is length {0}.'
58
+ .format(len(monthly_t)))
59
+ if len(monthly_mean_dlh) != 12:
60
+ raise ValueError(
61
+ 'monthly_mean_dlh should be length 12 but is length {0}.'
62
+ .format(len(monthly_mean_dlh)))
63
+
64
+ if year is None or not calendar.isleap(year):
65
+ month_days = _MONTHDAYS
66
+ else:
67
+ month_days = _LEAP_MONTHDAYS
68
+
69
+ # Negative temperatures should be set to zero
70
+ adj_monthly_t = [t * (t >= 0) for t in monthly_t]
71
+
72
+ # Calculate the heat index (I)
73
+ I = 0.0
74
+ for Tai in adj_monthly_t:
75
+ if Tai / 5.0 > 0.0:
76
+ I += (Tai / 5.0) ** 1.514
77
+
78
+ a = (6.75e-07 * I ** 3) - (7.71e-05 * I ** 2) + (1.792e-02 * I) + 0.49239
79
+
80
+ pet = []
81
+ for Ta, L, N in zip(adj_monthly_t, monthly_mean_dlh, month_days):
82
+ # Multiply by 10 to convert cm/month --> mm/month
83
+ pet.append(
84
+ 1.6 * (L / 12.0) * (N / 30.0) * ((10.0 * Ta / I) ** a) * 10.0)
85
+
86
+ return pet
87
+
88
+
89
+ def monthly_mean_daylight_hours(latitude, year=None):
90
+ """
91
+ Calculate mean daylight hours for each month of the year for a given
92
+ latitude.
93
+
94
+ :param latitude: Latitude [radians]
95
+ :param year: Year for the daylight hours are required. The only effect of
96
+ *year* is to change the number of days in Feb to 29 if it is a leap
97
+ year. If left as the default, None, then a normal (non-leap) year is
98
+ assumed.
99
+ :return: Mean daily daylight hours of each month of a year [hours]
100
+ :rtype: List of floats.
101
+ """
102
+ _check_latitude_rad(latitude)
103
+
104
+ if year is None or not calendar.isleap(year):
105
+ month_days = _MONTHDAYS
106
+ else:
107
+ month_days = _LEAP_MONTHDAYS
108
+ monthly_mean_dlh = []
109
+ doy = 1 # Day of the year
110
+ for mdays in month_days:
111
+ dlh = 0.0 # Cumulative daylight hours for the month
112
+ for daynum in range(1, mdays + 1):
113
+ sd = fao.sol_dec(doy)
114
+ sha = fao.sunset_hour_angle(latitude, sd)
115
+ dlh += fao.daylight_hours(sha)
116
+ doy += 1
117
+ # Calc mean daylight hours of the month
118
+ monthly_mean_dlh.append(dlh / mdays)
119
+ return monthly_mean_dlh
docs/request_data.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import io
3
+ import logging
4
+ import xarray as xr
5
+ from datetime import date, timedelta
6
+ import json
7
+
8
+
9
+ with open('credentials.txt') as f:
10
+ pwd = f.readlines()
11
+
12
+ response = requests.post(
13
+ "https://api.ombreapp.fr/api/v1.0/links/token/",
14
+ data={"email": "[email protected]", "password": pwd, "permission": True},
15
+ )
16
+
17
+ token = response.json()["access"]
18
+
19
+ def get_air_sensors_data(plot_id, position,date_from = False, date_to= False, night=False):
20
+
21
+ if not date_from:
22
+
23
+ # Get Yesterday data by default
24
+
25
+ if night :
26
+ date_from = (date.today() - timedelta(2)).strftime("%Y-%m-%dT%H:%M:%S")[0:11] + "12:00:00"
27
+ date_to = (date.today() - timedelta(1)).strftime("%Y-%m-%dT%H:%M:%S")[0:11] + "11:50:00"
28
+ else :
29
+ date_from = (date.today() - timedelta(1)).strftime("%Y-%m-%dT%H:%M:%S")
30
+ date_to = date_from[0:11] + "23:50:00"
31
+
32
+ headers = {'Authorization': f"Bearer {token}", "Accept": "application/x-netcdf"}
33
+ # Get Climatic dataset as netcdf file
34
+ response = requests.get(
35
+ f"https://api.ombreapp.fr/api/v2.0/plots/{plot_id}/climatic_dataset/",
36
+ params={
37
+ "start_time": date_from,
38
+ "end_time": date_to,
39
+ "position": position
40
+ },
41
+ headers=headers
42
+ )
43
+
44
+ if response.status_code == 200:
45
+ # Open Dataset
46
+ ds_out = xr.open_dataset(io.BytesIO(response.content))
47
+ else:
48
+ logging.error(f"Error {response.status_code}: {response.content}")
49
+ ds_out
50
+
51
+ df_air = ds_out[['air_temperature','relative_humidity','photon_flux_density','wind_speed']].to_dataframe()
52
+ return df_air
53
+
54
+ def get_lat_lon(plot_id):
55
+ headers = {'Authorization': f"Bearer {token}"}
56
+
57
+ response = requests.get(
58
+ "https://api.ombreapp.fr/api/v2.0/plots/",
59
+ headers=headers
60
+ )
61
+
62
+ plots = json.loads(response.content)
63
+ plot = [plots[i] for i in range(len(plots)) if plots[i]['id'] == plot_id][0]
64
+
65
+ return (plot['zone']['coordinates'][0][0][1],plot['zone']['coordinates'][0][0][0])