github-actions[bot] commited on
Commit ·
2dc4d04
1
Parent(s): e9832fd
Deploy from GitHub Actions
Browse files- plots/map.py +330 -26
- ui/pages/home.py +0 -1
- ui/pages/seasonal_maps.py +68 -11
plots/map.py
CHANGED
|
@@ -8,6 +8,7 @@ import numpy as np
|
|
| 8 |
import pandas as pd
|
| 9 |
import streamlit as st
|
| 10 |
from matplotlib.colors import LinearSegmentedColormap
|
|
|
|
| 11 |
from osgeo import gdal
|
| 12 |
|
| 13 |
from utils.data_loading import timer
|
|
@@ -213,8 +214,320 @@ def plot_seasonal_salinity_for_bays(
|
|
| 213 |
|
| 214 |
|
| 215 |
@timer(include_params=True)
|
| 216 |
-
def generate_seasonal_plot(
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
# Add debugging information
|
| 219 |
wbids = gpd.read_file(shapefile_path)
|
| 220 |
|
|
@@ -231,28 +544,19 @@ def generate_seasonal_plot(data, year, shapefile_path):
|
|
| 231 |
# Pre-transform to Web Mercator (EPSG:3857) here to avoid issues in plotting function
|
| 232 |
wbids = wbids.to_crs(epsg=3857)
|
| 233 |
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
}
|
| 250 |
-
)
|
| 251 |
-
|
| 252 |
-
return plot_seasonal_salinity_for_bays(
|
| 253 |
-
data,
|
| 254 |
-
year,
|
| 255 |
-
shapefile_path=shapefile_path,
|
| 256 |
-
wbids=wbids,
|
| 257 |
-
reporting_end_month=st.session_state.reporting_month,
|
| 258 |
)
|
|
|
|
| 8 |
import pandas as pd
|
| 9 |
import streamlit as st
|
| 10 |
from matplotlib.colors import LinearSegmentedColormap
|
| 11 |
+
from matplotlib.figure import Figure
|
| 12 |
from osgeo import gdal
|
| 13 |
|
| 14 |
from utils.data_loading import timer
|
|
|
|
| 214 |
|
| 215 |
|
| 216 |
@timer(include_params=True)
|
| 217 |
+
def generate_seasonal_plot(
|
| 218 |
+
data: pd.DataFrame,
|
| 219 |
+
parameter: str,
|
| 220 |
+
year: str,
|
| 221 |
+
shapefile_path: str,
|
| 222 |
+
reporting_end_month: int = 10,
|
| 223 |
+
basemap_provider=ctx.providers.USGS.USTopo, # type: ignore
|
| 224 |
+
alpha: float = 0.5,
|
| 225 |
+
) -> tuple[Figure, pd.DataFrame, pd.DataFrame]:
|
| 226 |
+
"""
|
| 227 |
+
Create seasonal plots of mean parameter values by WBID for specific bays.
|
| 228 |
+
|
| 229 |
+
Parameters
|
| 230 |
+
----------
|
| 231 |
+
data : pd.DataFrame
|
| 232 |
+
DataFrame containing measurements with lat/long
|
| 233 |
+
parameter : str
|
| 234 |
+
Parameter to plot (e.g., "Salinity", "Dissolved Oxygen")
|
| 235 |
+
year : str
|
| 236 |
+
Reporting Year to filter data for
|
| 237 |
+
shapefile_path : str
|
| 238 |
+
Path to WBID shapefile
|
| 239 |
+
reporting_end_month : int
|
| 240 |
+
Last month of reporting year (1-12)
|
| 241 |
+
basemap_provider : ctx.providers
|
| 242 |
+
Contextily map provider
|
| 243 |
+
alpha : float
|
| 244 |
+
Transparency of basemap
|
| 245 |
+
|
| 246 |
+
Returns
|
| 247 |
+
-------
|
| 248 |
+
tuple[Figure, pd.DataFrame, pd.DataFrame]
|
| 249 |
+
- Figure: Matplotlib figure containing the plot
|
| 250 |
+
- DataFrame: Raw data used in plot
|
| 251 |
+
- DataFrame: Processed quarterly means
|
| 252 |
+
"""
|
| 253 |
+
if st.session_state.get("DEBUG", False):
|
| 254 |
+
debugging_info(data, shapefile_path)
|
| 255 |
+
|
| 256 |
+
# Load WBIDs
|
| 257 |
+
wbids = gpd.read_file(shapefile_path)
|
| 258 |
+
if wbids.crs is None:
|
| 259 |
+
wbids.set_crs(epsg=6439, inplace=True)
|
| 260 |
+
wbids = wbids.to_crs(epsg=3857)
|
| 261 |
+
|
| 262 |
+
# Filter for specific bays
|
| 263 |
+
bay_wbids = [
|
| 264 |
+
"1061A",
|
| 265 |
+
"1061B",
|
| 266 |
+
"1061C",
|
| 267 |
+
"1061D",
|
| 268 |
+
"1061E",
|
| 269 |
+
"1061F",
|
| 270 |
+
"1061G",
|
| 271 |
+
"1061H",
|
| 272 |
+
"1055A",
|
| 273 |
+
]
|
| 274 |
+
year_data = data[
|
| 275 |
+
(data["Reporting_Year"] == int(year)) & (data["WBID"].isin(bay_wbids))
|
| 276 |
+
].copy()
|
| 277 |
+
|
| 278 |
+
# Calculate quarterly means
|
| 279 |
+
seasonal_means = calculate_quarterly_means(
|
| 280 |
+
year_data, parameter, reporting_end_month
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
# Create the plot
|
| 284 |
+
fig = create_quarterly_maps(
|
| 285 |
+
seasonal_means=seasonal_means,
|
| 286 |
+
wbids=wbids[wbids["WBID"].isin(bay_wbids)],
|
| 287 |
+
parameter=parameter,
|
| 288 |
+
year=year,
|
| 289 |
+
reporting_end_month=reporting_end_month,
|
| 290 |
+
basemap_provider=basemap_provider,
|
| 291 |
+
alpha=alpha,
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
return fig, year_data, seasonal_means
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def calculate_quarterly_means(
|
| 298 |
+
data: pd.DataFrame, parameter: str, reporting_end_month: int
|
| 299 |
+
) -> pd.DataFrame:
|
| 300 |
+
"""Calculate quarterly means for the parameter"""
|
| 301 |
+
# Add quarter information
|
| 302 |
+
data["quarter"] = data["Activity_Start_Date_Time"].apply(
|
| 303 |
+
lambda x: get_quarter(x, reporting_end_month)
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
# Calculate means
|
| 307 |
+
return (
|
| 308 |
+
data.groupby(["WBID", "quarter"], observed=True)["Org_Result_Value"]
|
| 309 |
+
.mean()
|
| 310 |
+
.reset_index()
|
| 311 |
+
.rename(columns={"Org_Result_Value": parameter})
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def get_quarter(date, reporting_end_month: int) -> str:
|
| 316 |
+
"""Calculate quarter based on reporting year end month"""
|
| 317 |
+
month = date.month
|
| 318 |
+
month_offset = (12 - reporting_end_month) % 12
|
| 319 |
+
adjusted_month = ((month + month_offset) % 12) or 12
|
| 320 |
+
return f"Q{((adjusted_month - 1) // 3) + 1}"
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def create_quarterly_maps(
|
| 324 |
+
seasonal_means: pd.DataFrame,
|
| 325 |
+
wbids: gpd.GeoDataFrame,
|
| 326 |
+
parameter: str,
|
| 327 |
+
year: str,
|
| 328 |
+
reporting_end_month: int,
|
| 329 |
+
basemap_provider,
|
| 330 |
+
alpha: float = 0.5,
|
| 331 |
+
) -> Figure:
|
| 332 |
+
"""Create the quarterly map visualization"""
|
| 333 |
+
fig = plt.figure(figsize=(20, 14))
|
| 334 |
+
|
| 335 |
+
# Set up color scheme
|
| 336 |
+
colors = ["#08519c", "#73a9cf", "#fee090", "#fc8d59", "#d73027"]
|
| 337 |
+
cmap = LinearSegmentedColormap.from_list("custom", colors, N=100)
|
| 338 |
+
|
| 339 |
+
# Calculate plot bounds
|
| 340 |
+
bounds = wbids.total_bounds
|
| 341 |
+
extent = calculate_map_extent(bounds)
|
| 342 |
+
|
| 343 |
+
# Create grid layout with tight spacing
|
| 344 |
+
gs = fig.add_gridspec(
|
| 345 |
+
2,
|
| 346 |
+
2,
|
| 347 |
+
width_ratios=[1, 1],
|
| 348 |
+
wspace=0.05,
|
| 349 |
+
hspace=-0.15,
|
| 350 |
+
left=0.02,
|
| 351 |
+
right=0.98,
|
| 352 |
+
top=0.95,
|
| 353 |
+
bottom=0.05,
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
# Plot each quarter
|
| 357 |
+
for idx, quarter in enumerate(["Q1", "Q2", "Q3", "Q4"]):
|
| 358 |
+
ax = fig.add_subplot(gs[idx // 2, idx % 2])
|
| 359 |
+
plot_quarter(
|
| 360 |
+
ax=ax,
|
| 361 |
+
quarter=quarter,
|
| 362 |
+
seasonal_means=seasonal_means,
|
| 363 |
+
wbids=wbids,
|
| 364 |
+
parameter=parameter,
|
| 365 |
+
year=year,
|
| 366 |
+
reporting_end_month=reporting_end_month,
|
| 367 |
+
cmap=cmap,
|
| 368 |
+
extent=extent,
|
| 369 |
+
basemap_provider=basemap_provider,
|
| 370 |
+
alpha=alpha,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
add_colorbar(fig, seasonal_means, parameter, cmap)
|
| 374 |
+
|
| 375 |
+
return fig
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def plot_quarter(
|
| 379 |
+
ax: plt.Axes, # type: ignore
|
| 380 |
+
quarter: str,
|
| 381 |
+
seasonal_means: pd.DataFrame,
|
| 382 |
+
wbids: gpd.GeoDataFrame,
|
| 383 |
+
parameter: str,
|
| 384 |
+
year: str,
|
| 385 |
+
reporting_end_month: int,
|
| 386 |
+
cmap: LinearSegmentedColormap,
|
| 387 |
+
extent: list[float],
|
| 388 |
+
basemap_provider,
|
| 389 |
+
alpha: float = 0.5,
|
| 390 |
+
) -> None:
|
| 391 |
+
"""Plot a single quarter's map"""
|
| 392 |
+
# Get data for this quarter
|
| 393 |
+
quarter_data = seasonal_means[seasonal_means["quarter"] == quarter]
|
| 394 |
+
merged = wbids.merge(quarter_data, on="WBID", how="left")
|
| 395 |
+
|
| 396 |
+
# Get value range for consistent colormap
|
| 397 |
+
vmin = seasonal_means[parameter].min()
|
| 398 |
+
vmax = get_parameter_max_value(parameter, seasonal_means[parameter].max())
|
| 399 |
+
|
| 400 |
+
# Plot WBIDs
|
| 401 |
+
merged.plot(
|
| 402 |
+
column=parameter,
|
| 403 |
+
ax=ax,
|
| 404 |
+
cmap=cmap,
|
| 405 |
+
vmin=vmin,
|
| 406 |
+
vmax=vmax,
|
| 407 |
+
alpha=0.7,
|
| 408 |
+
missing_kwds={"color": "lightgrey", "alpha": 0.5},
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
# Add basemap
|
| 412 |
+
ctx.add_basemap(ax, source=basemap_provider, zoom=11, alpha=alpha) # type: ignore
|
| 413 |
+
|
| 414 |
+
# Set map extent
|
| 415 |
+
ax.set_xlim(extent[0], extent[1])
|
| 416 |
+
ax.set_ylim(extent[2], extent[3])
|
| 417 |
+
|
| 418 |
+
# Get date range for this quarter
|
| 419 |
+
date_range = get_quarter_dates(quarter, int(year), reporting_end_month)
|
| 420 |
+
|
| 421 |
+
# Create title with appropriate padding based on position
|
| 422 |
+
title_pad = 15 if int(quarter[1]) <= 2 else 5 # Top row vs bottom row
|
| 423 |
+
ax.set_title(
|
| 424 |
+
f"Quarter {quarter[1]} Mean {parameter}\n{date_range}",
|
| 425 |
+
pad=title_pad,
|
| 426 |
+
fontsize=10,
|
| 427 |
+
)
|
| 428 |
+
ax.set_axis_off()
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def get_parameter_max_value(parameter: str, data_max: float) -> float:
|
| 432 |
+
"""Get the maximum value for colormap scaling based on parameter"""
|
| 433 |
+
parameter_limits = {
|
| 434 |
+
"Salinity": 40,
|
| 435 |
+
"Dissolved Oxygen": 12,
|
| 436 |
+
"pH": 9,
|
| 437 |
+
"Temperature, Water": 35,
|
| 438 |
+
"Turbidity": None, # Use data max
|
| 439 |
+
"Total Nitrogen": None,
|
| 440 |
+
"Total Phosphorus": None,
|
| 441 |
+
"Fecal Coliform (MPN)": None,
|
| 442 |
+
}
|
| 443 |
+
return parameter_limits.get(parameter, data_max)
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def calculate_map_extent(
|
| 447 |
+
bounds: np.ndarray, buffer_fraction: float = 0.05
|
| 448 |
+
) -> list[float]:
|
| 449 |
+
"""Calculate map extent with buffer"""
|
| 450 |
+
x_buffer = (bounds[2] - bounds[0]) * buffer_fraction
|
| 451 |
+
y_buffer = (bounds[3] - bounds[1]) * buffer_fraction
|
| 452 |
+
return [
|
| 453 |
+
bounds[0] - x_buffer, # xmin
|
| 454 |
+
bounds[2] + x_buffer, # xmax
|
| 455 |
+
bounds[1] - y_buffer, # ymin
|
| 456 |
+
bounds[3] + y_buffer, # ymax
|
| 457 |
+
]
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def get_quarter_dates(quarter: str, year: int, reporting_end_month: int) -> str:
|
| 461 |
+
"""Get date range string for a quarter"""
|
| 462 |
+
# Calculate first month of reporting year
|
| 463 |
+
first_month = (reporting_end_month % 12) + 1
|
| 464 |
+
|
| 465 |
+
# Calculate start month for each quarter
|
| 466 |
+
quarter_num = int(quarter[1])
|
| 467 |
+
start_month = ((first_month - 1 + ((quarter_num - 1) * 3)) % 12) + 1
|
| 468 |
+
end_month = ((start_month + 2) % 12) or 12
|
| 469 |
+
|
| 470 |
+
# Determine correct years for start and end dates
|
| 471 |
+
start_year = year - 1 if start_month > reporting_end_month else year
|
| 472 |
+
end_year = start_year if end_month >= start_month else start_year + 1
|
| 473 |
+
|
| 474 |
+
# Create date objects
|
| 475 |
+
start_date = pd.Timestamp(f"{start_year}-{start_month:02d}-01")
|
| 476 |
+
end_date = pd.Timestamp(
|
| 477 |
+
f"{end_year}-{end_month:02d}-{pd.Timestamp(f'{end_year}-{end_month:02d}').days_in_month}"
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
return f"{start_date.strftime('%b %d, %Y')} - {end_date.strftime('%b %d, %Y')}"
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def add_colorbar(
|
| 484 |
+
fig: Figure,
|
| 485 |
+
seasonal_means: pd.DataFrame,
|
| 486 |
+
parameter: str,
|
| 487 |
+
cmap: LinearSegmentedColormap,
|
| 488 |
+
) -> None:
|
| 489 |
+
"""Add colorbar to the figure"""
|
| 490 |
+
# Get value range
|
| 491 |
+
vmin = seasonal_means[parameter].min()
|
| 492 |
+
vmax = get_parameter_max_value(parameter, seasonal_means[parameter].max())
|
| 493 |
+
|
| 494 |
+
# Create colorbar
|
| 495 |
+
norm = plt.Normalize(vmin=vmin, vmax=vmax) # type: ignore
|
| 496 |
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
|
| 497 |
+
sm.set_array([])
|
| 498 |
+
|
| 499 |
+
# Get parameter unit
|
| 500 |
+
unit = get_parameter_unit(parameter)
|
| 501 |
+
label = f"{parameter} ({unit})" if unit else parameter
|
| 502 |
+
|
| 503 |
+
# Add colorbar to figure
|
| 504 |
+
fig.colorbar(
|
| 505 |
+
sm,
|
| 506 |
+
ax=fig.axes,
|
| 507 |
+
orientation="vertical",
|
| 508 |
+
label=label,
|
| 509 |
+
pad=0.01,
|
| 510 |
+
fraction=0.015,
|
| 511 |
+
ticks=np.arange(0, vmax + 5, 5), # Add ticks every 5 units
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def get_parameter_unit(parameter: str) -> str:
|
| 516 |
+
"""Get the unit for a parameter"""
|
| 517 |
+
parameter_units = {
|
| 518 |
+
"Salinity": "ppt",
|
| 519 |
+
"Dissolved Oxygen": "mg/L",
|
| 520 |
+
"pH": "",
|
| 521 |
+
"Temperature, Water": "°C",
|
| 522 |
+
"Turbidity": "NTU",
|
| 523 |
+
"Total Nitrogen": "mg/L",
|
| 524 |
+
"Total Phosphorus": "mg/L",
|
| 525 |
+
"Fecal Coliform (MPN)": "MPN/100mL",
|
| 526 |
+
}
|
| 527 |
+
return parameter_units.get(parameter, "")
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
def debugging_info(data: pd.DataFrame, shapefile_path: str) -> None:
|
| 531 |
# Add debugging information
|
| 532 |
wbids = gpd.read_file(shapefile_path)
|
| 533 |
|
|
|
|
| 544 |
# Pre-transform to Web Mercator (EPSG:3857) here to avoid issues in plotting function
|
| 545 |
wbids = wbids.to_crs(epsg=3857)
|
| 546 |
|
| 547 |
+
st.write("Debug Info:")
|
| 548 |
+
st.write(
|
| 549 |
+
{
|
| 550 |
+
"Shapefile CRS": wbids.crs,
|
| 551 |
+
"Input Data CRS": data.crs
|
| 552 |
+
if isinstance(data, gpd.GeoDataFrame)
|
| 553 |
+
else "Not a GeoDataFrame",
|
| 554 |
+
"GDAL Version": gdal.VersionInfo()
|
| 555 |
+
if "osgeo.gdal" in sys.modules
|
| 556 |
+
else "Not available",
|
| 557 |
+
"GeoPandas Version": gpd.__version__,
|
| 558 |
+
"Python Version": sys.version,
|
| 559 |
+
"File exists": Path(shapefile_path).exists(),
|
| 560 |
+
"Associated files": list(Path(shapefile_path).parent.glob("*.*")),
|
| 561 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
)
|
ui/pages/home.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
import io
|
| 2 |
|
| 3 |
-
import pandas as pd
|
| 4 |
import streamlit as st
|
| 5 |
|
| 6 |
from components import render_stations_map
|
|
|
|
| 1 |
import io
|
| 2 |
|
|
|
|
| 3 |
import streamlit as st
|
| 4 |
|
| 5 |
from components import render_stations_map
|
ui/pages/seasonal_maps.py
CHANGED
|
@@ -1,33 +1,90 @@
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
|
|
|
|
| 3 |
from dashboard_analytics import log_visit
|
| 4 |
from plots.map import generate_seasonal_plot
|
| 5 |
-
from utils.data_loading import
|
| 6 |
|
| 7 |
log_visit("Seasonal Maps")
|
| 8 |
|
| 9 |
-
|
| 10 |
st.title("Seasonal Variations")
|
| 11 |
raw_df = st.session_state.data["raw_df"]
|
| 12 |
-
# Use Reporting_Year instead of calendar year
|
| 13 |
-
years = sorted(raw_df["Reporting_Year"].unique())
|
| 14 |
|
| 15 |
# Move filters to sidebar
|
| 16 |
st.sidebar.markdown("### Filter Options")
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
)
|
|
|
|
|
|
|
|
|
|
| 20 |
selected_year = st.sidebar.selectbox(
|
| 21 |
-
"Select Year:",
|
| 22 |
)
|
| 23 |
|
| 24 |
if not raw_df.empty:
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
shapefile_path="data/waterbody_ids/Waterbody_IDs_(WBIDs).shp",
|
|
|
|
| 30 |
)
|
| 31 |
st.pyplot(fig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
else:
|
| 33 |
st.warning("No data available for seasonal analysis.")
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
|
| 3 |
import streamlit as st
|
| 4 |
|
| 5 |
+
from components import render_filtered_data_preview
|
| 6 |
from dashboard_analytics import log_visit
|
| 7 |
from plots.map import generate_seasonal_plot
|
| 8 |
+
from utils.data_loading import add_lat_long, get_stations_data
|
| 9 |
|
| 10 |
log_visit("Seasonal Maps")
|
| 11 |
|
|
|
|
| 12 |
st.title("Seasonal Variations")
|
| 13 |
raw_df = st.session_state.data["raw_df"]
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# Move filters to sidebar
|
| 16 |
st.sidebar.markdown("### Filter Options")
|
| 17 |
+
|
| 18 |
+
# Allow selection of any parameter
|
| 19 |
+
parameters = sorted(raw_df["Org_Analyte_Name"].unique())
|
| 20 |
+
selected_parameter = st.sidebar.selectbox(
|
| 21 |
+
"Select Parameter:",
|
| 22 |
+
parameters,
|
| 23 |
+
index=parameters.index("Salinity") if "Salinity" in parameters else 0,
|
| 24 |
+
key="seasonal_parameter_select",
|
| 25 |
)
|
| 26 |
+
|
| 27 |
+
# Use Reporting_Year for consistency
|
| 28 |
+
years = sorted(raw_df["Reporting_Year"].unique(), reverse=True)
|
| 29 |
selected_year = st.sidebar.selectbox(
|
| 30 |
+
"Select Year:", years, index=0, key="seasonal_year_select"
|
| 31 |
)
|
| 32 |
|
| 33 |
if not raw_df.empty:
|
| 34 |
+
# Filter data for selected parameter
|
| 35 |
+
param_data = raw_df[raw_df["Org_Analyte_Name"] == selected_parameter].copy()
|
| 36 |
+
|
| 37 |
+
# Add lat/long information using existing function
|
| 38 |
+
param_data = add_lat_long(param_data, get_stations_data())
|
| 39 |
+
|
| 40 |
+
# Generate plot and get data
|
| 41 |
+
fig, raw_data, plot_data = generate_seasonal_plot(
|
| 42 |
+
data=param_data,
|
| 43 |
+
parameter=selected_parameter,
|
| 44 |
+
year=str(selected_year),
|
| 45 |
shapefile_path="data/waterbody_ids/Waterbody_IDs_(WBIDs).shp",
|
| 46 |
+
reporting_end_month=st.session_state.reporting_month,
|
| 47 |
)
|
| 48 |
st.pyplot(fig)
|
| 49 |
+
|
| 50 |
+
# Add data viewers
|
| 51 |
+
with st.expander("Chart Data"):
|
| 52 |
+
render_filtered_data_preview(
|
| 53 |
+
plot_data, display_columns=plot_data.columns.tolist()
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
# Add CSV download button for chart data
|
| 57 |
+
csv_buffer = io.StringIO()
|
| 58 |
+
plot_data.to_csv(csv_buffer, index=False)
|
| 59 |
+
st.download_button(
|
| 60 |
+
label=f"Download Chart Data for {selected_parameter} (CSV)",
|
| 61 |
+
data=csv_buffer.getvalue(),
|
| 62 |
+
file_name=f"{selected_parameter}_seasonal_{selected_year}_chart_data.csv",
|
| 63 |
+
mime="text/csv",
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
with st.expander("Raw Data"):
|
| 67 |
+
render_filtered_data_preview(
|
| 68 |
+
raw_data,
|
| 69 |
+
[
|
| 70 |
+
"Activity_Start_Date_Time",
|
| 71 |
+
"Reporting_Year",
|
| 72 |
+
"WBID",
|
| 73 |
+
"Station_Number",
|
| 74 |
+
"Sample_Position",
|
| 75 |
+
"Org_Analyte_Name",
|
| 76 |
+
"Org_Result_Value",
|
| 77 |
+
],
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Add CSV download button for raw data
|
| 81 |
+
csv_buffer = io.StringIO()
|
| 82 |
+
raw_data.to_csv(csv_buffer, index=False)
|
| 83 |
+
st.download_button(
|
| 84 |
+
label=f"Download Raw Data for {selected_parameter} (CSV)",
|
| 85 |
+
data=csv_buffer.getvalue(),
|
| 86 |
+
file_name=f"{selected_parameter}_seasonal_{selected_year}_raw_data.csv",
|
| 87 |
+
mime="text/csv",
|
| 88 |
+
)
|
| 89 |
else:
|
| 90 |
st.warning("No data available for seasonal analysis.")
|