nakas Claude commited on
Commit
a01b84a
·
1 Parent(s): 1ad5cfb

Add comprehensive weather variable support with 30+ meteorological parameters

Browse files

- Expand variable mapping to include all DWD ICON Global variables:
* Temperature: min/max, dewpoint, soil temp, skin temp
* Wind: direction, gusts, u/v components
* Pressure: sea level, surface pressure
* Precipitation: rain, snow, convective types
* Cloud cover: total, low/mid/high levels
* Solar radiation: direct, diffuse, longwave
* Atmospheric: visibility, boundary layer height, CAPE/CIN
* Moisture: specific humidity, total water vapor, cloud water/ice

- Create comprehensive 9-panel visualization dashboard:
* Temperature panel with min/max and dewpoint
* Humidity/moisture with dual y-axis
* Wind with speed, direction, and gusts
* Pressure with sea level and surface
* Precipitation with rain/snow breakdown
* Multi-level cloud cover visualization
* Solar radiation components
* Atmospheric conditions panel
* Enhanced data summary with variable counts

- Add proper unit conversions for all variables:
* Temperature: Kelvin to Celsius
* Pressure: Pa to hPa
* Precipitation: kg/m²/s to mm/h
* Radiation: J/m² to W/m²
* Visibility: m to km
* Wind direction calculation from u/v components

- Update UI with detailed variable descriptions and enhanced interface

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

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

Files changed (2) hide show
  1. .DS_Store +0 -0
  2. app.py +313 -64
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
app.py CHANGED
@@ -119,12 +119,62 @@ def get_forecast_data(lat, lon, forecast_hour="00"):
119
  # Extract common meteorological variables
120
  variables = {}
121
  var_mapping = {
122
- 'temperature': ['t_2m', 't_s', 'temp_2m', 'temperature_2m', 't2m'],
123
- 'humidity': ['relhum_2m', 'rh_2m', 'humidity_2m', 'rh2m', 'qv_2m'],
124
- 'wind_u': ['u_10m', 'u10m', 'wind_u_10m', 'u10'],
125
- 'wind_v': ['v_10m', 'v10m', 'wind_v_10m', 'v10'],
126
- 'pressure': ['pmsl', 'msl', 'pressure_msl', 'ps'],
127
- 'precipitation': ['tot_prec', 'tp', 'precipitation', 'rain_gsp']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  }
129
 
130
  extracted_vars = {}
@@ -149,22 +199,62 @@ def get_forecast_data(lat, lon, forecast_hour="00"):
149
  except Exception:
150
  continue
151
 
152
- # Convert temperature from Kelvin to Celsius if needed
153
- if 'temperature' in extracted_vars:
154
- temp_vals = extracted_vars['temperature']
155
- if np.mean(temp_vals) > 200: # Likely in Kelvin
156
- extracted_vars['temperature'] = temp_vals - 273.15
 
 
 
 
157
 
158
- # Calculate wind speed from u and v components
159
  if 'wind_u' in extracted_vars and 'wind_v' in extracted_vars:
160
- wind_speed = np.sqrt(extracted_vars['wind_u']**2 + extracted_vars['wind_v']**2)
 
 
 
161
  extracted_vars['wind_speed'] = wind_speed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
- # Convert relative humidity from fraction to percentage if needed
164
- if 'humidity' in extracted_vars:
165
- humidity_vals = extracted_vars['humidity']
166
- if np.max(humidity_vals) <= 1.0: # Likely in fraction
167
- extracted_vars['humidity'] = humidity_vals * 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  # Get time coordinates
170
  if 'time' in ds.coords:
@@ -199,11 +289,39 @@ def get_forecast_data(lat, lon, forecast_hour="00"):
199
  'nearest_grid_lon': float(grid_lons[nearest_idx])
200
  }
201
 
202
- # Add additional variables if available
203
- if 'pressure' in extracted_vars:
204
- result['pressure'] = extracted_vars['pressure'][:max_hours]
205
- if 'precipitation' in extracted_vars:
206
- result['precipitation'] = extracted_vars['precipitation'][:max_hours]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  return result
209
 
@@ -234,37 +352,139 @@ def get_forecast_data(lat, lon, forecast_hour="00"):
234
  }
235
 
236
  def create_forecast_plot(forecast_data):
237
- """Create forecast visualization plots"""
238
  if isinstance(forecast_data, str):
239
  return forecast_data
240
 
