nakas commited on
Commit
8356f47
·
1 Parent(s): 5e7a2cb
Files changed (3) hide show
  1. .DS_Store +0 -0
  2. __pycache__/app.cpython-313.pyc +0 -0
  3. app.py +241 -88
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
__pycache__/app.cpython-313.pyc CHANGED
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
 
app.py CHANGED
@@ -187,86 +187,195 @@ class RainViewerMap:
187
  for storm_info in hurricane_data:
188
  storm_type = storm_info.get('type', 'unknown')
189
 
190
- if storm_type == 'current_storms':
191
- # Handle CurrentStorms.json format
192
- storm_data = storm_info['data']
193
- self.add_current_storms_markers(folium_map, storm_data)
 
 
194
 
195
- elif storm_type == 'gis_layer':
196
- # Handle GIS layer format
197
- geojson_data = storm_info['geojson']
198
- layer_name = storm_info.get('layer_name', 'Unknown Layer')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
- for feature in geojson_data.get('features', []):
201
- if feature['geometry']['type'] == 'Point':
202
- coords = feature['geometry']['coordinates']
203
- props = feature.get('properties', {})
204
-
205
- # Extract storm information
206
- storm_name = props.get('STORMNAME', props.get('STORMNUM', 'Unknown Storm'))
207
- storm_type_detail = props.get('STORMTYPE', 'Unknown')
208
- max_wind = props.get('MAXWIND', props.get('INTENSITY', 'N/A'))
209
- pressure = props.get('MSLP', 'N/A')
210
- forecast_hour = props.get('FHOUR', props.get('TAU', 'Current'))
211
-
212
- # Determine marker color based on storm intensity
213
- color = self.get_storm_color(max_wind)
214
-
215
- # Create popup with storm details
216
- popup_text = f"""
217
- <b>{storm_name}</b><br>
218
- Type: {storm_type_detail}<br>
219
- Max Wind: {max_wind} kt<br>
220
- Pressure: {pressure} mb<br>
221
- Forecast Hour: {forecast_hour}<br>
222
- Source: {layer_name}
223
- """
224
-
225
- # Add marker
226
- folium.CircleMarker(
227
- location=[coords[1], coords[0]],
228
- radius=8,
229
- popup=popup_text,
230
- color=color,
231
- fill=True,
232
- fillColor=color,
233
- fillOpacity=0.7,
234
- weight=2
235
- ).add_to(folium_map)
236
-
237
- elif storm_type == 'sample':
238
- # Handle sample data
239
- geojson_data = storm_info['geojson']
240
- for feature in geojson_data.get('features', []):
241
- if feature['geometry']['type'] == 'Point':
242
- coords = feature['geometry']['coordinates']
243
- props = feature.get('properties', {})
244
-
245
- storm_name = props.get('STORMNAME', 'Sample Storm')
246
- storm_type_detail = props.get('STORMTYPE', 'Sample')
247
- max_wind = props.get('MAXWIND', '75')
248
- pressure = props.get('MSLP', '980')
249
-
250
- color = self.get_storm_color(max_wind)
251
-
252
- popup_text = f"""
253
- <b>{storm_name} (Sample)</b><br>
254
- Type: {storm_type_detail}<br>
255
- Max Wind: {max_wind} kt<br>
256
- Pressure: {pressure} mb<br>
257
- Note: This is sample data for demonstration
258
- """
259
-
260
- folium.CircleMarker(
261
- location=[coords[1], coords[0]],
262
- radius=10,
263
- popup=popup_text,
264
- color=color,
265
- fill=True,
266
- fillColor=color,
267
- fillOpacity=0.7,
268
- weight=3
269
- ).add_to(folium_map)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  def add_current_storms_markers(self, folium_map, storm_data):
272
  """Add markers for storms from CurrentStorms.json format"""
@@ -382,7 +491,7 @@ def create_gradio_app():
382
  info="Select radar timestamp"
383
  )
384
 
