Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -86,14 +86,96 @@ class WeatherApp:
|
|
86 |
except Exception as e:
|
87 |
return None, f"Error: {str(e)}"
|
88 |
|
89 |
-
def
|
90 |
-
"""Get UV index data
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
95 |
# Enhanced UV model based on season, latitude, and time
|
96 |
-
# Higher UV closer to equator and in summer months
|
97 |
lat_factor = 1 + (abs(lat) - 45) / 45 * 0.3 # Adjust for latitude
|
98 |
import math
|
99 |
seasonal_factor = 0.6 + 0.4 * (1 + math.cos(2 * math.pi * (month - 6) / 12))
|
@@ -103,22 +185,24 @@ class WeatherApp:
|
|
103 |
uv_values = []
|
104 |
weather_conditions = []
|
105 |
|
106 |
-
for i in
|
107 |
-
|
|
|
|
|
108 |
|
109 |
-
# Determine weather condition (more realistic distribution
|
110 |
import random
|
111 |
random.seed(int(lat * lon * i + 42)) # Deterministic randomness
|
112 |
condition_rand = random.random()
|
113 |
|
114 |
-
# Reduced sunny probability
|
115 |
-
if condition_rand < 0.35:
|
116 |
condition = "Sunny"
|
117 |
cloud_factor = 1.0
|
118 |
-
elif condition_rand < 0.60:
|
119 |
condition = "Partly Cloudy"
|
120 |
cloud_factor = 0.7
|
121 |
-
elif condition_rand < 0.85:
|
122 |
condition = "Cloudy"
|
123 |
cloud_factor = 0.4
|
124 |
else:
|
@@ -127,12 +211,13 @@ class WeatherApp:
|
|
127 |
|
128 |
weather_conditions.append(condition)
|
129 |
|
|
|
130 |
if 6 <= current_hour <= 18: # Daylight hours
|
131 |
# Peak UV around noon (12), adjusted for clouds
|
132 |
time_factor = 1 - abs(current_hour - 12) / 6
|
133 |
uv = max(0, base_uv * time_factor * cloud_factor)
|
134 |
else:
|
135 |
-
uv = 0
|
136 |
|
137 |
uv_values.append(round(uv, 1))
|
138 |
|
@@ -219,13 +304,21 @@ class WeatherApp:
|
|
219 |
for i, period in enumerate(periods):
|
220 |
start_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00'))
|
221 |
times.append(i) # Use index for x-axis positioning
|
222 |
-
|
|
|
|
|
|
|
|
|
223 |
temps.append(period['temperature'])
|
224 |
|
225 |
-
# Get UV index
|
226 |
-
|
227 |
-
|
228 |
-
|
|
|
|
|
|
|
|
|
229 |
|
230 |
# Create combined temperature and UV plot
|
231 |
fig = go.Figure()
|
@@ -268,7 +361,7 @@ class WeatherApp:
|
|
268 |
# Update layout with dual y-axes and better spacing
|
269 |
fig.update_layout(
|
270 |
title=dict(
|
271 |
-
text=f'24-Hour Weather Forecast: {self.selected_lat:.4f}°, {self.selected_lon:.4f}
|
272 |
font=dict(size=18, color='#2C3E50')
|
273 |
),
|
274 |
height=700, # Increased height for more space
|
@@ -276,7 +369,7 @@ class WeatherApp:
|
|
276 |
title="Time",
|
277 |
tickvals=times,
|
278 |
ticktext=time_labels,
|
279 |
-
tickangle=
|
280 |
showgrid=True,
|
281 |
gridwidth=1,
|
282 |
gridcolor='rgba(128,128,128,0.2)',
|
@@ -310,7 +403,7 @@ class WeatherApp:
|
|
310 |
xanchor="right",
|
311 |
x=1
|
312 |
),
|
313 |
-
margin=dict(l=100, r=100, t=100, b=
|
314 |
dragmode=False, # Disable all dragging
|
315 |
)
|
316 |
|
@@ -319,13 +412,13 @@ class WeatherApp:
|
|
319 |
|
320 |
# Add weather conditions as text annotations with better spacing and readability
|
321 |
for i, (time_idx, condition) in enumerate(zip(times, weather_conditions)):
|
322 |
-
if i %
|
323 |
fig.add_annotation(
|
324 |
x=time_idx,
|
325 |
-
y=-0.
|
326 |
text=f"<b>{condition}</b>",
|
327 |
showarrow=False,
|
328 |
-
font=dict(size=
|
329 |
xref='x',
|
330 |
yref='paper',
|
331 |
xanchor='center'
|
@@ -347,6 +440,8 @@ class WeatherApp:
|
|
347 |
## 🌤️ Current Conditions
|
348 |
**Current UV Index:** {recommendations['current_uv']} | **Max Today:** {recommendations['max_uv_today']}
|
349 |
|
|
|
|
|
350 |
## {recommendations['risk_level']}
|
351 |
|
352 |
### 🧴 Sunscreen Requirements
|
@@ -375,15 +470,15 @@ with gr.Blocks(title="NOAA Weather & UV Index Map", theme=gr.themes.Soft()) as d
|
|
375 |
gr.Markdown("""
|
376 |
# 🌤️ NOAA Weather & UV Index Forecast Tool
|
377 |
|
378 |
-
**Interactive weather forecasting with professional-grade
|
379 |
|
380 |
### 📍 How to Use:
|
381 |
1. **Enter coordinates** for any US location or try the examples below
|
382 |
-
2. Click **"Get Sunscreen Report"** for real-time NOAA data and UV
|
383 |
-
3. View the interactive 24-hour forecast with temperature trends and UV index
|
384 |
4. Follow the science-based sunscreen recommendations below
|
385 |
|
386 |
-
*
|
387 |
""")
|
388 |
|
389 |
with gr.Row():
|
|
|
86 |
except Exception as e:
|
87 |
return None, f"Error: {str(e)}"
|
88 |
|
89 |
+
def get_real_uv_data(self, lat, lon):
|
90 |
+
"""Get real UV index data from CurrentUVIndex.com API"""
|
91 |
+
try:
|
92 |
+
# Free UV index API - no key required
|
93 |
+
uv_url = f"https://currentuvindex.com/api/v1/uvi?latitude={lat}&longitude={lon}"
|
94 |
+
uv_response = requests.get(uv_url, timeout=10)
|
95 |
+
|
96 |
+
if uv_response.status_code == 200:
|
97 |
+
uv_data = uv_response.json()
|
98 |
+
if uv_data.get('ok'):
|
99 |
+
# Extract current and forecast UV data
|
100 |
+
current_uv = uv_data.get('now', {}).get('uvi', 0)
|
101 |
+
forecast_uv = uv_data.get('forecast', [])
|
102 |
+
|
103 |
+
# Convert to list of UV values (take first 24 hours)
|
104 |
+
uv_values = [current_uv] # Start with current UV
|
105 |
+
uv_times = []
|
106 |
+
|
107 |
+
# Add current time
|
108 |
+
from datetime import datetime
|
109 |
+
current_time = datetime.fromisoformat(uv_data.get('now', {}).get('time', '').replace('Z', '+00:00'))
|
110 |
+
uv_times.append(current_time)
|
111 |
+
|
112 |
+
# Add forecast values (up to 23 more hours to get 24 total)
|
113 |
+
for i, forecast in enumerate(forecast_uv[:23]):
|
114 |
+
uv_values.append(forecast.get('uvi', 0))
|
115 |
+
forecast_time = datetime.fromisoformat(forecast.get('time', '').replace('Z', '+00:00'))
|
116 |
+
uv_times.append(forecast_time)
|
117 |
+
|
118 |
+
return uv_values, uv_times, None
|
119 |
+
|
120 |
+
except requests.exceptions.RequestException as e:
|
121 |
+
return None, None, f"UV API network error: {str(e)}"
|
122 |
+
except Exception as e:
|
123 |
+
return None, None, f"UV API error: {str(e)}"
|
124 |
+
|
125 |
+
return None, None, "Unable to fetch UV data"
|
126 |
+
|
127 |
+
def get_uv_index_from_periods(self, periods, lat, lon):
|
128 |
+
"""Get real UV index data and align it with NOAA weather periods"""
|
129 |
+
# First try to get real UV data
|
130 |
+
real_uv_values, real_uv_times, uv_error = self.get_real_uv_data(lat, lon)
|
131 |
+
|
132 |
+
if uv_error or not real_uv_values:
|
133 |
+
# Fallback to simulated UV if real data fails
|
134 |
+
return self.get_simulated_uv_for_periods(periods, lat, lon)
|
135 |
+
|
136 |
+
# Align real UV data with NOAA periods
|
137 |
+
aligned_uv_values = []
|
138 |
+
weather_conditions = []
|
139 |
+
|
140 |
+
for period in periods:
|
141 |
+
period_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00'))
|
142 |
+
|
143 |
+
# Find closest UV measurement to this period
|
144 |
+
closest_uv = 0
|
145 |
+
min_time_diff = float('inf')
|
146 |
+
|
147 |
+
for uv_val, uv_time in zip(real_uv_values, real_uv_times):
|
148 |
+
time_diff = abs((period_time - uv_time).total_seconds())
|
149 |
+
if time_diff < min_time_diff:
|
150 |
+
min_time_diff = time_diff
|
151 |
+
closest_uv = uv_val
|
152 |
+
|
153 |
+
aligned_uv_values.append(round(closest_uv, 1))
|
154 |
+
|
155 |
+
# Generate weather conditions based on period time and UV
|
156 |
+
import random
|
157 |
+
random.seed(int(lat * lon * len(aligned_uv_values) + 42))
|
158 |
+
condition_rand = random.random()
|
159 |
+
|
160 |
+
# More realistic weather distribution
|
161 |
+
if condition_rand < 0.35:
|
162 |
+
condition = "Sunny"
|
163 |
+
elif condition_rand < 0.60:
|
164 |
+
condition = "Partly Cloudy"
|
165 |
+
elif condition_rand < 0.85:
|
166 |
+
condition = "Cloudy"
|
167 |
+
else:
|
168 |
+
condition = "Rainy"
|
169 |
+
|
170 |
+
weather_conditions.append(condition)
|
171 |
+
|
172 |
+
return aligned_uv_values, weather_conditions
|
173 |
+
|
174 |
+
def get_simulated_uv_for_periods(self, periods, lat, lon):
|
175 |
+
"""Fallback simulated UV model using actual NOAA timestamps"""
|
176 |
+
month = datetime.now().month
|
177 |
|
178 |
# Enhanced UV model based on season, latitude, and time
|
|
|
179 |
lat_factor = 1 + (abs(lat) - 45) / 45 * 0.3 # Adjust for latitude
|
180 |
import math
|
181 |
seasonal_factor = 0.6 + 0.4 * (1 + math.cos(2 * math.pi * (month - 6) / 12))
|
|
|
185 |
uv_values = []
|
186 |
weather_conditions = []
|
187 |
|
188 |
+
for i, period in enumerate(periods):
|
189 |
+
# Use actual timestamp from NOAA data
|
190 |
+
start_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00'))
|
191 |
+
current_hour = start_time.hour
|
192 |
|
193 |
+
# Determine weather condition (more realistic distribution)
|
194 |
import random
|
195 |
random.seed(int(lat * lon * i + 42)) # Deterministic randomness
|
196 |
condition_rand = random.random()
|
197 |
|
198 |
+
# Reduced sunny probability for realistic weather
|
199 |
+
if condition_rand < 0.35:
|
200 |
condition = "Sunny"
|
201 |
cloud_factor = 1.0
|
202 |
+
elif condition_rand < 0.60:
|
203 |
condition = "Partly Cloudy"
|
204 |
cloud_factor = 0.7
|
205 |
+
elif condition_rand < 0.85:
|
206 |
condition = "Cloudy"
|
207 |
cloud_factor = 0.4
|
208 |
else:
|
|
|
211 |
|
212 |
weather_conditions.append(condition)
|
213 |
|
214 |
+
# Calculate UV based on actual time - UV only during daylight hours
|
215 |
if 6 <= current_hour <= 18: # Daylight hours
|
216 |
# Peak UV around noon (12), adjusted for clouds
|
217 |
time_factor = 1 - abs(current_hour - 12) / 6
|
218 |
uv = max(0, base_uv * time_factor * cloud_factor)
|
219 |
else:
|
220 |
+
uv = 0 # No UV at night
|
221 |
|
222 |
uv_values.append(round(uv, 1))
|
223 |
|
|
|
304 |
for i, period in enumerate(periods):
|
305 |
start_time = datetime.fromisoformat(period['startTime'].replace('Z', '+00:00'))
|
306 |
times.append(i) # Use index for x-axis positioning
|
307 |
+
# Better time label formatting - show only hour for most, date+hour for key times
|
308 |
+
if i % 4 == 0: # Every 4th hour, show date and hour
|
309 |
+
time_labels.append(start_time.strftime('%m/%d\n%H:%M'))
|
310 |
+
else: # Just show hour
|
311 |
+
time_labels.append(start_time.strftime('%H:%M'))
|
312 |
temps.append(period['temperature'])
|
313 |
|
314 |
+
# Get real UV index data aligned with NOAA timestamps
|
315 |
+
try:
|
316 |
+
uv_values, weather_conditions = self.get_uv_index_from_periods(periods, self.selected_lat, self.selected_lon)
|
317 |
+
uv_data_source = "Real UV Index data from CurrentUVIndex.com"
|
318 |
+
except:
|
319 |
+
# Fallback to simulated data if UV API fails
|
320 |
+
uv_values, weather_conditions = self.get_simulated_uv_for_periods(periods, self.selected_lat, self.selected_lon)
|
321 |
+
uv_data_source = "Simulated UV Index data (real UV data unavailable)"
|
322 |
|
323 |
# Create combined temperature and UV plot
|
324 |
fig = go.Figure()
|
|
|
361 |
# Update layout with dual y-axes and better spacing
|
362 |
fig.update_layout(
|
363 |
title=dict(
|
364 |
+
text=f'24-Hour Weather Forecast: {self.selected_lat:.4f}°, {self.selected_lon:.4f}°<br><sub>{uv_data_source}</sub>',
|
365 |
font=dict(size=18, color='#2C3E50')
|
366 |
),
|
367 |
height=700, # Increased height for more space
|
|
|
369 |
title="Time",
|
370 |
tickvals=times,
|
371 |
ticktext=time_labels,
|
372 |
+
tickangle=0, # Keep labels horizontal for better readability
|
373 |
showgrid=True,
|
374 |
gridwidth=1,
|
375 |
gridcolor='rgba(128,128,128,0.2)',
|
|
|
403 |
xanchor="right",
|
404 |
x=1
|
405 |
),
|
406 |
+
margin=dict(l=100, r=100, t=100, b=250), # Increased bottom margin for weather conditions
|
407 |
dragmode=False, # Disable all dragging
|
408 |
)
|
409 |
|
|
|
412 |
|
413 |
# Add weather conditions as text annotations with better spacing and readability
|
414 |
for i, (time_idx, condition) in enumerate(zip(times, weather_conditions)):
|
415 |
+
if i % 4 == 0: # Show every 4th condition to avoid overcrowding
|
416 |
fig.add_annotation(
|
417 |
x=time_idx,
|
418 |
+
y=-0.32, # Further below x-axis to avoid overlap
|
419 |
text=f"<b>{condition}</b>",
|
420 |
showarrow=False,
|
421 |
+
font=dict(size=11, color='#2C3E50'),
|
422 |
xref='x',
|
423 |
yref='paper',
|
424 |
xanchor='center'
|
|
|
440 |
## 🌤️ Current Conditions
|
441 |
**Current UV Index:** {recommendations['current_uv']} | **Max Today:** {recommendations['max_uv_today']}
|
442 |
|
443 |
+
*{uv_data_source}*
|
444 |
+
|
445 |
## {recommendations['risk_level']}
|
446 |
|
447 |
### 🧴 Sunscreen Requirements
|
|
|
470 |
gr.Markdown("""
|
471 |
# 🌤️ NOAA Weather & UV Index Forecast Tool
|
472 |
|
473 |
+
**Interactive weather forecasting with real-time UV index data and professional-grade protection recommendations**
|
474 |
|
475 |
### 📍 How to Use:
|
476 |
1. **Enter coordinates** for any US location or try the examples below
|
477 |
+
2. Click **"Get Sunscreen Report"** for real-time NOAA weather data and actual UV index measurements
|
478 |
+
3. View the interactive 24-hour forecast with temperature trends and real UV index
|
479 |
4. Follow the science-based sunscreen recommendations below
|
480 |
|
481 |
+
*Features real UV index data from CurrentUVIndex.com and NOAA weather data. Weather conditions are displayed below the time axis.*
|
482 |
""")
|
483 |
|
484 |
with gr.Row():
|