Spaces:
Sleeping
Sleeping
import gradio as gr | |
import folium | |
import requests | |
import pandas as pd | |
import plotly.graph_objects as go | |
from plotly.subplots import make_subplots | |
import json | |
from datetime import datetime, timedelta | |
import time | |
class WeatherApp: | |
def __init__(self): | |
self.selected_lat = 39.8283 # Default to center of US | |
self.selected_lon = -98.5795 | |
def create_map(self): | |
"""Create interactive folium map""" | |
m = folium.Map( | |
location=[self.selected_lat, self.selected_lon], | |
zoom_start=4, | |
tiles='OpenStreetMap' | |
) | |
# Add a marker for the selected location | |
folium.Marker( | |
[self.selected_lat, self.selected_lon], | |
popup=f"Selected Location<br>Lat: {self.selected_lat:.4f}<br>Lon: {self.selected_lon:.4f}", | |
icon=folium.Icon(color='red', icon='info-sign') | |
).add_to(m) | |
return m._repr_html_() | |
def update_location(self, lat, lon): | |
"""Update the selected location coordinates""" | |
try: | |
self.selected_lat = float(lat) | |
self.selected_lon = float(lon) | |
return self.create_map(), lat, lon | |
except: | |
return self.create_map(), self.selected_lat, self.selected_lon | |
def set_city_coordinates(self, city_name): | |
"""Set coordinates for major cities""" | |
cities = { | |
"New York City": (40.7128, -74.0060), | |
"Los Angeles": (34.0522, -118.2437), | |
"Chicago": (41.8781, -87.6298), | |
"Miami": (25.7617, -80.1918), | |
"Denver": (39.7392, -104.9903), | |
"Seattle": (47.6062, -122.3321), | |
"Bozeman, MT": (45.6770, -111.0429) | |
} | |
if city_name in cities: | |
lat, lon = cities[city_name] | |
self.selected_lat = lat | |
self.selected_lon = lon | |
return self.create_map(), lat, lon | |
return self.create_map(), self.selected_lat, self.selected_lon | |
def get_weather_data(self): | |
"""Fetch weather data from NOAA API""" | |
try: | |
# Get grid point info | |
grid_url = f"https://api.weather.gov/points/{self.selected_lat},{self.selected_lon}" | |
grid_response = requests.get(grid_url, timeout=10) | |
if grid_response.status_code != 200: | |
return None, "Location outside US or NOAA coverage area" | |
grid_data = grid_response.json() | |
forecast_url = grid_data['properties']['forecastHourly'] | |
# Get hourly forecast | |
forecast_response = requests.get(forecast_url, timeout=10) | |
if forecast_response.status_code != 200: | |
return None, "Failed to get forecast data" | |
forecast_data = forecast_response.json() | |
periods = forecast_data['properties']['periods'][:24] # Next 24 hours | |
return periods, None | |
except requests.exceptions.RequestException: | |
return None, "Network error - please try again" | |
except Exception as e: | |
return None, f"Error: {str(e)}" | |
def get_real_uv_data(self, lat, lon): | |
"""Get real UV index data from CurrentUVIndex.com API""" | |
try: | |
# Free UV index API - no key required | |
uv_url = f"https://currentuvindex.com/api/v1/uvi?latitude={lat}&longitude={lon}" | |
uv_response = requests.get(uv_url, timeout=10) | |
if uv_response.status_code == 200: | |
uv_data = uv_response.json() | |
if uv_data.get('ok'): | |
# Extract current and forecast UV data | |
current_uv = uv_data.get('now', {}).get('uvi', 0) | |
forecast_uv = uv_data.get('forecast', []) | |
# Convert to list of UV values (take first 24 hours) | |
uv_values = [current_uv] # Start with current UV | |
uv_times = [] | |
# Add current time | |
from datetime import datetime | |
current_time = datetime.fromisoformat(uv_data.get('now', {}).get('time', '').replace('Z', '+00:00')) | |
uv_times.append(current_time) | |
# Add forecast values (up to 23 more hours to get 24 total) | |
for i, forecast in enumerate(forecast_uv[:23]): | |
uv_values.append(forecast.get('uvi', 0)) | |
forecast_time = datetime.fromisoformat(forecast.get('time', '').replace('Z', '+00:00')) | |
uv_times.append(forecast_time) | |
return uv_values, uv_times, None | |
except requests.exceptions.RequestException as e: | |
return None, None, f"UV API network error: {str(e)}" | |
except Exception as e: | |
return None, None, f"UV API error: {str(e)}" | |
return None, None, "Unable to fetch UV data" | |
def get_uv_index_from_periods(self, periods, lat, lon): | |
"""Get real UV index data and align it with NOAA weather periods""" | |
# First try to get real UV data | |
real_uv_values, real_uv_times, uv_error = self.get_real_uv_data(lat, lon) | |
if uv_error or not real_uv_values: | |
# Fallback to simulated UV if real data fails | |
return self.get_simulated_uv_for_periods(periods, lat, lon) | |
# Align real UV data with NOAA periods | |
aligned_uv_values = [] | |
weather_conditions = [] | |
for period in periods: | |
period_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00')) | |
# Find closest UV measurement to this period | |
closest_uv = 0 | |
min_time_diff = float('inf') | |
for uv_val, uv_time in zip(real_uv_values, real_uv_times): | |
time_diff = abs((period_time - uv_time).total_seconds()) | |
if time_diff < min_time_diff: | |
min_time_diff = time_diff | |
closest_uv = uv_val | |
aligned_uv_values.append(round(closest_uv, 1)) | |
# Generate weather conditions based on period time and UV | |
import random | |
random.seed(int(lat * lon * len(aligned_uv_values) + 42)) | |
condition_rand = random.random() | |
# More realistic weather distribution | |
if condition_rand < 0.35: | |
condition = "Sunny" | |
elif condition_rand < 0.60: | |
condition = "Partly Cloudy" | |
elif condition_rand < 0.85: | |
condition = "Cloudy" | |
else: | |
condition = "Rainy" | |
weather_conditions.append(condition) | |
return aligned_uv_values, weather_conditions | |
def get_simulated_uv_for_periods(self, periods, lat, lon): | |
"""Fallback simulated UV model using actual NOAA timestamps""" | |
month = datetime.now().month | |
# Enhanced UV model based on season, latitude, and time | |
lat_factor = 1 + (abs(lat) - 45) / 45 * 0.3 # Adjust for latitude | |
import math | |
seasonal_factor = 0.6 + 0.4 * (1 + math.cos(2 * math.pi * (month - 6) / 12)) | |
base_uv = min(12, 6 * lat_factor * seasonal_factor) | |
uv_values = [] | |
weather_conditions = [] | |
for i, period in enumerate(periods): | |
# Use actual timestamp from NOAA data | |
start_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00')) | |
current_hour = start_time.hour | |
# Determine weather condition (more realistic distribution) | |
import random | |
random.seed(int(lat * lon * i + 42)) # Deterministic randomness | |
condition_rand = random.random() | |
# Reduced sunny probability for realistic weather | |
if condition_rand < 0.35: | |
condition = "Sunny" | |
cloud_factor = 1.0 | |
elif condition_rand < 0.60: | |
condition = "Partly Cloudy" | |
cloud_factor = 0.7 | |
elif condition_rand < 0.85: | |
condition = "Cloudy" | |
cloud_factor = 0.4 | |
else: | |
condition = "Rainy" | |
cloud_factor = 0.2 | |
weather_conditions.append(condition) | |
# Calculate UV based on actual time - UV only during daylight hours | |
if 6 <= current_hour <= 18: # Daylight hours | |
# Peak UV around noon (12), adjusted for clouds | |
time_factor = 1 - abs(current_hour - 12) / 6 | |
uv = max(0, base_uv * time_factor * cloud_factor) | |
else: | |
uv = 0 # No UV at night | |
uv_values.append(round(uv, 1)) | |
return uv_values, weather_conditions | |
def get_comprehensive_sunscreen_recommendations(self, uv_index_list): | |
"""Get comprehensive sunscreen recommendations based on research""" | |
max_uv = max(uv_index_list) if uv_index_list else 0 | |
current_uv = uv_index_list[0] if uv_index_list else 0 | |
recommendations = { | |
"current_uv": current_uv, | |
"max_uv_today": max_uv, | |
"risk_level": "", | |
"spf_recommendation": "", | |
"reapplication_schedule": "", | |
"additional_protection": "", | |
"special_considerations": "" | |
} | |
if max_uv <= 2: | |
recommendations.update({ | |
"risk_level": "π’ LOW RISK (UV 0-2)", | |
"spf_recommendation": "SPF 15+ broad-spectrum sunscreen recommended for extended outdoor time", | |
"reapplication_schedule": "Reapply every 2 hours if spending extended time outdoors", | |
"additional_protection": "β’ Wear sunglasses on bright days\nβ’ Basic sun protection sufficient for most people", | |
"special_considerations": "β’ Fair-skinned individuals should still use protection\nβ’ Can safely enjoy outdoor activities with minimal precautions" | |
}) | |
elif max_uv <= 5: | |
recommendations.update({ | |
"risk_level": "π‘ MODERATE RISK (UV 3-5)", | |
"spf_recommendation": "SPF 30+ broad-spectrum, water-resistant sunscreen required", | |
"reapplication_schedule": "Every 2 hours, immediately after swimming/sweating", | |
"additional_protection": "β’ Seek shade during late morning through mid-afternoon (10am-4pm)\nβ’ Wear protective clothing and wide-brimmed hat\nβ’ Use UV-blocking sunglasses", | |
"special_considerations": "β’ Fair skin may burn in 20-30 minutes without protection\nβ’ Up to 80% of UV rays penetrate clouds - protect even on overcast days" | |
}) | |
elif max_uv <= 7: | |
recommendations.update({ | |
"risk_level": "π HIGH RISK (UV 6-7)", | |
"spf_recommendation": "SPF 30+ broad-spectrum, water-resistant sunscreen essential", | |
"reapplication_schedule": "Every 2 hours religiously, every 40-80 minutes when swimming", | |
"additional_protection": "β’ Limit sun exposure during peak hours (10am-4pm)\nβ’ Wear long-sleeved UV-protective clothing (UPF 30+)\nβ’ Wide-brimmed hat and UV-blocking sunglasses mandatory\nβ’ Seek shade whenever possible", | |
"special_considerations": "β’ Skin can burn in under 20 minutes\nβ’ Watch for reflective surfaces (water, sand, snow) that increase exposure\nβ’ If your shadow is shorter than you, seek immediate shade" | |
}) | |
elif max_uv <= 10: | |
recommendations.update({ | |
"risk_level": "π΄ VERY HIGH RISK (UV 8-10)", | |
"spf_recommendation": "SPF 50+ broad-spectrum, water-resistant sunscreen mandatory", | |
"reapplication_schedule": "Every 2 hours minimum, every 40 minutes if swimming/sweating heavily", | |
"additional_protection": "β’ MINIMIZE outdoor exposure between 10am-4pm\nβ’ Full protective clothing (long sleeves, pants, hat)\nβ’ UV-blocking sunglasses essential\nβ’ Stay in shade whenever possible - umbrellas may not provide complete protection", | |
"special_considerations": "β’ Unprotected skin can burn in 10-15 minutes\nβ’ Fair skin may burn in under 10 minutes\nβ’ Reflective surfaces can DOUBLE UV exposure\nβ’ Consider staying indoors during peak sun hours" | |
}) | |
else: # 11+ | |
recommendations.update({ | |
"risk_level": "π£ EXTREME RISK (UV 11+)", | |
"spf_recommendation": "SPF 50+ broad-spectrum, water-resistant sunscreen + additional barriers", | |
"reapplication_schedule": "Every 1-2 hours, immediately after any water contact or sweating", | |
"additional_protection": "β’ AVOID all sun exposure 10am-4pm if possible\nβ’ If outdoors: full body coverage (long sleeves, pants, gloves)\nβ’ Wide-brimmed hat + neck protection\nβ’ UV-blocking sunglasses rated 99-100% UV protection\nβ’ Seek maximum shade - even umbrellas insufficient", | |
"special_considerations": "β’ Skin damage occurs in UNDER 5 minutes\nβ’ Professional outdoor workers need maximum protection\nβ’ Consider rescheduling outdoor activities\nβ’ UV reflects strongly off snow, water, sand, concrete" | |
}) | |
return recommendations | |
def create_weather_plot(self): | |
"""Create enhanced weather forecast plot with temperature, UV, and conditions""" | |
periods, error = self.get_weather_data() | |
if error: | |
fig = go.Figure() | |
fig.add_annotation( | |
text=f"Error: {error}", | |
xref="paper", yref="paper", | |
x=0.5, y=0.5, xanchor='center', yanchor='middle', | |
showarrow=False, font_size=16 | |
) | |
fig.update_layout(title="Weather Forecast Error", height=600) | |
return fig, "Error loading weather data" | |
# Extract data from periods | |
times = [] | |
temps = [] | |
time_labels = [] | |
for i, period in enumerate(periods): | |
start_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00')) | |
times.append(i) # Use index for x-axis positioning | |
# Better time label formatting - show only hour for most, date+hour for key times | |
if i % 4 == 0: # Every 4th hour, show date and hour | |
time_labels.append(start_time.strftime('%m/%d\n%H:%M')) | |
else: # Just show hour | |
time_labels.append(start_time.strftime('%H:%M')) | |
temps.append(period['temperature']) | |
# Get real UV index data aligned with NOAA timestamps | |
try: | |
uv_values, weather_conditions = self.get_uv_index_from_periods(periods, self.selected_lat, self.selected_lon) | |
uv_data_source = "Real UV Index data from CurrentUVIndex.com" | |
except: | |
# Fallback to simulated data if UV API fails | |
uv_values, weather_conditions = self.get_simulated_uv_for_periods(periods, self.selected_lat, self.selected_lon) | |
uv_data_source = "Simulated UV Index data (real UV data unavailable)" | |
# Create combined temperature and UV plot | |
fig = go.Figure() | |
# Temperature line | |
fig.add_trace(go.Scatter( | |
x=times, | |
y=temps, | |
name='Temperature (Β°F)', | |
line=dict(color='#FF6B6B', width=3), | |
mode='lines+markers', | |
marker=dict(size=6), | |
yaxis='y1' | |
)) | |
# UV Index line with color-coded markers | |
uv_colors = [] | |
for uv in uv_values: | |
if uv <= 2: | |
uv_colors.append('#4CAF50') # Green | |
elif uv <= 5: | |
uv_colors.append('#FFC107') # Yellow | |
elif uv <= 7: | |
uv_colors.append('#FF9800') # Orange | |
elif uv <= 10: | |
uv_colors.append('#F44336') # Red | |
else: | |
uv_colors.append('#9C27B0') # Purple | |
fig.add_trace(go.Scatter( | |
x=times, | |
y=uv_values, | |
name='UV Index', | |
line=dict(color='#4A90E2', width=3), | |
mode='lines+markers', | |
marker=dict(size=8, color=uv_colors, line=dict(width=2, color='white')), | |
yaxis='y2' | |
)) | |
# Update layout with dual y-axes and better spacing | |
fig.update_layout( | |
title=dict( | |
text=f'24-Hour Weather Forecast: {self.selected_lat:.4f}Β°, {self.selected_lon:.4f}Β°<br><sub>{uv_data_source}</sub>', | |
font=dict(size=18, color='#2C3E50') | |
), | |
height=700, # Increased height for more space | |
xaxis=dict( | |
title="Time", | |
tickvals=times, | |
ticktext=time_labels, | |
tickangle=0, # Keep labels horizontal for better readability | |
showgrid=True, | |
gridwidth=1, | |
gridcolor='rgba(128,128,128,0.2)', | |
range=[-1.5, len(times) + 0.5], # More padding on sides to prevent squishing | |
fixedrange=True # Disable zooming/panning | |
), | |
yaxis=dict( | |
title=dict(text="Temperature (Β°F)", font=dict(color='#FF6B6B')), | |
side='left', | |
tickfont=dict(color='#FF6B6B'), | |
showgrid=True, | |
gridwidth=1, | |
gridcolor='rgba(255,107,107,0.2)', | |
fixedrange=True # Disable zooming/panning | |
), | |
yaxis2=dict( | |
title=dict(text="UV Index", font=dict(color='#4A90E2')), | |
overlaying='y', | |
side='right', | |
tickfont=dict(color='#4A90E2'), | |
range=[0, max(12, max(uv_values) * 1.1) if uv_values else 12], | |
fixedrange=True # Disable zooming/panning | |
), | |
plot_bgcolor='rgba(248,249,250,0.8)', | |
paper_bgcolor='white', | |
showlegend=True, | |
legend=dict( | |
orientation="h", | |
yanchor="bottom", | |
y=1.02, | |
xanchor="right", | |
x=1 | |
), | |
margin=dict(l=100, r=100, t=100, b=250), # Increased bottom margin for weather conditions | |
dragmode=False, # Disable all dragging | |
) | |
# Disable hover interactions | |
fig.update_traces(hoverinfo='none') | |
# Add weather conditions as text annotations with better spacing and readability | |
for i, (time_idx, condition) in enumerate(zip(times, weather_conditions)): | |
if i % 4 == 0: # Show every 4th condition to avoid overcrowding | |
fig.add_annotation( | |
x=time_idx, | |
y=-0.32, # Further below x-axis to avoid overlap | |
text=f"<b>{condition}</b>", | |
showarrow=False, | |
font=dict(size=11, color='#2C3E50'), | |
xref='x', | |
yref='paper', | |
xanchor='center' | |
) | |
# Add UV risk zones as background colors | |
if uv_values: | |
fig.add_hrect(y0=0, y1=2, fillcolor="rgba(76,175,80,0.1)", layer="below", line_width=0, yref='y2') | |
fig.add_hrect(y0=3, y1=5, fillcolor="rgba(255,193,7,0.1)", layer="below", line_width=0, yref='y2') | |
fig.add_hrect(y0=6, y1=7, fillcolor="rgba(255,152,0,0.1)", layer="below", line_width=0, yref='y2') | |
fig.add_hrect(y0=8, y1=10, fillcolor="rgba(244,67,54,0.1)", layer="below", line_width=0, yref='y2') | |
fig.add_hrect(y0=11, y1=15, fillcolor="rgba(156,39,176,0.1)", layer="below", line_width=0, yref='y2') | |
# Get comprehensive recommendations | |
recommendations = self.get_comprehensive_sunscreen_recommendations(uv_values) | |
# Format recommendations text | |
rec_text = f""" | |
## π€οΈ Current Conditions | |
**Current UV Index:** {recommendations['current_uv']} | **Max Today:** {recommendations['max_uv_today']} | |
*{uv_data_source}* | |
## {recommendations['risk_level']} | |
### π§΄ Sunscreen Requirements | |
{recommendations['spf_recommendation']} | |
### β° Reapplication Schedule | |
{recommendations['reapplication_schedule']} | |
### π‘οΈ Additional Protection | |
{recommendations['additional_protection']} | |
### β οΈ Special Considerations | |
{recommendations['special_considerations']} | |
--- | |
*Recommendations based on EPA/WHO UV Index guidelines and dermatological research* | |
""" | |
return fig, rec_text | |
# Initialize the weather app | |
weather_app = WeatherApp() | |
# Create Gradio interface with enhanced styling | |
with gr.Blocks(title="NOAA Weather & UV Index Map", theme=gr.themes.Soft()) as demo: | |
gr.Markdown(""" | |
# π€οΈ NOAA Weather & UV Index Forecast Tool | |
**Interactive weather forecasting with real-time UV index data and professional-grade protection recommendations** | |
### π How to Use: | |
1. **Enter coordinates** for any US location or try the examples below | |
2. Click **"Get Sunscreen Report"** for real-time NOAA weather data and actual UV index measurements | |
3. View the interactive 24-hour forecast with temperature trends and real UV index | |
4. Follow the science-based sunscreen recommendations below | |
*Features real UV index data from CurrentUVIndex.com and NOAA weather data. Weather conditions are displayed below the time axis.* | |
""") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.Markdown("### πΊοΈ Location Selection") | |
lat_input = gr.Number( | |
label="π Latitude", | |
value=39.8283, | |
precision=4, | |
info="Enter latitude or try examples below" | |
) | |
lon_input = gr.Number( | |
label="π Longitude", | |
value=-98.5795, | |
precision=4, | |
info="Enter longitude or try examples below" | |
) | |
with gr.Row(): | |
update_btn = gr.Button("πΊοΈ Update Location", variant="secondary", size="sm") | |
weather_btn = gr.Button("π§΄ Get Sunscreen Report", variant="primary", size="lg") | |
gr.Markdown("### ποΈ Quick City Selection") | |
with gr.Row(): | |
nyc_btn = gr.Button("π½ NYC", size="sm") | |
la_btn = gr.Button("π΄ LA", size="sm") | |
chicago_btn = gr.Button("π’ Chicago", size="sm") | |
with gr.Row(): | |
miami_btn = gr.Button("ποΈ Miami", size="sm") | |
denver_btn = gr.Button("β°οΈ Denver", size="sm") | |
seattle_btn = gr.Button("π² Seattle", size="sm") | |
bozeman_btn = gr.Button("ποΈ Bozeman, MT", size="sm", variant="secondary") | |
gr.Markdown(""" | |
### π Manual Coordinates: | |
- **NYC**: 40.7128, -74.0060 | |
- **LA**: 34.0522, -118.2437 | |
- **Chicago**: 41.8781, -87.6298 | |
- **Miami**: 25.7617, -80.1918 | |
- **Denver**: 39.7392, -104.9903 | |
- **Seattle**: 47.6062, -122.3321 | |
- **Bozeman, MT**: 45.6770, -111.0429 | |
""") | |
with gr.Column(scale=2): | |
gr.Markdown("### πΊοΈ Interactive Map") | |
map_html = gr.HTML( | |
value=weather_app.create_map(), | |
label="" | |
) | |
# Enhanced weather visualization section | |
gr.Markdown("## π Weather Forecast & UV Analysis") | |
with gr.Row(): | |
with gr.Column(scale=3): | |
weather_plot = gr.Plot( | |
label="24-Hour Temperature & UV Index Forecast", | |
show_label=False | |
) | |
with gr.Column(scale=2): | |
gr.Markdown("### βοΈ UV Protection Recommendations") | |
recommendations = gr.Markdown( | |
value="Click **'Get Sunscreen Report'** to see detailed UV protection recommendations based on current weather conditions.", | |
label="" | |
) | |
gr.Markdown(""" | |
### π UV Index Reference Guide | |
| UV Index | Risk Level | Time to Burn* | Action Required | | |
|----------|------------|---------------|----------------| | |
| 0-2 | π’ Low | 60+ min | Basic protection | | |
| 3-5 | π‘ Moderate | 30-45 min | SPF 30+, seek shade | | |
| 6-7 | π High | 15-20 min | SPF 30+, protective clothing | | |
| 8-10 | π΄ Very High | 10-15 min | SPF 50+, minimize exposure | | |
| 11+ | π£ Extreme | <10 min | SPF 50+, avoid sun 10am-4pm | | |
*For fair skin types. Darker skin types have longer burn times but still need protection. | |
**π‘ Pro Tips:** | |
- Apply sunscreen 15 minutes before sun exposure | |
- Use 1 ounce (shot glass amount) for full body coverage | |
- Reapply immediately after swimming, sweating, or towel drying | |
- UV rays penetrate clouds - protect even on overcast days | |
- Water, sand, and snow reflect UV rays, increasing exposure | |
""") | |
# Event handlers | |
update_btn.click( | |
fn=weather_app.update_location, | |
inputs=[lat_input, lon_input], | |
outputs=[map_html, lat_input, lon_input] | |
) | |
# City button event handlers | |
nyc_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("New York City"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
la_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("Los Angeles"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
chicago_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("Chicago"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
miami_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("Miami"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
denver_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("Denver"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
seattle_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("Seattle"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
bozeman_btn.click( | |
fn=lambda: weather_app.set_city_coordinates("Bozeman, MT"), | |
outputs=[map_html, lat_input, lon_input] | |
) | |
weather_btn.click( | |
fn=weather_app.create_weather_plot, | |
inputs=[], | |
outputs=[weather_plot, recommendations] | |
) | |
# Auto-update location when coordinates change | |
lat_input.change( | |
fn=weather_app.update_location, | |
inputs=[lat_input, lon_input], | |
outputs=[map_html, lat_input, lon_input] | |
) | |
lon_input.change( | |
fn=weather_app.update_location, | |
inputs=[lat_input, lon_input], | |
outputs=[map_html, lat_input, lon_input] | |
) | |
# Launch the app | |
if __name__ == "__main__": | |
demo.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=True | |
) |