241
- fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 8))
 
242
 
243
  timestamps = forecast_data['timestamps']
244
 
245
- # Temperature plot
246
- ax1.plot(timestamps, forecast_data['temperature'], 'r-', linewidth=2)
247
- ax1.set_title('Temperature Forecast (°C)')
248
- ax1.set_ylabel('Temperature (°C)')
 
 
 
 
 
 
 
 
 
 
249
  ax1.grid(True, alpha=0.3)
250
- ax1.tick_params(axis='x', rotation=45)
251
-
252
- # Humidity plot
253
- ax2.plot(timestamps, forecast_data['humidity'], 'b-', linewidth=2)
254
- ax2.set_title('Humidity Forecast (%)')
255
- ax2.set_ylabel('Humidity (%)')
 
 
 
 
 
 
 
256
  ax2.grid(True, alpha=0.3)
257
- ax2.tick_params(axis='x', rotation=45)
258
-
259
- # Wind speed plot
260
- ax3.plot(timestamps, forecast_data['wind_speed'], 'g-', linewidth=2)
261
- ax3.set_title('Wind Speed Forecast (m/s)')
262
- ax3.set_ylabel('Wind Speed (m/s)')
 
 
 
 
 
 
 
 
 
 
263
  ax3.grid(True, alpha=0.3)
264
- ax3.tick_params(axis='x', rotation=45)
265
-
266
- # Summary info
267
- ax4.axis('off')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
  # Check if we have real data or fallback
270
  data_source = "Real DWD ICON Data" if 'error' not in forecast_data else "Fallback Synthetic Data"
@@ -273,34 +493,53 @@ def create_forecast_plot(forecast_data):
273
  # Grid point info
274
  grid_info = ""
275
  if 'nearest_grid_lat' in forecast_data and 'nearest_grid_lon' in forecast_data:
276
- grid_info = f"Nearest Grid: {forecast_data['nearest_grid_lat']:.2f}°N, {forecast_data['nearest_grid_lon']:.2f}°E\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
  summary_text = f"""
279
- Location: {forecast_data['lat']:.2f}°N, {forecast_data['lon']:.2f}°E
280
- {grid_info}Data Source: {data_source}
281
- Forecast: {forecast_info}
282
-
283
- Current Conditions:
284
- Temperature: {forecast_data['temperature'][0]:.1f}°C
285
- Humidity: {forecast_data['humidity'][0]:.1f}%
286
- Wind Speed: {forecast_data['wind_speed'][0]:.1f} m/s
287
-
288
- Forecast Range:
289
- Temp: {min(forecast_data['temperature']):.1f}°C to {max(forecast_data['temperature']):.1f}°C
290
- Humidity: {min(forecast_data['humidity']):.1f}% to {max(forecast_data['humidity']):.1f}%
291
- Wind: {min(forecast_data['wind_speed']):.1f} to {max(forecast_data['wind_speed']):.1f} m/s
292
- """
 
 
 
293
 
294
  # Add error info if present
295
  if 'error' in forecast_data:
296
- summary_text += f"\n\nNote: Using fallback data due to:\n{forecast_data['error'][:100]}..."
297
 
298
  color = 'lightgreen' if 'error' not in forecast_data else 'lightyellow'
299
- ax4.text(0.1, 0.9, summary_text, transform=ax4.transAxes, fontsize=9,
300
  verticalalignment='top', bbox=dict(boxstyle='round', facecolor=color, alpha=0.7))
301
 
302
  plt.tight_layout()
303
- plt.subplots_adjust(hspace=0.3)
304
 
305
  return fig
306
 
@@ -353,7 +592,17 @@ def create_attribution_text():
353
  # Create the Gradio interface
354
  with gr.Blocks(title="DWD ICON Global Weather Forecast") as app:
355
  gr.Markdown("# 🌦️ DWD ICON Global Weather Forecast")
356
- gr.Markdown("Click on the map to select a location and view the 4-day weather forecast from the DWD ICON Global model.")
 
 
 
 
 
 
 
 
 
 
357
 
358
  with gr.Row():
359
  with gr.Column(scale=2):
 
119
  # Extract common meteorological variables
120
  variables = {}