385
- gr.Markdown("### Hurricane Information")
386
  storm_list = gr.HTML(
387
  value="Loading storm data...",
388
  label="Active Storms"
@@ -466,15 +575,59 @@ def create_gradio_app():
466
  lat = display_props.get('lat', 'N/A')
467
  lon = display_props.get('lon', 'N/A')
468
 
469
- html_content += f"<strong>Name:</strong> {storm_name}<br>"
470
- html_content += f"<strong>Type:</strong> {storm_type_detail}<br>"
471
- html_content += f"<strong>Max Wind:</strong> {max_wind} kt<br>"
472
- html_content += f"<strong>Pressure:</strong> {pressure} mb<br>"
473
- html_content += f"<strong>Category:</strong> {ss_num if ss_num > 0 else 'N/A'}<br>"
474
- html_content += f"<strong>Position:</strong> {lat}°N, {abs(float(lon)) if lon != 'N/A' else 'N/A'}°W<br>"
475
- html_content += f"<strong>Advisory:</strong> #{advisory_num}<br>"
476
- html_content += f"<strong>Updated:</strong> {advisory_date}<br>"
477
- html_content += f"<strong>Data Points:</strong> {len(features)}<br>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
 
479
  elif storm_type == 'sample':
480
  html_content += "<em>Sample data for demonstration</em><br>"
 
187
  for storm_info in hurricane_data:
188
  storm_type = storm_info.get('type', 'unknown')
189
 
190
+ if storm_type == 'nhc_storm':
191
+ # Handle real NHC storm data
192
+ storm_id = storm_info.get('storm_id', 'Unknown')
193
+ forecast_points = storm_info.get('forecast_points', {})
194
+ forecast_track = storm_info.get('forecast_track', {})
195
+ forecast_cone = storm_info.get('forecast_cone', {})
196
 
197
+ # Add forecast track line
198
+ if forecast_track and forecast_track.get('features'):
199
+ for track_feature in forecast_track['features']:
200
+ if track_feature['geometry']['type'] == 'LineString':
201
+ coords = track_feature['geometry']['coordinates']
202
+ # Convert from [lon, lat] to [lat, lon] for Folium
203
+ track_coords = [[coord[1], coord[0]] for coord in coords]
204
+
205
+ track_props = track_feature.get('properties', {})
206
+ storm_name = track_props.get('stormname', storm_id)
207
+
208
+ folium.PolyLine(
209
+ locations=track_coords,
210
+ color='red',
211
+ weight=4,
212
+ opacity=0.8,
213
+ popup=f"Forecast Track: {storm_name}"
214
+ ).add_to(folium_map)
215
 
216
+ # Add forecast cone if available
217
+ if forecast_cone and forecast_cone.get('features'):
218
+ for cone_feature in forecast_cone['features']:
219
+ if cone_feature['geometry']['type'] == 'Polygon':
220
+ coords = cone_feature['geometry']['coordinates'][0] # Outer ring
221
+ # Convert from [lon, lat] to [lat, lon] for Folium
222
+ cone_coords = [[coord[1], coord[0]] for coord in coords]
223
+
224
+ cone_props = cone_feature.get('properties', {})
225
+ storm_name = cone_props.get('stormname', storm_id)
226
+
227
+ folium.Polygon(
228
+ locations=cone_coords,
229
+ color='orange',
230
+ weight=2,
231
+ opacity=0.6,
232
+ fillColor='yellow',
233
+ fillOpacity=0.2,
234
+ popup=f"Forecast Cone: {storm_name}"
235
+ ).add_to(folium_map)
236
+
237
+ # Add forecast points with spinning hurricane markers
238
+ if forecast_points and forecast_points.get('features'):
239
+ for feature in forecast_points['features']:
240
+ if feature['geometry']['type'] == 'Point':
241
+ coords = feature['geometry']['coordinates']
242
+ props = feature.get('properties', {})
243
+
244
+ # Extract detailed storm information
245
+ storm_name = props.get('stormname', 'Unknown Storm')
246
+ storm_type_detail = props.get('stormtype', 'Unknown')
247
+ max_wind = props.get('maxwind', 'N/A')
248
+ pressure = props.get('mslp', 'N/A')
249
+ gust = props.get('gust', 'N/A')
250
+ tau = props.get('tau', 0) # Forecast hour
251
+ date_label = props.get('datelbl', 'Unknown Time')
252
+ ss_num = props.get('ssnum', 0) # Saffir-Simpson number
253
+ advisory_num = props.get('advisnum', 'N/A')
254
+ advisory_date = props.get('advdate', 'N/A')
255
+
256
+ # Determine marker properties based on storm intensity
257
+ color = self.get_storm_color(max_wind)
258
+ radius = 8 + (ss_num * 2) # Larger markers for stronger storms
259
+
260
+ # Create detailed popup
261
+ popup_text = f"""
262
+ <div style='font-family: Arial; font-size: 12px; line-height: 1.4; min-width: 250px;'>
263
+ <div style='text-align: center; background: {color}; color: white; padding: 8px; margin: -8px -8px 8px -8px; border-radius: 5px 5px 0 0;'>
264
+ <span style='font-size: 20px;'>🌀</span>
265
+ <strong style='font-size: 16px;'>{storm_name}</strong>
266
+ </div>
267
+ <table style='width: 100%; border-collapse: collapse;'>
268
+ <tr><td style='padding: 3px; font-weight: bold;'>Type:</td><td style='padding: 3px;'>{storm_type_detail}</td></tr>
269
+ <tr><td style='padding: 3px; font-weight: bold;'>Max Winds:</td><td style='padding: 3px; color: {color}; font-weight: bold;'>{max_wind} kt</td></tr>
270
+ <tr><td style='padding: 3px; font-weight: bold;'>Gusts:</td><td style='padding: 3px;'>{gust} kt</td></tr>
271
+ <tr><td style='padding: 3px; font-weight: bold;'>Pressure:</td><td style='padding: 3px;'>{pressure} mb</td></tr>
272
+ <tr><td style='padding: 3px; font-weight: bold;'>Category:</td><td style='padding: 3px;'>{ss_num if ss_num > 0 else 'N/A'}</td></tr>
273
+ <tr><td style='padding: 3px; font-weight: bold;'>Time:</td><td style='padding: 3px;'>{date_label}</td></tr>
274
+ <tr><td style='padding: 3px; font-weight: bold;'>Forecast:</td><td style='padding: 3px;'>+{tau}h</td></tr>
275
+ <tr><td style='padding: 3px; font-weight: bold;'>Advisory:</td><td style='padding: 3px;'>#{advisory_num}</td></tr>
276
+ <tr><td style='padding: 3px; font-weight: bold;'>Position:</td><td style='padding: 3px;'>{coords[1]:.1f}°N, {abs(coords[0]):.1f}°W</td></tr>
277
+ </table>
278
+ <div style='text-align: center; margin-top: 8px; font-size: 10px; color: #666;'>
279
+ Updated: {advisory_date}
280
+ </div>
281
+ </div>
282
+ """
283
+
284
+ # Different marker styles for current vs forecast positions
285
+ if tau == 0: # Current position - SPINNING HURRICANE!
286
+ # Create spinning hurricane icon for current position
287
+ spinning_hurricane_html = f"""
288
+ <div style="
289
+ font-size: {radius * 2.5}px;
290
+ color: {color};
291
+ text-shadow: 3px 3px 6px rgba(0,0,0,0.7);
292
+ animation: spin 2s linear infinite;
293
+ display: flex;
294
+ align-items: center;
295
+ justify-content: center;
296
+ width: {radius * 4}px;
297
+ height: {radius * 4}px;
298
+ background: radial-gradient(circle, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.4) 70%, transparent 100%);
299
+ border-radius: 50%;
300
+ border: 3px solid {color};
301
+ box-shadow: 0 0 20px rgba(255,255,255,0.8), 0 0 40px {color};
302
+ ">
303
+ 🌀
304
+ </div>
305
+ <style>
306
+ @keyframes spin {{
307
+ from {{ transform: rotate(0deg); }}
308
+ to {{ transform: rotate(360deg); }}
309
+ }}
310
+ </style>
311
+ """
312
+
313
+ # Add spinning hurricane marker for current position
314
+ folium.Marker(
315
+ location=[coords[1], coords[0]],
316
+ popup=folium.Popup(popup_text, max_width=300),
317
+ icon=folium.DivIcon(
318
+ html=spinning_hurricane_html,
319
+ class_name="spinning-hurricane",
320
+ icon_size=(radius * 4, radius * 4),
321
+ icon_anchor=(radius * 2, radius * 2)
322
+ )
323
+ ).add_to(folium_map)
324
+
325
+ # Add multiple pulsing circles around current position
326
+ for i, pulse_radius in enumerate([radius + 10, radius + 20, radius + 30]):
327
+ folium.CircleMarker(
328
+ location=[coords[1], coords[0]],
329
+ radius=pulse_radius,
330
+ color=color,
331
+ fill=False,
332
+ weight=3 - i,
333
+ opacity=0.7 - (i * 0.2),
334
+ popup=f"Current Position: {storm_name}"
335
+ ).add_to(folium_map)
336
+
337
+ else: # Forecast position
338
+ # For forecast positions, use smaller hurricane icon
339
+ forecast_icon_html = f"""
340
+ <div style="
341
+ font-size: {radius + 4}px;
342
+ color: {color};
343
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
344
+ display: flex;
345
+ align-items: center;
346
+ justify-content: center;
347
+ width: {radius * 2.5}px;
348
+ height: {radius * 2.5}px;
349
+ background: rgba(255,255,255,0.9);
350
+ border-radius: 50%;
351
+ border: 2px solid {color};
352
+ box-shadow: 0 0 10px rgba(0,0,0,0.3);
353
+ ">
354
+ 🌀
355
+ </div>
356
+ """
357
+
358
+ folium.Marker(
359
+ location=[coords[1], coords[0]],
360
+ popup=folium.Popup(popup_text, max_width=300),
361
+ icon=folium.DivIcon(
362
+ html=forecast_icon_html,
363
+ class_name="forecast-hurricane",
364
+ icon_size=(radius * 2.5, radius * 2.5),
365
+ icon_anchor=(radius * 1.25, radius * 1.25)
366
+ )
367
+ ).add_to(folium_map)
368
+
369
+ # Add forecast time label
370
+ folium.Marker(
371
+ location=[coords[1] - 0.15, coords[0]],
372
+ icon=folium.DivIcon(
373
+ html=f'<div style="font-size: 9px; background: white; padding: 2px 4px; border-radius: 3px; border: 1px solid {color}; box-shadow: 0 1px 3px rgba(0,0,0,0.3);">{date_label}</div>',
374
+ class_name="forecast-label",
375
+ icon_size=(70, 20),
376
+ icon_anchor=(35, 10)
377
+ )
378
+ ).add_to(folium_map)
379
 
380
  def add_current_storms_markers(self, folium_map, storm_data):
381
  """Add markers for storms from CurrentStorms.json format"""
 
491
  info="Select radar timestamp"
492
  )
