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.

Files changed (65) hide show
  1. pypromice/__init__.py +2 -0
  2. pypromice/{qc → core/qc}/percentiles/compute_thresholds.py +2 -2
  3. pypromice/{qc → core/qc}/persistence.py +22 -29
  4. pypromice/{process → core/qc}/value_clipping.py +3 -3
  5. pypromice/core/variables/__init__.py +1 -0
  6. pypromice/core/variables/air_temperature.py +64 -0
  7. pypromice/core/variables/gps.py +221 -0
  8. pypromice/core/variables/humidity.py +111 -0
  9. pypromice/core/variables/precipitation.py +108 -0
  10. pypromice/core/variables/pressure_transducer_depth.py +79 -0
  11. pypromice/core/variables/radiation.py +422 -0
  12. pypromice/core/variables/station_boom_height.py +49 -0
  13. pypromice/core/variables/station_pose.py +375 -0
  14. pypromice/core/variables/wind.py +66 -0
  15. pypromice/io/bufr/__init__.py +0 -0
  16. pypromice/{postprocess → io/bufr}/bufr_to_csv.py +1 -1
  17. pypromice/{postprocess → io/bufr}/create_bufr_files.py +2 -2
  18. pypromice/{postprocess → io/bufr}/get_bufr.py +6 -6
  19. pypromice/{postprocess → io/bufr}/real_time_utilities.py +3 -3
  20. pypromice/io/ingest/__init__.py +0 -0
  21. pypromice/{utilities → io/ingest}/git.py +1 -3
  22. pypromice/io/ingest/l0.py +294 -0
  23. pypromice/io/ingest/l0_repository.py +103 -0
  24. pypromice/io/ingest/toa5.py +87 -0
  25. pypromice/{process → io}/write.py +1 -1
  26. pypromice/pipeline/L0toL1.py +291 -0
  27. pypromice/pipeline/L1toL2.py +233 -0
  28. pypromice/{process → pipeline}/L2toL3.py +102 -120
  29. pypromice/pipeline/__init__.py +4 -0
  30. pypromice/{process → pipeline}/aws.py +10 -82
  31. pypromice/{process → pipeline}/get_l2.py +2 -2
  32. pypromice/{process → pipeline}/get_l2tol3.py +19 -22
  33. pypromice/{process → pipeline}/join_l2.py +31 -32
  34. pypromice/{process → pipeline}/join_l3.py +16 -14
  35. pypromice/{process → pipeline}/resample.py +59 -46
  36. pypromice/{process → pipeline}/utilities.py +0 -22
  37. pypromice/resources/file_attributes.csv +4 -4
  38. pypromice/resources/variables.csv +27 -24
  39. {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/METADATA +1 -2
  40. pypromice-1.6.0.dist-info/RECORD +64 -0
  41. {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/WHEEL +1 -1
  42. pypromice-1.6.0.dist-info/entry_points.txt +12 -0
  43. pypromice/get/__init__.py +0 -1
  44. pypromice/get/get.py +0 -211
  45. pypromice/get/get_promice_data.py +0 -56
  46. pypromice/process/L0toL1.py +0 -536
  47. pypromice/process/L1toL2.py +0 -839
  48. pypromice/process/__init__.py +0 -4
  49. pypromice/process/load.py +0 -161
  50. pypromice-1.5.2.dist-info/RECORD +0 -53
  51. pypromice-1.5.2.dist-info/entry_points.txt +0 -13
  52. /pypromice/{postprocess → core}/__init__.py +0 -0
  53. /pypromice/{utilities → core}/dependency_graph.py +0 -0
  54. /pypromice/{qc → core/qc}/__init__.py +0 -0
  55. /pypromice/{qc → core/qc}/github_data_issues.py +0 -0
  56. /pypromice/{qc → core/qc}/percentiles/__init__.py +0 -0
  57. /pypromice/{qc → core/qc}/percentiles/outlier_detector.py +0 -0
  58. /pypromice/{qc → core/qc}/percentiles/thresholds.csv +0 -0
  59. /pypromice/{utilities → io}/__init__.py +0 -0
  60. /pypromice/{postprocess → io/bufr}/bufr_utilities.py +0 -0
  61. /pypromice/{postprocess → io/bufr}/positions_seed.csv +0 -0
  62. /pypromice/{station_configuration.py → io/bufr/station_configuration.py} +0 -0
  63. /pypromice/{postprocess → io}/make_metadata_csv.py +0 -0
  64. {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/licenses/LICENSE.txt +0 -0
  65. {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)