nakas commited on
Commit
39fd183
·
verified ·
1 Parent(s): cc76c8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -75
app.py CHANGED
@@ -18,48 +18,54 @@ def get_radar_frames(lat, lon, hours_back=6):
18
  """Get historical radar frames and create animation."""
19
  frames = []
20
  frame_files = []
21
- current_time = datetime.utcnow()
22
 
23
- # Get historical radar frames at 10-minute intervals
24
- for hour in range(hours_back, -1, -1):
25
- for minute in range(0, 60, 10):
26
- time = current_time - timedelta(hours=hour, minutes=minute)
27
-
28
- # Try both Level 2 and Level 3 radar products
29
- urls = [
30
- f"https://radar.weather.gov/ridge/RadarImg/N0R/KMSX_{time.strftime('%Y%m%d_%H%M')}_N0R.gif", # Base reflectivity
31
- f"https://radar.weather.gov/ridge/RadarImg/NCR/KMSX_{time.strftime('%Y%m%d_%H%M')}_NCR.gif", # Composite reflectivity
32
- f"https://radar.weather.gov/ridge/RadarImg/N0S/KMSX_{time.strftime('%Y%m%d_%H%M')}_N0S.gif", # Storm relative motion
33
- ]
34
-
35
- for url in urls:
36
- try:
37
- print(f"Trying URL: {url}") # Debug print
38
- response = requests.get(url, timeout=5)
39
- if response.status_code == 200:
40
- print(f"Successfully downloaded: {url}") # Debug print
41
- img = Image.open(io.BytesIO(response.content)).convert('RGB')
42
- img = crop_to_region(img, lat, lon)
43
-
44
- # Add timestamp
45
- draw = ImageDraw.Draw(img)
46
- radar_type = "Base" if "N0R" in url else "Composite" if "NCR" in url else "Storm"
47
- text = f"{radar_type} Radar\n{time.strftime('%Y-%m-%d %H:%M UTC')}"
48
- draw_text_with_outline(draw, text, (10, 10))
49
-
50
- # Save both individual frame and add to animation
51
- with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp:
52
- img.save(tmp.name)
53
- frame_files.append(tmp.name)
54
- frames.append(img)
55
- break # Successfully got frame for this time period
56
- except Exception as e:
57
- print(f"Error with URL {url}: {str(e)}") # Debug print
58
- continue
 
 
 
 
 
 
 
59
 
60
  if frames:
61
- print(f"Creating animation with {len(frames)} frames") # Debug print
62
- # Create animation
63
  with tempfile.NamedTemporaryFile(delete=False, suffix='.gif') as tmp:
64
  frames[0].save(
65
  tmp.name,
@@ -68,10 +74,10 @@ def get_radar_frames(lat, lon, hours_back=6):
68
  duration=200, # 0.2 seconds per frame
69
  loop=0
70
  )
71
- print(f"Animation saved to {tmp.name}") # Debug print
72
  return tmp.name, frame_files
73
  else:
74
- print("No frames collected for animation") # Debug print
75
 
76
  return None, []
77
 
@@ -87,28 +93,36 @@ def crop_to_region(img, lat, lon, zoom=1.5):
87
  x = (lon - lon_min) / (lon_max - lon_min) * img_width
88
  y = (lat_max - lat) / (lat_max - lat_min) * img_height
89
 
90
- # Calculate crop box
91
  crop_width = img_width / zoom
92
  crop_height = img_height / zoom
93
 
94
- # Center crop box on point
95
- x1 = max(0, x - crop_width/2)
96
- y1 = max(0, y - crop_height/2)
97
- x2 = min(img_width, x + crop_width/2)
98
- y2 = min(img_height, y + crop_height/2)
99
 
100
- # Ensure we don't crop outside bounds
101
- if x1 < 0: x2 -= x1; x1 = 0
102
- if y1 < 0: y2 -= y1; y1 = 0
103
- if x2 > img_width: x1 -= (x2 - img_width); x2 = img_width
104
- if y2 > img_height: y1 -= (y2 - img_height); y2 = img_height
 
 
 
 
 
 
 
 
105
 
106
- # Crop and resize
107
  cropped = img.crop((x1, y1, x2, y2))
108
  return cropped.resize((img_width, img_height), Image.Resampling.LANCZOS)
109
 
110
  def draw_text_with_outline(draw, text, pos, font_size=20, center=False):
111
- """Draw text with outline for visibility."""
112
  try:
113
  font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", font_size)
114
  except:
