pypromice 1.5.2__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pypromice might be problematic. Click here for more details.
- pypromice/__init__.py +2 -0
- pypromice/{qc → core/qc}/percentiles/compute_thresholds.py +2 -2
- pypromice/{qc → core/qc}/persistence.py +22 -29
- pypromice/{process → core/qc}/value_clipping.py +3 -3
- pypromice/core/variables/__init__.py +1 -0
- pypromice/core/variables/air_temperature.py +64 -0
- pypromice/core/variables/gps.py +221 -0
- pypromice/core/variables/humidity.py +111 -0
- pypromice/core/variables/precipitation.py +108 -0
- pypromice/core/variables/pressure_transducer_depth.py +79 -0
- pypromice/core/variables/radiation.py +422 -0
- pypromice/core/variables/station_boom_height.py +49 -0
- pypromice/core/variables/station_pose.py +375 -0
- pypromice/core/variables/wind.py +66 -0
- pypromice/io/bufr/__init__.py +0 -0
- pypromice/{postprocess → io/bufr}/bufr_to_csv.py +1 -1
- pypromice/{postprocess → io/bufr}/create_bufr_files.py +2 -2
- pypromice/{postprocess → io/bufr}/get_bufr.py +6 -6
- pypromice/{postprocess → io/bufr}/real_time_utilities.py +3 -3
- pypromice/io/ingest/__init__.py +0 -0
- pypromice/{utilities → io/ingest}/git.py +1 -3
- pypromice/io/ingest/l0.py +294 -0
- pypromice/io/ingest/l0_repository.py +103 -0
- pypromice/io/ingest/toa5.py +87 -0
- pypromice/{process → io}/write.py +1 -1
- pypromice/pipeline/L0toL1.py +291 -0
- pypromice/pipeline/L1toL2.py +233 -0
- pypromice/{process → pipeline}/L2toL3.py +102 -120
- pypromice/pipeline/__init__.py +4 -0
- pypromice/{process → pipeline}/aws.py +10 -82
- pypromice/{process → pipeline}/get_l2.py +2 -2
- pypromice/{process → pipeline}/get_l2tol3.py +19 -22
- pypromice/{process → pipeline}/join_l2.py +31 -32
- pypromice/{process → pipeline}/join_l3.py +16 -14
- pypromice/{process → pipeline}/resample.py +59 -46
- pypromice/{process → pipeline}/utilities.py +0 -22
- pypromice/resources/file_attributes.csv +4 -4
- pypromice/resources/variables.csv +27 -24
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/METADATA +1 -2
- pypromice-1.6.0.dist-info/RECORD +64 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/WHEEL +1 -1
- pypromice-1.6.0.dist-info/entry_points.txt +12 -0
- pypromice/get/__init__.py +0 -1
- pypromice/get/get.py +0 -211
- pypromice/get/get_promice_data.py +0 -56
- pypromice/process/L0toL1.py +0 -536
- pypromice/process/L1toL2.py +0 -839
- pypromice/process/__init__.py +0 -4
- pypromice/process/load.py +0 -161
- pypromice-1.5.2.dist-info/RECORD +0 -53
- pypromice-1.5.2.dist-info/entry_points.txt +0 -13
- /pypromice/{postprocess → core}/__init__.py +0 -0
- /pypromice/{utilities → core}/dependency_graph.py +0 -0
- /pypromice/{qc → core/qc}/__init__.py +0 -0
- /pypromice/{qc → core/qc}/github_data_issues.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/__init__.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/outlier_detector.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/thresholds.csv +0 -0
- /pypromice/{utilities → io}/__init__.py +0 -0
- /pypromice/{postprocess → io/bufr}/bufr_utilities.py +0 -0
- /pypromice/{postprocess → io/bufr}/positions_seed.csv +0 -0
- /pypromice/{station_configuration.py → io/bufr/station_configuration.py} +0 -0
- /pypromice/{postprocess → io}/make_metadata_csv.py +0 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
__all__ = ["correct_and_calculate_depth", "apply_offset"]
|
|
2
|
+
|
|
3
|
+
import xarray as xr
|
|
4
|
+
import numpy as np
|
|
5
|
+
import logging
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
def correct_and_calculate_depth(z_pt: xr.DataArray,
|
|
9
|
+
air_pressure: xr.DataArray,
|
|
10
|
+
pt_antifreeze: float,
|
|
11
|
+
pt_z_factor: float,
|
|
12
|
+
pt_z_coef: float,
|
|
13
|
+
pt_z_p_coef: float
|
|
14
|
+
) -> tuple[xr.DataArray, xr.DataArray]:
|
|
15
|
+
"""Adjust pressure depth and calculate pressure transducer depth based on
|
|
16
|
+
pressure transducer fluid density
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
z_pt : xr.DataArray
|
|
21
|
+
Pressure transducer height (corrected for offset)
|
|
22
|
+
air_pressure : xr.DataArray
|
|
23
|
+
Air pressure
|
|
24
|
+
pt_antifreeze : float
|
|
25
|
+
Pressure transducer anti-freeze percentage for fluid density
|
|
26
|
+
correction
|
|
27
|
+
pt_z_factor : float
|
|
28
|
+
Pressure transducer factor
|
|
29
|
+
pt_z_coef : float
|
|
30
|
+
Pressure transducer coefficient
|
|
31
|
+
pt_z_p_coef : float
|
|
32
|
+
Pressure transducer coefficient
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
z_pt_cor : xr.DataArray
|
|
37
|
+
Pressure transducer height corrected
|
|
38
|
+
z_pt : xr.DataArray
|
|
39
|
+
Pressure transducer depth
|
|
40
|
+
"""
|
|
41
|
+
# Calculate pressure transducer fluid density
|
|
42
|
+
# TODO: Implement function w/ reference (analytical or from LUT)
|
|
43
|
+
# TODO: Track uncertainty
|
|
44
|
+
if pt_antifreeze == 50:
|
|
45
|
+
rho_af = 1092
|
|
46
|
+
elif pt_antifreeze == 100:
|
|
47
|
+
rho_af = 1145
|
|
48
|
+
else:
|
|
49
|
+
rho_af = np.nan
|
|
50
|
+
logger.info('ERROR: Incorrect metadata: "pt_antifreeze" = ' +
|
|
51
|
+
f'{pt_antifreeze}. Antifreeze mix only supported at 50% or 100%')
|
|
52
|
+
# assert(False)
|
|
53
|
+
|
|
54
|
+
# Correct pressure depth
|
|
55
|
+
z_pt_cor = z_pt * pt_z_coef * pt_z_factor * 998.0 / rho_af + 100 * (pt_z_p_coef - air_pressure) / (rho_af * 9.81)
|
|
56
|
+
|
|
57
|
+
# Calculate pressure transducer depth
|
|
58
|
+
z_pt = z_pt * pt_z_coef * pt_z_factor * 998.0 / rho_af
|
|
59
|
+
|
|
60
|
+
return z_pt_cor, z_pt
|
|
61
|
+
|
|
62
|
+
def apply_offset(z_pt: xr.DataArray,
|
|
63
|
+
z_pt_offset: int
|
|
64
|
+
) -> xr.DataArray:
|
|
65
|
+
"""Apply defined offset to pressure transducer height
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
z_pt : xr.DataArray
|
|
70
|
+
Pressure transducer height
|
|
71
|
+
z_pt_offset : xr.DataArray
|
|
72
|
+
Transducer height offset
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
xr.DataArray
|
|
77
|
+
Adjusted pressure transducer height
|
|
78
|
+
"""
|
|
79
|
+
return z_pt + z_pt_offset
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
__all__ = ["convert_sr", "convert_lr", "filter_lr", "filter_sr",
|
|
2
|
+
"correct_sr", "calculate_albedo", "calculate_surface_temperature",
|
|
3
|
+
"calculate_cloud_coverage", "calculate_TOA"]
|
|
4
|
+
|
|
5
|
+
import xarray as xr
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
# Define coefficients for radiometer adjustments
|
|
9
|
+
T_0=273.15 # degrees Celsius to Kelvin conversion
|
|
10
|
+
deg2rad = np.pi / 180 # Degrees to radians conversion
|
|
11
|
+
emissivity=0.97
|
|
12
|
+
|
|
13
|
+
def convert_sr(sr: xr.DataArray,
|
|
14
|
+
sr_eng_coef: float) -> xr.DataArray:
|
|
15
|
+
"""Convert shortwave radiation measurements from engineering to
|
|
16
|
+
physical units, using a calibration coefficient (defined by
|
|
17
|
+
manufacturers usually)
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
sr : xr.DataArray
|
|
22
|
+
Shortwave radiation (upwelling or downwelling) measurements
|
|
23
|
+
sr_eng_coef : float
|
|
24
|
+
Shortwave engineering calibration coefficient
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
xr.DataArray
|
|
29
|
+
Converted shortwave measurements
|
|
30
|
+
"""
|
|
31
|
+
return (sr * 10) / sr_eng_coef
|
|
32
|
+
|
|
33
|
+
def convert_lr(lr: xr.DataArray,
|
|
34
|
+
t_rad: xr.DataArray,
|
|
35
|
+
lr_eng_coef: float) -> xr.DataArray:
|
|
36
|
+
"""Convert longwave radiation measurements from engineering to
|
|
37
|
+
physical units, using the reported radiometer temperature and
|
|
38
|
+
a calibration coefficient (defined by manufacturers usually)
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
lr : xr.DataArray
|
|
43
|
+
Longwave radiation (upwelling or downwelling) measurements
|
|
44
|
+
t_rad : xr.DataArray
|
|
45
|
+
Radiometer temperature
|
|
46
|
+
lr_eng_coef : float
|
|
47
|
+
Longwave engineering calibration coefficient
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
xr.DataArray
|
|
52
|
+
Converted shortwave measurements
|
|
53
|
+
"""
|
|
54
|
+
return ((lr * 10) / lr_eng_coef) + 5.67e-8 * (t_rad + T_0) **4
|
|
55
|
+
|
|
56
|
+
def filter_lr(lr: xr.DataArray,
|
|
57
|
+
t_rad: xr.DataArray) -> xr.DataArray:
|
|
58
|
+
"""Remove longwave radiation measurements that are missing
|
|
59
|
+
simultaneous radiometer temperature measurements
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
lr : xr.DataArray
|
|
64
|
+
Longwave radiation measurements (upwelling or downwelling)
|
|
65
|
+
t_rad : xr.DataArray
|
|
66
|
+
Radiometer temperature
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
xr.DataArray
|
|
71
|
+
Filtered radiation measurements
|
|
72
|
+
"""
|
|
73
|
+
return lr.where(t_rad.notnull())
|
|
74
|
+
|
|
75
|
+
def filter_sr(dsr: xr.DataArray,
|
|
76
|
+
usr: xr.DataArray,
|
|
77
|
+
cc : xr.DataArray,
|
|
78
|
+
ZenithAngle_rad: xr.DataArray,
|
|
79
|
+
ZenithAngle_deg: xr.DataArray,
|
|
80
|
+
AngleDif_deg: xr.DataArray
|
|
81
|
+
) -> tuple[xr.DataArray, xr.DataArray, tuple]:
|
|
82
|
+
"""Filter shortwave radiation data for tilt, station pose relative to sun position, and top-of-atmosphere (TOA)
|
|
83
|
+
irradiance
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
dsr : xr.DataArray
|
|
88
|
+
Downwelling shortwave radiation
|
|
89
|
+
usr : xr.DataArray
|
|
90
|
+
Upwelling shortwave radiation
|
|
91
|
+
cc : xr.DataArray
|
|
92
|
+
Cloud cover
|
|
93
|
+
ZenithAngle_deg : xr.DataArray
|
|
94
|
+
Zenith angle in degrees
|
|
95
|
+
ZenithAngle_rad : xr.DataArray
|
|
96
|
+
Zenith angle in radians
|
|
97
|
+
AngleDif_deg : xr.DataArray
|
|
98
|
+
Angle between sun and sensor in degrees
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
dsr_filtered : xr.DataArray
|
|
103
|
+
Filtered downwelling shortwave radiation
|
|
104
|
+
usr_filtered : xr. DataArray
|
|
105
|
+
Filtered upwelling shortwave radiation
|
|
106
|
+
tuple
|
|
107
|
+
Filter flags for 1) sun position below horizon; 2) sun on lower dome; 3) downwelling sr measurements greater
|
|
108
|
+
than TOA; and 4) upwelling sr measurements greater than TOA
|
|
109
|
+
"""
|
|
110
|
+
dsr_filtered = dsr.copy()
|
|
111
|
+
usr_filtered = usr.copy()
|
|
112
|
+
|
|
113
|
+
# Setting to zero when sun below the horizon.
|
|
114
|
+
bad = ZenithAngle_deg > 95
|
|
115
|
+
dsr_filtered[bad & dsr_filtered.notnull()] = 0
|
|
116
|
+
usr_filtered[bad & usr_filtered.notnull()] = 0
|
|
117
|
+
|
|
118
|
+
# Setting to zero when values are negative
|
|
119
|
+
dsr_filtered = dsr_filtered.clip(min=0)
|
|
120
|
+
usr_filtered = usr_filtered.clip(min=0)
|
|
121
|
+
|
|
122
|
+
# Filtering usr and dsr for sun on lower dome
|
|
123
|
+
# in theory, this is not a problem in cloudy conditions, but the cloud cover
|
|
124
|
+
# index is too uncertain at this point to be used
|
|
125
|
+
sunonlowerdome = (AngleDif_deg >= 90) & (ZenithAngle_deg <= 90)
|
|
126
|
+
|
|
127
|
+
# Relaxing the filter for cases where sensor tilt is unknown
|
|
128
|
+
mask = ~sunonlowerdome | AngleDif_deg.isnull()
|
|
129
|
+
|
|
130
|
+
# Perform filter
|
|
131
|
+
dsr_filtered = dsr_filtered.where(mask)
|
|
132
|
+
usr_filtered = usr_filtered.where(mask)
|
|
133
|
+
|
|
134
|
+
# Calculate TOA shortwave radiation
|
|
135
|
+
isr_toa = calculate_TOA(ZenithAngle_deg, ZenithAngle_rad)
|
|
136
|
+
|
|
137
|
+
# Filter dsr values that are greater than top of the atmosphere irradiance
|
|
138
|
+
# Case where no tilt is available. If it is, then the same filter is used
|
|
139
|
+
# after tilt correction.
|
|
140
|
+
tilt_correction_possible = AngleDif_deg.notnull() & cc.notnull()
|
|
141
|
+
TOA_crit_nopass_dsr = ~tilt_correction_possible & (dsr_filtered > (1.2 * isr_toa + 150))
|
|
142
|
+
dsr_filtered[TOA_crit_nopass_dsr] = np.nan
|
|
143
|
+
|
|
144
|
+
# The upward flux should not be higher than the TOA downward flux
|
|
145
|
+
TOA_crit_nopass_usr = (usr_filtered > 0.8 * (1.2 * isr_toa + 150))
|
|
146
|
+
usr_filtered[TOA_crit_nopass_usr] = np.nan
|
|
147
|
+
|
|
148
|
+
return dsr_filtered, usr_filtered, (bad, sunonlowerdome, TOA_crit_nopass_dsr, TOA_crit_nopass_usr)
|
|
149
|
+
|
|
150
|
+
def correct_sr(dsr_filtered: xr.DataArray,
|
|
151
|
+
usr_filtered: xr.DataArray,
|
|
152
|
+
cc: xr.DataArray,
|
|
153
|
+
phi_sensor_rad : xr.DataArray,
|
|
154
|
+
theta_sensor_rad : xr.DataArray,
|
|
155
|
+
lat: float,
|
|
156
|
+
Declination_rad : xr.DataArray,
|
|
157
|
+
HourAngle_rad : xr.DataArray,
|
|
158
|
+
ZenithAngle_rad : xr.DataArray,
|
|
159
|
+
ZenithAngle_deg : xr.DataArray,
|
|
160
|
+
AngleDif_deg: xr.DataArray
|
|
161
|
+
) -> tuple[xr.DataArray, xr.DataArray, tuple]:
|
|
162
|
+
"""Correct shortwave radiation data for station tilt and top-of-atmosphere (TOA) irradiance
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
dsr_filtered : xr.DataArray
|
|
167
|
+
Downwelling shortwave radiation (filtered for tilt)
|
|
168
|
+
usr_filtered : xr.DataArray
|
|
169
|
+
Upwelling shortwave radiation (filtered for tilt)
|
|
170
|
+
cc : xr.DataArray
|
|
171
|
+
Cloud cover
|
|
172
|
+
phi_sensor_rad : xr.DataArray
|
|
173
|
+
Spherical tilt coordinates
|
|
174
|
+
theta_sensor_rad : xr.DataArray
|
|
175
|
+
Total tilt of sensor, where 0 is horizontal
|
|
176
|
+
lat : float
|
|
177
|
+
Station latitude
|
|
178
|
+
Declination_rad : xr.DataArray
|
|
179
|
+
Sun declination
|
|
180
|
+
HourAngle_rad : xr.DataArray
|
|
181
|
+
Hour angle of sun
|
|
182
|
+
ZenithAngle_rad : xr.DataArray
|
|
183
|
+
Zenith angle in radians
|
|
184
|
+
ZenithAngle_deg : xr.DataArray
|
|
185
|
+
Zenith angle in degrees
|
|
186
|
+
AngleDif_deg : xr.DataArray
|
|
187
|
+
Angle between sun and sensor in degree
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
dsr_cor : xr.DataArray
|
|
192
|
+
Corrected downwelling shortwave radiation
|
|
193
|
+
usr_cor : xr.DataArray
|
|
194
|
+
Corrected upwelling shortwave radiation
|
|
195
|
+
TOA_crit_nopass_cor : xr.DataArray
|
|
196
|
+
Correction flags for invalid TOA values
|
|
197
|
+
"""
|
|
198
|
+
# Diffuse to direct irradiance fraction
|
|
199
|
+
DifFrac = 0.2 + 0.8 * cc
|
|
200
|
+
CorFac_all = calculate_correction_factor(phi_sensor_rad,
|
|
201
|
+
theta_sensor_rad,
|
|
202
|
+
Declination_rad,
|
|
203
|
+
HourAngle_rad,
|
|
204
|
+
ZenithAngle_rad,
|
|
205
|
+
ZenithAngle_deg,
|
|
206
|
+
lat,
|
|
207
|
+
DifFrac)
|
|
208
|
+
|
|
209
|
+
tilt_correction_possible = AngleDif_deg.notnull() & cc.notnull()
|
|
210
|
+
CorFac_all = CorFac_all.where(tilt_correction_possible)
|
|
211
|
+
|
|
212
|
+
# Apply correction to downwelling shortwave radiation and then mask upwelling values
|
|
213
|
+
dsr_cor = dsr_filtered * CorFac_all
|
|
214
|
+
usr_cor = usr_filtered.where(dsr_cor.notnull())
|
|
215
|
+
|
|
216
|
+
# Calculate TOA shortwave radiation
|
|
217
|
+
isr_toa = calculate_TOA(ZenithAngle_deg, ZenithAngle_rad)
|
|
218
|
+
|
|
219
|
+
# Remove data where TOA shortwave radiation invalid
|
|
220
|
+
TOA_crit_nopass_cor = dsr_cor > (1.2 * isr_toa + 150)
|
|
221
|
+
dsr_cor[TOA_crit_nopass_cor] = np.nan
|
|
222
|
+
usr_cor[TOA_crit_nopass_cor] = np.nan
|
|
223
|
+
|
|
224
|
+
return dsr_cor, usr_cor, TOA_crit_nopass_cor
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def calculate_albedo(dsr_filtered: xr.DataArray,
|
|
228
|
+
usr_filtered: xr.DataArray,
|
|
229
|
+
dsr_cor: xr.DataArray,
|
|
230
|
+
cc: xr.DataArray,
|
|
231
|
+
ZenithAngle_deg: xr.DataArray,
|
|
232
|
+
AngleDif_deg: xr.DataArray
|
|
233
|
+
) -> tuple[xr.DataArray, xr.DataArray]:
|
|
234
|
+
"""
|
|
235
|
+
Calculate surface albedo based on upwelling and downwelling shortwave
|
|
236
|
+
flux, the angle between the sun and sensor, and the sun zenith angle.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
dsr_filtered : xr.DataArray
|
|
241
|
+
Downwelling shortwave radiation
|
|
242
|
+
usr_filtered : xr.DataArray
|
|
243
|
+
Upwelling shortwave radiation
|
|
244
|
+
dsr_cor : xr.DataArray
|
|
245
|
+
Corrected downwelling shortwave radiation
|
|
246
|
+
cc : xr.DataArray
|
|
247
|
+
Cloud cover
|
|
248
|
+
ZenithAngle_deg : xr.DataArray
|
|
249
|
+
Sun zenith angle in degrees.
|
|
250
|
+
AngleDif_deg : xr.DataArray
|
|
251
|
+
Angle between the sun and the sensor in degrees
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
albedo : xr.DataArray
|
|
256
|
+
Calculated albedo
|
|
257
|
+
OKalbedos : xr.DataArray
|
|
258
|
+
Boolean mask indicating valid albedo values
|
|
259
|
+
"""
|
|
260
|
+
tilt_correction_possible = AngleDif_deg.notnull() & cc.notnull()
|
|
261
|
+
|
|
262
|
+
albedo = xr.where(tilt_correction_possible,
|
|
263
|
+
usr_filtered / dsr_cor,
|
|
264
|
+
usr_filtered / dsr_filtered)
|
|
265
|
+
|
|
266
|
+
OOL = (albedo >= 1) | (albedo <= 0)
|
|
267
|
+
good_zenith_angle = ZenithAngle_deg < 70
|
|
268
|
+
good_relative_zenith_angle = (AngleDif_deg < 70) | (AngleDif_deg.isnull())
|
|
269
|
+
OKalbedos = good_relative_zenith_angle & good_zenith_angle & ~OOL
|
|
270
|
+
albedo = albedo.where(OKalbedos)
|
|
271
|
+
return albedo, OKalbedos
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def calculate_TOA(ZenithAngle_deg: xr.DataArray,
|
|
275
|
+
ZenithAngle_rad: xr.DataArray
|
|
276
|
+
) -> xr.DataArray:
|
|
277
|
+
"""Calculate incoming shortwave radiation at the top of the atmosphere,
|
|
278
|
+
accounting for sunset periods
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
ZenithAngle_deg : xr.DataArray
|
|
283
|
+
Zenith angle in degrees
|
|
284
|
+
ZenithAngle_rad : xr.DataArray
|
|
285
|
+
Zenith angle in radians
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
isr_toa : float
|
|
290
|
+
Incoming shortwave radiation at the top of the atmosphere
|
|
291
|
+
"""
|
|
292
|
+
sundown = ZenithAngle_deg >= 90
|
|
293
|
+
|
|
294
|
+
# Incoming shortware radiation at the top of the atmosphere
|
|
295
|
+
isr_toa = 1372 * np.cos(ZenithAngle_rad)
|
|
296
|
+
isr_toa[sundown] = 0
|
|
297
|
+
return isr_toa
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def calculate_correction_factor(phi_sensor_rad: xr.DataArray,
|
|
301
|
+
theta_sensor_rad: xr.DataArray,
|
|
302
|
+
Declination_rad: xr.DataArray,
|
|
303
|
+
HourAngle_rad: xr.DataArray,
|
|
304
|
+
ZenithAngle_rad: xr.DataArray,
|
|
305
|
+
ZenithAngle_deg: xr.DataArray,
|
|
306
|
+
lat: float,
|
|
307
|
+
DifFrac: xr.DataArray
|
|
308
|
+
) -> xr.DataArray:
|
|
309
|
+
"""Calculate radiometer correction factor for direct beam radiation, as described
|
|
310
|
+
here: http://solardat.uoregon.edu/SolarRadiationBasics.html
|
|
311
|
+
|
|
312
|
+
Offset correction (where solar zenith angles are larger than 110 degrees) not
|
|
313
|
+
implemented as it should not improve the accuracy of well-calibrated
|
|
314
|
+
instruments.
|
|
315
|
+
|
|
316
|
+
It would go something like this:
|
|
317
|
+
ds['dsr'] = ds['dsr'] - ds['dwr_offset']
|
|
318
|
+
SRout = SRout - SRout_offset
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
Declination_rad : float
|
|
323
|
+
Declination in radians
|
|
324
|
+
phi_sensor_rad : xr.DataArray
|
|
325
|
+
Spherical tilt coordinates
|
|
326
|
+
theta_sensor_rad : xr.DataArray
|
|
327
|
+
Total tilt of sensor, where 0 is horizontal
|
|
328
|
+
HourAngle_rad : float
|
|
329
|
+
Sun hour angle in radians
|
|
330
|
+
ZenithAngle_rad : float
|
|
331
|
+
Zenith angle in radians
|
|
332
|
+
ZenithAngle_deg : float
|
|
333
|
+
Zenith Angle in degrees
|
|
334
|
+
lat : float
|
|
335
|
+
Latitude
|
|
336
|
+
DifFrac : xr.DataArray
|
|
337
|
+
Fractional cloud cover
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
CorFac_all : xr.DataArray
|
|
342
|
+
Correction factor
|
|
343
|
+
"""
|
|
344
|
+
CorFac = np.sin(Declination_rad) * np.sin(lat * deg2rad) \
|
|
345
|
+
* np.cos(theta_sensor_rad) \
|
|
346
|
+
- np.sin(Declination_rad) \
|
|
347
|
+
* np.cos(lat * deg2rad) \
|
|
348
|
+
* np.sin(theta_sensor_rad) \
|
|
349
|
+
* np.cos(phi_sensor_rad + np.pi) \
|
|
350
|
+
+ np.cos(Declination_rad) \
|
|
351
|
+
* np.cos(lat * deg2rad) \
|
|
352
|
+
* np.cos(theta_sensor_rad) \
|
|
353
|
+
* np.cos(HourAngle_rad) \
|
|
354
|
+
+ np.cos(Declination_rad) \
|
|
355
|
+
* np.sin(lat * deg2rad) \
|
|
356
|
+
* np.sin(theta_sensor_rad) \
|
|
357
|
+
* np.cos(phi_sensor_rad + np.pi) \
|
|
358
|
+
* np.cos(HourAngle_rad) \
|
|
359
|
+
+ np.cos(Declination_rad) \
|
|
360
|
+
* np.sin(theta_sensor_rad) \
|
|
361
|
+
* np.sin(phi_sensor_rad + np.pi) \
|
|
362
|
+
* np.sin(HourAngle_rad) \
|
|
363
|
+
|
|
364
|
+
CorFac = np.cos(ZenithAngle_rad) / CorFac
|
|
365
|
+
|
|
366
|
+
# Sun out of field of view upper sensor
|
|
367
|
+
CorFac[(CorFac < 0) | (ZenithAngle_deg > 90)] = 1
|
|
368
|
+
|
|
369
|
+
# Calculating ds['dsr'] over a horizontal surface corrected for station/sensor tilt
|
|
370
|
+
CorFac_all = CorFac / (1 - DifFrac + CorFac * DifFrac)
|
|
371
|
+
|
|
372
|
+
return CorFac_all.where(theta_sensor_rad.notnull())
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def calculate_cloud_coverage(dlr: xr.DataArray,
|
|
376
|
+
LR_overcast: xr.DataArray,
|
|
377
|
+
LR_clear: xr.DataArray
|
|
378
|
+
) -> xr.DataArray:
|
|
379
|
+
"""Calculate cloud cover using downwelling longwave radiation and the
|
|
380
|
+
overcast and clear cloud assumptions from Swinbank (1963) which are
|
|
381
|
+
derived from air temperature.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
dlr : xr.DataArray
|
|
386
|
+
Downwelling longwave radiation, with array of same length as T and T_0
|
|
387
|
+
LR_overcast : xr.DataArray
|
|
388
|
+
Cloud overcast assumption, from Swinbank (1963)
|
|
389
|
+
LR_clear : xr.DataArray
|
|
390
|
+
Cloud clear assumption, from Swinbank (1963)
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
cc : xr.DataArray
|
|
395
|
+
Cloud cover data array
|
|
396
|
+
"""
|
|
397
|
+
cc = (dlr - LR_clear) / (LR_overcast - LR_clear)
|
|
398
|
+
cc[cc > 1] = 1
|
|
399
|
+
cc[cc < 0] = 0
|
|
400
|
+
return cc
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def calculate_surface_temperature(dlr: xr.DataArray,
|
|
404
|
+
ulr: xr.DataArray
|
|
405
|
+
) -> xr.DataArray:
|
|
406
|
+
"""Calculate surface temperature from downwelling and
|
|
407
|
+
upwelling longwave radiation.
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
dlr : xr.DataArray
|
|
412
|
+
Downwelling longwave radiation
|
|
413
|
+
ulr : xr.DataArray
|
|
414
|
+
Upwelling longwave radiation
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
xr.DataArray
|
|
419
|
+
Calculated surface temperature
|
|
420
|
+
"""
|
|
421
|
+
t_surf = ((ulr - (1 - emissivity) * dlr) / emissivity / 5.67e-8)**0.25 - T_0
|
|
422
|
+
return t_surf
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
__all__ = ["adjust", "adjust_and_include_uncorrected_values"]
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import xarray as xr
|
|
5
|
+
|
|
6
|
+
T_0=273.15 # degrees Celsius to Kelvin conversion
|
|
7
|
+
|
|
8
|
+
def adjust(z_boom: xr.DataArray,
|
|
9
|
+
air_temperature: xr.DataArray
|
|
10
|
+
) -> xr.DataArray:
|
|
11
|
+
"""Adjust sonic ranger readings for sensitivity to air temperature
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
z_boom : xr.DataArray
|
|
16
|
+
Station boom height from sonic ranger
|
|
17
|
+
air_temperature : xr.DataArray
|
|
18
|
+
Air temperature
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
xr.DataArray
|
|
23
|
+
Adjusted station boom height
|
|
24
|
+
"""
|
|
25
|
+
return z_boom * ((air_temperature + T_0)/T_0)**0.5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def adjust_and_include_uncorrected_values(z_boom: xr.DataArray,
|
|
29
|
+
air_temperature: xr.DataArray
|
|
30
|
+
) -> xr.DataArray:
|
|
31
|
+
"""Adjust sonic ranger readings for sensitivity to air temperature,
|
|
32
|
+
and retain uncorrected values where air temperature measurements
|
|
33
|
+
are not available.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
z_boom : xr.DataArray
|
|
38
|
+
Station boom height from sonic ranger
|
|
39
|
+
air_temperature : xr.DataArray
|
|
40
|
+
Air temperature
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
xr.DataArray
|
|
45
|
+
Adjusted station boom height
|
|
46
|
+
"""
|
|
47
|
+
return xr.where(air_temperature.notnull(),
|
|
48
|
+
z_boom * ((air_temperature + T_0)/T_0)**0.5,
|
|
49
|
+
z_boom)
|