121
  var_mapping = {
122
+ # Core temperature variables
123
+ 'temperature': ['t_2m', 't_s', 'temp_2m', 'temperature_2m', 't2m', 'T_2M'],
124
+ 'temp_min': ['tmin_2m', 't_min_2m', 'T_MIN_2M'],
125
+ 'temp_max': ['tmax_2m', 't_max_2m', 'T_MAX_2M'],
126
+
127
+ # Humidity and moisture
128
+ 'humidity': ['relhum_2m', 'rh_2m', 'humidity_2m', 'rh2m', 'RELHUM_2M'],
129
+ 'specific_humidity': ['qv_2m', 'qv_s', 'QV_2M'],
130
+ 'dewpoint': ['td_2m', 'dewpoint_2m', 'TD_2M'],
131
+
132
+ # Wind components and derived
133
+ 'wind_u': ['u_10m', 'u10m', 'wind_u_10m', 'u10', 'U_10M'],
134
+ 'wind_v': ['v_10m', 'v10m', 'wind_v_10m', 'v10', 'V_10M'],
135
+ 'wind_gust': ['vmax_10m', 'wind_gust', 'gust', 'VMAX_10M'],
136
+
137
+ # Pressure variables
138
+ 'pressure': ['pmsl', 'msl', 'pressure_msl', 'ps', 'PMSL'],
139
+ 'surface_pressure': ['ps', 'sp', 'surface_pressure', 'PS'],
140
+
141
+ # Precipitation types
142
+ 'precipitation': ['tot_prec', 'tp', 'precipitation', 'rain_gsp', 'TOT_PREC'],
143
+ 'rain': ['rain_gsp', 'rain_con', 'RAIN_GSP'],
144
+ 'snow': ['snow_gsp', 'snow_con', 'SNOW_GSP'],
145
+ 'convective_precip': ['rain_con', 'snow_con', 'RAIN_CON'],
146
+
147
+ # Cloud variables
148
+ 'cloud_cover': ['clct', 'tcc', 'total_cloud_cover', 'CLCT'],
149
+ 'low_cloud': ['clcl', 'lcc', 'low_cloud_cover', 'CLCL'],
150
+ 'mid_cloud': ['clcm', 'mcc', 'mid_cloud_cover', 'CLCM'],
151
+ 'high_cloud': ['clch', 'hcc', 'high_cloud_cover', 'CLCH'],
152
+
153
+ # Radiation variables
154
+ 'solar_radiation': ['asob_s', 'ssr', 'surface_solar_radiation', 'ASOB_S'],
155
+ 'direct_radiation': ['aswdir_s', 'direct_solar_radiation', 'ASWDIR_S'],
156
+ 'diffuse_radiation': ['aswdif_s', 'diffuse_solar_radiation', 'ASWDIF_S'],
157
+ 'longwave_radiation': ['athb_s', 'surface_thermal_radiation', 'ATHB_S'],
158
+ 'net_radiation': ['aswdir_s', 'net_surface_radiation'],
159
+
160
+ # Visibility and atmospheric conditions
161
+ 'visibility': ['vis', 'visibility', 'VIS'],
162
+ 'fog': ['fog', 'FOG'],
163
+
164
+ # Boundary layer and atmospheric stability
165
+ 'boundary_layer_height': ['hpbl', 'pbl_height', 'HPBL'],
166
+ 'cape': ['cape_ml', 'cape', 'CAPE_ML'],
167
+ 'cin': ['cin_ml', 'cin', 'CIN_ML'],
168
+
169
+ # Additional surface variables
170
+ 'soil_temperature': ['t_soil', 'soil_temp', 'T_SOIL'],
171
+ 'skin_temperature': ['t_skin', 'skin_temp', 'T_S'],
172
+ 'albedo': ['alb_rad', 'albedo', 'ALB_RAD'],
173
+
174
+ # Integrated atmospheric variables
175
+ 'total_water_vapor': ['tqv', 'tcwv', 'total_column_water_vapor', 'TQV'],
176
+ 'total_cloud_water': ['tqc', 'total_cloud_water', 'TQC'],
177
+ 'total_cloud_ice': ['tqi', 'total_cloud_ice', 'TQI'],
178
  }
179
 
180
  extracted_vars = {}
 
199
  except Exception:
200
  continue
201
 
202
+ # Unit conversions and processing
203
+
204
+ # Convert temperature variables from Kelvin to Celsius if needed
205
+ temp_vars = ['temperature', 'temp_min', 'temp_max', 'dewpoint', 'soil_temperature', 'skin_temperature']
206
+ for temp_var in temp_vars:
207
+ if temp_var in extracted_vars:
208
+ temp_vals = extracted_vars[temp_var]
209
+ if np.mean(temp_vals) > 200: # Likely in Kelvin
210
+ extracted_vars[temp_var] = temp_vals - 273.15
211
 
