nakas Claude commited on
Commit
170f330
·
1 Parent(s): e9dd646

Add Dynamical Weather Catalog Viewer app

Browse files

- Interactive Gradio app for visualizing weather data from Dynamical.org
- Support for NOAA GFS, GEFS, and HRRR datasets
- Interactive map visualizations with Plotly
- Point forecast functionality for specific coordinates
- Time series plots and data tables

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +253 -0
  2. requirements.txt +8 -0
app.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import xarray as xr
3
+ import pandas as pd
4
+ import numpy as np
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ import warnings
8
+ warnings.filterwarnings('ignore')
9
+
10
+ # Catalog configuration
11
+ CATALOG = {
12
+ "NOAA GFS Analysis (Hourly)": {
13
+ "url": "https://data.dynamical.org/noaa/gfs/analysis-hourly/latest",
14
+ "type": "analysis",
15
+ "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m", "mean_sea_level_pressure"]
16
+ },
17
+ "NOAA GFS Forecast": {
18
+ "url": "https://data.dynamical.org/noaa/gfs/forecast/latest",
19
+ "type": "forecast",
20
+ "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m", "mean_sea_level_pressure"]
21
+ },
22
+ "NOAA GEFS Analysis": {
23
+ "url": "https://data.dynamical.org/noaa/gefs/analysis/latest",
24
+ "type": "analysis",
25
+ "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m"]
26
+ },
27
+ "NOAA GEFS Forecast (35-day)": {
28
+ "url": "https://data.dynamical.org/noaa/gefs/forecast/latest",
29
+ "type": "forecast",
30
+ "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m"]
31
+ },
32
+ "NOAA HRRR Forecast (48-hour)": {
33
+ "url": "https://data.dynamical.org/noaa/hrrr/forecast/latest",
34
+ "type": "forecast",
35
+ "variables": ["temperature_2m", "precipitation", "wind_u_10m", "wind_v_10m"]
36
+ }
37
+ }
38
+
39
+ # Cache for loaded datasets
40
+ dataset_cache = {}
41
+
42
+ def load_dataset(dataset_name, use_cache=True):
43
+ """Load a dataset from the Dynamical catalog"""
44
+ if use_cache and dataset_name in dataset_cache:
45
+ return dataset_cache[dataset_name]
46
+
47
+ try:
48
+ url = CATALOG[dataset_name]["url"]
49
+ ds = xr.open_zarr(url)
50
+ if use_cache:
51
+ dataset_cache[dataset_name] = ds
52
+ return ds
53
+ except Exception as e:
54
+ return None
55
+
56
+ def create_map_visualization(dataset_name, variable, time_index=0):
57
+ """Create an interactive map visualization of the selected variable"""
58
+ try:
59
+ ds = load_dataset(dataset_name)
60
+ if ds is None:
61
+ return None, f"Error loading dataset: {dataset_name}"
62
+
63
+ # Check if variable exists
64
+ if variable not in ds.variables:
65
+ available_vars = list(ds.data_vars)
66
+ return None, f"Variable '{variable}' not found. Available: {available_vars}"
67
+
68
+ # Get the data
69
+ data_var = ds[variable]
70
+
71
+ # Handle time dimension
72
+ if 'time' in data_var.dims:
73
+ if time_index >= len(ds.time):
74
+ time_index = 0
75
+ data_var = data_var.isel(time=time_index)
76
+
77
+ # Handle ensemble dimension if present
78
+ if 'ensemble' in data_var.dims:
79
+ data_var = data_var.isel(ensemble=0)
80
+
81
+ # Load data into memory (subsample for performance)
82
+ step = max(1, len(ds.latitude) // 200) # Limit to ~200 points per dimension
83
+ data_var = data_var.isel(latitude=slice(None, None, step), longitude=slice(None, None, step))
84
+ data_values = data_var.compute().values
85
+
86
+ # Get coordinates
87
+ lats = ds.latitude.isel(latitude=slice(None, None, step)).values
88
+ lons = ds.longitude.isel(longitude=slice(None, None, step)).values
89
+
90
+ # Create plotly figure
91
+ fig = go.Figure(data=go.Heatmap(
92
+ z=data_values,
93
+ x=lons,
94
+ y=lats,
95
+ colorscale='RdBu_r',
96
+ hovertemplate='Lat: %{y:.2f}<br>Lon: %{x:.2f}<br>Value: %{z:.2f}<extra></extra>'
97
+ ))
98
+
99
+ time_str = ""
100
+ if 'time' in ds[variable].dims:
101
+ time_val = pd.to_datetime(ds.time.isel(time=time_index).values)
102
+ time_str = f" - {time_val.strftime('%Y-%m-%d %H:%M UTC')}"
103
+
104
+ fig.update_layout(
105
+ title=f"{dataset_name}: {variable}{time_str}",
106
+ xaxis_title="Longitude",
107
+ yaxis_title="Latitude",
108
+ height=600,
109
+ hovermode='closest'
110
+ )
111
+
112
+ return fig, f"Successfully loaded {dataset_name}"
113
+
114
+ except Exception as e:
115
+ return None, f"Error creating visualization: {str(e)}"
116
+
117
+ def get_point_forecast(dataset_name, lat, lon, variable):
118
+ """Get forecast data for a specific point"""
119
+ try:
120
+ ds = load_dataset(dataset_name)
121
+ if ds is None:
122
+ return None, "Error loading dataset"
123
+
124
+ if variable not in ds.variables:
125
+ return None, f"Variable '{variable}' not found in dataset"
126
+
127
+ # Find nearest point
128
+ data_var = ds[variable].sel(latitude=lat, longitude=lon, method='nearest')
129
+
130
+ # Handle ensemble dimension
131
+ if 'ensemble' in data_var.dims:
132
+ data_var = data_var.isel(ensemble=0)
133
+
134
+ # Load data
135
+ data_values = data_var.compute().values
136
+
137
+ # Create time series plot
138
+ if 'time' in ds[variable].dims:
139
+ times = pd.to_datetime(ds.time.values)
140
+
141
+ fig = go.Figure()
142
+ fig.add_trace(go.Scatter(
143
+ x=times,
144
+ y=data_values,
145
+ mode='lines+markers',
146
+ name=variable
147
+ ))
148
+
149
+ fig.update_layout(
150
+ title=f"Point Forecast: {variable} at ({lat:.2f}, {lon:.2f})",
151
+ xaxis_title="Time (UTC)",
152
+ yaxis_title=variable,
153
+ height=400,
154
+ hovermode='x unified'
155
+ )
156
+
157
+ # Create data table
158
+ df = pd.DataFrame({
159
+ 'Time (UTC)': times,
160
+ variable: data_values
161
+ })
162
+
163
+ return fig, df.to_html(index=False)
164
+ else:
165
+ return None, f"No time dimension found for {variable}"
166
+
167
+ except Exception as e:
168
+ return None, f"Error getting point forecast: {str(e)}"
169
+
170
+ def update_available_variables(dataset_name):
171
+ """Update the variable dropdown based on selected dataset"""
172
+ try:
173
+ ds = load_dataset(dataset_name, use_cache=False)
174
+ if ds is None:
175
+ return gr.Dropdown(choices=CATALOG[dataset_name]["variables"], value=CATALOG[dataset_name]["variables"][0])
176
+
177
+ available_vars = list(ds.data_vars)
178
+ return gr.Dropdown(choices=available_vars, value=available_vars[0] if available_vars else None)
179
+ except:
180
+ return gr.Dropdown(choices=CATALOG[dataset_name]["variables"], value=CATALOG[dataset_name]["variables"][0])
181
+
182
+ # Create Gradio interface
183
+ with gr.Blocks(title="Dynamical Weather Catalog Viewer") as app:
184
+ gr.Markdown("""
185
+ # 🌍 Dynamical Weather Catalog Viewer
186
+
187
+ Explore weather analysis and forecast data from the [Dynamical.org catalog](https://dynamical.org/catalog/).
188
+
189
+ **Features:**
190
+ - Visualize global weather data on interactive maps
191
+ - Click a location to get point forecasts
192
+ - Browse multiple datasets: NOAA GFS, GEFS, and HRRR
193
+ """)
194
+
195
+ with gr.Row():
196
+ with gr.Column(scale=1):
197
+ dataset_dropdown = gr.Dropdown(
198
+ choices=list(CATALOG.keys()),
199
+ value=list(CATALOG.keys())[0],
200
+ label="Select Dataset"
201
+ )
202
+ variable_dropdown = gr.Dropdown(
203
+ choices=CATALOG[list(CATALOG.keys())[0]]["variables"],
204
+ value=CATALOG[list(CATALOG.keys())[0]]["variables"][0],
205
+ label="Select Variable"
206
+ )
207
+ time_slider = gr.Slider(
208
+ minimum=0,
209
+ maximum=10,
210
+ step=1,
211
+ value=0,
212
+ label="Time Index"
213
+ )
214
+ load_btn = gr.Button("Load Map", variant="primary")
215
+ status_text = gr.Textbox(label="Status", interactive=False)
216
+
217
+ with gr.Column(scale=2):
218
+ map_plot = gr.Plot(label="Map Visualization")
219
+
220
+ gr.Markdown("## 📍 Point Forecast")
221
+ gr.Markdown("Enter coordinates to get a time series forecast for a specific location")
222
+
223
+ with gr.Row():
224
+ lat_input = gr.Number(value=40.7, label="Latitude", precision=2)
225
+ lon_input = gr.Number(value=-74.0, label="Longitude", precision=2)
226
+ forecast_btn = gr.Button("Get Point Forecast", variant="secondary")
227
+
228
+ with gr.Row():
229
+ forecast_plot = gr.Plot(label="Time Series Forecast")
230
+
231
+ forecast_table = gr.HTML(label="Forecast Data")
232
+
233
+ # Event handlers
234
+ dataset_dropdown.change(
235
+ fn=update_available_variables,
236
+ inputs=[dataset_dropdown],
237
+ outputs=[variable_dropdown]
238
+ )
239
+
240
+ load_btn.click(
241
+ fn=create_map_visualization,
242
+ inputs=[dataset_dropdown, variable_dropdown, time_slider],
243
+ outputs=[map_plot, status_text]
244
+ )
245
+
246
+ forecast_btn.click(
247
+ fn=get_point_forecast,
248
+ inputs=[dataset_dropdown, lat_input, lon_input, variable_dropdown],
249
+ outputs=[forecast_plot, forecast_table]
250
+ )
251
+
252
+ if __name__ == "__main__":
253
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio==5.47.2
2
+ xarray
3
+ zarr
4
+ fsspec
5
+ aiohttp
6
+ pandas
7
+ numpy
8
+ plotly