File size: 16,812 Bytes
0b6f383
d1e1878
 
 
8eaab2a
d1e1878
4058a21
 
 
 
6fa1c28
4058a21
d1e1878
0b6f383
 
e8558a5
06c3644
e8558a5
 
cd9ce8a
0c50153
06c3644
e8558a5
 
5930638
06c3644
e8558a5
 
 
 
4a32de0
5930638
613f71e
e8558a5
613f71e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8558a5
613f71e
 
 
 
 
 
 
 
 
 
 
 
599750c
 
 
 
 
 
 
 
 
4058a21
6fa1c28
4058a21
 
 
 
 
 
 
 
 
 
 
 
 
 
6fa1c28
 
 
 
 
8eaab2a
4058a21
 
 
8eaab2a
4058a21
8eaab2a
 
 
4058a21
 
8eaab2a
 
 
 
 
 
4058a21
b2b67f8
4058a21
 
 
 
 
 
 
 
 
 
b2b67f8
 
 
 
 
 
4058a21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70699f1
 
 
4058a21
 
 
 
 
 
 
70699f1
 
 
 
 
 
4058a21
 
 
 
 
 
 
 
 
599750c
4058a21
 
 
 
 
70699f1
4058a21
70699f1
4058a21
70699f1
4058a21
 
 
 
 
 
 
 
 
 
 
 
 
70699f1
 
4058a21
 
 
 
 
e8558a5
 
4058a21
 
d1e1878
926a19b
 
 
 
 
 
 
 
 
 
 
 
fec3245
 
e8558a5
d1e1878
 
d48b6bc
926a19b
70699f1
d1e1878
70699f1
 
 
d1e1878
 
70699f1
 
 
 
d1e1878
 
e8558a5
70699f1
5930638
e8558a5
d1e1878
 
 
926a19b
 
 
 
 
 
 
d1e1878
e8558a5
d1e1878
 
 
 
 
f37af5b
d1e1878
599750c
 
 
 
 
 
 
 
d1e1878
599750c
 
d1e1878
 
599750c
 
d1e1878
599750c
 
d1e1878
599750c
d1e1878
 
599750c
 
d1e1878
 
 
599750c
 
 
 
 
e8558a5
599750c
70699f1
 
 
 
 
 
 
599750c
 
e8558a5
d1e1878
 
 
 
 
 
 
4058a21
 
d1e1878
 
 
 
 
e8558a5
d1e1878
599750c
 
 
 
 
 
 
 
 
 
 
 
 
4058a21
 
 
 
 
 
 
 
 
 
599750c
e8558a5
599750c
926a19b
 
70699f1
926a19b
70699f1
599750c
 
e8558a5
599750c
 
 
 
d1e1878
599750c
 
d1e1878
 
cd9ce8a
 
 
0339174
d1e1878
1504343
 
 
5930638
55e18c7
 
1504343
 
 
d48b6bc
d1e1878
cd9ce8a
d1e1878
9ae138d
 
 
 
 
 
 
 
4058a21
 
9ae138d
 
 
e8558a5
599750c
 
cd9ce8a
 
d1e1878
e8558a5
5930638
cd9ce8a
 
 
5930638
eba100b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
import numpy as np
import pandas as pd
import panel as pn
import xarray as xr
import param

import hvplot.xarray
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from datatree import open_datatree


def get_range(da):
    return  ( int(da.min().round() - 1), int(da.max().round() + 1),)

@pn.cache(max_items=32,policy='LRU',per_session=True)
def load_csv(path='./data/zarr_table.csv'):
    df = pd.read_csv(path,index_col=None)
    return df.sort_values(by="year") #inplace=True)

@pn.cache(max_items=4,policy='LRU',per_session=True)
def load_bathymetry(path='./data/bathy6min.nc'):
    return xr.open_dataset(path, decode_times=False, use_cftime=True)