212
+ # Calculate wind speed and direction from u and v components
213
  if 'wind_u' in extracted_vars and 'wind_v' in extracted_vars:
214
+ u_vals = extracted_vars['wind_u']
215
+ v_vals = extracted_vars['wind_v']
216
+ wind_speed = np.sqrt(u_vals**2 + v_vals**2)
217
+ wind_direction = (270 - np.degrees(np.arctan2(v_vals, u_vals))) % 360
218
  extracted_vars['wind_speed'] = wind_speed
219
+ extracted_vars['wind_direction'] = wind_direction
220
+
221
+ # Convert relative humidity and cloud cover from fraction to percentage if needed
222
+ percentage_vars = ['humidity', 'cloud_cover', 'low_cloud', 'mid_cloud', 'high_cloud']
223
+ for pct_var in percentage_vars:
224
+ if pct_var in extracted_vars:
225
+ vals = extracted_vars[pct_var]
226
+ if np.max(vals) <= 1.0: # Likely in fraction
227
+ extracted_vars[pct_var] = vals * 100
228
+
229
+ # Convert pressure from Pa to hPa if needed
230
+ pressure_vars = ['pressure', 'surface_pressure']
231
+ for press_var in pressure_vars:
232
+ if press_var in extracted_vars:
233
+ press_vals = extracted_vars[press_var]
234
+ if np.mean(press_vals) > 50000: # Likely in Pa
235
+ extracted_vars[press_var] = press_vals / 100 # Convert to hPa
236
 
237
+ # Convert precipitation from kg/m²/s to mm/h if needed
238
+ precip_vars = ['precipitation', 'rain', 'snow', 'convective_precip']
239
+ for precip_var in precip_vars:
240
+ if precip_var in extracted_vars:
241
+ precip_vals = extracted_vars[precip_var]
242
+ if np.max(precip_vals) < 1: # Likely in kg/m²/s
243
+ extracted_vars[precip_var] = precip_vals * 3600 # Convert to mm/h
244
+
245
+ # Convert radiation from J/m² to W/m² if needed (assuming 3-hour accumulation)
246
+ radiation_vars = ['solar_radiation', 'direct_radiation', 'diffuse_radiation', 'longwave_radiation']
247
+ for rad_var in radiation_vars:
248
+ if rad_var in extracted_vars:
249
+ rad_vals = extracted_vars[rad_var]
250
+ if np.max(rad_vals) > 10000: # Likely accumulated energy
251
+ extracted_vars[rad_var] = rad_vals / 10800 # Convert J/m² to W/m² (3h = 10800s)
252
+
253
+ # Convert visibility from m to km if needed
254
+ if 'visibility' in extracted_vars:
255
+ vis_vals = extracted_vars['visibility']
256
+ if np.mean(vis_vals) > 1000: # Likely in meters
257
+ extracted_vars['visibility'] = vis_vals / 1000 # Convert to km
258
 
259
  # Get time coordinates
260
  if 'time' in ds.coords:
 
289
  'nearest_grid_lon': float(grid_lons[nearest_idx])
290
  }
291
 
292
+ # Add all available additional variables
293
+ additional_vars = [
294
+ # Temperature variables
295
+ 'temp_min', 'temp_max', 'dewpoint', 'soil_temperature', 'skin_temperature',
296
+
297
+ # Wind variables
298
+ 'wind_direction', 'wind_gust',
299
+
300
+ # Pressure variables
301
+ 'pressure', 'surface_pressure',
302
+
303
+ # Precipitation variables
304
+ 'precipitation', 'rain', 'snow', 'convective_precip',
305
+
306
+ # Cloud variables
307
+ 'cloud_cover', 'low_cloud', 'mid_cloud', 'high_cloud',
308
+
309
+ # Radiation variables
310
+ 'solar_radiation', 'direct_radiation', 'diffuse_radiation', 'longwave_radiation',
311
+
312
+ # Atmospheric variables
313
+ 'visibility', 'boundary_layer_height', 'cape', 'cin',
314
+
315
+ # Moisture variables
316
+ 'specific_humidity', 'total_water_vapor', 'total_cloud_water', 'total_cloud_ice',
317
+
318
+ # Other surface variables
319
+ 'albedo', 'fog'
320
+ ]
321
+
322
+ for var in additional_vars:
323
+ if var in extracted_vars:
324
+ result[var] = extracted_vars[var][:max_hours]
325
 
