nakas commited on
Commit
6319e3a
·
verified ·
1 Parent(s): 250ac18

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -163
app.py CHANGED
@@ -18,6 +18,7 @@ def get_satellite_animation(lat, lon, hours_back=6):
18
  """Get GOES satellite data and create animation."""
19
  try:
20
  frames = []
 
21
  current_time = datetime.utcnow()
22
 
23
  # GOES-West (covers Montana)
@@ -36,6 +37,18 @@ def get_satellite_animation(lat, lon, hours_back=6):
36
  {
37
  "path": "/ABI/SECTOR/np/GEOCOLOR/", # True Color
38
  "title": "True Color"
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
  ]
41
 
@@ -56,7 +69,6 @@ def get_satellite_animation(lat, lon, hours_back=6):
56
  if response.status_code == 200:
57
  img = Image.open(io.BytesIO(response.content))
58
 
59
- # Convert to RGB if needed
60
  if img.mode != 'RGB':
61
  img = img.convert('RGB')
62
 
@@ -80,6 +92,11 @@ def get_satellite_animation(lat, lon, hours_back=6):
80
 
81
  product_frames.append(img)
82
 
 
 
 
 
 
83
  except Exception as e:
84
  print(f"Error getting frame for {timestamp}: {str(e)}")
85
  continue
@@ -97,155 +114,99 @@ def get_satellite_animation(lat, lon, hours_back=6):
97
  frames.append(tmp_file.name)
98
  print(f"Successfully created animation for {product['title']}")
99
 
100
- return frames
101
  except Exception as e:
102
  print(f"Error creating satellite animation: {str(e)}")
103
- return []
104
 
105
- def crop_to_region(img, lat, lon, zoom=1.5):
106
- """Crop image to focus on selected region."""
107
  try:
108
- # Convert lat/lon to image coordinates (approximate for CONUS images)
109
- # These values are tuned for NOAA's CONUS projection
110
- img_width, img_height = img.size
111
-
112
- # CONUS bounds (approximate)
113
- lat_min, lat_max = 25.0, 50.0 # Southern to Northern bounds
114
- lon_min, lon_max = -125.0, -65.0 # Western to Eastern bounds
115
-
116
- # Convert coordinates to image space
117
- x = (lon - lon_min) / (lon_max - lon_min) * img_width
118
- y = (lat_max - lat) / (lat_max - lat_min) * img_height
119
-
120
- # Calculate crop box
121
- crop_width = img_width / zoom
122
- crop_height = img_height / zoom
123
-
124
- # Center the crop box on the point
125
- x1 = max(0, x - crop_width/2)
126
- y1 = max(0, y - crop_height/2)
127
- x2 = min(img_width, x + crop_width/2)
128
- y2 = min(img_height, y + crop_height/2)
129
-
130
- # Ensure we don't crop outside image bounds
131
- if x1 < 0: x2 -= x1; x1 = 0
132
- if y1 < 0: y2 -= y1; y1 = 0
133
- if x2 > img_width: x1 -= (x2 - img_width); x2 = img_width
134
- if y2 > img_height: y1 -= (y2 - img_height); y2 = img_height
135
-
136
- # Crop the image
137
- cropped = img.crop((x1, y1, x2, y2))
138
- return cropped.resize((img_width, img_height), Image.Resampling.LANCZOS)
139
- except Exception as e:
140
- print(f"Error cropping image: {str(e)}")
141
- return img
142
-
143
- def get_snow_forecast(lat, lon):
144
- """Get snow forecast data from NOAA."""
145
- try:
146
- points_url = f"https://api.weather.gov/points/{lat},{lon}"
147
- response = requests.get(points_url, timeout=10)
148
- response.raise_for_status()
149
- forecast_url = response.json()['properties']['forecast']
150
-
151
- forecast_response = requests.get(forecast_url, timeout=10)
152
- forecast_response.raise_for_status()
153
- forecast_data = forecast_response.json()
154
-
155
- # Format text forecast
156
- forecast_text = "Weather Forecast:\n\n"
157
- for period in forecast_data['properties']['periods']:
158
- forecast_text += f"{period['name']}:\n"
159
- forecast_text += f"Temperature: {period['temperature']}°{period['temperatureUnit']}\n"
160
- forecast_text += f"Wind: {period['windSpeed']} {period['windDirection']}\n"
161
- forecast_text += f"{period['detailedForecast']}\n\n"
162
-
163
- # Check for snow-related keywords
164
- if any(word in period['detailedForecast'].lower() for word in
165
- ['snow', 'flurries', 'wintry mix', 'blizzard', 'winter storm']):
166
- forecast_text += "⚠️ SNOW EVENT PREDICTED ⚠️\n\n"
167
-
168
- return forecast_text
169
- except Exception as e:
170
- return f"Error getting forecast: {str(e)}"
171
-
172
- def get_map(lat, lon):
173
- """Create a map centered on the given coordinates with markers."""
174
- m = folium.Map(location=[lat, lon], zoom_start=9)
175
-
176
- # Add all Montana peaks
177
- for peak_name, coords in MONTANA_PEAKS.items():
178
- folium.Marker(
179
- coords,
180
- popup=f"{peak_name}<br>Lat: {coords[0]:.4f}, Lon: {coords[1]:.4f}",
181
- tooltip=peak_name
182
- ).add_to(m)
183
-
184
- # Add current location marker if not a peak
185
- if (lat, lon) not in MONTANA_PEAKS.values():
186
- folium.Marker([lat, lon], popup=f"Selected Location<br>Lat: {lat:.4f}, Lon: {lon:.4f}").add_to(m)
187
-
188
- m.add_child(folium.ClickForLatLng()) # Enable click events
189
- return m._repr_html_()
190
-
191
- def make_peak_click_handler(peak_name):
192
- """Creates a click handler for a specific peak."""
193
- def handler():
194
- coords = MONTANA_PEAKS[peak_name]
195
- return coords[0], coords[1]
196
- return handler
197
-
198
- def update_weather(lat, lon):
199
- """Update weather information based on coordinates."""
200
- try:
201
- # Validate coordinates
202
- lat = float(lat)
203
- lon = float(lon)
204
- if not (-90 <= lat <= 90 and -180 <= lon <= 180):
205
- return "Invalid coordinates", [], get_map(45.5, -111.0)
206
-
207
- # Get text forecast
208
- forecast_text = get_snow_forecast(lat, lon)
209
-
210
- # Get satellite animations first
211
- gallery_data = get_satellite_animation(lat, lon)
212
-
213
- # Process regular forecast products
214
  timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')
