import gradio as gr import xarray as xr import pandas as pd import numpy as np import plotly.graph_objects as go from datetime import datetime, timedelta import warnings import logging import traceback warnings.filterwarnings('ignore') # Set up detailed logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Catalog configuration - using latest.zarr URLs as per dynamical-org/notebooks CATALOG = { "NOAA GFS Analysis (Hourly)": { "url": "https://data.dynamical.org/noaa/gfs/analysis-hourly/latest.zarr?email=treesixtyweather@gmail.com", "type": "analysis", "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m", "mean_sea_level_pressure"] }, "NOAA GFS Forecast": { "url": "https://data.dynamical.org/noaa/gfs/forecast/latest.zarr?email=treesixtyweather@gmail.com", "type": "forecast", "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m", "mean_sea_level_pressure"] }, "NOAA GEFS Analysis": { "url": "https://data.dynamical.org/noaa/gefs/analysis/latest.zarr?email=treesixtyweather@gmail.com", "type": "analysis", "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m"] }, "NOAA GEFS Forecast (35-day)": { "url": "https://data.dynamical.org/noaa/gefs/forecast-35-day/latest.zarr?email=treesixtyweather@gmail.com", "type": "forecast", "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m"] }, "NOAA HRRR Forecast (48-hour)": { "url": "https://data.dynamical.org/noaa/hrrr/forecast-48-hour/latest.zarr?email=treesixtyweather@gmail.com", "type": "forecast", "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m"] } } # Cache for loaded datasets dataset_cache = {} def load_dataset(dataset_name, use_cache=True): """Load a dataset from the Dynamical catalog""" logger.info(f"=== Loading dataset: {dataset_name} ===") if use_cache and dataset_name in dataset_cache: logger.info(f"Dataset found in cache: {dataset_name}") return dataset_cache[dataset_name], None try: url = CATALOG[dataset_name]["url"] logger.info(f"Opening zarr store at: {url}") # Open zarr store - using approach from dynamical-org/notebooks logger.info("Opening zarr store with chunks=None") ds = xr.open_zarr(url, chunks=None) logger.info(f"Successfully opened zarr store") logger.info(f"Dataset dimensions: {dict(ds.dims)}") logger.info(f"Dataset variables: {list(ds.data_vars)}") logger.info(f"Dataset coordinates: {list(ds.coords)}") if use_cache: dataset_cache[dataset_name] = ds logger.info(f"Dataset cached: {dataset_name}") return ds, None except Exception as e: error_msg = f"Error loading dataset: {str(e)}" logger.error(f"=== ERROR loading {dataset_name} ===") logger.error(f"URL: {CATALOG[dataset_name]['url']}") logger.error(f"Exception type: {type(e).__name__}") logger.error(f"Exception message: {str(e)}") logger.error(f"Traceback:\n{traceback.format_exc()}") return None, error_msg def create_map_visualization(dataset_name, variable, time_index=0): """Create an interactive map visualization of the selected variable""" logger.info(f"=== Creating map visualization ===") logger.info(f"Dataset: {dataset_name}, Variable: {variable}, Time index: {time_index}") try: ds, error = load_dataset(dataset_name) if ds is None: logger.error(f"Dataset loading returned None: {error}") return None, f"Error loading dataset: {dataset_name}\n{error}" logger.info(f"Dataset loaded successfully") # Check if variable exists if variable not in ds.variables: available_vars = list(ds.data_vars) logger.error(f"Variable '{variable}' not found. Available: {available_vars}") return None, f"Variable '{variable}' not found. Available: {available_vars}" logger.info(f"Variable '{variable}' found in dataset") # Get the data data_var = ds[variable] logger.info(f"Variable shape: {data_var.shape}, dims: {data_var.dims}") # Handle time dimension if 'time' in data_var.dims: logger.info(f"Time dimension found, length: {len(ds.time)}") if time_index >= len(ds.time): time_index = 0 data_var = data_var.isel(time=time_index) logger.info(f"Selected time index: {time_index}") # Handle ensemble dimension if present if 'ensemble' in data_var.dims: logger.info(f"Ensemble dimension found, selecting ensemble 0") data_var = data_var.isel(ensemble=0) logger.info(f"Data variable shape after slicing: {data_var.shape}") # Load data into memory (subsample for performance) step = max(1, len(ds.latitude) // 200) # Limit to ~200 points per dimension logger.info(f"Subsampling with step: {step}") data_var = data_var.isel(latitude=slice(None, None, step), longitude=slice(None, None, step)) logger.info(f"Computing data values...") data_values = data_var.compute().values logger.info(f"Data values shape: {data_values.shape}, min: {data_values.min()}, max: {data_values.max()}") # Get coordinates lats = ds.latitude.isel(latitude=slice(None, None, step)).values lons = ds.longitude.isel(longitude=slice(None, None, step)).values logger.info(f"Lat range: [{lats.min()}, {lats.max()}], Lon range: [{lons.min()}, {lons.max()}]") # Create plotly figure fig = go.Figure(data=go.Heatmap( z=data_values, x=lons, y=lats, colorscale='RdBu_r', hovertemplate='Lat: %{y:.2f}
Lon: %{x:.2f}
Value: %{z:.2f}' )) time_str = "" if 'time' in ds[variable].dims: time_val = pd.to_datetime(ds.time.isel(time=time_index).values) time_str = f" - {time_val.strftime('%Y-%m-%d %H:%M UTC')}" fig.update_layout( title=f"{dataset_name}: {variable}{time_str}", xaxis_title="Longitude", yaxis_title="Latitude", height=600, hovermode='closest' ) logger.info(f"Map visualization created successfully") return fig, f"Successfully loaded {dataset_name}" except Exception as e: error_msg = f"Error creating visualization: {str(e)}" logger.error(f"=== ERROR creating visualization ===") logger.error(f"Exception type: {type(e).__name__}") logger.error(f"Exception message: {str(e)}") logger.error(f"Traceback:\n{traceback.format_exc()}") return None, error_msg def get_point_forecast(dataset_name, lat, lon, variable): """Get forecast data for a specific point""" logger.info(f"=== Getting point forecast ===") logger.info(f"Dataset: {dataset_name}, Lat: {lat}, Lon: {lon}, Variable: {variable}") try: ds, error = load_dataset(dataset_name) if ds is None: logger.error(f"Dataset loading failed: {error}") return None, f"Error loading dataset: {error}" if variable not in ds.variables: logger.error(f"Variable '{variable}' not found in dataset") return None, f"Variable '{variable}' not found in dataset" logger.info(f"Selecting nearest point to ({lat}, {lon})") # Find nearest point data_var = ds[variable].sel(latitude=lat, longitude=lon, method='nearest') # Handle ensemble dimension if 'ensemble' in data_var.dims: logger.info(f"Handling ensemble dimension") data_var = data_var.isel(ensemble=0) logger.info(f"Point data shape: {data_var.shape}, dims: {data_var.dims}") # Load data logger.info(f"Computing point data values...") data_values = data_var.compute().values logger.info(f"Point data computed, shape: {data_values.shape}") # Create time series plot if 'time' in ds[variable].dims: times = pd.to_datetime(ds.time.values) logger.info(f"Creating time series plot with {len(times)} time steps") fig = go.Figure() fig.add_trace(go.Scatter( x=times, y=data_values, mode='lines+markers', name=variable )) fig.update_layout( title=f"Point Forecast: {variable} at ({lat:.2f}, {lon:.2f})", xaxis_title="Time (UTC)", yaxis_title=variable, height=400, hovermode='x unified' ) # Create data table df = pd.DataFrame({ 'Time (UTC)': times, variable: data_values }) logger.info(f"Point forecast created successfully") return fig, df.to_html(index=False) else: logger.warning(f"No time dimension found for {variable}") return None, f"No time dimension found for {variable}" except Exception as e: error_msg = f"Error getting point forecast: {str(e)}" logger.error(f"=== ERROR getting point forecast ===") logger.error(f"Exception type: {type(e).__name__}") logger.error(f"Exception message: {str(e)}") logger.error(f"Traceback:\n{traceback.format_exc()}") return None, error_msg def update_available_variables(dataset_name): """Update the variable dropdown based on selected dataset""" logger.info(f"=== Updating available variables for {dataset_name} ===") try: ds, error = load_dataset(dataset_name, use_cache=False) if ds is None: logger.warning(f"Could not load dataset, using default variables: {error}") return gr.Dropdown(choices=CATALOG[dataset_name]["variables"], value=CATALOG[dataset_name]["variables"][0]) available_vars = list(ds.data_vars) logger.info(f"Available variables: {available_vars}") return gr.Dropdown(choices=available_vars, value=available_vars[0] if available_vars else None) except Exception as e: logger.error(f"Error updating variables: {str(e)}") logger.error(f"Traceback:\n{traceback.format_exc()}") return gr.Dropdown(choices=CATALOG[dataset_name]["variables"], value=CATALOG[dataset_name]["variables"][0]) # Create Gradio interface with gr.Blocks(title="Dynamical Weather Catalog Viewer") as app: gr.Markdown(""" # 🌍 Dynamical Weather Catalog Viewer Explore weather analysis and forecast data from the [Dynamical.org catalog](https://dynamical.org/catalog/). **Features:** - Visualize global weather data on interactive maps - Click a location to get point forecasts - Browse multiple datasets: NOAA GFS, GEFS, and HRRR """) with gr.Row(): with gr.Column(scale=1): dataset_dropdown = gr.Dropdown( choices=list(CATALOG.keys()), value=list(CATALOG.keys())[0], label="Select Dataset" ) variable_dropdown = gr.Dropdown( choices=CATALOG[list(CATALOG.keys())[0]]["variables"], value=CATALOG[list(CATALOG.keys())[0]]["variables"][0], label="Select Variable" ) time_slider = gr.Slider( minimum=0, maximum=10, step=1, value=0, label="Time Index" ) load_btn = gr.Button("Load Map", variant="primary") status_text = gr.Textbox(label="Status", interactive=False) with gr.Column(scale=2): map_plot = gr.Plot(label="Map Visualization") gr.Markdown("## 📍 Point Forecast") gr.Markdown("Enter coordinates to get a time series forecast for a specific location") with gr.Row(): lat_input = gr.Number(value=40.7, label="Latitude", precision=2) lon_input = gr.Number(value=-74.0, label="Longitude", precision=2) forecast_btn = gr.Button("Get Point Forecast", variant="secondary") with gr.Row(): forecast_plot = gr.Plot(label="Time Series Forecast") forecast_table = gr.HTML(label="Forecast Data") # Event handlers dataset_dropdown.change( fn=update_available_variables, inputs=[dataset_dropdown], outputs=[variable_dropdown] ) load_btn.click( fn=create_map_visualization, inputs=[dataset_dropdown, variable_dropdown, time_slider], outputs=[map_plot, status_text] ) forecast_btn.click( fn=get_point_forecast, inputs=[dataset_dropdown, lat_input, lon_input, variable_dropdown], outputs=[forecast_plot, forecast_table] ) if __name__ == "__main__": app.launch()