import cftime import fsspec import matplotlib.pyplot as plt import numpy as np import pandas as pd import panel as pn import xarray as xr from ipywidgets import ( Checkbox, # FloatRangeSlider, FloatSlider, IntRangeSlider, IntSlider, # interactive, ) import param pn.extension("tabulator") def greg_0h(jourjul): import math # Julian days start and end at noon. # Julian day 2440000 begins at 00 hours, May 23, 1968. # Round the input Julian day number to avoid precision errors fac = 10**9 jourjul = np.round(fac * jourjul + 0.5) / fac # Calculate seconds in the day secs = (jourjul % 1) * 24 * 3600 # Round seconds to avoid precision errors secs = np.round(fac * secs + 0.5) / fac # Gregorian calendar conversion j = math.floor(jourjul) - 1721119 in_ = 4 * j - 1 y = math.floor(in_ / 146097) j = in_ - 146097 * y in_ = math.floor(j / 4) in_ = 4 * in_ + 3 j = math.floor(in_ / 1461) d = math.floor(((in_ - 1461 * j) + 4) / 4) in_ = 5 * d - 3 m = math.floor(in_ / 153) d = math.floor(((in_ - 153 * m) + 5) / 5) y = y * 100 + j if m < 10: mo = m + 3 yr = y else: mo = m - 9 yr = y + 1 hour = math.floor(secs / 3600) mins = math.floor((secs % 3600) / 60) sec = int(secs % 60) gtime = [yr, mo, d, hour, mins, sec] return gtime def fix_time(ds): from datetime import datetime, timedelta time = ds["TIME"] time2 = time.dropna(dim="MAXT") time2 = time2.reindex_like(time, method="nearest") jourjul = [greg_0h(jourjul) for jourjul in time2.values] date = [ datetime(jourjul[0], jourjul[1], jourjul[2], jourjul[3], jourjul[4], jourjul[5]) for jourjul in jourjul ] date = xr.DataArray(date, dims="MAXT") return ds.assign(TIME=date) @pn.cache(max_items=32,policy='LRU',per_session=True) def load_csv(): df = pd.read_csv('./data/zarr_table.csv',index_col=None) df.sort_values(by="year") #inplace=True) return df @pn.cache(max_items=4,policy='LRU',per_session=True) def load_bathymetry(): #url="https://data-eurogoship.ifremer.fr/bathymetrie/bathy6min.nc" #fs = fsspec.filesystem("https") return xr.open_dataset('/data/bathy6min.nc', decode_times=False, use_cftime=True) @pn.cache(max_items=16,policy='LRU',per_session=True) def load_zarr(selected_file): from datatree import open_datatree tree= open_datatree('/data/1H_file.zarr', engine='zarr') return tree[selected_file+"/"].ds def filter_df(sorted_df,selected_file): 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", ] ) 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 quiver_depth_filterd(ax,ds_filtered, depth1, depth_range_slider, scale_factor_slider, color="blue"): import cartopy.crs as ccrs depth_filtered = ds_filtered.where( (depth1 > depth_range_slider.value[0]) & (depth1 <= depth_range_slider.value[1]) ) lon = ds_filtered.coords["LONGITUDE"].values lat = ds_filtered.coords["LATITUDE"].values # Moyenne des vecteurs de courant sur la plage de profondeur sélectionnée u_mean = depth_filtered.UCUR.mean(dim="MAXZ", skipna=True) v_mean = depth_filtered.VCUR.mean(dim="MAXZ", skipna=True) return ax.quiver( lon, lat, u_mean * scale_factor_slider.value, v_mean * scale_factor_slider.value, color=color, scale=2, width=0.001, headwidth=3, transform=ccrs.PlateCarree(), ) class SADCP_Viewer(param.Parameterized): df = load_csv() bathy = load_bathymetry() file_names = df["file_name"].tolist() years = sorted(df["year"].unique()) year_slider = pn.widgets.IntRangeSlider( name="Year Range", start=df["year"].min(), end=df["year"].max() ) file_dropdown = pn.widgets.Select(name="File Selector") data_table = pn.widgets.Tabulator(df, name="metadata", height=200, width=300) # data_table2 = pn.widgets.Tabulator(df2, name="metadata", height=900, width=400) 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") depth2_range_slider = pn.widgets.IntRangeSlider( start=100, end=300, value=(100, 300), step=1, name="Depth 2 Range" ) depth3_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") plot = pn.pane.HoloViews() plot_map = pn.pane.Matplotlib(width=800, height=600, sizing_mode="fixed") data_table = pn.widgets.Tabulator(width=400, height=200) metadata_table = pn.widgets.Tabulator(width=600, height=800) download_button = pn.widgets.Button(name="Download", button_type="primary") # plot = pn.Column() def __init__(self, **params): super(SADCP_Viewer, self).__init__(**params) self.file_dropdown.objects = self.get_file_list() 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): start_year, end_year = self.year_slider.value mask = (self.df["year"] >= start_year) & (self.df["year"] <= end_year) sorted_df = self.df[mask].sort_values(by="year") files = sorted_df["file_name"].unique().tolist() self.file_dropdown.options = files if files: selected_file = self.file_dropdown.value if not selected_file or selected_file not in files: selected_file = files[0] self.file_dropdown.value = selected_file self.data_table.value, self.metadata_table.value = filter_df(sorted_df,selected_file) self.ds=load_zarr(selected_file) lon_range = ( int(self.ds["LONGITUDE"].min().round() - 1), int(self.ds["LONGITUDE"].max().round() + 1), ) lat_range = ( int(self.ds["LATITUDE"].min().round() - 1), int(self.ds["LATITUDE"].max().round() + 1), ) self.depth1 = abs(self.ds.coords["PROFZ"]) deph_range = (int(self.depth1.min()), int(self.depth1.max())) self.longitude_slider.start = lon_range[0] self.longitude_slider.end = lon_range[1] self.longitude_slider.value = lon_range self.latitude_slider.start = lat_range[0] self.latitude_slider.end = lat_range[1] self.latitude_slider.value = lat_range self.depth_range_slider.start = deph_range[0] self.depth_range_slider.end = deph_range[1] self.depth_range_slider.value = deph_range self.depth2_range_slider.start = deph_range[0] self.depth2_range_slider.end = deph_range[1] self.depth2_range_slider.value = deph_range self.depth3_range_slider.start = deph_range[0] self.depth3_range_slider.end = deph_range[1] self.depth3_range_slider.value = deph_range # self.update_plots() self.ds.close() @param.depends( "year_slider.value", "file_dropdown.value", "depth_range_slider.value", "depth_2_checkbox.value", "depth_3_checkbox.value", "depth2_range_slider.value", "depth3_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): self.ds_filtered = self.filter_data() # vector_plot = self.vectors_plot() other_plots = self.hvplot_plots() # self.plot_map = pn.pane.Matplotlib(vector_plot, width=800, height=600, sizing_mode="fixed") self.plot = pn.Column( *(pn.pane.HoloViews(plot, width=400, height=200) for plot in other_plots), sizing_mode="stretch_width" ) vector_plot = self.vectors_plot() # Update vector plot self.plot_map.object = vector_plot # other_plots = self.plots() # Update other plots # self.plot.objects = [*(pn.pane.HoloViews(p, width=400, height=200) for p in other_plots)] # self.plot.object=plot return pn.Row(self.plot_map, self.plot, sizing_mode="stretch_both") def filter_data(self): return self.ds.where( (self.ds.LONGITUDE >= self.longitude_slider.start) & (self.ds.LONGITUDE <= self.longitude_slider.end) & (self.ds.LATITUDE >= self.latitude_slider.start) & (self.ds.LATITUDE <= self.latitude_slider.end), drop=True, ) def vectors_plot(self): import cartopy.crs as ccrs import cartopy.feature as cfeature self.ds_filtered = self.filter_data() # return self.ds_filtered['VSHIP'].hvplot(x='TIME',width=400, height=200) #if 'BATHY' in self.ds_filtered else hvplot.show(hvplot.text(0, 0, "No data available", fontsize=12)) fig, ax = plt.subplots( figsize=(8, 7), subplot_kw={"projection": ccrs.Mercator()} ) coords = ["LATITUDE", "LONGITUDE"] corsen = max(1, self.ds_filtered.MAXT.size // self.num_vectors_slider.value) self.ds_filtered = ( self.ds_filtered.reset_coords(coords) .coarsen({"MAXT": corsen}, boundary="trim") .mean() .set_coords(coords) ) # self.ds_filtered =self.ds_filtered.coarsen(MAXT = corsen, side = "center", boundary = "trim").mean()[["LONGITUDE", "LONGITUDE", "TIME"]].isel(MAXZ=0) quiver_depth_filterd(ax,self.ds_filtered, self.depth1, self.depth_range_slider,self.scale_factor_slider,color="blue") if self.depth_2_checkbox.value: quiver_depth_filterd(ax,self.ds_filtered, self.depth1, self.depth2_range_slider,self.scale_factor_slider,color="green") if self.depth_3_checkbox.value: quiver_depth_filterd(ax,self.ds_filtered, self.depth1, self.depth3_range_slider,self.scale_factor_slider,color="red") ax.add_feature(cfeature.COASTLINE) ax.add_feature(cfeature.BORDERS, linestyle=":") ax.add_feature(cfeature.LAND, color="lightgray") if self.bathy_checkbox.value: contour_levels = [-1000] ax.contour( self.bathy.longitude, self.bathy.latitude, self.bathy.z, levels=contour_levels, colors="black", transform=ccrs.PlateCarree(), ) ax.set_extent( [ self.longitude_slider.value[0], self.longitude_slider.value[1], self.latitude_slider.value[0], self.latitude_slider.value[1], ] ) ax.gridlines(draw_labels=True) plt.ylabel("Latitude", fontsize=15, labelpad=35) plt.xlabel("Longitude", fontsize=15, labelpad=20) plt.close() return fig # pn.pane.Matplotlib(fig,width=800, height=600, sizing_mode="fixed", name="Plot") # @param.depends( 'file_dropdown.value', 'longitude_slider.value', 'latitude_slider.value', watch=True) def hvplot_plots(self): import hvplot.xarray return [ self.ds_filtered["BATHY"].max(dim="MAXZ").hvplot(x="TIME", width=400, height=200), self.ds_filtered["USHIP"].max(dim="MAXZ").hvplot(x="TIME", width=400, height=200), self.ds_filtered["VSHIP"].max(dim="MAXZ").hvplot(x="TIME", width=400, height=200), self.ds_filtered["BOTTOM_DEPTH"].max(dim="MAXZ").hvplot(x="TIME", width=400, height=200), ] def get_file_list(self): return self.file_names 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 = [ """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.depth2_range_slider, explorer.depth3_range_slider, explorer.num_vectors_slider, explorer.scale_factor_slider, explorer.data_table, ] template = pn.template.FastListTemplate( title="SADCP data Viewer", sidebar=sidebar, main=[tabs] ) template.servable()