@pn.cache(max_items=16,policy='LRU',per_session=True)
def load_zarr(path='./data/1H_file.zarr'):
    return open_datatree(path, engine='zarr')

def load_file(tree,selected_file):
    return tree[selected_file+"/"].ds

def filter_df(sorted_df,selected_file):
    # include  user_interface_url here
    dataframe = sorted_df[sorted_df["file_name"] == selected_file].drop(
                columns=[
                    "file_name",
                    "title",
                    "Conventions",
                    "featureType",
                    "date_update",
                    "ADCP_beam_angle",
                    "ADCP_ship_angle",
                    "middle_bin1_depth",
                    "heading_corr",
                    "pitch_corr",
                    "ampli_corr",
                    "pitch_roll_used",
                    "date_creation",
                    "ADCP_type",
                    "data_type",
                ]
            )
    # include  LOCAL_CDI_ID here
    dataframe2 = sorted_df[sorted_df["file_name"] == selected_file].drop(
                columns=[
                    "file_name",
                    "date_start",
                    "date_end",
                    "ADCP_frequency(kHz)",
                    "bin_length(meter)",
                    "year",
                ]
            )
    return dataframe.transpose(), dataframe2.transpose()

def filter_data(ds,longitude_range,latitude_range):
    return ds.where(
        (ds.LONGITUDE >= longitude_range[0])
        & (ds.LONGITUDE <= longitude_range[1])
        & (ds.LATITUDE >= latitude_range[0])
        & (ds.LATITUDE <= latitude_range[1]),
        drop=True,
    )


def quiver_depth_filtered(ax, ds, depth_range, scale_factor, color="blue"):
    """
    Plot quiver plot of mean current vectors filtered by depth.

    Parameters:
        ax (matplotlib.axes.Axes): The matplotlib axes object to plot on.
        ds (xarray.Dataset): The dataset containing the current data.
        depth_range (tuple): Tuple containing the minimum and maximum depth values for filtering.
        scale_factor (float): Scaling factor for the magnitude of the current vectors.
        color (str, optional): Color of the quiver arrows. Defaults to "blue".

    Returns:
        matplotlib.quiver.Quiver: The quiver plot object.
    """
    # Filter data based on depth range
    ds = ds.sel( PROFZ=slice(depth_range[1],depth_range[0]))

    # Calculate mean current vectors within the selected depth range
    u_mean = ds.UCUR.mean(dim="PROFZ", skipna=True)
    v_mean = ds.VCUR.mean(dim="PROFZ", skipna=True)

    # Extract longitude and latitude coordinates
    lon = ds.coords["LONGITUDE"].values
    lat = ds.coords["LATITUDE"].values

    # Plot quiver plot
    return ax.quiver(
        lon,
        lat,
        u_mean * scale_factor,
        v_mean * scale_factor,
        color=color,
        scale=2,
        width=0.001,
        headwidth=3,
        transform=ccrs.PlateCarree(),
    )

def bathy_uship_vship_bottom_depth(ds):
    """
    Plot maximum values of bathymetry, USHIP, VSHIP, and bottom depth over time.

    Parameters:
        ds (xarray.Dataset): Dataset containing the required variables.

    Returns:
        list: List of hvplot objects representing the plots of maximum values of bathymetry,
              USHIP, VSHIP, and bottom depth over time.
    """
    return [
        ds["BATHY"].max(dim="PROFZ").hvplot(x="TIME", width=400, height=200),
        ds["USHIP"].max(dim="PROFZ").hvplot(x="TIME", width=400, height=200),
        ds["VSHIP"].max(dim="PROFZ").hvplot(x="TIME", width=400, height=200),
        ds["BOTTOM_DEPTH"].max(dim="PROFZ").hvplot(x="TIME", width=400, height=200),
    ]


