import gradio as gr import requests import folium from datetime import datetime, timedelta import tempfile import os from PIL import Image, ImageDraw, ImageFont import io # Montana Mountain Peaks coordinates MONTANA_PEAKS = { "Lone Peak (Big Sky)": (45.27806, -111.45028), # 45°16′41″N 111°27′01″W "Sacajawea Peak": (45.89583, -110.96861), # 45°53′45″N 110°58′7″W "Pioneer Mountain": (45.231835, -111.450505) # 45°13′55″N 111°27′2″W } def get_snow_forecast(lat, lon): """Get snow forecast data from NOAA.""" try: points_url = f"https://api.weather.gov/points/{lat},{lon}" response = requests.get(points_url, timeout=10) response.raise_for_status() forecast_url = response.json()['properties']['forecast'] forecast_response = requests.get(forecast_url, timeout=10) forecast_response.raise_for_status() forecast_data = forecast_response.json() # Format text forecast forecast_text = "Weather Forecast:\n\n" for period in forecast_data['properties']['periods']: forecast_text += f"{period['name']}:\n" forecast_text += f"Temperature: {period['temperature']}°{period['temperatureUnit']}\n" forecast_text += f"Wind: {period['windSpeed']} {period['windDirection']}\n" forecast_text += f"{period['detailedForecast']}\n\n" # Check for snow-related keywords if any(word in period['detailedForecast'].lower() for word in ['snow', 'flurries', 'wintry mix', 'blizzard', 'winter storm']): forecast_text += "⚠️ SNOW EVENT PREDICTED ⚠️\n\n" return forecast_text except Exception as e: return f"Error getting forecast: {str(e)}" def get_forecast_images(): """Get forecast images.""" try: gallery_images = [] current_time = datetime.utcnow() # List of forecast product URLs with descriptions forecast_products = [ { "url": "https://graphical.weather.gov/images/conus/MaxT1_conus.png", "title": "Maximum Temperature Forecast" }, { "url": "https://graphical.weather.gov/images/conus/MinT1_conus.png", "title": "Minimum Temperature Forecast" }, { "url": "https://graphical.weather.gov/images/conus/Wx1_conus.png", "title": "Weather Type Forecast" }, { "url": "https://graphical.weather.gov/images/conus/PoP1_conus.png", "title": "Precipitation Probability (Day 1)" }, { "url": "https://graphical.weather.gov/images/conus/PoP2_conus.png", "title": "Precipitation Probability (Day 2)" }, { "url": "https://graphical.weather.gov/images/conus/Snow1_conus.png", "title": "Snowfall Amount (Day 1)" }, { "url": "https://graphical.weather.gov/images/conus/Snow2_conus.png", "title": "Snowfall Amount (Day 2)" }, { "url": "https://www.wpc.ncep.noaa.gov/pwpf/24hr_pwpf_fill.gif", "title": "24-hour Winter Precipitation Probability" }, { "url": "https://www.wpc.ncep.noaa.gov/pwpf/48hr_pwpf_fill.gif", "title": "48-hour Winter Precipitation Probability" }, { "url": "https://www.wpc.ncep.noaa.gov/pwpf/72hr_pwpf_fill.gif", "title": "72-hour Winter Precipitation Probability" }, { "url": "https://www.wpc.ncep.noaa.gov/wwd/24wp_d1_psnow.gif", "title": "24-hour Snowfall Probability" }, { "url": "https://www.wpc.ncep.noaa.gov/wwd/48wp_d2_psnow.gif", "title": "48-hour Snowfall Probability" }, { "url": "https://www.wpc.ncep.noaa.gov/wwd/72wp_d3_psnow.gif", "title": "72-hour Snowfall Probability" } ] for product in forecast_products: try: response = requests.get(product["url"], timeout=10) if response.status_code == 200: img_data = response.content caption = f"{product['title']} (Valid: {current_time.strftime('%Y-%m-%d %H:%M UTC')})" gallery_images.append((img_data, caption)) print(f"Successfully added {product['title']}") except Exception as e: print(f"Error processing {product['title']}: {str(e)}") continue return gallery_images except Exception as e: print(f"Error getting forecast images: {str(e)}") return [] def get_map(lat, lon): """Create a map centered on the given coordinates with markers.""" m = folium.Map(location=[lat, lon], zoom_start=9) # Add all Montana peaks for peak_name, coords in MONTANA_PEAKS.items(): folium.Marker( coords, popup=f"{peak_name}
Lat: {coords[0]:.4f}, Lon: {coords[1]:.4f}", tooltip=peak_name ).add_to(m) # Add current location marker if not a peak if (lat, lon) not in MONTANA_PEAKS.values(): folium.Marker([lat, lon], popup=f"Selected Location
Lat: {lat:.4f}, Lon: {lon:.4f}").add_to(m) m.add_child(folium.ClickForLatLng()) # Enable click events return m._repr_html_() def make_peak_click_handler(peak_name): """Creates a click handler for a specific peak.""" def handler(): coords = MONTANA_PEAKS[peak_name] return coords[0], coords[1] return handler def update_weather(lat, lon): """Update weather information based on coordinates.""" try: # Validate coordinates lat = float(lat) lon = float(lon) if not (-90 <= lat <= 90 and -180 <= lon <= 180): return "Invalid coordinates", [], get_map(45.5, -111.0) # Get text forecast forecast_text = get_snow_forecast(lat, lon) # Get forecast images gallery_images = get_forecast_images() # Get map map_html = get_map(lat, lon) return forecast_text, gallery_images, map_html except Exception as e: return f"Error: {str(e)}", [], get_map(45.5, -111.0) # Create Gradio interface with gr.Blocks(title="Montana Mountain Weather") as demo: gr.Markdown("# Montana Mountain Weather") with gr.Row(): with gr.Column(scale=1): lat_input = gr.Number( label="Latitude", value=45.5, minimum=-90, maximum=90 ) lon_input = gr.Number( label="Longitude", value=-111.0, minimum=-180, maximum=180 ) # Quick access buttons for Montana peaks gr.Markdown("### Quick Access - Montana Peaks") peak_buttons = [] for peak_name in MONTANA_PEAKS.keys(): peak_buttons.append(gr.Button(f"📍 {peak_name}")) submit_btn = gr.Button("Get Weather", variant="primary") with gr.Column(scale=2): map_display = gr.HTML(get_map(45.5, -111.0)) with gr.Row(): with gr.Column(scale=1): forecast_output = gr.Textbox( label="Weather Forecast", lines=12, placeholder="Select a location to see the forecast..." ) with gr.Column(scale=2): forecast_images = gr.Gallery( label="Weather Forecast Products", show_label=True, columns=2, rows=2, height="auto", object_fit="contain" ) # Handle submit button click submit_btn.click( fn=update_weather, inputs=[lat_input, lon_input], outputs=[ forecast_output, forecast_images, map_display ] ) # Handle peak button clicks for i, peak_name in enumerate(MONTANA_PEAKS.keys()): peak_buttons[i].click( fn=make_peak_click_handler(peak_name), inputs=[], outputs=[lat_input, lon_input] ).then( fn=update_weather, inputs=[lat_input, lon_input], outputs=[ forecast_output, forecast_images, map_display ] ) gr.Markdown(""" ## Instructions 1. Use the quick access buttons to check specific Montana peaks 2. Or enter coordinates manually / click on the map 3. Click "Get Weather" to see the forecast and weather products **Montana Peaks Included:** - Lone Peak (Big Sky): 45°16′41″N 111°27′01″W - Sacajawea Peak: 45°53′45″N 110°58′7″W - Pioneer Mountain: 45°13′55″N 111°27′2″W **Available Forecast Products:** - Temperature Forecasts (Max/Min) - Weather Type Forecast - Precipitation Probability (Days 1-2) - Snowfall Amount Forecasts (Days 1-2) - Winter Precipitation Probability (24/48/72-hour) - Snowfall Probability (24/48/72-hour) **Note**: This app uses the NOAA Weather API and may have occasional delays or service interruptions. Mountain weather can change rapidly - always check multiple sources for safety. """) demo.queue().launch()