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()