215
 
216
  # List of forecast products
217
- forecast_products = [
 
 
 
 
 
 
 
 
 
218
  {
219
- "url": "https://radar.weather.gov/ridge/standard/CONUS_0.gif",
220
- "title": "Current Radar",
221
- "crop": True
222
  },
223
  {
224
- "url": "https://radar.weather.gov/ridge/standard/CONUS_BR0.gif",
225
- "title": "Base Reflectivity",
226
- "crop": True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  },
228
  {
229
- "url": "https://radar.weather.gov/ridge/standard/CONUS_CV0.gif",
230
- "title": "Composite Reflectivity",
231
- "crop": True
232
  }
233
  ]
234
 
235
- for product in forecast_products:
236
  try:
237
  response = requests.get(product["url"], timeout=10)
238
  if response.status_code == 200:
239
- # Load the image
240
  img = Image.open(io.BytesIO(response.content))
241
 
242
- # Convert to RGB if needed
243
  if img.mode != 'RGB':
244
  img = img.convert('RGB')
245
 
246
- # Crop if specified
247
- if product.get("crop", False):
248
- img = crop_to_region(img, lat, lon)
249
 
250
  # Add title and timestamp
251
  draw = ImageDraw.Draw(img)
@@ -256,46 +217,61 @@ def update_weather(lat, lon):
256
 
257
  text = f"{product['title']}\n{timestamp}"
258
 
259
- # Draw text with outline for visibility
260
  x, y = 10, 10
261
  for dx, dy in [(-1,-1), (-1,1), (1,-1), (1,1)]:
262
  draw.text((x+dx, y+dy), text, fill='black', font=font)
263
  draw.text((x, y), text, fill='white', font=font)
264
 
265
- # Save to temporary file
266
- with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_file:
267
- img.save(tmp_file.name, format="PNG")
268
  gallery_data.append(tmp_file.name)
269
  print(f"Successfully saved {product['title']}")
270
  except Exception as e:
271
  print(f"Error processing {product['title']}: {str(e)}")
272
  continue
273
 