326
  return result
327
 
 
352
  }
353
 
354
  def create_forecast_plot(forecast_data):
355
+ """Create comprehensive forecast visualization plots"""
356
  if isinstance(forecast_data, str):
357
  return forecast_data
358
 
359
+ # Create a larger figure with more subplots for all variables
360
+ fig = plt.figure(figsize=(16, 12))
361
 
362
  timestamps = forecast_data['timestamps']
363
 
364
+ # Create a 3x3 grid of subplots
365
+ gs = fig.add_gridspec(3, 3, hspace=0.4, wspace=0.3)
366
+
367
+ # Temperature plot with min/max if available
368
+ ax1 = fig.add_subplot(gs[0, 0])
369
+ ax1.plot(timestamps, forecast_data['temperature'], 'r-', linewidth=2, label='Temperature')
370
+ if 'temp_max' in forecast_data:
371
+ ax1.plot(timestamps, forecast_data['temp_max'], 'r--', linewidth=1, alpha=0.7, label='Max')
372
+ if 'temp_min' in forecast_data:
373
+ ax1.plot(timestamps, forecast_data['temp_min'], 'b--', linewidth=1, alpha=0.7, label='Min')
374
+ if 'dewpoint' in forecast_data:
375
+ ax1.plot(timestamps, forecast_data['dewpoint'], 'c-', linewidth=1, alpha=0.8, label='Dewpoint')
376
+ ax1.set_title('Temperature (°C)')
377
+ ax1.set_ylabel('°C')
378
  ax1.grid(True, alpha=0.3)
379
+ ax1.legend(fontsize=8)
380
+ ax1.tick_params(axis='x', rotation=45, labelsize=8)
381
+
382
+ # Humidity and moisture
383
+ ax2 = fig.add_subplot(gs[0, 1])
384
+ ax2.plot(timestamps, forecast_data['humidity'], 'b-', linewidth=2, label='Rel. Humidity')
385
+ if 'specific_humidity' in forecast_data:
386
+ ax2_twin = ax2.twinx()
387
+ ax2_twin.plot(timestamps, forecast_data['specific_humidity'], 'g-', linewidth=1, alpha=0.7, label='Spec. Humidity')
388
+ ax2_twin.set_ylabel('g/kg', color='g')
389
+ ax2_twin.tick_params(axis='y', labelcolor='g')
390
+ ax2.set_title('Humidity (%)')
391
+ ax2.set_ylabel('%')
392
  ax2.grid(True, alpha=0.3)
393
+ ax2.legend(fontsize=8)
394
+ ax2.tick_params(axis='x', rotation=45, labelsize=8)
395
+
396
+ # Wind speed, direction, and gusts
397
+ ax3 = fig.add_subplot(gs[0, 2])
398
+ ax3.plot(timestamps, forecast_data['wind_speed'], 'g-', linewidth=2, label='Wind Speed')
399
+ if 'wind_gust' in forecast_data:
400
+ ax3.plot(timestamps, forecast_data['wind_gust'], 'orange', linewidth=1, alpha=0.7, label='Gusts')
401
+ if 'wind_direction' in forecast_data:
402
+ ax3_twin = ax3.twinx()
403
+ ax3_twin.scatter(timestamps, forecast_data['wind_direction'], c='purple', s=10, alpha=0.6, label='Direction')
404
+ ax3_twin.set_ylabel('Direction (°)', color='purple')
405
+ ax3_twin.set_ylim(0, 360)
406
+ ax3_twin.tick_params(axis='y', labelcolor='purple')
407
+ ax3.set_title('Wind (m/s)')
408
+ ax3.set_ylabel('m/s')
409
  ax3.grid(True, alpha=0.3)
