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 )