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 with click-to-select functionality""" | |
| 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}<br><i>Click on map to change location</i>", | |
| icon=folium.Icon(color='red', icon='info-sign') | |
| ).add_to(m) | |
| # Add click functionality with JavaScript | |
| click_js = f""" | |
| <script> | |
| function onMapClick(e) {{ | |
| // Update the location inputs in Gradio | |
| const lat = e.latlng.lat.toFixed(4); | |
| const lon = e.latlng.lng.toFixed(4); | |
| // Try to find and update the Gradio inputs | |
| const latInput = document.querySelector('input[data-testid*="number-input"]'); | |
| const lonInputs = document.querySelectorAll('input[data-testid*="number-input"]'); | |
| if (latInput && lonInputs.length >= 2) {{ | |
| latInput.value = lat; | |
| lonInputs[1].value = lon; | |
| // Dispatch input events to trigger Gradio updates | |
| latInput.dispatchEvent(new Event('input', {{ bubbles: true }})); | |
| lonInputs[1].dispatchEvent(new Event('input', {{ bubbles: true }})); | |
| }} | |
| }} | |
| // Wait for map to be ready then add click listener | |
| setTimeout(function() {{ | |
| if (typeof window.map_{id(m)} !== 'undefined') {{ | |
| window.map_{id(m)}.on('click', onMapClick); | |
| }} | |
| }}, 1000); | |
| </script> | |
| """ | |
| return m._repr_html_() + click_js | |
| 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="Temperature (Β°F)", | |
| side='left', | |
| titlefont=dict(color='#FF6B6B'), | |
| tickfont=dict(color='#FF6B6B'), | |
| showgrid=True, | |
| gridwidth=1, | |
| gridcolor='rgba(255,107,107,0.2)' | |
| ), | |
| yaxis2=dict( | |
| title="UV Index", | |
| overlaying='y', | |
| side='right', | |
| titlefont=dict(color='#4A90E2'), | |
| 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. **Click anywhere on the map** or enter coordinates for any US location | |
| 2. Click **"Get Weather Forecast"** 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="Click on map or enter manually" | |
| ) | |
| lon_input = gr.Number( | |
| label="π Longitude", | |
| value=-98.5795, | |
| precision=4, | |
| info="Click on map or enter manually" | |
| ) | |
| with gr.Row(): | |
| update_btn = gr.Button("πΊοΈ Update Location", variant="secondary", size="sm") | |
| weather_btn = gr.Button("π€οΈ Get Weather Forecast", 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") | |
| gr.Markdown("*Click anywhere on the map to select a new location*") | |
| 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 Weather Forecast'** to see detailed sunscreen recommendations based on current UV 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 | |
| ) |