410
+ ax3.legend(fontsize=8)
411
+ ax3.tick_params(axis='x', rotation=45, labelsize=8)
412
+
413
+ # Pressure
414
+ ax4 = fig.add_subplot(gs[1, 0])
415
+ if 'pressure' in forecast_data:
416
+ ax4.plot(timestamps, forecast_data['pressure'], 'purple', linewidth=2, label='Sea Level')
417
+ if 'surface_pressure' in forecast_data:
418
+ ax4.plot(timestamps, forecast_data['surface_pressure'], 'indigo', linewidth=1, alpha=0.7, label='Surface')
419
+ ax4.set_title('Pressure (hPa)')
420
+ ax4.set_ylabel('hPa')
421
+ ax4.grid(True, alpha=0.3)
422
+ ax4.legend(fontsize=8)
423
+ ax4.tick_params(axis='x', rotation=45, labelsize=8)
424
+
425
+ # Precipitation
426
+ ax5 = fig.add_subplot(gs[1, 1])
427
+ if 'precipitation' in forecast_data:
428
+ ax5.bar(timestamps, forecast_data['precipitation'], alpha=0.7, color='blue', label='Total', width=0.1)
429
+ if 'rain' in forecast_data:
430
+ ax5.bar(timestamps, forecast_data['rain'], alpha=0.5, color='lightblue', label='Rain', width=0.08)
431
+ if 'snow' in forecast_data:
432
+ ax5.bar(timestamps, forecast_data['snow'], alpha=0.5, color='white', edgecolor='gray', label='Snow', width=0.06)
433
+ ax5.set_title('Precipitation (mm/h)')
434
+ ax5.set_ylabel('mm/h')
435
+ ax5.grid(True, alpha=0.3)
436
+ ax5.legend(fontsize=8)
437
+ ax5.tick_params(axis='x', rotation=45, labelsize=8)
438
+
439
+ # Cloud cover
440
+ ax6 = fig.add_subplot(gs[1, 2])
441
+ if 'cloud_cover' in forecast_data:
442
+ ax6.fill_between(timestamps, forecast_data['cloud_cover'], alpha=0.3, color='gray', label='Total')
443
+ if 'low_cloud' in forecast_data:
444
+ ax6.plot(timestamps, forecast_data['low_cloud'], 'brown', linewidth=1, label='Low')
445
+ if 'mid_cloud' in forecast_data:
446
+ ax6.plot(timestamps, forecast_data['mid_cloud'], 'orange', linewidth=1, label='Mid')
447
+ if 'high_cloud' in forecast_data:
448
+ ax6.plot(timestamps, forecast_data['high_cloud'], 'lightblue', linewidth=1, label='High')
449
+ ax6.set_title('Cloud Cover (%)')
450
+ ax6.set_ylabel('%')
451
+ ax6.set_ylim(0, 100)
452
+ ax6.grid(True, alpha=0.3)
453
+ ax6.legend(fontsize=8)
454
+ ax6.tick_params(axis='x', rotation=45, labelsize=8)
455
+
456
+ # Solar radiation
457
+ ax7 = fig.add_subplot(gs[2, 0])
458
+ if 'solar_radiation' in forecast_data:
459
+ ax7.fill_between(timestamps, forecast_data['solar_radiation'], alpha=0.3, color='yellow', label='Solar')
460
+ if 'direct_radiation' in forecast_data:
461
+ ax7.plot(timestamps, forecast_data['direct_radiation'], 'orange', linewidth=1, label='Direct')
462
+ if 'diffuse_radiation' in forecast_data:
463
+ ax7.plot(timestamps, forecast_data['diffuse_radiation'], 'gold', linewidth=1, label='Diffuse')
464
+ ax7.set_title('Solar Radiation (W/m²)')
465
+ ax7.set_ylabel('W/m²')
466
+ ax7.grid(True, alpha=0.3)
467
+ ax7.legend(fontsize=8)
468
+ ax7.tick_params(axis='x', rotation=45, labelsize=8)
469
+
470
+ # Additional atmospheric parameters
471
+ ax8 = fig.add_subplot(gs[2, 1])
472
+ if 'visibility' in forecast_data:
473
+ ax8.plot(timestamps, forecast_data['visibility'], 'teal', linewidth=2, label='Visibility (km)')
474
+ if 'boundary_layer_height' in forecast_data:
475
+ ax8_twin = ax8.twinx()
476
+ ax8_twin.plot(timestamps, forecast_data['boundary_layer_height'], 'brown', linewidth=1, alpha=0.7, label='BL Height (m)')
477
+ ax8_twin.set_ylabel('BL Height (m)', color='brown')
478
+ ax8_twin.tick_params(axis='y', labelcolor='brown')
479
+ ax8.set_title('Atmospheric Conditions')
480
+ ax8.set_ylabel('Visibility (km)')
481
+ ax8.grid(True, alpha=0.3)
482
+ ax8.legend(fontsize=8)
483
+ ax8.tick_params(axis='x', rotation=45, labelsize=8)
484
+
485
+ # Summary info panel
486
+ ax9 = fig.add_subplot(gs[2, 2])
487
+ ax9.axis('off')
488
 
