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