Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -205,7 +205,133 @@ class TyphoonAnalyzer:
|
|
205 |
finally:
|
206 |
if os.path.exists(temp_file):
|
207 |
os.remove(temp_file)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
def convert_oni_ascii_to_csv(self, input_file, output_file):
|
210 |
data = defaultdict(lambda: [''] * 12)
|
211 |
season_to_month = {
|
|
|
205 |
finally:
|
206 |
if os.path.exists(temp_file):
|
207 |
os.remove(temp_file)
|
208 |
+
def create_wind_analysis(self, data):
|
209 |
+
"""Create wind speed analysis plot"""
|
210 |
+
fig = px.scatter(data,
|
211 |
+
x='ONI',
|
212 |
+
y='USA_WIND',
|
213 |
+
color='Category',
|
214 |
+
color_discrete_map=COLOR_MAP,
|
215 |
+
title='Wind Speed vs ONI Index',
|
216 |
+
labels={
|
217 |
+
'ONI': 'Oceanic Niño Index',
|
218 |
+
'USA_WIND': 'Maximum Wind Speed (kt)'
|
219 |
+
},
|
220 |
+
hover_data=['NAME', 'ISO_TIME', 'Category']
|
221 |
+
)
|
222 |
+
|
223 |
+
# Add regression line
|
224 |
+
x = data['ONI']
|
225 |
+
y = data['USA_WIND']
|
226 |
+
slope, intercept = np.polyfit(x, y, 1)
|
227 |
+
fig.add_trace(
|
228 |
+
go.Scatter(
|
229 |
+
x=x,
|
230 |
+
y=slope * x + intercept,
|
231 |
+
mode='lines',
|
232 |
+
name=f'Regression (slope={slope:.2f})',
|
233 |
+
line=dict(color='black', dash='dash')
|
234 |
+
)
|
235 |
+
)
|
236 |
+
|
237 |
+
return fig
|
238 |
+
def create_typhoon_animation(self, year, typhoon_id):
|
239 |
+
"""Create animated visualization of typhoon path"""
|
240 |
+
if not typhoon_id:
|
241 |
+
return go.Figure(), "Please select a typhoon"
|
242 |
+
|
243 |
+
storm_data = self.typhoon_data[self.typhoon_data['SID'] == typhoon_id]
|
244 |
+
if storm_data.empty:
|
245 |
+
return go.Figure(), "No data available for selected typhoon"
|
246 |
+
|
247 |
+
storm_data = storm_data.sort_values('ISO_TIME')
|
248 |
+
|
249 |
+
fig = go.Figure()
|
250 |
+
|
251 |
+
# Base map settings
|
252 |
+
fig.update_layout(
|
253 |
+
title=f"Typhoon Path Animation - {storm_data['NAME'].iloc[0]}",
|
254 |
+
showlegend=True,
|
255 |
+
geo=dict(
|
256 |
+
projection_type='mercator',
|
257 |
+
showland=True,
|
258 |
+
showcoastlines=True,
|
259 |
+
landcolor='rgb(243, 243, 243)',
|
260 |
+
countrycolor='rgb(204, 204, 204)',
|
261 |
+
coastlinecolor='rgb(214, 214, 214)',
|
262 |
+
showocean=True,
|
263 |
+
oceancolor='rgb(230, 250, 255)',
|
264 |
+
lataxis=dict(range=[0, 50]),
|
265 |
+
lonaxis=dict(range=[100, 180]),
|
266 |
+
center=dict(lat=20, lon=140)
|
267 |
+
)
|
268 |
+
)
|
269 |
+
|
270 |
+
# Create animation frames
|
271 |
+
frames = []
|
272 |
+
for i in range(len(storm_data)):
|
273 |
+
frame = go.Frame(
|
274 |
+
data=[
|
275 |
+
go.Scattergeo(
|
276 |
+
lon=storm_data['LON'].iloc[:i+1],
|
277 |
+
lat=storm_data['LAT'].iloc[:i+1],
|
278 |
+
mode='lines+markers',
|
279 |
+
line=dict(width=2, color='red'),
|
280 |
+
marker=dict(size=8, color='red'),
|
281 |
+
name='Path',
|
282 |
+
hovertemplate=(
|
283 |
+
f"Time: {storm_data['ISO_TIME'].iloc[i]:%Y-%m-%d %H:%M}<br>" +
|
284 |
+
f"Wind: {storm_data['USA_WIND'].iloc[i]:.1f} kt<br>" +
|
285 |
+
f"Pressure: {storm_data['WMO_PRES'].iloc[i]:.1f} hPa<br>" +
|
286 |
+
f"Lat: {storm_data['LAT'].iloc[i]:.2f}°N<br>" +
|
287 |
+
f"Lon: {storm_data['LON'].iloc[i]:.2f}°E"
|
288 |
+
)
|
289 |
+
)
|
290 |
+
],
|
291 |
+
name=f'frame{i}'
|
292 |
+
)
|
293 |
+
frames.append(frame)
|
294 |
|
295 |
+
fig.frames = frames
|
296 |
+
|
297 |
+
# Add animation controls
|
298 |
+
fig.update_layout(
|
299 |
+
updatemenus=[{
|
300 |
+
'buttons': [
|
301 |
+
{
|
302 |
+
'args': [None, {'frame': {'duration': 100, 'redraw': True},
|
303 |
+
'fromcurrent': True}],
|
304 |
+
'label': 'Play',
|
305 |
+
'method': 'animate'
|
306 |
+
},
|
307 |
+
{
|
308 |
+
'args': [[None], {'frame': {'duration': 0, 'redraw': True},
|
309 |
+
'mode': 'immediate',
|
310 |
+
'transition': {'duration': 0}}],
|
311 |
+
'label': 'Pause',
|
312 |
+
'method': 'animate'
|
313 |
+
}
|
314 |
+
],
|
315 |
+
'type': 'buttons',
|
316 |
+
'showactive': False,
|
317 |
+
'x': 0.1,
|
318 |
+
'y': 0,
|
319 |
+
'xanchor': 'right',
|
320 |
+
'yanchor': 'top'
|
321 |
+
}]
|
322 |
+
)
|
323 |
+
|
324 |
+
info_text = f"""
|
325 |
+
### Typhoon Information
|
326 |
+
- Name: {storm_data['NAME'].iloc[0]}
|
327 |
+
- Start Date: {storm_data['ISO_TIME'].iloc[0]:%Y-%m-%d %H:%M}
|
328 |
+
- End Date: {storm_data['ISO_TIME'].iloc[-1]:%Y-%m-%d %H:%M}
|
329 |
+
- Maximum Wind Speed: {storm_data['USA_WIND'].max():.1f} kt
|
330 |
+
- Minimum Pressure: {storm_data['WMO_PRES'].min():.1f} hPa
|
331 |
+
"""
|
332 |
+
|
333 |
+
return fig, info_text
|
334 |
+
|
335 |
def convert_oni_ascii_to_csv(self, input_file, output_file):
|
336 |
data = defaultdict(lambda: [''] * 12)
|
337 |
season_to_month = {
|