nakas commited on
Commit
ef1ec06
·
1 Parent(s): f1d1dd9
Files changed (1) hide show
  1. app.py +175 -9
app.py CHANGED
@@ -4,11 +4,14 @@ import requests
4
  import json
5
  from datetime import datetime
6
  import time
 
7
 
8
  class RainViewerMap:
9
  def __init__(self):
10
  self.api_url = "https://api.rainviewer.com/public/weather-maps.json"
11
  self.tile_host = "https://tilecache.rainviewer.com"
 
 
12
 
13
  def get_radar_data(self):
14
  """Fetch available radar timestamps from RainViewer API"""
@@ -20,8 +23,71 @@ class RainViewerMap:
20
  print(f"Error fetching radar data: {e}")
21
  return None
22
 
23
- def create_map(self, lat=40.7128, lon=-74.0060, zoom=8, show_radar=True, time_index=0):
24
- """Create a Folium map with optional radar overlay"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  # Create base map
26
  m = folium.Map(
27
  location=[lat, lon],
@@ -64,11 +130,107 @@ class RainViewerMap:
64
  icon=folium.Icon(color='blue', icon='info-sign')
65
  ).add_to(m)
66
 
 
 
 
 
67
  # Add layer control
68
  folium.LayerControl().add_to(m)
69
 
70
  return m._repr_html_()
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  def get_available_times(self):
73
  """Get list of available radar times for dropdown"""
74
  radar_data = self.get_radar_data()
@@ -93,8 +255,8 @@ def create_gradio_app():
93
  time_options = rain_viewer.get_available_times()
94
 
95
  with gr.Blocks(title="RainViewer Radar Map") as demo:
96
- gr.Markdown("# RainViewer Radar Map")
97
- gr.Markdown("Interactive weather radar map powered by RainViewer API")
98
 
99
  with gr.Row():
100
  with gr.Column(scale=1):
@@ -119,6 +281,10 @@ def create_gradio_app():
119
  label="Show Radar",
120
  value=True
121
  )
 
 
 
 
122
  time_dropdown = gr.Dropdown(
123
  choices=time_options,
124
  label="Radar Time",
@@ -135,12 +301,12 @@ def create_gradio_app():
135
  label="Radar Map"
136
  )
137
 
138
- def update_map(lat, lon, zoom, show_radar_flag, selected_time):
139
  time_index = 0
140
  if selected_time and ":" in selected_time:
141
  time_index = int(selected_time.split(":")[0])
142
 
143
- return rain_viewer.create_map(lat, lon, zoom, show_radar_flag, time_index)
144
 
145
  def refresh_times():
146
  new_times = rain_viewer.get_available_times()
@@ -148,7 +314,7 @@ def create_gradio_app():
148
 
149
  update_btn.click(
150
  fn=update_map,
151
- inputs=[lat_input, lon_input, zoom_input, show_radar, time_dropdown],
152
  outputs=map_html
153
  )
154
 
@@ -158,10 +324,10 @@ def create_gradio_app():
158
  )
159
 
160
  # Auto-update on input changes
161
- for input_component in [lat_input, lon_input, zoom_input, show_radar, time_dropdown]:
162
  input_component.change(
163
  fn=update_map,
164
- inputs=[lat_input, lon_input, zoom_input, show_radar, time_dropdown],
165
  outputs=map_html
166
  )
167
 
 
4
  import json
5
  from datetime import datetime
6
  import time
7
+ import urllib.parse
8
 
9
  class RainViewerMap:
10
  def __init__(self):
11
  self.api_url = "https://api.rainviewer.com/public/weather-maps.json"
12
  self.tile_host = "https://tilecache.rainviewer.com"
13
+ self.nhc_api_base = "https://mapservices.weather.noaa.gov/tropical/rest/services/tropical/NHC_tropical_weather/MapServer"
14
+ self.storm_cache = {}
15
 
16
  def get_radar_data(self):
17
  """Fetch available radar timestamps from RainViewer API"""
 
23
  print(f"Error fetching radar data: {e}")
24
  return None
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):
68
+ """Fetch track data for a specific storm layer"""
69
+ try:
70
+ # Look for corresponding track layer (usually layer_id - 1 or nearby)
71
+ track_layer_id = layer_id - 1 if layer_id > 0 else layer_id + 1
72
+
73
+ query_url = f"{self.nhc_api_base}/{track_layer_id}/query"
74
+ params = {
75
+ 'where': '1=1',
76
+ 'outFields': '*',
77
+ 'f': 'geojson'
78
+ }
79
+
80
+ response = requests.get(query_url, params=params)
81
+ if response.status_code == 200:
82
+ return response.json()
83
+ return None
84
+
85
+ except Exception as e:
86
+ print(f"Error fetching track data: {e}")
87
+ return None
88
+
89
+ def create_map(self, lat=40.7128, lon=-74.0060, zoom=8, show_radar=True, time_index=0, show_hurricanes=True):
90
+ """Create a Folium map with optional radar overlay and hurricane tracking"""
91
  # Create base map
92
  m = folium.Map(
93
  location=[lat, lon],
 
130
  icon=folium.Icon(color='blue', icon='info-sign')
131
  ).add_to(m)
132
 
133
+ # Add hurricane tracking
134
+ if show_hurricanes:
135
+ self.add_hurricane_layers(m)
136
+
137
  # Add layer control
138
  folium.LayerControl().add_to(m)
139
 
140
  return m._repr_html_()
141
 
142
+ def add_hurricane_layers(self, folium_map):
143
+ """Add hurricane tracking layers to the map"""
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"""
195
+ for feature in track_data.get('features', []):
196
+ if feature['geometry']['type'] == 'LineString':
197
+ coords = feature['geometry']['coordinates']
198
+ # Convert coordinates from [lon, lat] to [lat, lon] for Folium
199
+ track_coords = [[coord[1], coord[0]] for coord in coords]
200
+
201
+ props = feature.get('properties', {})
202
+ storm_name = props.get('STORMNAME', 'Storm Track')
203
+
204
+ folium.PolyLine(
205
+ locations=track_coords,
206
+ color='red',
207
+ weight=3,
208
+ opacity=0.8,
209
+ popup=f"Track: {storm_name}"
210
+ ).add_to(folium_map)
211
+
212
+ def get_storm_color(self, max_wind):
213
+ """Get color based on storm intensity (Saffir-Simpson scale)"""
214
+ try:
215
+ wind_speed = float(max_wind) if max_wind != 'N/A' else 0
216
+
217
+ if wind_speed >= 137: # Category 5
218
+ return 'purple'
219
+ elif wind_speed >= 113: # Category 4
220
+ return 'red'
221
+ elif wind_speed >= 96: # Category 3
222
+ return 'orange'
223
+ elif wind_speed >= 83: # Category 2
224
+ return 'yellow'
225
+ elif wind_speed >= 64: # Category 1
226
+ return 'green'
227
+ elif wind_speed >= 34: # Tropical Storm
228
+ return 'blue'
229
+ else: # Tropical Depression
230
+ return 'lightblue'
231
+ except:
232
+ return 'gray'
233
+
234
  def get_available_times(self):