274
- if not gallery_data:
275
- # Create error image
276
- with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
277
- img = Image.new('RGB', (800, 600), color='black')
278
- draw = ImageDraw.Draw(img)
279
- message = "No forecast images available\nPlease check weather.gov"
280
- try:
281
- font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
282
- except:
283
- font = ImageFont.load_default()
284
- draw.text((400, 300), message, fill='white', anchor="mm", align="center", font=font)
285
- img.save(tmp_file.name, format='PNG')
286
- gallery_data = [tmp_file.name]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  # Get map
289
  map_html = get_map(lat, lon)
290
 
291
- return forecast_text, gallery_data, map_html
292
 
293
  except Exception as e:
294
- return f"Error: {str(e)}", [], get_map(45.5, -111.0)
295
 
296
  # Create Gradio interface
297
- with gr.Blocks(title="Montana Mountain Weather") as demo:
298
- gr.Markdown("# Montana Mountain Weather")
299
 
300
  with gr.Row():
301
  with gr.Column(scale=1):
@@ -331,14 +307,23 @@ with gr.Blocks(title="Montana Mountain Weather") as demo:
331
  placeholder="Select a location to see the forecast..."
332
  )
333
  with gr.Column(scale=2):
334
- forecast_gallery = gr.Gallery(
335
- label="Weather Products",
336
  show_label=True,
337
  columns=3,
338
- height=800,
339
  object_fit="contain"
340
  )
341
 
 
 
 
 
 
 
 
 
 
342
  # Handle submit button click
343
  submit_btn.click(
344
  fn=update_weather,
@@ -346,6 +331,8 @@ with gr.Blocks(title="Montana Mountain Weather") as demo:
346
  outputs=[
347
  forecast_output,
348
  forecast_gallery,
 
 
349
  map_display
350
  ]
351
  )
@@ -362,6 +349,8 @@ with gr.Blocks(title="Montana Mountain Weather") as demo:
362
  outputs=[
363
  forecast_output,
364
  forecast_gallery,
 
 
365
  map_display
366
  ]
367
  )
@@ -370,26 +359,27 @@ with gr.Blocks(title="Montana Mountain Weather") as demo:
370
  ## Instructions
371
  1. Use the quick access buttons to check specific Montana peaks
372
  2. Or enter coordinates manually / click on the map
373
- 3. Click "Get Weather" to see the forecast and weather products
374
 
375
  **Montana Peaks Included:**
376
  - Lone Peak (Big Sky): 45°16′41″N 111°27′01″W
377
  - Sacajawea Peak: 45°53′45″N 110°58′7″W
378
  - Pioneer Mountain: 45°13′55″N 111°27′2″W
379
 
380
- **Weather Products Shown:**
381
- - GOES-18 Satellite Animations:
382
- - Visible Cloud Cover (10-minute intervals)
383
- - Infrared Clean (temperature/height)
384
- - True Color Imagery
385
 
386
- - Current Conditions:
387
- - Current Radar
388
- - Base Reflectivity
389
- - Composite Reflectivity
 
390
 
391
- All images are automatically cropped and zoomed to focus on the selected location.
392
- Each satellite animation shows the last 6 hours of data with 10-minute updates.
393
 
394
  **Note**: This app uses NOAA and GOES-West satellite data.
395
  Mountain weather can change rapidly - always check multiple sources for safety.
 
18
  """Get GOES satellite data and create animation."""
19
  try:
20
  frames = []
21
+ frame_files = []
22
  current_time = datetime.utcnow()
23
 
24
  # GOES-West (covers Montana)
 
37
  {
38
  "path": "/ABI/SECTOR/np/GEOCOLOR/", # True Color
39
  "title": "True Color"
40
+ },
41
+ {
42
+ "path": "/ABI/SECTOR/np/BAND08/", # Water Vapor
43
+ "title": "Water Vapor"
44
+ },
45
+ {
46
+ "path": "/ABI/SECTOR/np/BAND09/", # Mid-Level Water Vapor
47
+ "title": "Mid-Level Water Vapor"
48
+ },
49
+ {
50
+ "path": "/ABI/SECTOR/np/BAND10/", # Low-Level Water Vapor
51
+ "title": "Low-Level Water Vapor"
52
  }
53
  ]
54
 
 
69
  if response.status_code == 200:
70
  img = Image.open(io.BytesIO(response.content))
71
 
 
72
  if img.mode != 'RGB':
73
  img = img.convert('RGB')
74
 
 
92
 
93
  product_frames.append(img)
94
 
