Spaces:
Sleeping
Sleeping
Add global heatmap preview and full-wave defaults
Browse files
app.py
CHANGED
|
@@ -274,6 +274,69 @@ def prepare_results(
|
|
| 274 |
return display_df, fig, status
|
| 275 |
|
| 276 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
def run_query(
|
| 278 |
model: str,
|
| 279 |
variable: str,
|
|
@@ -342,12 +405,13 @@ def run_global_download(
|
|
| 342 |
max_horizon: int,
|
| 343 |
grid_step: float,
|
| 344 |
raw_members: str,
|
| 345 |
-
|
|
|
|
| 346 |
try:
|
| 347 |
token = get_token()
|
| 348 |
|
| 349 |
selected_from_dropdown = [name.lower() for name in (variables or [])]
|
| 350 |
-
custom_list = parse_variable_list(custom_variables)
|
| 351 |
variable_names = list(dict.fromkeys([*selected_from_dropdown, *custom_list]))
|
| 352 |
if not variable_names:
|
| 353 |
raise ValueError("Select at least one variable or enter custom variable names.")
|
|
@@ -361,10 +425,12 @@ def run_global_download(
|
|
| 361 |
|
| 362 |
members = build_members_list(raw_members, model)
|
| 363 |
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
|
|
|
|
|
|
| 368 |
|
| 369 |
df = fetch_wave_history(
|
| 370 |
token=token,
|
|
@@ -392,6 +458,10 @@ def run_global_download(
|
|
| 392 |
for col in ("forecasted_time", "forecasted_at"):
|
| 393 |
if col in df.columns:
|
| 394 |
df[col] = pd.to_datetime(df[col], utc=True, errors="coerce")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
preview = df.head(500).copy()
|
| 397 |
preview_rows = len(preview)
|
|
@@ -402,17 +472,37 @@ def run_global_download(
|
|
| 402 |
finally:
|
| 403 |
tmp.close()
|
| 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
status = (
|
| 406 |
f"Fetched {len(df)} rows across {len(variable_names)} variable(s) "
|
| 407 |
-
f"for {valid_iso}. Showing the first {preview_rows} rows."
|
| 408 |
)
|
| 409 |
|
| 410 |
-
return status, preview, tmp.name
|
| 411 |
|
| 412 |
except ConfigurationError as exc:
|
| 413 |
-
return f"⚠️ {exc}", None, None
|
| 414 |
except Exception as exc: # noqa: BLE001
|
| 415 |
-
return f"❌ {exc}", None, None
|
| 416 |
|
| 417 |
|
| 418 |
def default_time_window(hours_back: int = 6, hours_forward: int = 24) -> Tuple[str, str]:
|
|
@@ -543,7 +633,7 @@ def build_interface() -> gr.Blocks:
|
|
| 543 |
global_variables_input = gr.CheckboxGroup(
|
| 544 |
label="Variables",
|
| 545 |
choices=[code.upper() for code in WAVE_VARIABLES.keys()],
|
| 546 |
-
value=[
|
| 547 |
info="Select one or more parameters to include in the download.",
|
| 548 |
)
|
| 549 |
global_custom_variables_input = gr.Textbox(
|
|
@@ -582,6 +672,12 @@ def build_interface() -> gr.Blocks:
|
|
| 582 |
placeholder="e.g. 0,1,2,3",
|
| 583 |
info="Leave blank for default control member. Ignored for deterministic model.",
|
| 584 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
global_submit = gr.Button("Download global snapshot", variant="primary")
|
| 586 |
|
| 587 |
with gr.Column(scale=2):
|
|
@@ -592,6 +688,7 @@ def build_interface() -> gr.Blocks:
|
|
| 592 |
interactive=False,
|
| 593 |
wrap=False,
|
| 594 |
)
|
|
|
|
| 595 |
global_file_output = gr.File(label="Download CSV")
|
| 596 |
|
| 597 |
global_submit.click(
|
|
@@ -605,8 +702,9 @@ def build_interface() -> gr.Blocks:
|
|
| 605 |
global_max_horizon_input,
|
| 606 |
global_grid_step_input,
|
| 607 |
global_members_input,
|
|
|
|
| 608 |
],
|
| 609 |
-
outputs=[global_status_output, global_preview_output, global_file_output],
|
| 610 |
)
|
| 611 |
|
| 612 |
demo.queue()
|
|
|
|
| 274 |
return display_df, fig, status
|
| 275 |
|
| 276 |
|
| 277 |
+
def make_global_heatmap(
|
| 278 |
+
df: pd.DataFrame,
|
| 279 |
+
alias: str,
|
| 280 |
+
variable_label: str,
|
| 281 |
+
valid_time_iso: str,
|
| 282 |
+
) -> Optional[go.Figure]:
|
| 283 |
+
"""Generate a Plotly heatmap from a gridded dataframe."""
|
| 284 |
+
if alias not in df.columns:
|
| 285 |
+
return None
|
| 286 |
+
|
| 287 |
+
subset = df.dropna(subset=[alias, "lat", "lon"]).copy()
|
| 288 |
+
if subset.empty:
|
| 289 |
+
return None
|
| 290 |
+
|
| 291 |
+
subset["lat"] = subset["lat"].astype(float)
|
| 292 |
+
subset["lon"] = subset["lon"].astype(float)
|
| 293 |
+
|
| 294 |
+
if "forecasted_time" in subset.columns:
|
| 295 |
+
subset["forecasted_time"] = pd.to_datetime(
|
| 296 |
+
subset["forecasted_time"], utc=True, errors="coerce"
|
| 297 |
+
)
|
| 298 |
+
subset = subset.dropna(subset=["forecasted_time"])
|
| 299 |
+
if not subset.empty:
|
| 300 |
+
target_time = subset["forecasted_time"].min()
|
| 301 |
+
subset = subset[subset["forecasted_time"] == target_time]
|
| 302 |
+
|
| 303 |
+
if "member" in subset.columns:
|
| 304 |
+
subset = subset.sort_values("member").groupby(["lat", "lon"], as_index=False).first()
|
| 305 |
+
|
| 306 |
+
subset = subset.sort_values(["lat", "lon"])
|
| 307 |
+
subset = subset.drop_duplicates(subset=["lat", "lon"], keep="first")
|
| 308 |
+
|
| 309 |
+
if subset.empty:
|
| 310 |
+
return None
|
| 311 |
+
|
| 312 |
+
pivot = subset.pivot(index="lat", columns="lon", values=alias)
|
| 313 |
+
if pivot.empty:
|
| 314 |
+
return None
|
| 315 |
+
|
| 316 |
+
pivot = pivot.sort_index(ascending=False)
|
| 317 |
+
lats = pivot.index.to_list()
|
| 318 |
+
lons = pivot.columns.to_list()
|
| 319 |
+
values = pivot.values
|
| 320 |
+
|
| 321 |
+
fig = go.Figure(
|
| 322 |
+
data=go.Heatmap(
|
| 323 |
+
x=lons,
|
| 324 |
+
y=lats,
|
| 325 |
+
z=values,
|
| 326 |
+
colorscale="Viridis",
|
| 327 |
+
colorbar=dict(title=variable_label),
|
| 328 |
+
)
|
| 329 |
+
)
|
| 330 |
+
fig.update_layout(
|
| 331 |
+
title=f"{variable_label} at {valid_time_iso}",
|
| 332 |
+
xaxis_title="Longitude",
|
| 333 |
+
yaxis_title="Latitude",
|
| 334 |
+
template="plotly_white",
|
| 335 |
+
margin=dict(l=60, r=20, t=60, b=40),
|
| 336 |
+
)
|
| 337 |
+
return fig
|
| 338 |
+
|
| 339 |
+
|
| 340 |
def run_query(
|
| 341 |
model: str,
|
| 342 |
variable: str,
|
|
|
|
| 405 |
max_horizon: int,
|
| 406 |
grid_step: float,
|
| 407 |
raw_members: str,
|
| 408 |
+
preview_variable: str,
|
| 409 |
+
) -> Tuple[str, Optional[pd.DataFrame], Optional[go.Figure], Optional[str]]:
|
| 410 |
try:
|
| 411 |
token = get_token()
|
| 412 |
|
| 413 |
selected_from_dropdown = [name.lower() for name in (variables or [])]
|
| 414 |
+
custom_list = [name.lower() for name in parse_variable_list(custom_variables)]
|
| 415 |
variable_names = list(dict.fromkeys([*selected_from_dropdown, *custom_list]))
|
| 416 |
if not variable_names:
|
| 417 |
raise ValueError("Select at least one variable or enter custom variable names.")
|
|
|
|
| 425 |
|
| 426 |
members = build_members_list(raw_members, model)
|
| 427 |
|
| 428 |
+
alias_map = {}
|
| 429 |
+
variables_payload = []
|
| 430 |
+
for name in variable_names:
|
| 431 |
+
alias = make_alias(name)
|
| 432 |
+
alias_map[name] = alias
|
| 433 |
+
variables_payload.append({"name": name, "level": DEFAULT_LEVEL, "alias": alias})
|
| 434 |
|
| 435 |
df = fetch_wave_history(
|
| 436 |
token=token,
|
|
|
|
| 458 |
for col in ("forecasted_time", "forecasted_at"):
|
| 459 |
if col in df.columns:
|
| 460 |
df[col] = pd.to_datetime(df[col], utc=True, errors="coerce")
|
| 461 |
+
if {"forecasted_time", "forecasted_at"}.issubset(df.columns):
|
| 462 |
+
df["lead_time_hours"] = (
|
| 463 |
+
(df["forecasted_time"] - df["forecasted_at"]).dt.total_seconds() / 3600.0
|
| 464 |
+
)
|
| 465 |
|
| 466 |
preview = df.head(500).copy()
|
| 467 |
preview_rows = len(preview)
|
|
|
|
| 472 |
finally:
|
| 473 |
tmp.close()
|
| 474 |
|
| 475 |
+
preview_choice = (preview_variable or variable_names[0]).strip().lower()
|
| 476 |
+
if preview_choice not in alias_map:
|
| 477 |
+
preview_choice = variable_names[0]
|
| 478 |
+
alias_for_map = alias_map[preview_choice]
|
| 479 |
+
variable_label, unit = WAVE_VARIABLES.get(
|
| 480 |
+
preview_choice,
|
| 481 |
+
(preview_choice.upper(), ""),
|
| 482 |
+
)
|
| 483 |
+
display_label = f"{variable_label} ({unit})" if unit else variable_label
|
| 484 |
+
|
| 485 |
+
map_fig = make_global_heatmap(df, alias_for_map, display_label, valid_iso)
|
| 486 |
+
|
| 487 |
+
lead_info = ""
|
| 488 |
+
if "lead_time_hours" in df.columns:
|
| 489 |
+
lead_series = df["lead_time_hours"].dropna()
|
| 490 |
+
if not lead_series.empty:
|
| 491 |
+
lead_info = (
|
| 492 |
+
f" Lead times {lead_series.min():.0f}–{lead_series.max():.0f} h."
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
status = (
|
| 496 |
f"Fetched {len(df)} rows across {len(variable_names)} variable(s) "
|
| 497 |
+
f"for {valid_iso}.{lead_info} Showing the first {preview_rows} rows."
|
| 498 |
)
|
| 499 |
|
| 500 |
+
return status, preview, map_fig, tmp.name
|
| 501 |
|
| 502 |
except ConfigurationError as exc:
|
| 503 |
+
return f"⚠️ {exc}", None, None, None
|
| 504 |
except Exception as exc: # noqa: BLE001
|
| 505 |
+
return f"❌ {exc}", None, None, None
|
| 506 |
|
| 507 |
|
| 508 |
def default_time_window(hours_back: int = 6, hours_forward: int = 24) -> Tuple[str, str]:
|
|
|
|
| 633 |
global_variables_input = gr.CheckboxGroup(
|
| 634 |
label="Variables",
|
| 635 |
choices=[code.upper() for code in WAVE_VARIABLES.keys()],
|
| 636 |
+
value=[code.upper() for code in WAVE_VARIABLES.keys()],
|
| 637 |
info="Select one or more parameters to include in the download.",
|
| 638 |
)
|
| 639 |
global_custom_variables_input = gr.Textbox(
|
|
|
|
| 672 |
placeholder="e.g. 0,1,2,3",
|
| 673 |
info="Leave blank for default control member. Ignored for deterministic model.",
|
| 674 |
)
|
| 675 |
+
global_preview_variable_input = gr.Dropdown(
|
| 676 |
+
label="Preview variable for map",
|
| 677 |
+
choices=[code.upper() for code in WAVE_VARIABLES.keys()],
|
| 678 |
+
value="SWH",
|
| 679 |
+
info="Used for the heatmap preview below.",
|
| 680 |
+
)
|
| 681 |
global_submit = gr.Button("Download global snapshot", variant="primary")
|
| 682 |
|
| 683 |
with gr.Column(scale=2):
|
|
|
|
| 688 |
interactive=False,
|
| 689 |
wrap=False,
|
| 690 |
)
|
| 691 |
+
global_map_output = gr.Plot(label="Global map preview", show_label=True)
|
| 692 |
global_file_output = gr.File(label="Download CSV")
|
| 693 |
|
| 694 |
global_submit.click(
|
|
|
|
| 702 |
global_max_horizon_input,
|
| 703 |
global_grid_step_input,
|
| 704 |
global_members_input,
|
| 705 |
+
global_preview_variable_input,
|
| 706 |
],
|
| 707 |
+
outputs=[global_status_output, global_preview_output, global_map_output, global_file_output],
|
| 708 |
)
|
| 709 |
|
| 710 |
demo.queue()
|