Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 98 additions & 48 deletions mcass-dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def read_snow_situation_file(filepath):
df = pd.read_csv(filepath)
# Make sure the Date column is of type datetime
df['date'] = pd.to_datetime(df['date'])
print(f"read_snow_situation_file: df.head: \n{df.columns}\n{df.head()}")
# print(f"read_snow_situation_file: df.head: \n{df.columns}\n{df.head()}")
return df

def read_basin_geometry(filepath):
Expand All @@ -66,6 +66,32 @@ def read_basin_geometry(filepath):
#gdf = gpd.read_file('www/CA-discharge_basins_for_viz.gpkg') # No overlaps
gdf = gpd.read_file(filepath) # With overlaps

# Print column names and head of the geodataframe
print(f"read_basin_geometry: gdf.head: \n{gdf.columns}\n{gdf.head()}")

# Print unique values of gauges_COUNTRY
print(f"read_basin_geometry: unique values of gauges_COUNTRY: {gdf['gauges_COUNTRY'].unique()}")

# Drop basins of the country of Kyrgystan or don't have a country reference
gdf = gdf[gdf['gauges_COUNTRY'] != 'KYG']
gdf = gdf[gdf['gauges_COUNTRY'] != 'None']

# Further drop selected basins like 16936
gdf = gdf[gdf['CODE'] != '16936']

# Also drop the river with name "Kulduk"
gdf = gdf[gdf['gauges_RIVER'] != 'Kulduk']

# And drop the rivers with name "Karadarya" and "Chatkal"
gdf = gdf[gdf['gauges_RIVER'] != 'Karadarya']

# Now we drop transboundary rivers in the south of the Ferghana valley
#gdf = gdf[gdf['gauges_RIVER'] != 'Isfara']
gdf = gdf[gdf['CODE'] != '16175'] # River Koksu

# Drop Kyzylsu West river
gdf = gdf[gdf['gauges_RIVER'] != 'Kyzylsu West']

# Convert Multipolygons to Polygons, keeping the largest polygon
gdf['geometry'] = gdf['geometry'].apply(
lambda x: max(x.geoms, key=lambda a: a.area) if x.geom_type == 'MultiPolygon' else x)
Expand All @@ -88,35 +114,48 @@ def read_basin_geometry(filepath):
# Replace gauges_RIVER=='None' with 'Name unknown'
gdf['gauges_RIVER'] = gdf['gauges_RIVER'].replace('None', '<river name unknown>')

# Create a column with labels for the basins
gdf['label'] = gdf['CODE'] + ' - ' + gdf['gauges_RIVER']
# Create a display_name column with river name and basin area
gdf['display_name'] = gdf.apply(
lambda row: f"{row['gauges_RIVER']} ({int(row['area_km2'])} km²)"
if pd.notna(row['area_km2'])
else row['gauges_RIVER'],
axis=1
)

# Create a column with labels for the basins (used for map hover)
gdf['label'] = gdf['display_name']

# Sort rows by area_km2 in descending order (plot smallest last)
gdf = gdf.sort_values('area_km2', ascending=False)

# Read subbasins snow situation file
# Currently, not operational. The file shows snow situation data from end of
# March 2024.
df = read_snow_situation_file('data/subbasins_merged_data.csv')

# Merge columns 'swe_threshold' and 'hs_threshold' from df into gdf by 'CODE'
gdf = gdf.merge(df[['basin_id', 'swe_threshold', 'hs_threshold']],
left_on='CODE', right_on='basin_id', how='left')

# Read the regional snow situation file
df_regional = read_snow_situation_file('data/regions_merged_data.csv')
# Rename the swe and hs threshold columns to avoid conflicts
df_regional = df_regional.rename(columns={'swe_threshold': 'swe_threshold_regional',
'hs_threshold': 'hs_threshold_regional'})
# Merge columns 'swe_threshold_regional' and 'hs_threshold_regional' from
# df_regional into gdf by 'REGION'.
# We need to do a fuzzy merge because the 'REGION' column in gdf contains
# the full name of the region, while the 'REGION' column in df_regional
# contains the abbreviation of the region.
# Split the values in 'REGION' in gdf by '_' and take the first part
gdf['REG'] = gdf['REGION'].str.split('_').str[0]
gdf = gdf.merge(df_regional[['basin_id', 'swe_threshold_regional', 'hs_threshold_regional']],
left_on='REG', right_on='basin_id', how='left')

# Only merge subbasins_merged_data.csv if it exists and contains current year data
subbasins_file = 'data/subbasins_merged_data.csv'
if os.path.exists(subbasins_file):
try:
df = read_snow_situation_file(subbasins_file)
# Check if current year is present in the data
current_year = dt.datetime.now().year
if (df['date'].dt.year == current_year).any():
gdf = gdf.merge(df[['basin_id', 'swe_threshold', 'hs_threshold']],
left_on='CODE', right_on='basin_id', how='left')
except Exception as e:
print(f"Warning: Could not merge {subbasins_file}: {e}")

