Skip to content

Commit c4ba1af

Browse files
committed
waves: add waves accessor
1 parent 10295f0 commit c4ba1af

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed

tests/test_plot_spectra.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,20 @@ def test_plot_spectra_withargs(test_data, tmpdir, plot):
3838
plt.show()
3939

4040
plt.close('all')
41+
42+
43+
def test_plot_spectra_accessor(test_data, plot):
44+
csv_in = test_data / 'csv/omb3.csv'
45+
ds = read_omb_csv(csv_in)
46+
print(ds)
47+
print(ds.elevation_energy_spectrum)
48+
print(ds.frequencies_waves_imu)
49+
50+
plt.figure()
51+
ds.isel(trajectory=0).elevation_energy_spectrum.wave.plot(
52+
ds.isel(trajectory=0).time_waves_imu.squeeze())
53+
54+
if plot:
55+
plt.show()
56+
57+
plt.close('all')

trajan/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from . import skill as _
1616
from . import readers as _
1717

18+
from . import waves as _
19+
1820
logger = logging.getLogger(__name__)
1921

2022
__version__ = importlib.metadata.version("trajan")

trajan/waves/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import xarray as xr
2+
import logging
3+
import numpy as np
4+
5+
from .plot import Plot
6+
7+
# recommended by cf-xarray
8+
xr.set_options(keep_attrs=True)
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
@xr.register_dataarray_accessor('wave')
14+
class Wave:
15+
def __init__(self, ds):
16+
self.ds = ds
17+
self.__plot__ = None
18+
19+
@property
20+
def plot(self) -> Plot:
21+
"""
22+
See :class:`trajan.waves.Plot`.
23+
"""
24+
if self.__plot__ is None:
25+
logger.debug(f'Setting up new plot object.')
26+
self.__plot__ = Plot(self.ds)
27+
28+
return self.__plot__

trajan/waves/plot.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import logging
2+
import matplotlib.pyplot as plt
3+
import cartopy.crs as ccrs
4+
import numpy as np
5+
import xarray as xr
6+
import cf_xarray as _
7+
8+
from trajan.accessor import detect_time_dim
9+
10+
logger = logging.getLogger(__name__)
11+
logging.getLogger('matplotlib.font_manager').disabled = True
12+
13+
14+
class Plot:
15+
ds: xr.Dataset
16+
17+
# A lon-lat projection with the currently used globe.
18+
gcrs = None
19+
20+
DEFAULT_LINE_COLOR = 'gray'
21+
22+
def __init__(self, ds):
23+
self.ds = ds
24+
self.gcrs = ccrs.PlateCarree()
25+
26+
def set_up_map(self, **kwargs):
27+
"""
28+
Set up axes for plotting.
29+
30+
Args:
31+
32+
ax: An existing axes to use.
33+
34+
Returns:
35+
36+
An matplotlib axes with a Cartopy projection.
37+
38+
"""
39+
ax = kwargs.get('ax', plt.axes())
40+
return ax
41+
42+
def __call__(self, *args, **kwargs):
43+
if self.ds.attrs['standard_name'] == 'sea_surface_wave_variance_spectral_density':
44+
return self.spectra(*args, **kwargs)
45+
else:
46+
raise ValueError('Unknown wave variable')
47+
48+
def spectra(self, time, *args, **kwargs):
49+
"""
50+
Plot the wave spectra information from a trajan compatible xarray.
51+
52+
Args:
53+
54+
time: DataArray with times.
55+
56+
vrange: can be either:
57+
- None to use the default log range [-3.0, 1.0]
58+
- a tuple of float to set the log range explicitely
59+
60+
`nseconds_gap`: float
61+
Number of seconds between 2 consecutive
62+
spectra for one instrument above which we consider that there is a
63+
data loss that should be filled with NaN. This is to avoid "stretching"
64+
neighboring spectra over long times if an instrument gets offline.
65+
66+
Returns:
67+
68+
ax: plt.Axes
69+
"""
70+
vrange = kwargs.pop('vrange', None)
71+
nseconds_gap = kwargs.pop('nseconds_gap', 6 * 3600)
72+
73+
ax = self.set_up_map(**kwargs)
74+
75+
if vrange is None:
76+
vmin_pcolor = -3.0
77+
vmax_pcolor = 1.0
78+
else:
79+
vmin_pcolor = vrange[0]
80+
vmax_pcolor = vrange[1]
81+
82+
spectra_frequencies = self.ds.cf['wave_frequency']
83+
84+
crrt_spectra = self.ds.to_numpy()
85+
# crrt_spectra_times = detect_time_dim(self.ds, 'obs_waves_imu').to_numpy()
86+
crrt_spectra_times = time.to_numpy()
87+
88+
list_datetimes = []
89+
list_spectra = []
90+
91+
# avoid streching at the left
92+
list_datetimes.append(
93+
crrt_spectra_times[0] - np.timedelta64(2, 'm'))
94+
list_spectra.append(np.full(len(spectra_frequencies), np.nan))
95+
96+
for crrt_spectra_ind in range(1, crrt_spectra.shape[0], 1):
97+
if np.isnan(crrt_spectra_times[crrt_spectra_ind]):
98+
continue
99+
100+
# if a gap with more than nseconds_gap seconds, fill with NaNs
101+
# to avoid stretching neighbors over missing data
102+
seconds_after_previous = float(
103+
crrt_spectra_times[crrt_spectra_ind] - crrt_spectra_times[crrt_spectra_ind-1]) / 1e9
104+
if seconds_after_previous > nseconds_gap:
105+
logger.debug(
106+
f"spectrum index {crrt_spectra_ind} is {seconds_after_previous} seconds \
107+
after the previous one; insert nan spectra in between to avoid stretching")
108+
list_datetimes.append(
109+
crrt_spectra_times[crrt_spectra_ind-1] + np.timedelta64(2, 'h'))
110+
list_spectra.append(
111+
np.full(len(spectra_frequencies), np.nan))
112+
list_datetimes.append(
113+
crrt_spectra_times[crrt_spectra_ind] - np.timedelta64(2, 'h'))
114+
list_spectra.append(
115+
np.full(len(spectra_frequencies), np.nan))
116+
117+
list_spectra.append(crrt_spectra[crrt_spectra_ind, :])
118+
list_datetimes.append(crrt_spectra_times[crrt_spectra_ind])
119+
120+
# avoid stretching at the right
121+
last_datetime = list_datetimes[-1]
122+
list_datetimes.append(last_datetime + np.timedelta64(2, 'm'))
123+
list_spectra.append(np.full(len(spectra_frequencies), np.nan))
124+
125+
pclr = ax.pcolor(list_datetimes, spectra_frequencies, np.log10(
126+
np.transpose(np.array(list_spectra))), vmin=vmin_pcolor, vmax=vmax_pcolor)
127+
128+
return ax
129+

0 commit comments

Comments
 (0)