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
Lat: {self.selected_lat:.4f}
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()
except:
return self.create_map()
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_uv_index(self, lat, lon):
"""Get UV index data (enhanced model based on research)"""
now = datetime.now()
hour = now.hour
month = now.month
# Enhanced UV model based on season, latitude, and time
# Higher UV closer to equator and in summer months
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 in range(24):
current_hour = (hour + i) % 24
# Determine weather condition (simplified model)
import random
random.seed(int(lat * lon * i)) # Deterministic randomness
condition_rand = random.random()
if condition_rand < 0.6:
condition = "☀️ Sunny"
cloud_factor = 1.0
elif condition_rand < 0.8:
condition = "⛅ Partly Cloudy"
cloud_factor = 0.7
elif condition_rand < 0.95:
condition = "☁️ Cloudy"
cloud_factor = 0.4
else:
condition = "🌧️ Rainy"
cloud_factor = 0.2
weather_conditions.append(condition)
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
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
time_labels.append(start_time.strftime('%m/%d\n%H:%M'))
temps.append(period['temperature'])
# Get UV index and weather conditions
uv_values, weather_conditions = self.get_uv_index(self.selected_lat, self.selected_lon)
uv_values = uv_values[:len(times)]
weather_conditions = weather_conditions[:len(times)]
# 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
fig.update_layout(
title=dict(
text=f'24-Hour Weather Forecast: {self.selected_lat:.4f}°, {self.selected_lon:.4f}°',
font=dict(size=18, color='#2C3E50')
),
height=600, # Increased height for more space
xaxis=dict(
title="Time",
tickvals=times,
ticktext=time_labels,
tickangle=45,
showgrid=True,
gridwidth=1,
gridcolor='rgba(128,128,128,0.2)'
),
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)'
),
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)]
),
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=60, r=60, t=80, b=150) # More margin for conditions
)
# Add weather conditions as annotations below x-axis
for i, (time_idx, condition) in enumerate(zip(times, weather_conditions)):
if i % 3 == 0: # Show every 3rd condition to avoid crowding
fig.add_annotation(
x=time_idx,
y=-0.15, # Below x-axis
text=condition,
showarrow=False,
font=dict(size=10),
xref='x',
yref='paper',
xanchor='center'
)
# Add UV risk zones as background colors
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']}
## {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 professional-grade UV 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 data and UV analysis
3. View the interactive 24-hour forecast with temperature trends and UV index
4. Follow the science-based sunscreen recommendations below
*Weather conditions are displayed below the time axis. UV risk zones are color-coded on the chart.*
""")
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("""
### 📍 Popular Locations:
- **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
""")
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]
)
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]
)
lon_input.change(
fn=weather_app.update_location,
inputs=[lat_input, lon_input],
outputs=[map_html]
)
# Launch the app
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True
)