pypromice 1.5.2__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pypromice might be problematic. Click here for more details.
- pypromice/__init__.py +2 -0
- pypromice/{qc → core/qc}/percentiles/compute_thresholds.py +2 -2
- pypromice/{qc → core/qc}/persistence.py +22 -29
- pypromice/{process → core/qc}/value_clipping.py +3 -3
- pypromice/core/variables/__init__.py +1 -0
- pypromice/core/variables/air_temperature.py +64 -0
- pypromice/core/variables/gps.py +221 -0
- pypromice/core/variables/humidity.py +111 -0
- pypromice/core/variables/precipitation.py +108 -0
- pypromice/core/variables/pressure_transducer_depth.py +79 -0
- pypromice/core/variables/radiation.py +422 -0
- pypromice/core/variables/station_boom_height.py +49 -0
- pypromice/core/variables/station_pose.py +375 -0
- pypromice/core/variables/wind.py +66 -0
- pypromice/io/bufr/__init__.py +0 -0
- pypromice/{postprocess → io/bufr}/bufr_to_csv.py +1 -1
- pypromice/{postprocess → io/bufr}/create_bufr_files.py +2 -2
- pypromice/{postprocess → io/bufr}/get_bufr.py +6 -6
- pypromice/{postprocess → io/bufr}/real_time_utilities.py +3 -3
- pypromice/io/ingest/__init__.py +0 -0
- pypromice/{utilities → io/ingest}/git.py +1 -3
- pypromice/io/ingest/l0.py +294 -0
- pypromice/io/ingest/l0_repository.py +103 -0
- pypromice/io/ingest/toa5.py +87 -0
- pypromice/{process → io}/write.py +1 -1
- pypromice/pipeline/L0toL1.py +291 -0
- pypromice/pipeline/L1toL2.py +233 -0
- pypromice/{process → pipeline}/L2toL3.py +102 -120
- pypromice/pipeline/__init__.py +4 -0
- pypromice/{process → pipeline}/aws.py +10 -82
- pypromice/{process → pipeline}/get_l2.py +2 -2
- pypromice/{process → pipeline}/get_l2tol3.py +19 -22
- pypromice/{process → pipeline}/join_l2.py +31 -32
- pypromice/{process → pipeline}/join_l3.py +16 -14
- pypromice/{process → pipeline}/resample.py +59 -46
- pypromice/{process → pipeline}/utilities.py +0 -22
- pypromice/resources/file_attributes.csv +4 -4
- pypromice/resources/variables.csv +27 -24
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/METADATA +1 -2
- pypromice-1.6.0.dist-info/RECORD +64 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/WHEEL +1 -1
- pypromice-1.6.0.dist-info/entry_points.txt +12 -0
- pypromice/get/__init__.py +0 -1
- pypromice/get/get.py +0 -211
- pypromice/get/get_promice_data.py +0 -56
- pypromice/process/L0toL1.py +0 -536
- pypromice/process/L1toL2.py +0 -839
- pypromice/process/__init__.py +0 -4
- pypromice/process/load.py +0 -161
- pypromice-1.5.2.dist-info/RECORD +0 -53
- pypromice-1.5.2.dist-info/entry_points.txt +0 -13
- /pypromice/{postprocess → core}/__init__.py +0 -0
- /pypromice/{utilities → core}/dependency_graph.py +0 -0
- /pypromice/{qc → core/qc}/__init__.py +0 -0
- /pypromice/{qc → core/qc}/github_data_issues.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/__init__.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/outlier_detector.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/thresholds.csv +0 -0
- /pypromice/{utilities → io}/__init__.py +0 -0
- /pypromice/{postprocess → io/bufr}/bufr_utilities.py +0 -0
- /pypromice/{postprocess → io/bufr}/positions_seed.csv +0 -0
- /pypromice/{station_configuration.py → io/bufr/station_configuration.py} +0 -0
- /pypromice/{postprocess → io}/make_metadata_csv.py +0 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
__all__ = ["convert_and_filter_tilt", "apply_tilt_factor",
|
|
2
|
+
"smooth_tilt_with_moving_window", "interpolate_tilt",
|
|
3
|
+
"interpolate_rotation", "calculate_spherical_tilt",
|
|
4
|
+
"calculate_declination", "calculate_hour_angle",
|
|
5
|
+
"calculate_sun_direction_degrees", "calculate_zenith",
|
|
6
|
+
"calculate_angle_difference"]
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import xarray as xr
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
tilt_smoothing_win_size = 7 # Station tilt smoothing window size
|
|
13
|
+
tilt_stddev_threshold = 0.2 # Station tilt interpolation standard deviation threshold
|
|
14
|
+
rot_stddev_threshold = 4 # Station rotation interpolation standard deviation threshold
|
|
15
|
+
tilt_threshold = -100 # Station tilt threshold
|
|
16
|
+
deg2rad = np.pi / 180 # Degrees to radians conversion
|
|
17
|
+
rad2deg = 1 / deg2rad # Radians to degrees conversion
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def apply_tilt_factor(tilt: xr.DataArray,
|
|
21
|
+
tilt_correction_factor: float
|
|
22
|
+
) -> xr.DataArray:
|
|
23
|
+
"""Apply tilt correction factor to station tilt values
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
tilt : xr.DataArray
|
|
28
|
+
Tilt array (either 'tilt_x' or 'tilt_y')
|
|
29
|
+
tilt_correction_factor : float
|
|
30
|
+
Correction factor to apply to tilt measurements
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
xr.DataArray
|
|
35
|
+
Corrected tilt measurements
|
|
36
|
+
"""
|
|
37
|
+
return tilt * tilt_correction_factor
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def convert_and_filter_tilt(tilt: xr.DataArray
|
|
41
|
+
) -> xr.DataArray:
|
|
42
|
+
"""Convert station tilt from voltage to degrees,
|
|
43
|
+
and filter tilt with given threshold. Voltage-to-degrees
|
|
44
|
+
conversion is based on the equation in 3.2.9 in Fausto
|
|
45
|
+
et al. (2021) https://doi.org/10.5194/essd-13-3819-2021
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
tilt : xr.DataArray
|
|
50
|
+
Array (either 'tilt_x' or 'tilt_y'), tilt values (voltage)
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
xr.DataArray
|
|
55
|
+
Array (either 'tilt_x' or 'tilt_y'), tilt values (degrees)
|
|
56
|
+
"""
|
|
57
|
+
# IDL version:
|
|
58
|
+
# notOKtiltX = where(tiltX lt -100, complement=OKtiltX) & notOKtiltY = where(tiltY lt -100, complement=OKtiltY)
|
|
59
|
+
# tiltX = tiltX/10.
|
|
60
|
+
# tiltnonzero = where(tiltX ne 0 and tiltX gt -40 and tiltX lt 40)
|
|
61
|
+
# if n_elements(tiltnonzero) ne 1 then tiltX[tiltnonzero] = tiltX[tiltnonzero]/abs(tiltX[tiltnonzero])*(-0.49*(abs(tiltX[tiltnonzero]))^4 + 3.6*(abs(tiltX[tiltnonzero]))^3 - 10.4*(abs(tiltX[tiltnonzero]))^2 +21.1*(abs(tiltX[tiltnonzero])))
|
|
62
|
+
# tiltY = tiltY/10.
|
|
63
|
+
# tiltnonzero = where(tiltY ne 0 and tiltY gt -40 and tiltY lt 40)
|
|
64
|
+
# if n_elements(tiltnonzero) ne 1 then tiltY[tiltnonzero] = tiltY[tiltnonzero]/abs(tiltY[tiltnonzero])*(-0.49*(abs(tiltY[tiltnonzero]))^4 + 3.6*(abs(tiltY[tiltnonzero]))^3 - 10.4*(abs(tiltY[tiltnonzero]))^2 +21.1*(abs(tiltY[tiltnonzero])))
|
|
65
|
+
|
|
66
|
+
# if n_elements(OKtiltX) gt 1 then tiltX[notOKtiltX] = interpol(tiltX[OKtiltX],OKtiltX,notOKtiltX) ; Interpolate over gaps for radiation correction; set to -999 again below.
|
|
67
|
+
|
|
68
|
+
# Define valid tilt values and create mask
|
|
69
|
+
notOKtilt = (tilt < tilt_threshold)
|
|
70
|
+
OKtilt = (tilt >= tilt_threshold)
|
|
71
|
+
|
|
72
|
+
# Convert tilt values
|
|
73
|
+
dst = tilt / 10
|
|
74
|
+
nz = (dst != 0) & (np.abs(dst) < 40)
|
|
75
|
+
dst = dst.where(~nz, other = dst / np.abs(dst)
|
|
76
|
+
* (-0.49
|
|
77
|
+
* (np.abs(dst))**4 + 3.6
|
|
78
|
+
* (np.abs(dst))**3 - 10.4
|
|
79
|
+
* (np.abs(dst))**2 + 21.1
|
|
80
|
+
* (np.abs(dst))))
|
|
81
|
+
|
|
82
|
+
# Apply filtering mask
|
|
83
|
+
dst = dst.where(~notOKtilt)
|
|
84
|
+
|
|
85
|
+
# TODO: Filling w/o considering time gaps to re-create IDL/GDL outputs.
|
|
86
|
+
# Should fill with coordinate not False. Also consider 'max_gap' option?
|
|
87
|
+
return dst.interpolate_na(dim='time', use_coordinate=False)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def smooth_tilt_with_moving_window(tilt: xr.DataArray
|
|
91
|
+
) -> tuple[str, np.ndarray]:
|
|
92
|
+
"""Smooth tilt values using the pandas 'rolling' window method
|
|
93
|
+
e.g. a value of 7 spans 70 minutes using 10-minute data.
|
|
94
|
+
This is translated from the previous IDL/GDL smoothing algorithm:
|
|
95
|
+
tiltX = smooth(tiltX,7,/EDGE_MIRROR,MISSING=-999) & tiltY = smooth(tiltY,7,/EDGE_MIRROR, MISSING=-999)
|
|
96
|
+
endif
|
|
97
|
+
In Python, this should be
|
|
98
|
+
dstxy = dstxy.rolling(time=7, win_type='boxcar', center=True).mean()
|
|
99
|
+
But the EDGE_MIRROR makes it a bit more complicated
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
tilt : xr.DataArray
|
|
104
|
+
Array (either 'tilt_x' or 'tilt_y'), tilt values (can be in degrees or voltage)
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
tdf_rolling : tuple, as: (str, numpy.ndarray)
|
|
109
|
+
The numpy array is the tilt values, smoothed with a rolling mean
|
|
110
|
+
"""
|
|
111
|
+
s = int(tilt_smoothing_win_size/2)
|
|
112
|
+
tdf = tilt.to_dataframe()
|
|
113
|
+
mirror_start = tdf.iloc[:s][::-1]
|
|
114
|
+
mirror_end = tdf.iloc[-s:][::-1]
|
|
115
|
+
mirrored_tdf = pd.concat([mirror_start, tdf, mirror_end])
|
|
116
|
+
|
|
117
|
+
tdf_rolling = (
|
|
118
|
+
("time"),
|
|
119
|
+
mirrored_tdf.rolling(
|
|
120
|
+
tilt_smoothing_win_size, win_type="boxcar", min_periods=1, center=True
|
|
121
|
+
).mean()[s:-s].values.flatten()
|
|
122
|
+
)
|
|
123
|
+
return tdf_rolling
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def interpolate_tilt(tilt: xr.DataArray,
|
|
127
|
+
) -> xr.DataArray:
|
|
128
|
+
"""Interpolate (fill data gaps) and smooth station tilt
|
|
129
|
+
using a moving standard deviation over a 3-day sliding
|
|
130
|
+
window.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
tilt : xr.DataArray
|
|
135
|
+
Either X or Y tilt inclinometer measurements
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
xr.DataArray
|
|
140
|
+
Either X or Y smoothed tilt inclinometer measurements
|
|
141
|
+
"""
|
|
142
|
+
# We calculate the moving standard deviation over a 3-day sliding window
|
|
143
|
+
# hourly resampling is necessary to make sure the same threshold can be used
|
|
144
|
+
# for 10 min and hourly data
|
|
145
|
+
moving_std_gap_filled = tilt.to_series().resample("h").median().rolling(
|
|
146
|
+
3*24, center=True, min_periods=2
|
|
147
|
+
).std().reindex(tilt.time, method="bfill").values
|
|
148
|
+
|
|
149
|
+
# We select the good timestamps and gapfill assuming that
|
|
150
|
+
# - when tilt goes missing the last available value is used
|
|
151
|
+
# - when tilt is not available for the very first time steps, the first
|
|
152
|
+
# good value is used for backfill
|
|
153
|
+
return tilt.where(
|
|
154
|
+
moving_std_gap_filled < tilt_stddev_threshold
|
|
155
|
+
).ffill(dim="time").bfill(dim="time")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def interpolate_rotation(rot: xr.DataArray,
|
|
159
|
+
threshold=4):
|
|
160
|
+
"""Interpolate and smooth station rotation
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
rot : xr.DataArray
|
|
165
|
+
Rotation measurements from inclinometer
|
|
166
|
+
threshold : float
|
|
167
|
+
threshold used in a standard deviation based filter
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
xr.DataArray
|
|
172
|
+
smoothed rotation measurements from inclinometer
|
|
173
|
+
"""
|
|
174
|
+
moving_std_gap_filled = rot.to_series().resample("h").median().rolling(
|
|
175
|
+
3*24, center=True, min_periods=2
|
|
176
|
+
).std().reindex(rot.time, method="bfill").values
|
|
177
|
+
|
|
178
|
+
# Same as for interpolate_tilt() with, in addition:
|
|
179
|
+
# - a resampling to daily values
|
|
180
|
+
# - a two-week median smoothing
|
|
181
|
+
# - a resampling from these daily values to the original temporal resolution
|
|
182
|
+
return ("time", (rot.where(moving_std_gap_filled < rot_stddev_threshold)
|
|
183
|
+
.ffill(dim="time")
|
|
184
|
+
.to_series().resample("D").median()
|
|
185
|
+
.rolling(7*2,center=True,min_periods=2).median()
|
|
186
|
+
.reindex(rot.time, method="bfill").values
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def calculate_spherical_tilt(
|
|
191
|
+
tilt_x: xr.DataArray,
|
|
192
|
+
tilt_y: xr.DataArray
|
|
193
|
+
) -> tuple[xr.DataArray, xr.DataArray]:
|
|
194
|
+
"""Calculate station tilt
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
tilt_x : xr.DataArray
|
|
199
|
+
X tilt inclinometer measurements
|
|
200
|
+
tilt_y : xr.DataArray
|
|
201
|
+
Y tilt inclinometer measurements
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
phi_sensor_rad : xr.DataArray
|
|
206
|
+
Spherical tilt coordinates
|
|
207
|
+
theta_sensor_rad : xr.DataArray
|
|
208
|
+
Total tilt of sensor, where 0 is horizontal
|
|
209
|
+
"""
|
|
210
|
+
# Tilt as radians
|
|
211
|
+
tx = tilt_x * deg2rad
|
|
212
|
+
ty = tilt_y * deg2rad
|
|
213
|
+
|
|
214
|
+
# Calculate cartesian coordinates
|
|
215
|
+
X = np.sin(tx) * np.cos(tx) * np.sin(ty)**2 + np.sin(tx) * np.cos(ty)**2
|
|
216
|
+
Y = np.sin(ty) * np.cos(ty) * np.sin(tx)**2 + np.sin(ty) * np.cos(tx)**2
|
|
217
|
+
Z = np.cos(tx) * np.cos(ty) + np.sin(tx)**2 * np.sin(ty)**2
|
|
218
|
+
|
|
219
|
+
# Calculate spherical coordinates
|
|
220
|
+
phi_sensor_rad = -np.pi /2 - np.arctan(Y/X)
|
|
221
|
+
phi_sensor_rad[X > 0] += np.pi
|
|
222
|
+
phi_sensor_rad[(X == 0) & (Y < 0)] = np.pi
|
|
223
|
+
phi_sensor_rad[(X == 0) & (Y == 0)] = 0
|
|
224
|
+
phi_sensor_rad[phi_sensor_rad < 0] += 2*np.pi
|
|
225
|
+
|
|
226
|
+
# Total tilt of the sensor, i.e. 0 when horizontal
|
|
227
|
+
theta_sensor_rad = np.arccos(Z / (X**2 + Y**2 + Z**2)**0.5)
|
|
228
|
+
return phi_sensor_rad, theta_sensor_rad
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def calculate_declination(doy: xr.DataArray,
|
|
232
|
+
hour: xr.DataArray,
|
|
233
|
+
minute: xr.DataArray
|
|
234
|
+
) -> xr.DataArray:
|
|
235
|
+
"""Calculate sun declination based on time
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
doy : xr.DataArray
|
|
240
|
+
Day of year
|
|
241
|
+
hour : xr.DataArray
|
|
242
|
+
Hour of day
|
|
243
|
+
minute : xr.DataArray
|
|
244
|
+
Minute of hour
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
xr.DataArray
|
|
249
|
+
Sun declination in radians
|
|
250
|
+
"""
|
|
251
|
+
d0_rad = 2 * np.pi * (doy + (hour + minute / 60) / 24 -1) / 365
|
|
252
|
+
return np.arcsin(0.006918 - 0.399912
|
|
253
|
+
* np.cos(d0_rad) + 0.070257
|
|
254
|
+
* np.sin(d0_rad) - 0.006758
|
|
255
|
+
* np.cos(2 * d0_rad) + 0.000907
|
|
256
|
+
* np.sin(2 * d0_rad) - 0.002697
|
|
257
|
+
* np.cos(3 * d0_rad) + 0.00148
|
|
258
|
+
* np.sin(3 * d0_rad))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def calculate_hour_angle(hour: xr.DataArray,
|
|
262
|
+
minute: xr.DataArray,
|
|
263
|
+
lon: float
|
|
264
|
+
) -> xr.DataArray:
|
|
265
|
+
"""Calculate hour angle of sun based on time and longitude. Make sure that
|
|
266
|
+
time is set to UTC and longitude is positive when west. Hour angle should
|
|
267
|
+
be 0 at noon
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
hour : xr.DataArray
|
|
272
|
+
Hour of day
|
|
273
|
+
minute : xr.DataArray
|
|
274
|
+
Minute of hour
|
|
275
|
+
lon : float
|
|
276
|
+
Longitude
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
xr.DataArray
|
|
281
|
+
Hour angle of sun
|
|
282
|
+
"""
|
|
283
|
+
return 2 * np.pi * (((hour + minute / 60) / 24 - 0.5) - lon/360)
|
|
284
|
+
# ; - 15.*timezone/360.)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def calculate_sun_direction_degrees(HourAngle_rad: xr.DataArray
|
|
288
|
+
) -> xr.DataArray:
|
|
289
|
+
"""Calculate sun direction as degrees. This is an alternative to
|
|
290
|
+
calculate_hour_angle that is currently not implemented into the official
|
|
291
|
+
L0>>L3 workflow. Here, 180 degrees is at noon (NH), as opposed to
|
|
292
|
+
HourAngle
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
HourAngle_rad : xr.DataArray
|
|
297
|
+
Sun hour angle in radians
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
DirectionSun_deg : xr.DataArray
|
|
302
|
+
Sun direction in degrees
|
|
303
|
+
"""
|
|
304
|
+
DirectionSun_deg = HourAngle_rad * 180/np.pi - 180
|
|
305
|
+
DirectionSun_deg[DirectionSun_deg < 0] += 360
|
|
306
|
+
DirectionSun_deg[DirectionSun_deg < 0] += 360
|
|
307
|
+
return DirectionSun_deg
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def calculate_zenith(lat: float,
|
|
311
|
+
Declination_rad: xr.DataArray,
|
|
312
|
+
HourAngle_rad: xr.DataArray
|
|
313
|
+
) -> tuple[xr.DataArray, xr.DataArray]:
|
|
314
|
+
"""Calculate sun zenith in radians and degrees
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
lat : float
|
|
319
|
+
Latitude
|
|
320
|
+
Declination_rad : xr.DataArray
|
|
321
|
+
Sun declination in radians
|
|
322
|
+
HourAngle_rad : xr.DataArray
|
|
323
|
+
Sun hour angle in radians
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
ZenithAngle_rad : xr.DataArray
|
|
328
|
+
Zenith angle in radians
|
|
329
|
+
ZenithAngle_deg : xr.DataArray
|
|
330
|
+
Zenith angle in degrees
|
|
331
|
+
"""
|
|
332
|
+
ZenithAngle_rad = np.arccos(np.cos(lat * deg2rad)
|
|
333
|
+
* np.cos(Declination_rad)
|
|
334
|
+
* np.cos(HourAngle_rad)
|
|
335
|
+
+ np.sin(lat * deg2rad)
|
|
336
|
+
* np.sin(Declination_rad))
|
|
337
|
+
|
|
338
|
+
ZenithAngle_deg = ZenithAngle_rad * rad2deg
|
|
339
|
+
return ZenithAngle_rad, ZenithAngle_deg
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def calculate_angle_difference(ZenithAngle_rad: xr.DataArray,
|
|
343
|
+
HourAngle_rad: xr.DataArray,
|
|
344
|
+
phi_sensor_rad: xr.DataArray,
|
|
345
|
+
theta_sensor_rad: xr.DataArray
|
|
346
|
+
) -> xr.DataArray:
|
|
347
|
+
"""Calculate angle between sun and upper sensor (to determine when sun is
|
|
348
|
+
in sight of upper radiometer sensor)
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
ZenithAngle_rad : xr.DataArray
|
|
353
|
+
Zenith angle in radians
|
|
354
|
+
HourAngle_rad : xr.DataArray
|
|
355
|
+
Sun hour angle in radians
|
|
356
|
+
phi_sensor_rad : xarray.DataArray
|
|
357
|
+
Spherical tilt coordinates
|
|
358
|
+
theta_sensor_rad : xarray.DataArray
|
|
359
|
+
Total tilt of sensor, where 0 is horizontal
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
xr.DataArray
|
|
364
|
+
Angle between sun and sensor
|
|
365
|
+
"""
|
|
366
|
+
return 180 / np.pi * np.arccos(np.sin(ZenithAngle_rad)
|
|
367
|
+
* np.cos(HourAngle_rad + np.pi)
|
|
368
|
+
* np.sin(theta_sensor_rad)
|
|
369
|
+
* np.cos(phi_sensor_rad)
|
|
370
|
+
+ np.sin(ZenithAngle_rad)
|
|
371
|
+
* np.sin(HourAngle_rad + np.pi)
|
|
372
|
+
* np.sin(theta_sensor_rad)
|
|
373
|
+
* np.sin(phi_sensor_rad)
|
|
374
|
+
+ np.cos(ZenithAngle_rad)
|
|
375
|
+
* np.cos(theta_sensor_rad))
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
__all__=['correct_wind_speed', 'filter_wind_direction', 'calculate_directional_wind_speed']
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import xarray as xr
|
|
5
|
+
|
|
6
|
+
DEG2RAD=np.pi/180
|
|
7
|
+
|
|
8
|
+
def correct_wind_speed(wspd: xr.DataArray, coefficient) -> xr.DataArray:
|
|
9
|
+
"""Correct wind speed with a linear correction coefficient. For example, the conversion from a standard
|
|
10
|
+
Young anemometer to an Arctic Young anemometer is 1.7.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
wspd : xr.DataArray
|
|
15
|
+
Wind speed
|
|
16
|
+
coefficient : float
|
|
17
|
+
Correction coefficient
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
xr.DataArray
|
|
22
|
+
Corrected wind speed
|
|
23
|
+
"""
|
|
24
|
+
return wspd * coefficient
|
|
25
|
+
|
|
26
|
+
def filter_wind_direction(wdir: xr.DataArray, wspd: xr.DataArray) -> xr.DataArray:
|
|
27
|
+
"""Filter wind direction by wind speed, where wind direction values are removed if
|
|
28
|
+
wind speed is zero.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
wdir : xr.DataArray
|
|
33
|
+
Wind direction
|
|
34
|
+
wspd : xr.DataArray
|
|
35
|
+
Wind speed
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
xr.DataArray
|
|
40
|
+
Filtered wind direction
|
|
41
|
+
"""
|
|
42
|
+
return wdir.where(wspd != 0)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def calculate_directional_wind_speed(wspd: xr.DataArray, wdir: xr.DataArray):
|
|
46
|
+
"""Calculate directional wind speed from wind speed and direction
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
wspd : xr.DataArray
|
|
51
|
+
Wind speed data array
|
|
52
|
+
wdir : xr.DataArray
|
|
53
|
+
Wind direction data array
|
|
54
|
+
deg2rad : float
|
|
55
|
+
Degree to radians coefficient. The default is np.pi/180
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
wspd_x : xr.DataArray
|
|
60
|
+
Wind speed in X direction
|
|
61
|
+
wspd_y : xr.DatArray
|
|
62
|
+
Wind speed in Y direction
|
|
63
|
+
"""
|
|
64
|
+
wspd_x = wspd * np.sin(wdir * DEG2RAD)
|
|
65
|
+
wspd_y = wspd * np.cos(wdir * DEG2RAD)
|
|
66
|
+
return wspd_x, wspd_y
|
|
File without changes
|
|
@@ -3,9 +3,9 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Sequence, List
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
|
-
from pypromice.station_configuration import load_station_configuration_mapping
|
|
6
|
+
from pypromice.io.bufr.station_configuration import load_station_configuration_mapping
|
|
7
7
|
|
|
8
|
-
from pypromice.
|
|
8
|
+
from pypromice.io.bufr.get_bufr import (
|
|
9
9
|
get_bufr,
|
|
10
10
|
DEFAULT_LIN_REG_TIME_LIMIT,
|
|
11
11
|
DEFAULT_POSITION_SEED_PATH,
|
|
@@ -23,11 +23,11 @@ from typing import List, Dict, Optional, Sequence, Mapping
|
|
|
23
23
|
import numpy as np
|
|
24
24
|
import pandas as pd
|
|
25
25
|
|
|
26
|
-
from pypromice.
|
|
27
|
-
from pypromice.
|
|
26
|
+
from pypromice.io.bufr.bufr_utilities import write_bufr_message, BUFRVariables
|
|
27
|
+
from pypromice.io.bufr.real_time_utilities import get_latest_data
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
from pypromice.station_configuration import (
|
|
30
|
+
from pypromice.io.bufr.station_configuration import (
|
|
31
31
|
StationConfiguration,
|
|
32
32
|
load_station_configuration_mapping,
|
|
33
33
|
)
|
|
@@ -45,7 +45,7 @@ REQUIRED_KEYS = (
|
|
|
45
45
|
"gps_lat_fit",
|
|
46
46
|
"gps_lon_fit",
|
|
47
47
|
"gps_alt_fit",
|
|
48
|
-
"
|
|
48
|
+
"z_boom_cor_u_smooth",
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
@@ -335,7 +335,7 @@ def get_bufr_variables(
|
|
|
335
335
|
heightOfSensorAboveLocalGroundOrDeckOfMarinePlatformTempRH = np.nan
|
|
336
336
|
else:
|
|
337
337
|
heightOfSensorAboveLocalGroundOrDeckOfMarinePlatformTempRH = (
|
|
338
|
-
data["
|
|
338
|
+
data["z_boom_cor_u_smooth"]
|
|
339
339
|
+ station_configuration.temperature_from_sonic_ranger
|
|
340
340
|
)
|
|
341
341
|
|
|
@@ -343,7 +343,7 @@ def get_bufr_variables(
|
|
|
343
343
|
heightOfSensorAboveLocalGroundOrDeckOfMarinePlatformWSPD = np.nan
|
|
344
344
|
else:
|
|
345
345
|
heightOfSensorAboveLocalGroundOrDeckOfMarinePlatformWSPD = (
|
|
346
|
-
data["
|
|
346
|
+
data["z_boom_cor_u_smooth"] + station_configuration.anemometer_from_sonic_ranger
|
|
347
347
|
)
|
|
348
348
|
|
|
349
349
|
output_row = BUFRVariables(
|
|
@@ -29,14 +29,14 @@ def get_latest_data(
|
|
|
29
29
|
|
|
30
30
|
* A valid timestamp is a timestamp with relevant instantaneous variables. See source code.
|
|
31
31
|
* Location smoothing: Fit a linear regression model on gps coordinate over the period lin_reg_time_limit to determine latest values.
|
|
32
|
-
*
|
|
32
|
+
* z_boom_cor_u: Apply rolling window median filter smooth data
|
|
33
33
|
|
|
34
34
|
The output series contains the same variables as the input dataframe plus smoothed variables:
|
|
35
35
|
|
|
36
36
|
* gps_lat_fit
|
|
37
37
|
* gps_lon_fit
|
|
38
38
|
* gps_alt_fit
|
|
39
|
-
*
|
|
39
|
+
* z_boom_cor_u_smooth
|
|
40
40
|
|
|
41
41
|
Parameters
|
|
42
42
|
----------
|
|
@@ -73,7 +73,7 @@ def get_latest_data(
|
|
|
73
73
|
|
|
74
74
|
# Apply smoothing to z_boom_u
|
|
75
75
|
# require at least 2 hourly obs? Sometimes seeing once/day data for z_boom_u
|
|
76
|
-
df_limited = rolling_window(df_limited, "
|
|
76
|
+
df_limited = rolling_window(df_limited, "z_boom_cor_u", "72h", 2, 3)
|
|
77
77
|
|
|
78
78
|
# limit to single most recent valid row (convert to series)
|
|
79
79
|
s_current = df_limited.loc[last_valid_index]
|
|
File without changes
|