95
+ # Save individual frame
96
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
97
+ img.save(tmp_file.name, format='PNG')
98
+ frame_files.append(tmp_file.name)
99
+
100
  except Exception as e:
101
  print(f"Error getting frame for {timestamp}: {str(e)}")
102
  continue
 
114
  frames.append(tmp_file.name)
115
  print(f"Successfully created animation for {product['title']}")
116
 
117
+ return frames, frame_files
118
  except Exception as e:
119
  print(f"Error creating satellite animation: {str(e)}")
120
+ return [], []
121
 
122
+ def get_forecast_products(lat, lon):
123
+ """Get various forecast products."""
124
  try:
125
+ gallery_data = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')
127
 
128
  # List of forecast products
129
+ products = [
130
+ # Temperature Forecasts
131
+ {
132
+ "url": "https://graphical.weather.gov/images/conus/MaxT1_conus.png",
133
+ "title": "Maximum Temperature"
134
+ },
135
+ {
136
+ "url": "https://graphical.weather.gov/images/conus/MinT1_conus.png",
137
+ "title": "Minimum Temperature"
138
+ },
139
  {
140
+ "url": "https://graphical.weather.gov/images/conus/MaxT2_conus.png",
141
+ "title": "Maximum Temperature (Day 2)"
 
142
  },
143
  {
144
+ "url": "https://graphical.weather.gov/images/conus/MinT2_conus.png",
145
+ "title": "Minimum Temperature (Day 2)"
146
+ },
147
+
148
+ # Precipitation Forecasts
149
+ {
150
+ "url": "https://graphical.weather.gov/images/conus/QPF06_conus.png",
151
+ "title": "6-Hour Precipitation"
152
+ },
153
+ {
154
+ "url": "https://graphical.weather.gov/images/conus/QPF12_conus.png",
155
+ "title": "12-Hour Precipitation"
156
+ },
157
+ {
158
+ "url": "https://graphical.weather.gov/images/conus/QPF24_conus.png",
159
+ "title": "24-Hour Precipitation"
160
+ },
161
+ {
162
+ "url": "https://graphical.weather.gov/images/conus/QPF48_conus.png",
163
+ "title": "48-Hour Precipitation"
164
+ },
165
+
166
+ # Snow Forecasts
167
+ {
168
+ "url": "https://graphical.weather.gov/images/conus/Snow1_conus.png",
169
+ "title": "Snowfall Amount"
170
+ },
171
+ {
172
+ "url": "https://www.wpc.ncep.noaa.gov/pwpf/24hr_pwpf_fill.gif",
173
+ "title": "24hr Snow Probability"
174
+ },
175
+ {
176
+ "url": "https://www.wpc.ncep.noaa.gov/pwpf/48hr_pwpf_fill.gif",
177
+ "title": "48hr Snow Probability"
178
+ },
179
+ {
180
+ "url": "https://www.wpc.ncep.noaa.gov/pwpf/72hr_pwpf_fill.gif",
181
+ "title": "72hr Snow Probability"
182
+ },
183
+
184
+ # Additional Forecasts
185
+ {
186
+ "url": "https://graphical.weather.gov/images/conus/Wx1_conus.png",
187
+ "title": "Weather Type"
188
+ },
189
+ {
190
+ "url": "https://graphical.weather.gov/images/conus/Wx2_conus.png",
191
+ "title": "Weather Type (Day 2)"
192
  },
193
  {
194
+ "url": "https://www.wpc.ncep.noaa.gov/noaa/noaa.gif",
195
+ "title": "Surface Analysis"
 
196
  }
197
  ]
198
 
199
+ for product in products:
200
  try:
201
  response = requests.get(product["url"], timeout=10)
202
  if response.status_code == 200:
 
203
  img = Image.open(io.BytesIO(response.content))
204
 
 
205
  if img.mode != 'RGB':
206
  img = img.convert('RGB')
207
 
208
+ # Crop to region
209
+ img = crop_to_region(img, lat, lon)
 
210
 
211
  # Add title and timestamp
212
  draw = ImageDraw.Draw(img)
 
217
 
218
  text = f"{product['title']}\n{timestamp}"
219
 
220
+ # Draw text with outline
221
  x, y = 10, 10
222
  for dx, dy in [(-1,-1), (-1,1), (1,-1), (1,1)]:
223
  draw.text((x+dx, y+dy), text, fill='black', font=font)