489
  # Check if we have real data or fallback
490
  data_source = "Real DWD ICON Data" if 'error' not in forecast_data else "Fallback Synthetic Data"
 
493
  # Grid point info
494
  grid_info = ""
495
  if 'nearest_grid_lat' in forecast_data and 'nearest_grid_lon' in forecast_data:
496
+ grid_info = f"Grid: {forecast_data['nearest_grid_lat']:.2f}°N, {forecast_data['nearest_grid_lon']:.2f}°E\n"
497
+
498
+ # Count available variables
499
+ available_vars = []
500
+ var_categories = {
501
+ 'Temperature': ['temperature', 'temp_min', 'temp_max', 'dewpoint'],
502
+ 'Wind': ['wind_speed', 'wind_direction', 'wind_gust'],
503
+ 'Pressure': ['pressure', 'surface_pressure'],
504
+ 'Precipitation': ['precipitation', 'rain', 'snow'],
505
+ 'Clouds': ['cloud_cover', 'low_cloud', 'mid_cloud', 'high_cloud'],
506
+ 'Radiation': ['solar_radiation', 'direct_radiation', 'diffuse_radiation'],
507
+ 'Atmosphere': ['visibility', 'boundary_layer_height', 'cape', 'humidity']
508
+ }
509
+
510
+ for category, vars_list in var_categories.items():
511
+ count = sum(1 for var in vars_list if var in forecast_data)
512
+ if count > 0:
513
+ available_vars.append(f"{category}: {count}")
514
 
515
  summary_text = f"""
516
+ Location: {forecast_data['lat']:.2f}°N, {forecast_data['lon']:.2f}°E
517
+ {grid_info}
518
+ Data: {data_source}
519
+ Forecast: {forecast_info}
520
+
521
+ Available Variables:
522
+ {chr(10).join(available_vars)}
523
+
524
+ Current Conditions:
525
+ Temp: {forecast_data['temperature'][0]:.1f}°C
526
+ Humidity: {forecast_data['humidity'][0]:.1f}%
527
+ Wind: {forecast_data['wind_speed'][0]:.1f} m/s
528
+ """
529
+
530
+ # Add pressure if available
531
+ if 'pressure' in forecast_data:
532
+ summary_text += f"Pressure: {forecast_data['pressure'][0]:.1f} hPa\n"
533
 
534
  # Add error info if present
535
  if 'error' in forecast_data:
536
+ summary_text += f"\nNote: Using fallback data\nReason: {forecast_data['error'][:80]}..."
537
 
538
  color = 'lightgreen' if 'error' not in forecast_data else 'lightyellow'
539
+ ax9.text(0.05, 0.95, summary_text, transform=ax9.transAxes, fontsize=8,
540
  verticalalignment='top', bbox=dict(boxstyle='round', facecolor=color, alpha=0.7))
541
 
542
  plt.tight_layout()
 
543
 
544
  return fig
545
 
 
592
  # Create the Gradio interface
593
  with gr.Blocks(title="DWD ICON Global Weather Forecast") as app:
594
  gr.Markdown("# 🌦️ DWD ICON Global Weather Forecast")
595
+ gr.Markdown("""
596
+ **Comprehensive Weather Forecasting Dashboard** - Click on the map to select any location and view detailed 4-day forecasts with:
597
+
598
+ 📊 **9 Weather Panels**: Temperature, Humidity/Moisture, Wind, Pressure, Precipitation, Cloud Cover, Solar Radiation, Atmospheric Conditions, and Data Summary
599
+
600
+ 🔢 **30+ Weather Variables**: Temperature (min/max/dewpoint), Wind (speed/direction/gusts), Pressure (sea level/surface),
601
+ Precipitation (rain/snow/convective), Cloud layers (low/mid/high/total), Solar radiation (direct/diffuse/longwave),
602
+ Visibility, Boundary layer height, Atmospheric stability (CAPE/CIN), and more!
603
+
604
+ 🎯 **Real DWD ICON Data** from the German Weather Service via OpenClimateFix
605
+ """)
606
 
607
  with gr.Row():
608
  with gr.Column(scale=2):