Spaces:
Build error
Build error
| 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) | |
| def load_csv(): | |
| url="https://data-eurogoship.ifremer.fr/data/table.csv" | |
| fs = fsspec.filesystem("https") | |
| with fs.open(url) as f: | |
| df = pd.read_csv(f,index_col=None) | |
| df.sort_values(by="year") #inplace=True) | |
| return df | |
| def load_bathymetry(): | |
| url="https://data-eurogoship.ifremer.fr/bathymetrie/bathy6min.nc" | |
| fs = fsspec.filesystem("https") | |
| return xr.open_dataset(fs.open(url), decode_times=False, use_cftime=True) | |
| def load_netCDF(selected_file): | |
| import os | |
| data_dir = "https://data-eurogoship.ifremer.fr/copy_seadatanet/" | |
| file_path = os.path.join(data_dir, selected_file) | |
| fs = fsspec.filesystem("https") | |
| ds = ( | |
| xr.open_dataset( | |
| fs.open(file_path), decode_cf=True, decode_times=False, engine="scipy" | |
| ) | |
| .squeeze() | |
| .set_coords(["LONGITUDE", "LATITUDE", "TIME", "PROFZ"]) | |
| .set_xindex("PROFZ") | |
| .set_xindex("TIME") | |
| ) | |
| ds = ds.where( | |
| (ds.VCUR_SEADATANET_QC == 49) | |
| & (ds.UCUR_SEADATANET_QC == 49) | |
| #& (ds.WCUR_SEADATANET_QC == 49) | |
| #& (ds.ECUR_SEADATANET_QC == 49) | |
| # & (ds.PGOOD_SEADATANET_QC == 49) | |
| #& (ds.ECI_SEADATANET_QC == 49) | |
| #& (ds.BOTTOM_DEPTH_SEADATANET_QC == 49) | |
| # & (ds.BATHY_SEADATANET_QC == 49) | |
| & (ds.USHIP_SEADATANET_QC == 49) | |
| & (ds.VSHIP_SEADATANET_QC == 49), | |
| drop=False) | |
| ds = ds[ | |
| [ | |
| "TIME", | |
| "USHIP", "VSHIP", "BATHY", "BOTTOM_DEPTH", | |
| "UCUR", "VCUR", | |
| # "ROLL", "PITCH", "TR_TEMP", "HEADING", "U_BOTTOM", "V_BOTTOM", | |
| # "WCUR", "ECUR", "PGOOD", "ECI", "UTIDE", "VTIDE", | |
| ] | |
| ] | |
| return fix_time(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() | |
| 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_netCDF(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() | |
| 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() | |