Spaces:
Sleeping
Sleeping
ok3
Browse files
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
|
24 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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("#
|
97 |
-
gr.Markdown("Interactive weather radar
|
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 |
|