493
 
494
+ gr.Markdown("### 🌀 Active Hurricane Information")
495
  storm_list = gr.HTML(
496
  value="Loading storm data...",
497
  label="Active Storms"
 
575
  lat = display_props.get('lat', 'N/A')
576
  lon = display_props.get('lon', 'N/A')
577
 
578
+ # Create detailed storm information table with spinning icon
579
+ html_content += f"""
580
+ <div style='background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); padding: 15px; border-radius: 10px; margin: 5px 0;'>
581
+ <div style='text-align: center; margin-bottom: 10px;'>
582
+ <span style='font-size: 24px; animation: spin 3s linear infinite;'>🌀</span>
583
+ <h3 style='margin: 5px 0; color: {self.get_storm_color(max_wind)};'>{storm_name}</h3>
584
+ </div>
585
+ <table style='width: 100%; border-collapse: collapse; font-size: 12px;'>
586
+ <tr style='background: rgba(255,255,255,0.7);'>
587
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Type:</td>
588
+ <td style='padding: 5px; border: 1px solid #ddd;'>{storm_type_detail}</td>
589
+ </tr>
590
+ <tr>
591
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Max Winds:</td>
592
+ <td style='padding: 5px; border: 1px solid #ddd; color: {self.get_storm_color(max_wind)};'><strong>{max_wind} kt</strong></td>
593
+ </tr>
594
+ <tr style='background: rgba(255,255,255,0.7);'>
595
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Category:</td>
596
+ <td style='padding: 5px; border: 1px solid #ddd;'>{f'Category {ss_num}' if ss_num > 0 else 'Tropical Storm/Depression'}</td>
597
+ </tr>
598
+ <tr>
599
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Pressure:</td>
600
+ <td style='padding: 5px; border: 1px solid #ddd;'>{pressure} mb</td>
601
+ </tr>
602
+ <tr style='background: rgba(255,255,255,0.7);'>
603
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Position:</td>
604
+ <td style='padding: 5px; border: 1px solid #ddd;'>{lat}°N, {abs(float(lon)) if lon != 'N/A' else 'N/A'}°W</td>
605
+ </tr>
606
+ <tr>
607
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Advisory:</td>
608
+ <td style='padding: 5px; border: 1px solid #ddd;'>#{advisory_num}</td>
609
+ </tr>
610
+ <tr style='background: rgba(255,255,255,0.7);'>
611
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Updated:</td>
612
+ <td style='padding: 5px; border: 1px solid #ddd;'>{advisory_date}</td>
613
+ </tr>
614
+ <tr>
615
+ <td style='padding: 5px; border: 1px solid #ddd; font-weight: bold;'>Forecast Points:</td>
616
+ <td style='padding: 5px; border: 1px solid #ddd;'>{len(features)} positions</td>
617
+ </tr>
618
+ </table>
619
+
620
+ <div style='margin-top: 10px; font-size: 11px; color: #666;'>
621
+ <strong>🌀 Forecast Track:</strong> Click hurricane markers on map for detailed timing
622
+ </div>
623
+ </div>
624
+ <style>
625
+ @keyframes spin {{
626
+ from {{ transform: rotate(0deg); }}
627
+ to {{ transform: rotate(360deg); }}
628
+ }}
629
+ </style>
630
+ """
631
 
632
  elif storm_type == 'sample':
633
  html_content += "<em>Sample data for demonstration</em><br>"