def corsen_data(ds, sample):
    """
    Downsample the dataset `ds` based on the number of vectors specified by `sample`.

    Parameters:
        ds (xarray.Dataset): Dataset to be downsampled.
        sample (int): Number of vectors used for downsampling.

    Returns:
        xarray.Dataset: Downsampled dataset.
    """
    coords = ["LATITUDE", "LONGITUDE"]
    corsen = max(1, ds.TIME.size // sample)
    return (
        ds.reset_coords(coords)
        .coarsen({"TIME": corsen}, boundary="trim")
        .mean()
        .set_coords(coords)
    )



def vectors_plot(ds, bathy, longitude_range, latitude_range ,
                 depth_range, depth_2_range, depth_3_range,
                 scale_factor, sample,
                 depth_2_checkbox=False, depth_3_checkbox=False, bathy_checkbox=False):
    """
    Plot vectors filtered by depth on a map with specified features.

    Parameters:
        ds (xarray.Dataset): Dataset containing current data.
        bathy (xarray.Dataset): Dataset containing bathymetry data.
        longitude_range (tuple): Tuple containing the minimum and maximum longitude values.
        latitude_range  (tuple): Tuple containing the minimum and maximum latitude values.
        depth_range (tuple): Tuple containing the minimum and maximum depth values for filtering.
        depth_2_range (tuple): Tuple containing the minimum and maximum depth values for filtering depth 2.
        depth_3_range (tuple): Tuple containing the minimum and maximum depth values for filtering depth 3.
        scale_factor (float): Scaling factor for the magnitude of the current vectors.
        sample (int): Number of vectors used for downsampling.
        depth_2_checkbox (bool, optional): Whether to plot vectors for depth 2. Defaults to False.
        depth_3_checkbox (bool, optional): Whether to plot vectors for depth 3. Defaults to False.
        bathy_checkbox (bool, optional): Whether to plot bathymetry. Defaults to False.

    Returns:
        matplotlib.figure.Figure: The generated plot.
    """
    # Create subplot with Mercator projection
    fig, ax = plt.subplots(figsize=(5, 4), subplot_kw={"projection": ccrs.Mercator()})

    # Apply data downsampling
    ds = corsen_data(ds, sample)

    # Plot vectors filtered by depth
    quiver_depth_filtered(ax, ds, depth_range, scale_factor, color="blue")
    if depth_2_checkbox:
        quiver_depth_filtered(ax, ds, depth_2_range, scale_factor, color="green")
    if depth_3_checkbox:
        quiver_depth_filtered(ax, ds, depth_3_range, scale_factor, color="red")

    # Add map features
    ax.add_feature(cfeature.COASTLINE)
    ax.add_feature(cfeature.BORDERS, linestyle=":")
    ax.add_feature(cfeature.LAND, color="lightgray")

    # Plot bathymetry if provided
    if bathy_checkbox:
        contour_levels = [-1000]
        ax.contour(bathy.longitude, bathy.latitude, bathy.z,
                   levels=contour_levels, colors="black", transform=ccrs.PlateCarree())

    # Set extent and add gridlines
    ax.set_extent([longitude_range[0], longitude_range[1],
                   latitude_range[0], latitude_range[1]])
    ax.gridlines(draw_labels=True)

    # Set labels and close plot
    plt.ylabel("Latitude", fontsize=15, labelpad=35)
    plt.xlabel("Longitude", fontsize=15, labelpad=20)
    #https://panel.holoviz.org/reference/panes/Matplotlib.html#using-the-matplotlib-pyplot-interface
    plt.close(fig) 
    return fig

class SADCP_Viewer(param.Parameterized):
    """
    A parameterized class for viewing SADCP data.
    
    This class provides widgets for selecting data parameters, updating data based on selections,
    and generating plots to visualize the SADCP data.
    
    Available functions:
        - update_name_options: Update dropdown options and slider ranges based on selected years and file.
        - update_plots: Update plots based on selected data and parameters.
    """

    # Load data and initialize widgets
    df = load_csv()
    bathy = load_bathymetry()
    tree=load_zarr()
    file_names = df["file_name"].tolist()
    years = sorted(df["year"].unique())

    # Widgets for selecting data parameters
    year_slider = pn.widgets.IntRangeSlider(name="Year Range", start=df["year"].min(), end=df["year"].max())
    file_dropdown = pn.widgets.Select(name="File Selector")
    longitude_slider = pn.widgets.RangeSlider(name="Longitude Range", start=-180, end=180, step=1)
    latitude_slider = pn.widgets.RangeSlider(name="Latitude Range", start=-90, end=90, step=1)
    depth_range_slider = pn.widgets.IntRangeSlider(start=100, end=300, value=(100, 300), step=1, name="Depth Range")
    depth_2_checkbox = pn.widgets.Checkbox(value=False, name="Depth 2 Checkbox")
    depth_3_checkbox = pn.widgets.Checkbox(value=False, name="Depth 3 Checkbox")
    depth_2_range_slider = pn.widgets.IntRangeSlider(start=100, end=300, value=(100, 300), step=1, name="Depth 2 Range")
    depth_3_range_slider = pn.widgets.IntRangeSlider(start=100, end=300, value=(100, 300), step=1, name="Depth 3 Range")
    num_vectors_slider = pn.widgets.IntSlider(start=40, end=800, step=1, value=100, name="Number of Vectors")
    scale_factor_slider = pn.widgets.FloatSlider(start=0.1, end=1, step=0.1, value=0.5, name="Scale Factor")
    bathy_checkbox = pn.widgets.Checkbox(value=False, name="Bathy Checkbox")

 
    data_table = pn.widgets.Tabulator(width=400, height=200)
    metadata_table = pn.widgets.Tabulator(width=600, height=800)
    # Download button is not working : TODO
    download_button = pn.widgets.Button(name="Download", button_type="primary")

    def __init__(self, **params):
        """
        Initialize the SADCP_Viewer class.
        
        Parameters:
            **params: Additional parameters to be passed to the superclass.
        """

        super(SADCP_Viewer, self).__init__(**params)
        self.file_dropdown.objects = self.file_names
        self.file_dropdown.value = (
            self.file_dropdown.objects[0] if self.file_dropdown.objects else None
        )
        self.update_name_options()

    @param.depends("year_slider.value", "file_dropdown.value", watch=True)
    def update_name_options(self):
        """
        Update dropdown options and slider ranges based on selected years and file.

        This function updates the dropdown options and slider ranges based on the selected years
        and file. It also loads the selected file's data and adjusts slider ranges accordingly.

        """
        # Extract selected start and end years
        start_year, end_year = self.year_slider.value
        
        # Filter DataFrame based on selected years and sort by year
        mask = (self.df["year"] >= start_year) & (self.df["year"] <= end_year)
        sorted_df = self.df[mask].sort_values(by="year")
        
        # Get unique file names
        files = sorted_df["file_name"].unique().tolist()
        
        # Update file dropdown options
        self.file_dropdown.options = files
        
        if files:
            selected_file = self.file_dropdown.value
            
            # Set default selected file if not selected or not in options
            if not selected_file or selected_file not in files:
                selected_file = files[0]
                self.file_dropdown.value = selected_file
            
            # Update data table and metadata table based on selected file
            self.data_table.value, self.metadata_table.value = filter_df(sorted_df, selected_file)
            
            # Load selected file's data
            self.ds = load_file(self.tree,selected_file)
            
            # Update slider ranges for longitude, latitude, and depth
            for slider, coord in zip([self.longitude_slider, self.latitude_slider, self.depth_range_slider,
                          self.depth_2_range_slider, self.depth_3_range_slider],
                         [self.ds.LONGITUDE, self.ds.LATITUDE, self.ds.PROFZ,self.ds.PROFZ,self.ds.PROFZ]):
                coord_range = get_range(coord)
                slider.start, slider.end, slider.value = coord_range[0], coord_range[1], coord_range


            # Close dataset to free up resources
            # self.ds.close()

    @param.depends(
        "year_slider.value",
        "file_dropdown.value",
        "depth_range_slider.value",
        "depth_2_checkbox.value",
        "depth_3_checkbox.value",
        "depth_2_range_slider.value",
        "depth_3_range_slider.value",
        "longitude_slider.value",
        "latitude_slider.value",
        "num_vectors_slider.value",
        "scale_factor_slider.value",
        "bathy_checkbox.value",
        watch=False,)
    def update_plots(self):
        """
        This function updates the plots based on the selected data and parameters.

        The function filters the data, generates additional plots, and updates the main vector plot based on the selected parameters.

        Returns:
            pn.Row: A Panel row containing the updated map plot and additional plots.

        """
        # Filter the data
        self.ds_filtered = filter_data(self.ds,self.longitude_slider.value,self.latitude_slider.value)

        # Prepare the plots shown in left
        # Update vector plots
        vector_plot = vectors_plot(self.ds_filtered, self.bathy, 
                                   self.longitude_slider.value, self.latitude_slider.value,
                                   self.depth_range_slider.value, self.depth_2_range_slider.value, self.depth_3_range_slider.value,
                                   self.scale_factor_slider.value,self.num_vectors_slider.value,
                                   depth_2_checkbox= self.depth_2_checkbox.value,
                                   depth_3_checkbox= self.depth_3_checkbox.value,
                                   bathy_checkbox=self.bathy_checkbox.value,
                                   )


        # Generate plots which will be plotted on the left row.
        self.plot_left = pn.Column(
                              # Here adjust the style option later TODO
                              # https://panel.holoviz.org/how_to/styling/matplotlib.html
                               pn.pane.Matplotlib(vector_plot, dpi=144),
                              # Add here the hvplot block of contour TODO
                                sizing_mode="stretch_both")

        # Generate additional plots which will be plotted on the right row.
        other_plots = bathy_uship_vship_bottom_depth(self.ds_filtered)
        self.plot_right = pn.Column(
            *(pn.pane.HoloViews(plot, width=400, height=200) for plot in other_plots),
            sizing_mode="stretch_width"
        )

        # Return a Panel row containing the updated map plot and additional plots
        return pn.Row(self.plot_left, self.plot_right, sizing_mode="stretch_both")


pn.extension("tabulator")
pn.config.theme = 'dark'

explorer = SADCP_Viewer()
# Instantiate the SADCP_Viewer class and create a template
tabs = pn.Tabs(
    ("Plots", pn.Column(explorer.update_plots)),
    (
        "Metadata",
        pn.Column(
            explorer.metadata_table, explorer.download_button, height=500, margin=10
        ),
    ),
)

sidebar = [
    pn.panel('./EuroGO-SHIP_logo_wide_tagline_1.2.png',width=300 ),
    """This application, developed in the frame of Euro Go Shop, helps to interactively visualise and download ship ADCP data.""",
    explorer.year_slider,
    explorer.file_dropdown,
    explorer.longitude_slider,
    explorer.latitude_slider,
    explorer.bathy_checkbox,
    explorer.depth_range_slider,
    explorer.depth_2_checkbox,
    explorer.depth_3_checkbox,
    explorer.depth_2_range_slider,
    explorer.depth_3_range_slider,
    explorer.num_vectors_slider,
    explorer.scale_factor_slider,
    explorer.data_table,
    """You can consult detailed information on this data in the metadata tab shown on the right.
       To download full dataset, please go to https://cdi.seadatanet.org/search 
      and search with LOCAL_CDI_ID indicated above.""",
    #pn.panel('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png', ),
#width=10)
]

template = pn.template.FastListTemplate(
    title="SADCP data Viewer", logo='https://avatars.githubusercontent.com/u/123177533?s=200&v=4',
    sidebar=sidebar, main=[tabs]
    
)
template.servable()