pypromice 1.5.3__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/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 +97 -118
- 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 +58 -45
- pypromice/{process → pipeline}/utilities.py +0 -22
- pypromice/resources/file_attributes.csv +4 -4
- pypromice/resources/variables.csv +27 -24
- {pypromice-1.5.3.dist-info → pypromice-1.6.0.dist-info}/METADATA +1 -2
- pypromice-1.6.0.dist-info/RECORD +64 -0
- 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 -564
- pypromice/process/L1toL2.py +0 -824
- pypromice/process/__init__.py +0 -4
- pypromice/process/load.py +0 -161
- pypromice-1.5.3.dist-info/RECORD +0 -54
- pypromice-1.5.3.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/{process → core/variables}/wind.py +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.3.dist-info → pypromice-1.6.0.dist-info}/WHEEL +0 -0
- {pypromice-1.5.3.dist-info → pypromice-1.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pypromice-1.5.3.dist-info → pypromice-1.6.0.dist-info}/top_level.txt +0 -0
pypromice/process/L1toL2.py
DELETED
|
@@ -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
|