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
@@ -1,839 +0,0 @@
1
- #!/usr/bin/env python
2
- """
3
- AWS Level 1 (L1) to Level 2 (L2) data processing
4
- """
5
- import logging
6
- from pathlib import Path
7
-
8
- import numpy as np
9
- import pandas as pd
10
- import xarray as xr
11
-
12
- from pypromice.qc.github_data_issues import flagNAN, adjustTime, adjustData
13
- from pypromice.qc.percentiles.outlier_detector import ThresholdBasedOutlierDetector
14
- from pypromice.qc.persistence import persistence_qc
15
- from pypromice.process.value_clipping import clip_values
16
-
17
- __all__ = [
18
- "toL2",
19
- ]
20
-
21
- logger = logging.getLogger(__name__)
22
-
23
-
24
- def toL2(
25
- L1: xr.Dataset,
26
- vars_df: pd.DataFrame,
27
- data_flags_dir: Path,
28
- data_adjustments_dir: Path,
29
- T_0=273.15,
30
- ews=1013.246,
31
- ei0=6.1071,
32
- eps_overcast=1.0,
33
- eps_clear=9.36508e-6,
34
- emissivity=0.97,
35
- ) -> xr.Dataset:
36
- '''Process one Level 1 (L1) product to Level 2.
37
- In this step we do:
38
- - manual flagging and adjustments
39
- - automated QC: persistence, percentile
40
- - custom filter: gps_alt filter, NaN t_rad removed from dlr & ulr
41
- - smoothing of tilt and rot
42
- - calculation of rh with regards to ice in subfreezin conditions
43
- - calculation of cloud coverage
44
- - correction of dsr and usr for tilt
45
- - filtering of dsr based on a theoritical TOA irradiance and grazing light
46
- - calculation of albedo
47
- - calculation of directional wind speed
48
-
49
- Parameters
50
- ----------
51
- L1 : xarray.Dataset
52
- Level 1 dataset
53
- vars_df : pd.DataFrame
54
- Metadata dataframe
55
- T_0 : float
56
- Ice point temperature in K. The default is 273.15.
57
- ews : float
58
- Saturation pressure (normal atmosphere) at steam point temperature.
59
- The default is 1013.246.
60
- ei0 : float
61
- Saturation pressure (normal atmosphere) at ice-point temperature. The
62
- default is 6.1071.
63
- eps_overcast : int
64
- Cloud overcast. The default is 1..
65
- eps_clear : float
66
- Cloud clear. The default is 9.36508e-6.
67
- emissivity : float
68
- Emissivity. The default is 0.97.
69
-
70
- Returns
71
- -------
72
- ds : xarray.Dataset
73
- Level 2 dataset
74
- '''
75
- ds = L1.copy(deep=True) # Reassign dataset
76
- ds.attrs['level'] = 'L2'
77
- try:
78
- ds = adjustTime(ds, adj_dir=data_adjustments_dir.as_posix()) # Adjust time after a user-defined csv files
79
- ds = flagNAN(ds, flag_dir=data_flags_dir.as_posix()) # Flag NaNs after a user-defined csv files
80
- ds = adjustData(ds, adj_dir=data_adjustments_dir.as_posix()) # Adjust data after a user-defined csv files
81
- except Exception:
82
- logger.exception('Flagging and fixing failed:')
83
-
84
- ds = persistence_qc(ds) # Flag and remove persistence outliers
85
- # if ds.attrs['format'] == 'TX':
86
- # # TODO: The configuration should be provided explicitly
87
- # outlier_detector = ThresholdBasedOutlierDetector.default()
88
- # ds = outlier_detector.filter_data(ds) # Flag and remove percentile outliers
89
-
90
- # filtering gps_lat, gps_lon and gps_alt based on the difference to a baseline elevation
91
- # right now baseline elevation is gapfilled monthly median elevation
92
- baseline_elevation = (ds.gps_alt.to_series().resample('MS').median()
93
- .reindex(ds.time.to_series().index, method='nearest')
94
- .ffill().bfill())
95
- mask = (np.abs(ds.gps_alt - baseline_elevation) < 100) | ds.gps_alt.isnull()
96
- ds[['gps_alt','gps_lon', 'gps_lat']] = ds[['gps_alt','gps_lon', 'gps_lat']].where(mask)
97
-
98
- # removing dlr and ulr that are missing t_rad
99
- # this is done now becasue t_rad can be filtered either manually or with persistence
100
- ds['dlr'] = ds.dlr.where(ds.t_rad.notnull())
101
- ds['ulr'] = ds.ulr.where(ds.t_rad.notnull())
102
-
103
- # calculating realtive humidity with regard to ice
104
- T_100 = _getTempK(T_0)
105
- ds['rh_u_wrt_ice_or_water'] = adjustHumidity(ds['rh_u'], ds['t_u'],
106
- T_0, T_100, ews, ei0)
107
-
108
- if ds.attrs['number_of_booms']==2:
109
- ds['rh_l_wrt_ice_or_water'] = adjustHumidity(ds['rh_l'], ds['t_l'],
110
- T_0, T_100, ews, ei0)
111
-
112
- if hasattr(ds,'t_i'):
113
- if ~ds['t_i'].isnull().all():
114
- ds['rh_i_wrt_ice_or_water'] = adjustHumidity(ds['rh_i'], ds['t_i'],
115
- T_0, T_100, ews, ei0)
116
-
117
- # Determiune cloud cover for on-ice stations
118
- cc = calcCloudCoverage(ds['t_u'], T_0, eps_overcast, eps_clear, # Calculate cloud coverage
119
- ds['dlr'], ds.attrs['station_id'])
120
- ds['cc'] = (('time'), cc.data)
121
-
122
- # Determine surface temperature
123
- ds['t_surf'] = calcSurfaceTemperature(T_0, ds['ulr'], ds['dlr'], # Calculate surface temperature
124
- emissivity)
125
- if not ds.attrs['bedrock']:
126
- ds['t_surf'] = xr.where(ds['t_surf'] > 0, 0, ds['t_surf'])
127
-
128
- # Determine station position relative to sun
129
- doy = ds['time'].to_dataframe().index.dayofyear.values # Gather variables to calculate sun pos
130
- hour = ds['time'].to_dataframe().index.hour.values
131
- minute = ds['time'].to_dataframe().index.minute.values
132
-
133
- if hasattr(ds, 'latitude') and hasattr(ds, 'longitude'):
134
- lat = ds.attrs['latitude'] # TODO Why is mean GPS lat lon not preferred for calcs?
135
- lon = ds.attrs['longitude']
136
- else:
137
- lat = ds['gps_lat'].mean()
138
- lon = ds['gps_lon'].mean()
139
-
140
- # smoothing tilt and rot
141
- ds['tilt_x'] = smoothTilt(ds['tilt_x'])
142
- ds['tilt_y'] = smoothTilt(ds['tilt_y'])
143
- ds['rot'] = smoothRot(ds['rot'])
144
-
145
- deg2rad, rad2deg = _getRotation() # Get degree-radian conversions
146
- phi_sensor_rad, theta_sensor_rad = calcTilt(ds['tilt_x'], ds['tilt_y'], # Calculate station tilt
147
- deg2rad)
148
-
149
- Declination_rad = calcDeclination(doy, hour, minute) # Calculate declination
150
- HourAngle_rad = calcHourAngle(hour, minute, lon) # Calculate hour angle
151
- ZenithAngle_rad, ZenithAngle_deg = calcZenith(lat, Declination_rad, # Calculate zenith
152
- HourAngle_rad, deg2rad,
153
- rad2deg)
154
-
155
-
156
- # Correct Downwelling shortwave radiation
157
- DifFrac = 0.2 + 0.8 * cc
158
- CorFac_all = calcCorrectionFactor(Declination_rad, phi_sensor_rad, # Calculate correction
159
- theta_sensor_rad, HourAngle_rad,
160
- ZenithAngle_rad, ZenithAngle_deg,
161
- lat, DifFrac, deg2rad)
162
- CorFac_all = xr.where(ds['cc'].notnull(), CorFac_all, 1)
163
- ds['dsr_cor'] = ds['dsr'].copy(deep=True) * CorFac_all # Apply correction
164
-
165
- AngleDif_deg = calcAngleDiff(ZenithAngle_rad, HourAngle_rad, # Calculate angle between sun and sensor
166
- phi_sensor_rad, theta_sensor_rad)
167
-
168
- ds['albedo'], OKalbedos = calcAlbedo(ds['usr'], ds['dsr_cor'], # Determine albedo
169
- AngleDif_deg, ZenithAngle_deg)
170
-
171
- # Correct upwelling and downwelling shortwave radiation
172
- sunonlowerdome =(AngleDif_deg >= 90) & (ZenithAngle_deg <= 90) # Determine when sun is in FOV of lower sensor, assuming sensor measures only diffuse radiation
173
- ds['dsr_cor'] = ds['dsr_cor'].where(~sunonlowerdome,
174
- other=ds['dsr'] / DifFrac) # Apply to downwelling
175
- ds['usr_cor'] = ds['usr'].copy(deep=True)
176
- ds['usr_cor'] = ds['usr_cor'].where(~sunonlowerdome,
177
- other=ds['albedo'] * ds['dsr'] / DifFrac) # Apply to upwelling
178
- bad = (ZenithAngle_deg > 95) | (ds['dsr_cor'] <= 0) | (ds['usr_cor'] <= 0) # Set to zero for solar zenith angles larger than 95 deg or either values are (less than) zero
179
- ds['dsr_cor'][bad] = 0
180
- ds['usr_cor'][bad] = 0
181
- ds['dsr_cor'] = ds['usr_cor'].copy(deep=True) / ds['albedo'] # Correct DWR using more reliable USWR when sun not in sight of upper sensor
182
- ds['albedo'] = ds['albedo'].where(OKalbedos) #TODO remove?
183
-
184
- # Remove data where TOA shortwave radiation invalid
185
- isr_toa = calcTOA(ZenithAngle_deg, ZenithAngle_rad) # Calculate TOA shortwave radiation
186
- TOA_crit_nopass = (ds['dsr_cor'] > (0.9 * isr_toa + 10)) # Determine filter
187
- ds['dsr_cor'][TOA_crit_nopass] = np.nan # Apply filter and interpolate
188
- ds['usr_cor'][TOA_crit_nopass] = np.nan
189
-
190
- ds['dsr_cor'] = ds.dsr_cor.where(ds.dsr.notnull())
191
- ds['usr_cor'] = ds.usr_cor.where(ds.usr.notnull())
192
- # # Check sun position
193
- # sundown = ZenithAngle_deg >= 90
194
- # _checkSunPos(ds, OKalbedos, sundown, sunonlowerdome, TOA_crit_nopass)
195
-
196
- if hasattr(ds, 'correct_precip'): # Correct precipitation
197
- precip_flag=ds.attrs['correct_precip']
198
- else:
199
- precip_flag=True
200
- if ~ds['precip_u'].isnull().all() and precip_flag:
201
- ds['precip_u_cor'], ds['precip_u_rate'] = correctPrecip(ds['precip_u'],
202
- ds['wspd_u'])
203
- if ds.attrs['number_of_booms']==2:
204
- if ~ds['precip_l'].isnull().all() and precip_flag: # Correct precipitation
205
- ds['precip_l_cor'], ds['precip_l_rate']= correctPrecip(ds['precip_l'],
206
- ds['wspd_l'])
207
-
208
- get_directional_wind_speed(ds) # Get directional wind speed
209
-
210
- ds = clip_values(ds, vars_df)
211
- return ds
212
-
213
- def get_directional_wind_speed(ds: xr.Dataset) -> xr.Dataset:
214
- """
215
- Calculate directional wind speed from wind speed and direction and mutates the dataset
216
- """
217
-
218
- ds['wdir_u'] = ds['wdir_u'].where(ds['wspd_u'] != 0)
219
- ds['wspd_x_u'], ds['wspd_y_u'] = calcDirWindSpeeds(ds['wspd_u'], ds['wdir_u'])
220
-
221
- if ds.attrs['number_of_booms']==2:
222
- ds['wdir_l'] = ds['wdir_l'].where(ds['wspd_l'] != 0)
223
- ds['wspd_x_l'], ds['wspd_y_l'] = calcDirWindSpeeds(ds['wspd_l'], ds['wdir_l'])
224
-
225
- if hasattr(ds, 'wdir_i'):
226
- if ~ds['wdir_i'].isnull().all() and ~ds['wspd_i'].isnull().all():
227
- ds['wdir_i'] = ds['wdir_i'].where(ds['wspd_i'] != 0)
228
- ds['wspd_x_i'], ds['wspd_y_i'] = calcDirWindSpeeds(ds['wspd_i'], ds['wdir_i'])
229
- return ds
230
-
231
-
232
- def calcDirWindSpeeds(wspd, wdir, deg2rad=np.pi/180):
233
- '''Calculate directional wind speed from wind speed and direction
234
-
235
- Parameters
236
- ----------
237
- wspd : xr.Dataarray
238
- Wind speed data array
239
- wdir : xr.Dataarray
240
- Wind direction data array
241
- deg2rad : float
242
- Degree to radians coefficient. The default is np.pi/180
243
-
244
- Returns
245
- -------
246
- wspd_x : xr.Dataarray
247
- Wind speed in X direction
248
- wspd_y : xr.Datarray
249
- Wind speed in Y direction
250
- '''
251
- wspd_x = wspd * np.sin(wdir * deg2rad)
252
- wspd_y = wspd * np.cos(wdir * deg2rad)
253
- return wspd_x, wspd_y
254
-
255
-
256
- def calcCloudCoverage(T, T_0, eps_overcast, eps_clear, dlr, station_id):
257
- '''Calculate cloud cover from T and T_0
258
-
259
- Parameters
260
- ----------
261
- T : xarray.DataArray
262
- Air temperature 1
263
- T_0 : xarray.DataArray
264
- Air temperature 0
265
- eps_overcast : int
266
- Cloud overcast assumption, from Swinbank (1963)
267
- eps_clear : int
268
- Cloud clear assumption, from Swinbank (1963)
269
- dlr : xarray.DataArray
270
- Downwelling longwave radiation, with array of same length as T and T_0
271
- station_id : str
272
- Station ID string, for special cases at selected stations where cloud
273
- overcast and cloud clear assumptions are pre-defined. Currently
274
- KAN_M and KAN_U are special cases, but this will need to be done for
275
- all stations eventually
276
-
277
- Returns
278
- -------
279
- cc : xarray.DataArray
280
- Cloud cover data array
281
- '''
282
- if station_id == 'KAN_M':
283
- LR_overcast = 315 + 4*T
284
- LR_clear = 30 + 4.6e-13 * (T + T_0)**6
285
- elif station_id == 'KAN_U':
286
- LR_overcast = 305 + 4*T
287
- LR_clear = 220 + 3.5*T
288
- else:
289
- LR_overcast = eps_overcast * 5.67e-8 *(T + T_0)**4
290
- LR_clear = eps_clear * 5.67e-8 * (T + T_0)**6
291
- cc = (dlr - LR_clear) / (LR_overcast - LR_clear)
292
- cc[cc > 1] = 1
293
- cc[cc < 0] = 0
294
- return cc
295
-
296
-
297
- def calcSurfaceTemperature(T_0, ulr, dlr, emissivity):
298
- '''Calculate surface temperature from air temperature, upwelling and
299
- downwelling radiation and emissivity
300
-
301
- Parameters
302
- ----------
303
- T_0 : xarray.DataArray
304
- Air temperature
305
- ulr : xarray.DataArray
306
- Upwelling longwave radiation
307
- dlr : xarray.DataArray
308
- Downwelling longwave radiation
309
- emissivity : int
310
- Assumed emissivity
311
-
312
- Returns
313
- -------
314
- xarray.DataArray
315
- Calculated surface temperature
316
- '''
317
- t_surf = ((ulr - (1 - emissivity) * dlr) / emissivity / 5.67e-8)**0.25 - T_0
318
- return t_surf
319
-
320
-
321
- def smoothTilt(da: xr.DataArray, threshold=0.2):
322
- '''Smooth the station tilt
323
-
324
- Parameters
325
- ----------
326
- da : xarray.DataArray
327
- either X or Y tilt inclinometer measurements
328
- threshold : float
329
- threshold used in a standrad.-deviation based filter
330
-
331
- Returns
332
- -------
333
- xarray.DataArray
334
- either X or Y smoothed tilt inclinometer measurements
335
- '''
336
- # we calculate the moving standard deviation over a 3-day sliding window
337
- # hourly resampling is necessary to make sure the same threshold can be used
338
- # for 10 min and hourly data
339
- moving_std_gap_filled = da.to_series().resample('h').median().rolling(
340
- 3*24, center=True, min_periods=2
341
- ).std().reindex(da.time, method='bfill').values
342
- # we select the good timestamps and gapfill assuming that
343
- # - when tilt goes missing the last available value is used
344
- # - when tilt is not available for the very first time steps, the first
345
- # good value is used for backfill
346
- return da.where(
347
- moving_std_gap_filled < threshold
348
- ).ffill(dim='time').bfill(dim='time')
349
-
350
-
351
- def smoothRot(da: xr.DataArray, threshold=4):
352
- '''Smooth the station rotation
353
-
354
- Parameters
355
- ----------
356
- da : xarray.DataArray
357
- rotation measurements from inclinometer
358
- threshold : float
359
- threshold used in a standrad-deviation based filter
360
-
361
- Returns
362
- -------
363
- xarray.DataArray
364
- smoothed rotation measurements from inclinometer
365
- '''
366
- moving_std_gap_filled = da.to_series().resample('h').median().rolling(
367
- 3*24, center=True, min_periods=2
368
- ).std().reindex(da.time, method='bfill').values
369
- # same as for tilt with, in addition:
370
- # - a resampling to daily values
371
- # - a two week median smoothing
372
- # - a resampling from these daily values to the original temporal resolution
373
- return ('time', (da.where(moving_std_gap_filled <4).ffill(dim='time')
374
- .to_series().resample('D').median()
375
- .rolling(7*2,center=True,min_periods=2).median()
376
- .reindex(da.time, method='bfill').values
377
- ))
378
-
379
-
380
- def calcTilt(tilt_x, tilt_y, deg2rad):
381
- '''Calculate station tilt
382
-
383
- Parameters
384
- ----------
385
- tilt_x : xarray.DataArray
386
- X tilt inclinometer measurements
387
- tilt_y : xarray.DataArray
388
- Y tilt inclinometer measurements
389
- deg2rad : float
390
- Degrees to radians conversion
391
-
392
- Returns
393
- -------
394
- phi_sensor_rad : xarray.DataArray
395
- Spherical tilt coordinates
396
- theta_sensor_rad : xarray.DataArray
397
- Total tilt of sensor, where 0 is horizontal
398
- '''
399
- # Tilt as radians
400
- tx = tilt_x * deg2rad
401
- ty = tilt_y * deg2rad
402
-
403
- # Calculate cartesian coordinates
404
- X = np.sin(tx) * np.cos(tx) * np.sin(ty)**2 + np.sin(tx) * np.cos(ty)**2
405
- Y = np.sin(ty) * np.cos(ty) * np.sin(tx)**2 + np.sin(ty) * np.cos(tx)**2
406
- Z = np.cos(tx) * np.cos(ty) + np.sin(tx)**2 * np.sin(ty)**2
407
-
408
- # Calculate spherical coordinates
409
- phi_sensor_rad = -np.pi /2 - np.arctan(Y/X)
410
- phi_sensor_rad[X > 0] += np.pi
411
- phi_sensor_rad[(X == 0) & (Y < 0)] = np.pi
412
- phi_sensor_rad[(X == 0) & (Y == 0)] = 0
413
- phi_sensor_rad[phi_sensor_rad < 0] += 2*np.pi
414
-
415
- # Total tilt of the sensor, i.e. 0 when horizontal
416
- theta_sensor_rad = np.arccos(Z / (X**2 + Y**2 + Z**2)**0.5)
417
- # phi_sensor_deg = phi_sensor_rad * rad2deg #TODO take these out if not needed
418
- # theta_sensor_deg = theta_sensor_rad * rad2deg
419
- return phi_sensor_rad, theta_sensor_rad
420
-
421
-
422
- def adjustHumidity(rh, T, T_0, T_100, ews, ei0): #TODO figure out if T replicate is needed
423
- '''Adjust relative humidity so that values are given with respect to
424
- saturation over ice in subfreezing conditions, and with respect to
425
- saturation over water (as given by the instrument) above the melting
426
- point temperature. Saturation water vapors are calculated after
427
- Groff & Gratch method.
428
-
429
- Parameters
430
- ----------
431
- rh : xarray.DataArray
432
- Relative humidity
433
- T : xarray.DataArray
434
- Air temperature
435
- T_0 : float
436
- Ice point temperature in K
437
- T_100 : float
438
- Steam point temperature in K
439
- ews : float
440
- Saturation pressure (normal atmosphere) at steam point temperature
441
- ei0 : float
442
- Saturation pressure (normal atmosphere) at ice-point temperature
443
-
444
- Returns
445
- -------
446
- rh_wrt_ice_or_water : xarray.DataArray
447
- Corrected relative humidity
448
- '''
449
- # Convert to hPa (Groff & Gratch)
450
- e_s_wtr = 10**(-7.90298 * (T_100 / (T + T_0) - 1)
451
- + 5.02808 * np.log10(T_100 / (T + T_0))
452
- - 1.3816E-7 * (10**(11.344 * (1 - (T + T_0) / T_100)) - 1)
453
- + 8.1328E-3 * (10**(-3.49149 * (T_100/(T + T_0) - 1)) -1)
454
- + np.log10(ews))
455
- e_s_ice = 10**(-9.09718 * (T_0 / (T + T_0) - 1)
456
- - 3.56654 * np.log10(T_0 / (T + T_0))
457
- + 0.876793 * (1 - (T + T_0) / T_0)
458
- + np.log10(ei0))
459
-
460
- # Define freezing point. Why > -100?
461
- freezing = (T < 0) & (T > -100).values
462
-
463
- # Set to Groff & Gratch values when freezing, otherwise just rh
464
- rh_wrt_ice_or_water = rh.where(~freezing, other = rh*(e_s_wtr / e_s_ice))
465
- return rh_wrt_ice_or_water
466
-
467
-
468
- def correctPrecip(precip, wspd):
469
- '''Correct precipitation with the undercatch correction method used in
470
- Yang et al. (1999) and Box et al. (2022), based on Goodison et al. (1998)
471
-
472
- Yang, D., Ishida, S., Goodison, B. E., and Gunther, T.: Bias correction of
473
- daily precipitation measurements for Greenland,
474
- https://doi.org/10.1029/1998jd200110, 1999.
475
-
476
- Box, J., Wehrle, A., van As, D., Fausto, R., Kjeldsen, K., Dachauer, A.,
477
- Ahlstrom, A. P., and Picard, G.: Greenland Ice Sheet rainfall, heat and
478
- albedo feedback imapacts from the Mid-August 2021 atmospheric river,
479
- Geophys. Res. Lett. 49 (11), e2021GL097356,
480
- https://doi.org/10.1029/2021GL097356, 2022.
481
-
482
- Goodison, B. E., Louie, P. Y. T., and Yang, D.: Solid Precipitation
483
- Measurement Intercomparison, WMO, 1998
484
-
485
- Parameters
486
- ----------
487
- precip : xarray.DataArray
488
- Cumulative precipitation measurements
489
- wspd : xarray.DataArray
490
- Wind speed measurements
491
-
492
- Returns
493
- -------
494
- precip_cor : xarray.DataArray
495
- Cumulative precipitation corrected
496
- precip_rate : xarray.DataArray
497
- Precipitation rate corrected
498
- '''
499
- # Calculate undercatch correction factor
500
- corr=100/(100.00-4.37*wspd+0.35*wspd*wspd)
501
-
502
- # Fix all values below 1.02 to 1.02
503
- corr = corr.where(corr>1.02, other=1.02)
504
-
505
- # Fill nan values in precip with preceding value
506
- precip = precip.ffill(dim='time')
507
-
508
- # Calculate precipitation rate
509
- precip_rate = precip.diff(dim='time', n=1)
510
-
511
- # Apply correction to rate
512
- precip_rate = precip_rate*corr
513
-
514
- # Flag rain bucket reset
515
- precip_rate = precip_rate.where(precip_rate>-0.01, other=np.nan)
516
- b = precip_rate.to_dataframe('precip_flag').notna().to_xarray()
517
-
518
- # Get corrected cumulative precipitation, reset if rain bucket flag
519
- precip_cor = precip_rate.cumsum()-precip_rate.cumsum().where(~b['precip_flag']).ffill(dim='time').fillna(0).astype(float)
520
-
521
- return precip_cor, precip_rate
522
-
523
-
524
- def calcDeclination(doy, hour, minute):
525
- '''Calculate sun declination based on time
526
-
527
- Parameters
528
- ----------
529
- doy : int
530
- Day of year
531
- hour : int
532
- Hour of day
533
- minute : int
534
- Minute of hour
535
-
536
- Returns
537
- -------
538
- float
539
- Sun declination
540
- '''
541
- d0_rad = 2 * np.pi * (doy + (hour + minute / 60) / 24 -1) / 365
542
- return np.arcsin(0.006918 - 0.399912
543
- * np.cos(d0_rad) + 0.070257
544
- * np.sin(d0_rad) - 0.006758
545
- * np.cos(2 * d0_rad) + 0.000907
546
- * np.sin(2 * d0_rad) - 0.002697
547
- * np.cos(3 * d0_rad) + 0.00148
548
- * np.sin(3 * d0_rad))
549
-
550
- def calcHourAngle(hour, minute, lon):
551
- '''Calculate hour angle of sun based on time and longitude. Make sure that
552
- time is set to UTC and longitude is positive when west. Hour angle should
553
- be 0 at noon
554
-
555
- Parameters
556
- ----------
557
- hour : int
558
- Hour of day
559
- minute : int
560
- Minute of hour
561
- lon : float
562
- Longitude
563
-
564
- Returns
565
- -------
566
- float
567
- Hour angle of sun
568
- '''
569
- return 2 * np.pi * (((hour + minute / 60) / 24 - 0.5) - lon/360)
570
- # ; - 15.*timezone/360.)
571
-
572
-
573
- def calcDirectionDeg(HourAngle_rad): #TODO remove if not plan to use this
574
- '''Calculate sun direction as degrees. This is an alternative to
575
- _calcHourAngle that is currently not implemented into the offical L0>>L3
576
- workflow. Here, 180 degrees is at noon (NH), as opposed to HourAngle
577
-
578
- Parameters
579
- ----------
580
- HourAngle_rad : float
581
- Sun hour angle in radians
582
-
583
- Returns
584
- -------
585
- DirectionSun_deg
586
- Sun direction in degrees
587
- '''
588
- DirectionSun_deg = HourAngle_rad * 180/np.pi - 180
589
- DirectionSun_deg[DirectionSun_deg < 0] += 360
590
- DirectionSun_deg[DirectionSun_deg < 0] += 360
591
- return DirectionSun_deg
592
-
593
- def calcZenith(lat, Declination_rad, HourAngle_rad, deg2rad, rad2deg):
594
- '''Calculate sun zenith in radians and degrees
595
-
596
- Parameters
597
- ----------
598
- lat : float
599
- Latitude
600
- Declination_Rad : float
601
- Sun declination in radians
602
- HourAngle_rad : float
603
- Sun hour angle in radians
604
- deg2rad : float
605
- Degrees to radians conversion
606
- rad2deg : float
607
- Radians to degrees conversion
608
-
609
- Returns
610
- -------
611
- ZenithAngle_rad : float
612
- Zenith angle in radians
613
- ZenithAngle_deg : float
614
- Zenith angle in degrees
615
- '''
616
- ZenithAngle_rad = np.arccos(np.cos(lat * deg2rad)
617
- * np.cos(Declination_rad)
618
- * np.cos(HourAngle_rad)
619
- + np.sin(lat * deg2rad)
620
- * np.sin(Declination_rad))
621
-
622
- ZenithAngle_deg = ZenithAngle_rad * rad2deg
623
- return ZenithAngle_rad, ZenithAngle_deg
624
-
625
-
626
- def calcAngleDiff(ZenithAngle_rad, HourAngle_rad, phi_sensor_rad,
627
- theta_sensor_rad):
628
- '''Calculate angle between sun and upper sensor (to determine when sun is
629
- in sight of upper sensor
630
-
631
- Parameters
632
- ----------
633
- ZenithAngle_rad : float
634
- Zenith angle in radians
635
- HourAngle_rad : float
636
- Sun hour angle in radians
637
- phi_sensor_rad : xarray.DataArray
638
- Spherical tilt coordinates
639
- theta_sensor_rad : xarray.DataArray
640
- Total tilt of sensor, where 0 is horizontal
641
-
642
- Returns
643
- -------
644
- float
645
- Angle between sun and sensor
646
- '''
647
- return 180 / np.pi * np.arccos(np.sin(ZenithAngle_rad)
648
- * np.cos(HourAngle_rad + np.pi)
649
- * np.sin(theta_sensor_rad)
650
- * np.cos(phi_sensor_rad)
651
- + np.sin(ZenithAngle_rad)
652
- * np.sin(HourAngle_rad + np.pi)
653
- * np.sin(theta_sensor_rad)
654
- * np.sin(phi_sensor_rad)
655
- + np.cos(ZenithAngle_rad)
656
- * np.cos(theta_sensor_rad))
657
-
658
- def calcAlbedo(usr, dsr_cor, AngleDif_deg, ZenithAngle_deg):
659
- '''Calculate surface albedo based on upwelling and downwelling shorwave
660
- flux, the angle between the sun and sensor, and the sun zenith
661
-
662
- Parameters
663
- ----------
664
- usr : xarray.DataArray
665
- Upwelling shortwave radiation
666
- dsr_cor : xarray.DataArray
667
- Downwelling shortwave radiation corrected
668
- AngleDif_def : float
669
- Angle between sun and sensor in degrees
670
- ZenithAngle_deg: float
671
- Zenith angle in degrees
672
-
673
- Returns
674
- -------
675
- albedo : xarray.DataArray
676
- Derived albedo
677
- OKalbedos : xarray.DataArray
678
- Valid albedo measurements
679
- '''
680
- albedo = usr / dsr_cor
681
-
682
- # NaN bad data
683
- OKalbedos = (AngleDif_deg < 70) & (ZenithAngle_deg < 70) & (albedo < 1) & (albedo > 0)
684
- albedo[~OKalbedos] = np.nan
685
-
686
- # Interpolate all. Note "use_coordinate=False" is used here to force
687
- # comparison against the GDL code when that is run with *only* a TX file.
688
- # Should eventually set to default (True) and interpolate based on time,
689
- # not index.
690
- albedo = albedo.interpolate_na(dim='time', use_coordinate=False)
691
- albedo = albedo.ffill(dim='time').bfill(dim='time') #TODO remove this line and one above?
692
- return albedo, OKalbedos
693
-
694
-
695
- def calcTOA(ZenithAngle_deg, ZenithAngle_rad):
696
- '''Calculate incoming shortwave radiation at the top of the atmosphere,
697
- accounting for sunset periods
698
-
699
- Parameters
700
- ----------
701
- ZenithAngle_deg : float
702
- Zenith angle in degrees
703
- ZenithAngle_rad : float
704
- Zenith angle in radians
705
-
706
- Returns
707
- -------
708
- isr_toa : float
709
- Incoming shortwave radiation at the top of the atmosphere
710
- '''
711
- sundown = ZenithAngle_deg >= 90
712
-
713
- # Incoming shortware radiation at the top of the atmosphere
714
- isr_toa = 1372 * np.cos(ZenithAngle_rad)
715
- isr_toa[sundown] = 0
716
- return isr_toa
717
-
718
- def calcCorrectionFactor(Declination_rad, phi_sensor_rad, theta_sensor_rad,
719
- HourAngle_rad, ZenithAngle_rad, ZenithAngle_deg,
720
- lat, DifFrac, deg2rad):
721
- '''Calculate correction factor for direct beam radiation, as described
722
- here: http://solardat.uoregon.edu/SolarRadiationBasics.html
723
-
724
- Offset correction (where solar zenith angles larger than 110 degrees) not
725
- implemented as it should not improve the accuracy of well-calibrated
726
- instruments. It would go something like this:
727
- ds['dsr'] = ds['dsr'] - ds['dwr_offset']
728
- SRout = SRout - SRout_offset
729
-
730
- Parameters
731
- ----------
732
- Declination_rad : float
733
- Declination in radians
734
- phi_sensor_rad : xarray.DataArray
735
- Spherical tilt coordinates
736
- theta_sensor_rad : xarray.DataArray
737
- Total tilt of sensor, where 0 is horizontal
738
- HourAngle_rad : float
739
- Sun hour angle in radians
740
- ZenithAngle_rad : float
741
- Zenith angle in radians
742
- ZenithAngle_deg : float
743
- Zenith Angle in degrees
744
- lat : float
745
- Latitude
746
- DifFrac : float
747
- Fractional cloud cover
748
- deg2rad : float
749
- Degrees to radians conversion
750
-
751
- Returns
752
- -------
753
- CorFac_all : xarray.DataArray
754
- Correction factor
755
- '''
756
- CorFac = np.sin(Declination_rad) * np.sin(lat * deg2rad) \
757
- * np.cos(theta_sensor_rad) \
758
- - np.sin(Declination_rad) \
759
- * np.cos(lat * deg2rad) \
760
- * np.sin(theta_sensor_rad) \
761
- * np.cos(phi_sensor_rad + np.pi) \
762
- + np.cos(Declination_rad) \
763
- * np.cos(lat * deg2rad) \
764
- * np.cos(theta_sensor_rad) \
765
- * np.cos(HourAngle_rad) \
766
- + np.cos(Declination_rad) \
767
- * np.sin(lat * deg2rad) \
768
- * np.sin(theta_sensor_rad) \
769
- * np.cos(phi_sensor_rad + np.pi) \
770
- * np.cos(HourAngle_rad) \
771
- + np.cos(Declination_rad) \
772
- * np.sin(theta_sensor_rad) \
773
- * np.sin(phi_sensor_rad + np.pi) \
774
- * np.sin(HourAngle_rad) \
775
-
776
- CorFac = np.cos(ZenithAngle_rad) / CorFac
777
- # sun out of field of view upper sensor
778
- CorFac[(CorFac < 0) | (ZenithAngle_deg > 90)] = 1
779
-
780
- # Calculating ds['dsr'] over a horizontal surface corrected for station/sensor tilt
781
- CorFac_all = CorFac / (1 - DifFrac + CorFac * DifFrac)
782
-
783
- return CorFac_all
784
-
785
-
786
- def _checkSunPos(ds, OKalbedos, sundown, sunonlowerdome, TOA_crit_nopass):
787
- '''Check sun position
788
-
789
- Parameters
790
- ----------
791
- ds : xarray.Dataset
792
- Data set
793
- OKalbedos : xarray.DataArray
794
- Valid measurements flag
795
- sundown : xarray.DataArray
796
- Sun below horizon flag
797
- sunonlowerdome : xarray.DataArray
798
- Sun in view of lower sensor flag
799
- TOA_crit_nopass : xarray.DataArray
800
- Top-of-Atmosphere flag
801
- '''
802
- valid = (~(ds['dsr_cor'].isnull())).sum()
803
- print('Sun in view of upper sensor / workable albedos:', OKalbedos.sum().values,
804
- (100*OKalbedos.sum()/valid).round().values, "%")
805
- print('Sun below horizon:', sundown.sum(),
806
- (100*sundown.sum()/valid).round().values, "%")
807
- print('Sun in view of lower sensor:', sunonlowerdome.sum().values,
808
- (100*sunonlowerdome.sum()/valid).round().values, "%")
809
- print('Spikes removed using TOA criteria:', TOA_crit_nopass.sum().values,
810
- (100*TOA_crit_nopass.sum()/valid).round().values, "%")
811
- print('Mean net SR change by corrections:',
812
- (ds['dsr_cor']-ds['usr_cor']-ds['dsr']+ds['usr']).sum().values/valid.values,
813
- "W/m2")
814
-
815
- def _getTempK(T_0): #TODO same as L2toL3._getTempK()
816
- '''Return steam point temperature in Kelvins
817
-
818
- Parameters
819
- ----------
820
- T_0 : float
821
- Ice point temperature in K
822
-
823
- Returns
824
- -------
825
- float
826
- Steam point temperature in K'''
827
- return T_0+100
828
-
829
-
830
- def _getRotation(): #TODO same as L2toL3._getRotation()
831
- '''Return degrees-to-radians and radians-to-degrees'''
832
- deg2rad = np.pi / 180
833
- rad2deg = 1 / deg2rad
834
- return deg2rad, rad2deg
835
-
836
-
837
- if __name__ == "__main__":
838
- # unittest.main()
839
- pass