235
  """Get list of available radar times for dropdown"""
236
  radar_data = self.get_radar_data()
 
255
  time_options = rain_viewer.get_available_times()
256
 
257
  with gr.Blocks(title="RainViewer Radar Map") as demo:
258
+ gr.Markdown("# Weather Radar & Hurricane Tracker")
259
+ gr.Markdown("Interactive weather map with radar data from RainViewer and hurricane tracking from NOAA NHC")
260
 
261
  with gr.Row():
262
  with gr.Column(scale=1):
 
281
  label="Show Radar",
282
  value=True
283
  )
284
+ show_hurricanes = gr.Checkbox(
285
+ label="Show Hurricanes",
286
+ value=True
287
+ )
288
  time_dropdown = gr.Dropdown(
289
  choices=time_options,
290
  label="Radar Time",
 
301
  label="Radar Map"
302
  )
303
 
304
+ def update_map(lat, lon, zoom, show_radar_flag, show_hurricanes_flag, selected_time):
305
  time_index = 0
306
  if selected_time and ":" in selected_time:
307
  time_index = int(selected_time.split(":")[0])
308
 
309
+ return rain_viewer.create_map(lat, lon, zoom, show_radar_flag, time_index, show_hurricanes_flag)
310
 
311
  def refresh_times():
312
  new_times = rain_viewer.get_available_times()
 
314
 
315
  update_btn.click(
316
  fn=update_map,
317
+ inputs=[lat_input, lon_input, zoom_input, show_radar, show_hurricanes, time_dropdown],
318
  outputs=map_html
319
  )
320
 
 
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(
329
  fn=update_map,
330
+ inputs=[lat_input, lon_input, zoom_input, show_radar, show_hurricanes, time_dropdown],
331
  outputs=map_html
332
  )
333