@@ -116,17 +130,17 @@ def draw_text_with_outline(draw, text, pos, font_size=20, center=False):
116
 
117
  x, y = pos
118
  if center:
119
- # Get text size for centering
120
  bbox = draw.textbbox((0, 0), text, font=font)
121
  text_width = bbox[2] - bbox[0]
122
  text_height = bbox[3] - bbox[1]
123
  x = x - text_width // 2
124
  y = y - text_height // 2
125
 
126
- # Draw outline
127
- for dx, dy in [(-1,-1), (-1,1), (1,-1), (1,1)]:
128
- draw.text((x+dx, y+dy), text, fill='black', font=font)
129
- # Draw text
130
  draw.text((x, y), text, fill='white', font=font)
131
 
132
  def get_forecast_products(lat, lon):
@@ -159,12 +173,12 @@ def get_forecast_products(lat, lon):
159
  img = Image.open(io.BytesIO(response.content)).convert('RGB')
160
  img = crop_to_region(img, lat, lon)
161
 
162
- # Add title
163
  draw = ImageDraw.Draw(img)
164
  text = f"{title}\n{timestamp}"
165
  draw_text_with_outline(draw, text, (10, 10))
166
 
167
- # Save processed image
168
  with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp:
169
  img.save(tmp.name)
170
  gallery_data.append(tmp.name)
@@ -177,7 +191,7 @@ def get_map(lat, lon):
177
  """Create a map centered on the given coordinates with markers."""
178
  m = folium.Map(location=[lat, lon], zoom_start=9)
179
 
180
- # Add all Montana peaks
181
  for peak_name, coords in MONTANA_PEAKS.items():
182
  folium.Marker(
183
  coords,
@@ -185,7 +199,7 @@ def get_map(lat, lon):
185
  tooltip=peak_name
186
  ).add_to(m)
187
 
188
- # Add current location marker if not a peak
189
  if (lat, lon) not in MONTANA_PEAKS.values():
190
  folium.Marker([lat, lon], popup=f"Selected Location<br>Lat: {lat:.4f}, Lon: {lon:.4f}").add_to(m)
191
 
@@ -195,15 +209,15 @@ def get_map(lat, lon):
195
  def get_noaa_forecast(lat, lon):
196
  """Get NOAA text forecast."""
197
  try:
198
- # Get forecast URL
199
  points_url = f"https://api.weather.gov/points/{lat},{lon}"
200
  response = requests.get(points_url, timeout=10)
201
  forecast_url = response.json()['properties']['forecast']
202
 
203
- # Get forecast data
204
  forecast = requests.get(forecast_url, timeout=10).json()
205
 
206
- # Format text forecast
207
  text = "Weather Forecast:\n\n"
208
  for period in forecast['properties']['periods']:
209
  text += f"{period['name']}:\n"
@@ -211,9 +225,9 @@ def get_noaa_forecast(lat, lon):
211
  text += f"Wind: {period['windSpeed']} {period['windDirection']}\n"
212
  text += f"{period['detailedForecast']}\n\n"
213
 
214
- # Highlight snow events
215
  if any(word in period['detailedForecast'].lower() for word in
216
- ['snow', 'flurries', 'wintry mix', 'blizzard']):
217
  text += "⚠️ SNOW EVENT PREDICTED ⚠️\n\n"
218
 
219
  return text
@@ -223,7 +237,8 @@ def get_noaa_forecast(lat, lon):
223
  def make_peak_click_handler(peak_name):
224
  """Creates a click handler for a specific peak."""
225
  def handler():
226
- return MONTANA_PEAKS[peak_name]
 
227
  return handler
228
 
229
  def update_weather(lat, lon):
@@ -238,16 +253,16 @@ def update_weather(lat, lon):
238
  # Get text forecast
239
  forecast_text = get_noaa_forecast(lat, lon)
240
 
241
- # Get radar animation and frames
242
  radar_animation, frames = get_radar_frames(lat, lon)
243
 
244
- # Get forecast products
245
  forecast_frames = get_forecast_products(lat, lon)
246
 
247
- # Combine all images for gallery
248
  gallery_data = frames + forecast_frames
249
 
250
- # Get map
251
  map_html = get_map(lat, lon)
252
 
253
  return forecast_text, gallery_data, radar_animation, map_html
@@ -351,7 +366,7 @@ with gr.Blocks(title="Montana Mountain Weather") as demo:
351
  - Standard Radar
352
  - Base Reflectivity
353
  - Composite Reflectivity
354
- All images are cropped to focus on selected location.
355
  Radar animation shows the last 6 hours of data.
