nakas commited on
Commit
f1aa366
·
1 Parent(s): ef1ec06
Files changed (1) hide show
  1. app.py +251 -73
app.py CHANGED
@@ -25,43 +25,103 @@ class RainViewerMap:
25
 
26
  def get_hurricane_data(self):
27
  """Fetch active hurricane/tropical storm data from NOAA NHC"""
 
 
 
 
28
  try:
29
- # Get list of available layers
30
- layers_url = f"{self.nhc_api_base}/layers?f=json"
31
- layers_response = requests.get(layers_url)
32
- layers_data = layers_response.json()
 
 
 
 
 
 
 
 
 
 
33
 
34
- active_storms = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- # Look for active storm layers (forecast points, tracks, etc.)
37
- for layer in layers_data.get('layers', []):
38
- layer_name = layer.get('name', '')
39
- layer_id = layer.get('id')
40
-
41
- # Focus on forecast points which contain current storm positions
42
- if 'forecast points' in layer_name.lower() and layer_id is not None:
43
- # Query the layer for features
44
- query_url = f"{self.nhc_api_base}/{layer_id}/query"
45
- params = {
46
- 'where': '1=1',
47
- 'outFields': '*',
48
- 'f': 'geojson'
 
 
 
 
 
 
 
 
 
 
49
  }
50
-
51
- query_response = requests.get(query_url, params=params)
52
- if query_response.status_code == 200:
53
- geojson_data = query_response.json()
54
- if geojson_data.get('features'):
55
- active_storms.append({
56
- 'layer_name': layer_name,
57
- 'layer_id': layer_id,
58
- 'geojson': geojson_data
59
- })
60
 
61
  return active_storms
62
 
63
  except Exception as e:
64
- print(f"Error fetching hurricane data: {e}")
 
 
65
  return []
66
 
67
  def get_storm_track_data(self, layer_id):
@@ -144,51 +204,94 @@ class RainViewerMap:
144
  hurricane_data = self.get_hurricane_data()
145
 
146
  for storm_info in hurricane_data:
147
- layer_name = storm_info['layer_name']
148
- layer_id = storm_info['layer_id']
149
- geojson_data = storm_info['geojson']
150
 
151
- # Add forecast points
152
- for feature in geojson_data.get('features', []):
153
- if feature['geometry']['type'] == 'Point':
154
- coords = feature['geometry']['coordinates']
155
- props = feature.get('properties', {})
156
-
157
- # Extract storm information
158
- storm_name = props.get('STORMNAME', 'Unknown Storm')
159
- storm_type = props.get('STORMTYPE', 'Unknown')
160
- max_wind = props.get('MAXWIND', 'N/A')
161
- pressure = props.get('MSLP', 'N/A')
162
- forecast_hour = props.get('FHOUR', 'Current')
163
-
164
- # Determine marker color based on storm intensity
165
- color = self.get_storm_color(max_wind)
166
-
167
- # Create popup with storm details
168
- popup_text = f"""
169
- <b>{storm_name}</b><br>
170
- Type: {storm_type}<br>
171
- Max Wind: {max_wind} kt<br>
172
- Pressure: {pressure} mb<br>
173
- Forecast Hour: {forecast_hour}
174
- """
175
-
176
- # Add marker
177
- folium.CircleMarker(
178
- location=[coords[1], coords[0]],
179
- radius=8,
180
- popup=popup_text,
181
- color=color,
182
- fill=True,
183
- fillColor=color,
184
- fillOpacity=0.7,
185
- weight=2
186
- ).add_to(folium_map)
187
-
188
- # Try to get and add track data
189
- track_data = self.get_storm_track_data(layer_id)
190
- if track_data:
191
- self.add_storm_tracks(folium_map, track_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  def add_storm_tracks(self, folium_map, track_data):
194
  """Add storm track lines to the map"""
@@ -292,8 +395,15 @@ def create_gradio_app():
292
  info="Select radar timestamp"
293
  )
294
 
 
 
 
 
 
 
295
  update_btn = gr.Button("Update Map", variant="primary")
296
  refresh_times_btn = gr.Button("Refresh Times")
 
297
 
298
  with gr.Column(scale=2):