# Only merge regions_merged_data.csv if it exists and contains current year data
regions_file = 'data/regions_merged_data.csv'
if os.path.exists(regions_file):
try:
df_regional = read_snow_situation_file(regions_file)
current_year = dt.datetime.now().year
if (df_regional['date'].dt.year == current_year).any():
df_regional = df_regional.rename(columns={'swe_threshold': 'swe_threshold_regional',
'hs_threshold': 'hs_threshold_regional'})
gdf['REG'] = gdf['REGION'].str.split('_').str[0]
gdf = gdf.merge(df_regional[['basin_id', 'swe_threshold_regional', 'hs_threshold_regional']],
left_on='REG', right_on='basin_id', how='left')
except Exception as e:
print(f"Warning: Could not merge {regions_file}: {e}")

print(gdf.head())

Expand All @@ -131,10 +170,10 @@ def get_basin_selector_names_in_list(gdf):
gdf (geodataframe) basin geometry

Return:
list of basin codes and basin names
list of display names (river names with basin area, no CODEs)
"""
gdf_for_names = gdf.sort_values(['REGION', 'CODE'], ascending=[True, True])
options_list = list(gdf_for_names['label'].values)
gdf_for_names = gdf.sort_values(['REGION', 'gauges_RIVER'], ascending=[True, True])
options_list = list(gdf_for_names['display_name'].values)
return options_list

def get_region_selector_names_in_list(gdf):
Expand All @@ -160,13 +199,13 @@ def update_gdf_with_selected_basin(selected_basin, view_option):

Arguments:
gdf (geodataframe): GeoDataFrame with basin geometries
selected_basin (list): list of the selected basins
selected_basin (list): list of the selected basins (display names for sub-basin, region names for regional)

Return:
geodataframe with updated selected column
"""
#print("DEBUG: calling update_gdf_with_selected_basin with selected_basin: ", selected_basin)
#print("DEBUG: calling update_gdf_with_selected_basin with view_option: ", view_option)
print("DEBUG: calling update_gdf_with_selected_basin with selected_basin: ", selected_basin)
print("DEBUG: calling update_gdf_with_selected_basin with view_option: ", view_option)
try:
if selected_basin is not None and len(selected_basin) > 0:
if view_option == 'Regional':
Expand All @@ -177,8 +216,11 @@ def update_gdf_with_selected_basin(selected_basin, view_option):
else:
# Set all basins to False
gdf['selected'] = False
# Set the selected basin to True only if it is not empty
gdf.loc[gdf['label'] == selected_basin[0], 'selected'] = True
# Convert display name to CODE for matching
basin_code = display_name_to_basin_code.get(selected_basin[0])
if basin_code:
# Set the selected basin to True only if it is not empty
gdf.loc[gdf['CODE'] == basin_code, 'selected'] = True
return gdf
except Exception as e:
print(f'Error in update_gdf_with_selected_basin: \n {e}')
Expand Down Expand Up @@ -223,10 +265,10 @@ def get_subbasin_code_from_tap(x, y):
# Get the polygon with the minimum distance
clicked = gdf_projected.iloc[clicked_polygon_index]
#output.object=output.object+f'\nclicked: {clicked}'
#print("DEBUG: clicked['label']: ", clicked['label'])
basin_code = clicked['label']
#basin_name = clicked['BASIN']
return basin_code
#print("DEBUG: clicked['display_name']: ", clicked['display_name'])
# Return the display name (river name with basin area)
display_name = clicked['display_name']
return display_name
return None

def get_region_from_tap(x, y):
Expand Down Expand Up @@ -275,7 +317,7 @@ def read_current_data_for_basin(basin_code):
delimiter='\t')
# Make sure the Date column is of type datetime
dfcurrent['date'] = pd.to_datetime(dfcurrent['date'])
print(f"read_current_data_for_basin: dfcurrent.head: \n{dfcurrent.columns}\n{dfcurrent.tail()}")
#print(f"read_current_data_for_basin: dfcurrent.head: \n{dfcurrent.columns}\n{dfcurrent.head()}")
return dfcurrent
except Exception as e:
return f'Error in read_current_data_for_basin: \n {e}'
Expand All @@ -295,6 +337,7 @@ def read_previous_year_data_for_basin(basin_code):
# Add the difference in years between the first date in dfcurrent and
# the first date in dfprevious to the dates in dfprevious
dfprevious['date'] = dfprevious['date'] + pd.DateOffset(years=dfcurrent['date'].dt.year.values[0] - dfprevious['date'].dt.year.values[0])
print(f"read_previous_year_data_for_basin: dfprevious.head: \n{dfprevious.columns}\n{dfprevious.head()}")
return dfprevious
except Exception as e:
return f'Error in read_previous_year_data_for_basin: \n {e}'
Expand All @@ -315,6 +358,7 @@ def read_climate_data_for_basin(basin_code):
# Add the difference in years between the first date in dfcurrent and
# the first date in dfclimate to the dates in dfclimate
dfclimate['date'] = dfclimate['date'] + pd.DateOffset(years=dfcurrent['date'].dt.year.values[0] - dfclimate['date'].dt.year.values[0])
print(f"read_climate_data_for_basin: dfclimate.head: \n{dfclimate.columns}\n{dfclimate.head()}")
return dfclimate
except Exception as e:
return f'Error in read_climate_data_for_basin: \n {e}'
Expand All @@ -330,9 +374,13 @@ def read_climate_data_for_basin(basin_code):
basins_list = get_basin_selector_names_in_list(gdf)
regions_list = get_region_selector_names_in_list(gdf)