224
  draw.text((x, y), text, fill='white', font=font)
225
 
226
+ # Save processed image
227
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.png') as tmp_file:
228
+ img.save(tmp_file.name, format='PNG')
229
  gallery_data.append(tmp_file.name)
230
  print(f"Successfully saved {product['title']}")
231
  except Exception as e:
232
  print(f"Error processing {product['title']}: {str(e)}")
233
  continue
234
 
235
+ return gallery_data
236
+
237
+ except Exception as e:
238
+ print(f"Error getting forecast products: {str(e)}")
239
+ return []
240
+
241
+ [Previous crop_to_region, get_snow_forecast, get_map, and make_peak_click_handler functions remain the same]
242
+
243
+ def update_weather(lat, lon):
244
+ """Update weather information based on coordinates."""
245
+ try:
246
+ # Validate coordinates
247
+ lat = float(lat)
248
+ lon = float(lon)
249
+ if not (-90 <= lat <= 90 and -180 <= lon <= 180):
250
+ return "Invalid coordinates", [], [], [], get_map(45.5, -111.0)
251
+
252
+ # Get text forecast
253
+ forecast_text = get_snow_forecast(lat, lon)
254
+
255
+ # Get satellite animations and frames
256
+ satellite_animations, satellite_frames = get_satellite_animation(lat, lon)
257
+
258
+ # Get forecast products
259
+ forecast_products = get_forecast_products(lat, lon)
260
+
261
+ # Combine all images for gallery
262
+ gallery_data = satellite_frames + forecast_products
263
 
264
  # Get map
265
  map_html = get_map(lat, lon)
266
 
267
+ return forecast_text, gallery_data, satellite_animations, [], map_html
268
 
269
  except Exception as e:
270
+ return f"Error: {str(e)}", [], [], [], get_map(45.5, -111.0)
271
 
272
  # Create Gradio interface
273
+ with gr.Blocks(title="Montana Mountain Weather - Satellite & Forecast") as demo:
274
+ gr.Markdown("# Montana Mountain Weather - Satellite & Forecast")
275
 
276
  with gr.Row():
277
  with gr.Column(scale=1):
 
307
  placeholder="Select a location to see the forecast..."
308
  )
309
  with gr.Column(scale=2):
310
+ satellite_gallery = gr.Gallery(
311
+ label="Satellite Animations",
312
  show_label=True,
313
  columns=3,
314
+ height=300,
315
  object_fit="contain"
316
  )
317
 
318
+ with gr.Row():
319
+ forecast_gallery = gr.Gallery(
320
+ label="Forecast Products",
321
+ show_label=True,
322
+ columns=4,
323
+ height=800,
324
+ object_fit="contain"
325
+ )
326
+
327
  # Handle submit button click
328
  submit_btn.click(
329
  fn=update_weather,
 
331
  outputs=[
332
  forecast_output,
333
  forecast_gallery,
334
+ satellite_gallery,
335
+ satellite_frames,
336
  map_display
337
  ]
338
  )
 
349
  outputs=[
350
  forecast_output,
351
  forecast_gallery,
352
+ satellite_gallery,
353
+ satellite_frames,
354
  map_display
355
  ]
356
  )
 
359
  ## Instructions
360
  1. Use the quick access buttons to check specific Montana peaks
361
  2. Or enter coordinates manually / click on the map
362
+ 3. Click "Get Weather" to see the forecast and satellite data
363
 
364
  **Montana Peaks Included:**
365
  - Lone Peak (Big Sky): 45°16′41″N 111°27′01″W
366
  - Sacajawea Peak: 45°53′45″N 110°58′7″W
367
  - Pioneer Mountain: 45°13′55″N 111°27′2″W
368
 
369
+ **Satellite Products:**
370
+ - Visible Cloud Cover
371
+ - Infrared Clean
372
+ - True Color
373
+ - Water Vapor (Multiple Levels)
374
 
375
+ **Forecast Products:**
376
+ - Temperature (Max/Min, Days 1-2)
377
+ - Precipitation (6/12/24/48 Hour)
378
+ - Snow Probability and Amount
379
+ - Weather Type and Surface Analysis
380
 
381
+ The satellite animations show the last 6 hours with 10-minute intervals.
382
+ All images are automatically cropped to focus on the selected location.
383
 
384
  **Note**: This app uses NOAA and GOES-West satellite data.
385
  Mountain weather can change rapidly - always check multiple sources for safety.