299
  map_html = gr.HTML(
@@ -312,6 +422,63 @@ def create_gradio_app():
312
  new_times = rain_viewer.get_available_times()
313
  return gr.Dropdown(choices=new_times, value=new_times[-1] if new_times else None)
314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  update_btn.click(
316
  fn=update_map,
317
  inputs=[lat_input, lon_input, zoom_input, show_radar, show_hurricanes, time_dropdown],
@@ -323,6 +490,17 @@ def create_gradio_app():
323
  outputs=time_dropdown
324
  )
325
 
 
 
 
 
 
 
 
 
 
 
 
326
  # Auto-update on input changes
327
  for input_component in [lat_input, lon_input, zoom_input, show_radar, show_hurricanes, time_dropdown]:
328
  input_component.change(
 
25
 
26
  def get_hurricane_data(self):
27
  """Fetch active hurricane/tropical storm data from NOAA NHC"""
28
+ active_storms = []
29
+ error_messages = []
30
+
31
+ # Try multiple approaches to get hurricane data
32
  try:
33
+ # Approach 1: Try the simplified hurricane data endpoint
34
+ simple_url = "https://www.nhc.noaa.gov/CurrentStorms.json"
35
+ try:
36
+ response = requests.get(simple_url, timeout=10)
37
+ if response.status_code == 200:
38
+ storms_data = response.json()
39
+ for storm in storms_data.get('activeStorms', []):
40
+ active_storms.append({
41
+ 'source': 'NHC CurrentStorms',
42
+ 'data': storm,
43
+ 'type': 'current_storms'
44
+ })
45
+ except Exception as e:
46
+ error_messages.append(f"CurrentStorms.json failed: {e}")
47
 
48
+ # Approach 2: Try NOAA GIS layers for specific storm data
49
+ if not active_storms:
50
+ layers_url = f"{self.nhc_api_base}/layers?f=json"
51
+ try:
52
+ layers_response = requests.get(layers_url, timeout=15)
53
+ if layers_response.status_code == 200:
54
+ layers_data = layers_response.json()
55
+
56
+ # Look for Atlantic storms (AT1-AT5) forecast points
57
+ for layer in layers_data.get('layers', []):
58
+ layer_name = layer.get('name', '').lower()
59
+ layer_id = layer.get('id')
60
+
61
+ if ('at1' in layer_name or 'at2' in layer_name or 'at3' in layer_name) and 'forecast points' in layer_name:
62
+ query_url = f"{self.nhc_api_base}/{layer_id}/query"
63
+ params = {
64
+ 'where': '1=1',
65
+ 'outFields': '*',
66
+ 'f': 'geojson',
67
+ 'returnGeometry': 'true'
68
+ }
69
+
70
+ try:
71
+ query_response = requests.get(query_url, params=params, timeout=15)
72
+ if query_response.status_code == 200:
73
+ geojson_data = query_response.json()
74
+ if geojson_data.get('features'):
75
+ active_storms.append({
76
+ 'source': 'NOAA GIS',
77
+ 'layer_name': layer.get('name', ''),
78
+ 'layer_id': layer_id,
79
+ 'geojson': geojson_data,
80
+ 'type': 'gis_layer'
81
+ })
82
+ except Exception as e:
83
+ error_messages.append(f"Layer {layer_id} query failed: {e}")
84
+ except Exception as e:
85
+ error_messages.append(f"Layers API failed: {e}")
86
 
87
+ # Approach 3: Add sample data if no real data available (for testing)
88
+ if not active_storms:
89
+ sample_storm = {
90
+ 'source': 'Sample Data',
91
+ 'type': 'sample',
92
+ 'geojson': {
93
+ 'type': 'FeatureCollection',
94
+ 'features': [
95
+ {
96
+ 'type': 'Feature',
97
+ 'geometry': {
98
+ 'type': 'Point',
99
+ 'coordinates': [-80.0, 26.0]
100
+ },
101
+ 'properties': {
102
+ 'STORMNAME': 'Sample Hurricane',
103
+ 'STORMTYPE': 'Hurricane',
104
+ 'MAXWIND': '75',
105
+ 'MSLP': '980',
106
+ 'FHOUR': 'Current'
107
+ }
108
+ }
109
+ ]
110
  }
111
+ }
112
+ active_storms.append(sample_storm)
113
+ error_messages.append("No real storm data available, showing sample data")
114
+
115
+ # Store error messages for debugging
116
+ self.storm_cache['errors'] = error_messages
117
+ self.storm_cache['last_update'] = datetime.now().isoformat()
 
 
 
118
 
119
  return active_storms
120
 
121
  except Exception as e:
122
+ error_messages.append(f"General error fetching hurricane data: {e}")
123
+ self.storm_cache['errors'] = error_messages
124
+ print(f"Hurricane data fetch error: {e}")
125
  return []
126
 
127
  def get_storm_track_data(self, layer_id):
 
204
  hurricane_data = self.get_hurricane_data()
205
 
206
  for storm_info in hurricane_data:
207
+ storm_type = storm_info.get('type', 'unknown')
 
 
208
 
209
+ if storm_type == 'current_storms':
210
+ # Handle CurrentStorms.json format
211
+ storm_data = storm_info['data']
212
+ self.add_current_storms_markers(folium_map, storm_data)
213
+
214
+ elif storm_type == 'gis_layer':
215
+ # Handle GIS layer format
216
+ geojson_data = storm_info['geojson']
217
+ layer_name = storm_info.get('layer_name', 'Unknown Layer')
218
+
219
+ for feature in geojson_data.get('features', []):
220
+ if feature['geometry']['type'] == 'Point':
221
+ coords = feature['geometry']['coordinates']
222
+ props = feature.get('properties', {})
223
+
224
+ # Extract storm information
225
+ storm_name = props.get('STORMNAME', props.get('STORMNUM', 'Unknown Storm'))
226
+ storm_type_detail = props.get('STORMTYPE', 'Unknown')
227
+ max_wind = props.get('MAXWIND', props.get('INTENSITY', 'N/A'))
228
+ pressure = props.get('MSLP', 'N/A')
229
+ forecast_hour = props.get('FHOUR', props.get('TAU', 'Current'))
230
+
231
+ # Determine marker color based on storm intensity
232
+ color = self.get_storm_color(max_wind)
233
+
234
+ # Create popup with storm details
235
+ popup_text = f"""
236
+ <b>{storm_name}</b><br>
237
+ Type: {storm_type_detail}<br>
238
+ Max Wind: {max_wind} kt<br>
239
+ Pressure: {pressure} mb<br>
240
+ Forecast Hour: {forecast_hour}<br>
241
+ Source: {layer_name}
242
+ """
243
+
244
+ # Add marker
245
+ folium.CircleMarker(
246
+ location=[coords[1], coords[0]],
247
+ radius=8,
248
+ popup=popup_text,
249
+ color=color,
250
+ fill=True,
251
+ fillColor=color,
252
+ fillOpacity=0.7,
253
+ weight=2
254
+ ).add_to(folium_map)
255
+
256
+ elif storm_type == 'sample':
257
+ # Handle sample data
258
+ geojson_data = storm_info['geojson']
259
+ for feature in geojson_data.get('features', []):
260
+ if feature['geometry']['type'] == 'Point':
261
+ coords = feature['geometry']['coordinates']
262
+ props = feature.get('properties', {})
263
+
264
+ storm_name = props.get('STORMNAME', 'Sample Storm')
265
+ storm_type_detail = props.get('STORMTYPE', 'Sample')
266
+ max_wind = props.get('MAXWIND', '75')
267
+ pressure = props.get('MSLP', '980')
268
+
269
+ color = self.get_storm_color(max_wind)
270
+
271
+ popup_text = f"""
272
+ <b>{storm_name} (Sample)</b><br>
273
+ Type: {storm_type_detail}<br>
274
+ Max Wind: {max_wind} kt<br>
275
+ Pressure: {pressure} mb<br>
276
+ Note: This is sample data for demonstration
277
+ """
278
+
279
+ folium.CircleMarker(
280
+ location=[coords[1], coords[0]],
281
+ radius=10,
282
+ popup=popup_text,
283
+ color=color,
284
+ fill=True,
285
+ fillColor=color,
286
+ fillOpacity=0.7,
287
+ weight=3
288
+ ).add_to(folium_map)
289
+
290
+ def add_current_storms_markers(self, folium_map, storm_data):
291
+ """Add markers for storms from CurrentStorms.json format"""
292
+ # This method handles the specific format from NHC CurrentStorms.json
293
+ # Implementation depends on the actual JSON structure
294
+ pass
295
 
296
  def add_storm_tracks(self, folium_map, track_data):
297
  """Add storm track lines to the map"""
 
395
  info="Select radar timestamp"
396
  )
397
 
398
+ gr.Markdown("### Hurricane Information")
399
+ storm_list = gr.HTML(
400
+ value="Loading storm data...",
401
+ label="Active Storms"
402
+ )
403
+
404
  update_btn = gr.Button("Update Map", variant="primary")
405
  refresh_times_btn = gr.Button("Refresh Times")
406
+ refresh_storms_btn = gr.Button("Refresh Storms")
407
 
408
  with gr.Column(scale=2):
409
  map_html = gr.HTML(
 
422
  new_times = rain_viewer.get_available_times()
423
  return gr.Dropdown(choices=new_times, value=new_times[-1] if new_times else None)
424
 
425
+ def get_storm_list_html():
426
+ """Generate HTML for storm list display"""
427
+ hurricane_data = rain_viewer.get_hurricane_data()
428
+
429
+ if not hurricane_data:
430
+ return "<p>No active storms currently detected.</p>"
431
+
432
+ html_content = "<div style='font-family: Arial, sans-serif;'>"
433
+ html_content += "<h4>Active Storms:</h4>"
434
+
435
+ for i, storm_info in enumerate(hurricane_data):
436
+ storm_type = storm_info.get('type', 'unknown')
437
+ source = storm_info.get('source', 'Unknown')
438
+
439
+ html_content += f"<div style='margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px;'>"
440
+ html_content += f"<strong>Storm #{i+1}</strong> - Source: {source}<br>"
441
+
442
+ if storm_type == 'gis_layer':
443
+ layer_name = storm_info.get('layer_name', 'Unknown')
444
+ geojson = storm_info.get('geojson', {})
445
+ feature_count = len(geojson.get('features', []))
446
+ html_content += f"Layer: {layer_name}<br>"
447
+ html_content += f"Data points: {feature_count}<br>"
448
+
449
+ # Show first feature details if available
450
+ features = geojson.get('features', [])
451
+ if features:
452
+ first_feature = features[0]
453
+ props = first_feature.get('properties', {})
454
+ storm_name = props.get('STORMNAME', props.get('STORMNUM', 'N/A'))
455
+ max_wind = props.get('MAXWIND', props.get('INTENSITY', 'N/A'))
456
+ pressure = props.get('MSLP', 'N/A')
457
+
458
+ html_content += f"Name: {storm_name}<br>"
459
+ html_content += f"Max Wind: {max_wind} kt<br>"
460
+ html_content += f"Pressure: {pressure} mb<br>"
461
+
462
+ elif storm_type == 'sample':
463
+ html_content += "<em>Sample data for demonstration</em><br>"
464
+ html_content += "Name: Sample Hurricane<br>"
465
+ html_content += "Max Wind: 75 kt<br>"
466
+ html_content += "Pressure: 980 mb<br>"
467
+
468
+ html_content += "</div>"
469
+
470
+ # Add error information if available
471
+ errors = rain_viewer.storm_cache.get('errors', [])
472
+ if errors:
473
+ html_content += "<div style='margin-top: 15px; padding: 10px; background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 5px;'>"
474
+ html_content += "<strong>Debug Info:</strong><br>"
475
+ for error in errors[-3:]: # Show last 3 errors
476
+ html_content += f"• {error}<br>"
477
+ html_content += "</div>"
478
+
479
+ html_content += "</div>"
480
+ return html_content
481
+
482
  update_btn.click(
483
  fn=update_map,
484
  inputs=[lat_input, lon_input, zoom_input, show_radar, show_hurricanes, time_dropdown],
 
490
  outputs=time_dropdown
491
  )
492
 
493
+ refresh_storms_btn.click(
494
+ fn=get_storm_list_html,
495
+ outputs=storm_list
496
+ )
497
+
498
+ # Initialize storm list on load
499
+ demo.load(
500
+ fn=get_storm_list_html,
501
+ outputs=storm_list
502
+ )
503
+
504
  # Auto-update on input changes
505
  for input_component in [lat_input, lon_input, zoom_input, show_radar, show_hurricanes, time_dropdown]:
506
  input_component.change(