356
 
357
  **Forecast Products:**
@@ -364,4 +379,4 @@ with gr.Blocks(title="Montana Mountain Weather") as demo:
364
  Mountain weather can change rapidly - always check multiple sources for safety.
365
  """)
366
 
367
- demo.queue().launch()
 
18
  """Get historical radar frames and create animation."""
19
  frames = []
20
  frame_files = []
 
21
 
22
+ # Adjust current time to allow for NOAA delay and round down to the nearest 10 minutes.
23
+ now = datetime.utcnow() - timedelta(minutes=20)
24
+ now = now - timedelta(minutes=now.minute % 10, seconds=now.second, microseconds=now.microsecond)
25
+
26
+ total_frames = (hours_back * 60) // 10 + 1 # one frame every 10 minutes
27
+ for i in range(total_frames):
28
+ time_point = now - timedelta(minutes=i * 10)
29
+ # Try multiple radar products
30
+ urls = [
31
+ f"https://radar.weather.gov/ridge/RadarImg/N0R/KMSX_{time_point.strftime('%Y%m%d_%H%M')}_N0R.gif", # Base reflectivity
32
+ f"https://radar.weather.gov/ridge/RadarImg/NCR/KMSX_{time_point.strftime('%Y%m%d_%H%M')}_NCR.gif", # Composite reflectivity
33
+ f"https://radar.weather.gov/ridge/RadarImg/N0S/KMSX_{time_point.strftime('%Y%m%d_%H%M')}_N0S.gif", # Storm relative motion
34
+ ]
35
+
36
+ for url in urls:
37
+ try:
38
+ print(f"Trying URL: {url}")
39
+ response = requests.get(url, timeout=5)
40
+ if response.status_code == 200:
41
+ print(f"Successfully downloaded: {url}")
42
+ img = Image.open(io.BytesIO(response.content)).convert('RGB')
43
+ img = crop_to_region(img, lat, lon)
44
+
45
+ # Add timestamp overlay
46
+ draw = ImageDraw.Draw(img)
47
+ if "N0R" in url:
48
+ radar_type = "Base"
49
+ elif "NCR" in url:
50
+ radar_type = "Composite"
51
+ else:
52
+ radar_type = "Storm"
53
+ text = f"{radar_type} Radar\n{time_point.strftime('%Y-%m-%d %H:%M UTC')}"
54
+ draw_text_with_outline(draw, text, (10, 10))
55
+
56
+ # Save individual frame and add to animation list
57
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp:
58
+ img.save(tmp.name)
59
+ frame_files.append(tmp.name)
60
+ frames.append(img)
61
+ break # Use the first available image for this time_point
62
+ except Exception as e:
63
+ print(f"Error with URL {url}: {str(e)}")
64
+ continue
65
 
66
  if frames:
67
+ print(f"Creating animation with {len(frames)} frames")
68
+ # Create animated gif from frames
69
  with tempfile.NamedTemporaryFile(delete=False, suffix='.gif') as tmp:
70
  frames[0].save(
71
  tmp.name,
 
74
  duration=200, # 0.2 seconds per frame
75
  loop=0
76
  )
77
+ print(f"Animation saved to {tmp.name}")
78
  return tmp.name, frame_files
79
  else:
80
+ print("No frames collected for animation")
81
 
82
  return None, []
83
 
 
93
  x = (lon - lon_min) / (lon_max - lon_min) * img_width
94
  y = (lat_max - lat) / (lat_max - lat_min) * img_height
95
 
96
+ # Calculate crop box dimensions
97
  crop_width = img_width / zoom
98
  crop_height = img_height / zoom
99
 
100
+ # Center crop box on the point
101
+ x1 = max(0, x - crop_width / 2)
102
+ y1 = max(0, y - crop_height / 2)
103
+ x2 = min(img_width, x + crop_width / 2)
104
+ y2 = min(img_height, y + crop_height / 2)
105
 
106
+ # Adjust if crop box goes out of bounds
107
+ if x1 < 0:
108
+ x2 -= x1
109
+ x1 = 0
110
+ if y1 < 0:
111
+ y2 -= y1
112
+ y1 = 0
113
+ if x2 > img_width:
114
+ x1 -= (x2 - img_width)
115
+ x2 = img_width
116
+ if y2 > img_height:
117
+ y1 -= (y2 - img_height)
118
+ y2 = img_height
119
 
120
+ # Crop and resize back to full image dimensions
121
  cropped = img.crop((x1, y1, x2, y2))
122
  return cropped.resize((img_width, img_height), Image.Resampling.LANCZOS)
123
 
124
  def draw_text_with_outline(draw, text, pos, font_size=20, center=False):
125
+ """Draw text with outline for better visibility."""
126
  try:
127
  font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", font_size)
128
  except:
 
130
 
131
  x, y = pos
132
  if center:
133
+ # Calculate text size for centering
134
  bbox = draw.textbbox((0, 0), text, font=font)
135
  text_width = bbox[2] - bbox[0]
136
  text_height = bbox[3] - bbox[1]
137
  x = x - text_width // 2
138
  y = y - text_height // 2
139
 
140
+ # Draw text outline for contrast
141
+ for dx, dy in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
142
+ draw.text((x + dx, y + dy), text, fill='black', font=font)
143
+ # Draw the main text
144
  draw.text((x, y), text, fill='white', font=font)
145
 
146
  def get_forecast_products(lat, lon):
 
173
  img = Image.open(io.BytesIO(response.content)).convert('RGB')
174
  img = crop_to_region(img, lat, lon)
175
 
176
+ # Add title overlay
177
  draw = ImageDraw.Draw(img)
178
  text = f"{title}\n{timestamp}"
179
  draw_text_with_outline(draw, text, (10, 10))
180
 
181
+ # Save the processed image
182
  with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp:
183
  img.save(tmp.name)
184
  gallery_data.append(tmp.name)
 
191
  """Create a map centered on the given coordinates with markers."""
192
  m = folium.Map(location=[lat, lon], zoom_start=9)
193
 
194
+ # Add all Montana peaks to the map
195
  for peak_name, coords in MONTANA_PEAKS.items():
196
  folium.Marker(
197
  coords,
 
199
  tooltip=peak_name
200
  ).add_to(m)
201
 
202
+ # Add a marker for the selected location if it isn’t one of the peaks
203
  if (lat, lon) not in MONTANA_PEAKS.values():
204
  folium.Marker([lat, lon], popup=f"Selected Location<br>Lat: {lat:.4f}, Lon: {lon:.4f}").add_to(m)
205
 
 
209
  def get_noaa_forecast(lat, lon):
210
  """Get NOAA text forecast."""
211
  try:
212
+ # Get forecast URL from the NOAA points API
213
  points_url = f"https://api.weather.gov/points/{lat},{lon}"
214
  response = requests.get(points_url, timeout=10)
215
  forecast_url = response.json()['properties']['forecast']
216
 
217
+ # Retrieve forecast data
218
  forecast = requests.get(forecast_url, timeout=10).json()
219
 
220
+ # Format the text forecast
221
  text = "Weather Forecast:\n\n"
222
  for period in forecast['properties']['periods']:
223
  text += f"{period['name']}:\n"
 
225
  text += f"Wind: {period['windSpeed']} {period['windDirection']}\n"
226
  text += f"{period['detailedForecast']}\n\n"
227
 
228
+ # Highlight potential snow events
229
  if any(word in period['detailedForecast'].lower() for word in
230
+ ['snow', 'flurries', 'wintry mix', 'blizzard']):
231
  text += "⚠️ SNOW EVENT PREDICTED ⚠️\n\n"
232
 
233
  return text
 
237
  def make_peak_click_handler(peak_name):
238
  """Creates a click handler for a specific peak."""
239
  def handler():
240
+ lat, lon = MONTANA_PEAKS[peak_name]
241
+ return lat, lon
242
  return handler
243
 
244
  def update_weather(lat, lon):
 
253
  # Get text forecast
254
  forecast_text = get_noaa_forecast(lat, lon)
255
 
256
+ # Get radar animation and individual frames
257
  radar_animation, frames = get_radar_frames(lat, lon)
258
 
259
+ # Get forecast products images
260
  forecast_frames = get_forecast_products(lat, lon)
261
 
262
+ # Combine all images for the gallery display
263
  gallery_data = frames + forecast_frames
264
 
265
+ # Generate the map HTML
266
  map_html = get_map(lat, lon)
267
 
268
  return forecast_text, gallery_data, radar_animation, map_html
 
366
  - Standard Radar
367
  - Base Reflectivity
368
  - Composite Reflectivity
369
+ All images are cropped to focus on the selected location.
370
  Radar animation shows the last 6 hours of data.
371
 
372
  **Forecast Products:**
 
379
  Mountain weather can change rapidly - always check multiple sources for safety.
380
  """)
381
 
382
+ demo.queue().launch()