# Create mapping dictionaries for CODE <-> display name conversion
basin_code_to_display_name = dict(zip(gdf['CODE'], gdf['display_name']))
display_name_to_basin_code = dict(zip(gdf['display_name'], gdf['CODE']))

# Create a StaticText widget
basin_code_widget = pn.widgets.StaticText(name='Basin Code', value='')
output = pn.pane.Str("Default message. Prints: Basin code and name upon click on a basin.")
basin_code_widget = pn.widgets.StaticText(name='Basin name', value='')
output = pn.pane.Str("Default message. Prints: Basin name upon click on a basin.")

# Toggle variable in a widget
variable_options = pn.widgets.RadioButtonGroup(
Expand Down Expand Up @@ -491,9 +539,8 @@ def update_basin_selection_widget_with_region_selection(view_option):
basin_selection.param.value)
def plot_subbasin_data(variable, basin):
try:
# Read the basin code
#basin_code = get_subbasin_code(x, y)
basin_code = basin[0].split(' - ')[0]
# Read the basin code from display name
basin_code = display_name_to_basin_code.get(basin[0])
# Read the current data for the basin
dfcurrent = read_current_data_for_basin(basin_code)
# Get forecast data for the basin
Expand Down Expand Up @@ -529,7 +576,7 @@ def plot_subbasin_data(variable, basin):
dfforecast, vdims=['Q50_SWE'], label='Forecast',
kdims=['date']).opts(line_dash='dashed',
color=current_year_color, tools=['hover'])
title_str = f'SWE situation for basin of river {river_name} (gauge {basin_code})'
title_str = f'SWE situation for basin of river {river_name}'
ylabel_str = 'SWE (mm)'
elif variable == 'HS':
# Create an empty hv.curve object
Expand Down Expand Up @@ -559,7 +606,7 @@ def plot_subbasin_data(variable, basin):
dfforecast, vdims=['Q50_HS'], label='Forecast',
kdims=['date']).opts(line_dash='dashed',
color=current_year_color, tools=['hover'])
title_str = f'HS situation for basin of river {river_name} (gauge {basin_code})'
title_str = f'HS situation for basin of river {river_name}'
ylabel_str = 'HS (m)'
else:
area_climate = hv.Area(
Expand All @@ -582,7 +629,7 @@ def plot_subbasin_data(variable, basin):
dfforecast, vdims=['Q50_ROF'], label='Forecast',
kdims=['date']).opts(line_dash='dashed',
color=current_year_color, tools=['hover'])
title_str = f'Melt situation for basin of river {river_name} (gauge {basin_code})'
title_str = f'Melt situation for basin of river {river_name}'
ylabel_str = 'SM (mm)'
fig = (area_climate * curve_previous * curve_current * curve_forecast)\
.opts(
Expand Down Expand Up @@ -611,14 +658,17 @@ def plot_region_data(variable, basin):
basin_code = basin[0].split(' - ')[0]
# Read the current data for the basin
dfcurrent = read_current_data_for_basin(basin_code)
print(f"current data for basin {basin_code}: dfcurrent.head: \n{dfcurrent.columns}\n{dfcurrent.head()}")
# Get forecast data for the basin
dfforecast = dfcurrent[dfcurrent['FC'] == True]
dfcurrent = dfcurrent[dfcurrent['FC'] == False]
#output.object=f'\n\n{dfcurrent.head()}'
# Read previous year data for the basin
dfprevious = read_previous_year_data_for_basin(basin_code)
print(f"previous year data for basin {basin_code}: dfprevious.head: \n{dfprevious.columns}\n{dfprevious.head()}")
# Read the climate data for the basin
dfclimate = read_climate_data_for_basin(basin_code)
print(f"climate data for basin {basin_code}: dfclimate.head: \n{dfclimate.columns}\n{dfclimate.head()}")
#output.object=output.object+f'\n\n{dfclimate.head()}'
# Adapt the name of the river basin for the title
if basin_code == 'AMU_DARYA':
Expand Down Expand Up @@ -790,7 +840,7 @@ def get_snow_plot(value, variable, basin):
variable_options,
pn.pane.Markdown("<b>Select granularity of view:</b>\nRegional view: Show snow development in a regional basin.\nSub-basin view: Show snow development in a sub-basin."),
view_options,
pn.pane.Markdown("<b>Search for sub-basin by hydropost code:</b>"),
pn.pane.Markdown("<b>Search for sub-basin by river name:</b>"),
basin_selection,
pn.pane.Markdown(""),
refs],
Expand Down