| import gradio as gr |
| import pandas as pd |
| import folium |
| import requests |
| from datetime import datetime, timedelta |
| import io |
| import os |
| import xarray as xr |
| import cfgrib |
| import numpy as np |
| import s3fs |
| import fsspec |
|
|
| |
| |
| NOAA_API_TOKEN = os.getenv("NOAA_API_TOKEN") |
|
|
| |
| GFS_S3_BASE = "s3://noaa-gfs-bdp-pds/" |
|
|
| |
|
|
| def fetch_gfs_wind_data(forecast_date: datetime, forecast_hour_utc: int = 0): |
| """ |
| Fetches 10m wind data (u and v components) from GFS on AWS S3 for a given date and UTC hour. |
| Returns a pandas DataFrame. |
| """ |
| date_str = forecast_date.strftime("%Y%m%d") |
| hour_str = f"{forecast_hour_utc:02d}" |
| |
| |
| |
| grib_file_path = f"{GFS_S3_BASE}{date_str}/{hour_str}/atmos/gfs.t{hour_str}z.pgrb2.0p25.f000.grib2" |
| |
| print(f"Attempting to open GFS file: {grib_file_path}") |
|
|
| try: |
| |
| |
| |
| cached_grib_file_path = f"simplecache::{grib_file_path}" |
| |
| |
| |
| storage_options = { |
| "simplecache": { |
| "cache_storage": "/tmp/gfs_cache" |
| }, |
| "s3": { |
| "anon": True |
| } |
| } |
|
|
| ds = xr.open_dataset( |
| cached_grib_file_path, |
| engine="cfgrib", |
| backend_kwargs={'filter_by_keys': {'typeOfLevel': 'heightAboveGround', 'level': 10, 'shortName': ['u', 'v']}}, |
| |
| chunks='auto', |
| storage_options=storage_options |
| ) |
| |
| |
| |
| |
| u_var = [v for v in ds.data_vars if 'u' in v.lower() and '10m' in v.lower() or 'u' == v][0] |
| v_var = [v for v in ds.data_vars if 'v' in v.lower() and '10m' in v.lower() or 'v' == v][0] |
|
|
| ds['wind_speed'] = np.sqrt(ds[u_var]**2 + ds[v_var]**2) |
| |
| |
| |
| |
| ds['wind_direction'] = (270 - np.degrees(np.arctan2(ds[u_var], ds[v_var]))) % 360 |
| |
| |
| df = ds[['wind_speed', 'wind_direction']].to_dataframe().reset_index() |
| |
| |
| |
| if 'latitude' not in df.columns and 'lat' in df.columns: |
| df = df.rename(columns={'lat': 'latitude'}) |
| if 'longitude' not in df.columns and 'lon' in df.columns: |
| df = df.rename(columns={'lon': 'longitude'}) |
|
|
| |
| df = df.dropna(subset=['wind_speed', 'wind_direction', 'latitude', 'longitude']) |
| |
| return df |
|
|
| except Exception as e: |
| print(f"Error fetching GFS data: {e}") |
| |
| print(f"Current directory: {os.getcwd()}") |
| if os.path.exists('/tmp'): |
| print(f"/tmp contents: {os.listdir('/tmp')}") |
| return pd.DataFrame() |
|
|
|
|
| |
|
|
| def visualize_global_wind(gfs_run_hour: str, days_ago: int): |
| """ |
| Fetches global GFS wind data and generates a Folium map. |
| """ |
| |
| |
| |
| |
| |
| |
| |
| current_utc_time = datetime.utcnow() |
| |
| |
| |
| target_utc_date = current_utc_time - timedelta(days=days_ago) |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| df_wind = fetch_gfs_wind_data(target_utc_date, forecast_hour_utc=int(gfs_run_hour)) |
|
|
| if df_wind.empty: |
| return "<h3>Could not retrieve global wind data for the selected parameters. This might happen if the GFS data for this specific run is not yet available on AWS S3 or if there was an error processing it. Try a different run hour or 'Days Ago'.</h3>" |
|
|
| |
| |
| |
| if not df_wind.empty and 'latitude' in df_wind.columns and 'longitude' in df_wind.columns: |
| center_lat = df_wind['latitude'].mean() |
| center_lon = df_wind['longitude'].mean() |
| zoom_start = 2 |
| else: |
| center_lat = 0 |
| center_lon = 0 |
| zoom_start = 2 |
|
|
| m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom_start) |
|
|
| |
| |
| sample_df = df_wind.sample(n=min(10000, len(df_wind)), random_state=42) |
|
|
| for _, row in sample_df.iterrows(): |
| |
| wind_speed_knots = row['wind_speed'] * 1.944 |
|
|
| folium.CircleMarker( |
| location=[row['latitude'], row['longitude']], |
| radius=3 + (wind_speed_knots / 8), |
| color='blue', |
| fill=True, |
| fill_color='darkblue', |
| fill_opacity=0.6, |
| tooltip=f"Speed: {wind_speed_knots:.1f} knots, Dir: {row['wind_direction']:.0f}°" |
| ).add_to(m) |
|
|
| |
| map_html = m._repr_html_() |
| return map_html |
|
|
| |
|
|
| iface = gr.Interface( |
| fn=visualize_global_wind, |
| inputs=[ |
| gr.Dropdown(choices=["00", "06", "12", "18"], label="GFS Run Hour (UTC)", value="12"), |
| gr.Slider(minimum=0, maximum=3, value=0, step=1, label="Days Ago (for GFS Run Date)") |
| ], |
| outputs=gr.HTML(label="Interactive Global Wind Map (GFS)"), |
| title="Global Wind Data Visualizer (Gradio + Folium)", |
| description=( |
| "Visualize recent global wind data from NOAA's GFS model on an interactive map. " |
| "Select the GFS model run hour (UTC) and how many days ago.<br>" |
| "**Note:** Data is from AWS S3 (public access). Visualization samples points for performance." |
| ) |
| ) |
|
|
| if __name__ == "__main__": |
| |
| iface.launch(share=True) |
|
|
|
|