Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -16,6 +16,8 @@ import csv
|
|
16 |
from collections import defaultdict
|
17 |
import filecmp
|
18 |
import uuid
|
|
|
|
|
19 |
|
20 |
# Command-line argument parsing
|
21 |
parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
|
@@ -294,7 +296,7 @@ def generate_main_analysis(start_year, start_month, end_year, end_month, enso_ph
|
|
294 |
|
295 |
return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
|
296 |
|
297 |
-
#
|
298 |
def categorize_typhoon_by_standard(wind_speed, standard):
|
299 |
if standard == 'taiwan':
|
300 |
wind_speed_ms = wind_speed * 0.514444
|
@@ -320,9 +322,9 @@ def categorize_typhoon_by_standard(wind_speed, standard):
|
|
320 |
return 'Tropical Storm', atlantic_standard['Tropical Storm']['color']
|
321 |
return 'Tropical Depression', atlantic_standard['Tropical Depression']['color']
|
322 |
|
323 |
-
def
|
324 |
if not typhoon:
|
325 |
-
return
|
326 |
|
327 |
typhoon_id = typhoon.split('(')[-1].strip(')')
|
328 |
storm = ibtracs.get_storm(typhoon_id)
|
@@ -333,15 +335,11 @@ def generate_track_gallery(year, typhoon, standard):
|
|
333 |
lat_padding = max((max_lat - min_lat) * 0.3, 5)
|
334 |
lon_padding = max((max_lon - min_lon) * 0.3, 5)
|
335 |
|
336 |
-
#
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
# Generate a sequence of figures and save as images
|
341 |
for i in range(len(storm.time)):
|
342 |
fig = go.Figure()
|
343 |
-
|
344 |
-
# Add the growing track up to the current point
|
345 |
category, color = categorize_typhoon_by_standard(storm.vmax[i], standard)
|
346 |
fig.add_trace(go.Scattergeo(
|
347 |
lon=storm.lon[:i+1],
|
@@ -353,8 +351,6 @@ def generate_track_gallery(year, typhoon, standard):
|
|
353 |
text=[f"Time: {storm.time[j].strftime('%Y-%m-%d %H:%M')}<br>Wind: {storm.vmax[j]:.1f} kt<br>Category: {categorize_typhoon_by_standard(storm.vmax[j], standard)[0]}" for j in range(i+1)],
|
354 |
hoverinfo="text"
|
355 |
))
|
356 |
-
|
357 |
-
# Add category legend
|
358 |
standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard
|
359 |
for cat, details in standard_dict.items():
|
360 |
fig.add_trace(go.Scattergeo(
|
@@ -363,10 +359,8 @@ def generate_track_gallery(year, typhoon, standard):
|
|
363 |
name=cat,
|
364 |
showlegend=True
|
365 |
))
|
366 |
-
|
367 |
-
# Update layout
|
368 |
fig.update_layout(
|
369 |
-
title=f"{year} {storm.name}
|
370 |
geo=dict(
|
371 |
projection_type='natural earth',
|
372 |
showland=True,
|
@@ -389,13 +383,53 @@ def generate_track_gallery(year, typhoon, standard):
|
|
389 |
bgcolor="rgba(255, 255, 255, 0.8)"
|
390 |
)
|
391 |
)
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
|
400 |
# Logistic regression functions
|
401 |
def perform_wind_regression(start_year, start_month, end_year, end_month):
|
@@ -444,7 +478,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
444 |
### Features:
|
445 |
- **Track Visualization**: View typhoon tracks by time period and ENSO phase
|
446 |
- **Statistical Analysis**: Examine relationships between ONI values and typhoon characteristics
|
447 |
-
- **Path Animation**:
|
448 |
- **Regression Analysis**: Perform statistical regression on typhoon data
|
449 |
|
450 |
Select a tab above to begin your analysis.
|
@@ -604,15 +638,16 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
604 |
typhoon_dropdown = gr.Dropdown(label="Typhoon")
|
605 |
standard_dropdown = gr.Dropdown(label="Classification Standard", choices=['atlantic', 'taiwan'], value='atlantic')
|
606 |
|
607 |
-
animate_btn = gr.Button("Generate Animation
|
608 |
-
|
609 |
animation_info = gr.Markdown("""
|
610 |
### Animation Instructions
|
611 |
1. Select a year and typhoon from the dropdowns
|
612 |
2. Choose a classification standard (Atlantic or Taiwan)
|
613 |
-
3. Click "Generate Animation
|
614 |
-
4.
|
615 |
-
5.
|
|
|
616 |
""")
|
617 |
|
618 |
def update_typhoon_options(year):
|
@@ -623,29 +658,9 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
|
|
623 |
|
624 |
year_dropdown.change(fn=update_typhoon_options, inputs=year_dropdown, outputs=typhoon_dropdown)
|
625 |
animate_btn.click(
|
626 |
-
fn=
|
627 |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
|
628 |
-
outputs=
|
629 |
)
|
630 |
|
631 |
-
|
632 |
-
gr.HTML("""
|
633 |
-
<style>
|
634 |
-
#tracks_plot {
|
635 |
-
height: 700px !important;
|
636 |
-
width: 100%;
|
637 |
-
}
|
638 |
-
#path_gallery .gallery-item {
|
639 |
-
height: 700px !important;
|
640 |
-
width: 100% !important;
|
641 |
-
}
|
642 |
-
.plot-container {
|
643 |
-
min-height: 600px;
|
644 |
-
}
|
645 |
-
.gr-plotly {
|
646 |
-
width: 100% !important;
|
647 |
-
}
|
648 |
-
</style>
|
649 |
-
""")
|
650 |
-
|
651 |
-
demo.launch(share=True) # Enable public link sharing
|
|
|
16 |
from collections import defaultdict
|
17 |
import filecmp
|
18 |
import uuid
|
19 |
+
import base64
|
20 |
+
from io import BytesIO
|
21 |
|
22 |
# Command-line argument parsing
|
23 |
parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
|
|
|
296 |
|
297 |
return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
|
298 |
|
299 |
+
# Custom animation function with HTML and JavaScript
|
300 |
def categorize_typhoon_by_standard(wind_speed, standard):
|
301 |
if standard == 'taiwan':
|
302 |
wind_speed_ms = wind_speed * 0.514444
|
|
|
322 |
return 'Tropical Storm', atlantic_standard['Tropical Storm']['color']
|
323 |
return 'Tropical Depression', atlantic_standard['Tropical Depression']['color']
|
324 |
|
325 |
+
def generate_track_animation(year, typhoon, standard):
|
326 |
if not typhoon:
|
327 |
+
return "<p>No typhoon selected.</p>"
|
328 |
|
329 |
typhoon_id = typhoon.split('(')[-1].strip(')')
|
330 |
storm = ibtracs.get_storm(typhoon_id)
|
|
|
335 |
lat_padding = max((max_lat - min_lat) * 0.3, 5)
|
336 |
lon_padding = max((max_lon - min_lon) * 0.3, 5)
|
337 |
|
338 |
+
# Generate frames as images and encode them in base64
|
339 |
+
frames = []
|
340 |
+
dates = []
|
|
|
|
|
341 |
for i in range(len(storm.time)):
|
342 |
fig = go.Figure()
|
|
|
|
|
343 |
category, color = categorize_typhoon_by_standard(storm.vmax[i], standard)
|
344 |
fig.add_trace(go.Scattergeo(
|
345 |
lon=storm.lon[:i+1],
|
|
|
351 |
text=[f"Time: {storm.time[j].strftime('%Y-%m-%d %H:%M')}<br>Wind: {storm.vmax[j]:.1f} kt<br>Category: {categorize_typhoon_by_standard(storm.vmax[j], standard)[0]}" for j in range(i+1)],
|
352 |
hoverinfo="text"
|
353 |
))
|
|
|
|
|
354 |
standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard
|
355 |
for cat, details in standard_dict.items():
|
356 |
fig.add_trace(go.Scattergeo(
|
|
|
359 |
name=cat,
|
360 |
showlegend=True
|
361 |
))
|
|
|
|
|
362 |
fig.update_layout(
|
363 |
+
title=f"{year} {storm.name}",
|
364 |
geo=dict(
|
365 |
projection_type='natural earth',
|
366 |
showland=True,
|
|
|
383 |
bgcolor="rgba(255, 255, 255, 0.8)"
|
384 |
)
|
385 |
)
|
386 |
+
# Convert figure to PNG and encode in base64
|
387 |
+
img_bytes = fig.to_image(format="png", width=1000, height=700)
|
388 |
+
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
389 |
+
frames.append(f"data:image/png;base64,{img_base64}")
|
390 |
+
dates.append(storm.time[i].strftime('%Y-%m-%d %H:%M'))
|
391 |
+
|
392 |
+
# JavaScript to handle animation
|
393 |
+
js_script = """
|
394 |
+
<div style="text-align: center;">
|
395 |
+
<img id="animationFrame" style="max-width: 100%; height: 700px;" src="{frames[0]}">
|
396 |
+
<p id="dateDisplay" style="font-size: 18px; margin: 10px;">{dates[0]}</p>
|
397 |
+
<button id="startBtn" style="margin: 5px;">Start</button>
|
398 |
+
<button id="pauseBtn" style="margin: 5px;">Pause</button>
|
399 |
+
</div>
|
400 |
+
<script>
|
401 |
+
const frames = {frames_json};
|
402 |
+
const dates = {dates_json};
|
403 |
+
let currentFrame = 0;
|
404 |
+
let intervalId = null;
|
405 |
+
|
406 |
+
function updateFrame() {{
|
407 |
+
document.getElementById('animationFrame').src = frames[currentFrame];
|
408 |
+
document.getElementById('dateDisplay').textContent = dates[currentFrame];
|
409 |
+
currentFrame = (currentFrame + 1) % frames.length;
|
410 |
+
}}
|
411 |
+
|
412 |
+
document.getElementById('startBtn').addEventListener('click', function() {{
|
413 |
+
if (!intervalId) {{
|
414 |
+
intervalId = setInterval(updateFrame, 200); // 200ms per frame
|
415 |
+
}}
|
416 |
+
}});
|
417 |
+
|
418 |
+
document.getElementById('pauseBtn').addEventListener('click', function() {{
|
419 |
+
if (intervalId) {{
|
420 |
+
clearInterval(intervalId);
|
421 |
+
intervalId = null;
|
422 |
+
}}
|
423 |
+
}});
|
424 |
+
</script>
|
425 |
+
""".format(
|
426 |
+
frames_json=str(frames).replace("'", '"'), # JSON-safe string
|
427 |
+
dates_json=str(dates).replace("'", '"'),
|
428 |
+
frames=frames[0], # Initial frame
|
429 |
+
dates=dates[0] # Initial date
|
430 |
+
)
|
431 |
+
|
432 |
+
return js_script
|
433 |
|
434 |
# Logistic regression functions
|
435 |
def perform_wind_regression(start_year, start_month, end_year, end_month):
|
|
|
478 |
### Features:
|
479 |
- **Track Visualization**: View typhoon tracks by time period and ENSO phase
|
480 |
- **Statistical Analysis**: Examine relationships between ONI values and typhoon characteristics
|
481 |
+
- **Path Animation**: Watch an animated typhoon path with start/pause controls
|
482 |
- **Regression Analysis**: Perform statistical regression on typhoon data
|
483 |
|
484 |
Select a tab above to begin your analysis.
|
|
|
638 |
typhoon_dropdown = gr.Dropdown(label="Typhoon")
|
639 |
standard_dropdown = gr.Dropdown(label="Classification Standard", choices=['atlantic', 'taiwan'], value='atlantic')
|
640 |
|
641 |
+
animate_btn = gr.Button("Generate Animation")
|
642 |
+
path_animation = gr.HTML(label="Typhoon Path Animation")
|
643 |
animation_info = gr.Markdown("""
|
644 |
### Animation Instructions
|
645 |
1. Select a year and typhoon from the dropdowns
|
646 |
2. Choose a classification standard (Atlantic or Taiwan)
|
647 |
+
3. Click "Generate Animation"
|
648 |
+
4. Use the "Start" button to begin the animation and "Pause" to stop it
|
649 |
+
5. The date below the image updates with each frame
|
650 |
+
6. The blue line grows to show the typhoon's path, with markers indicating intensity
|
651 |
""")
|
652 |
|
653 |
def update_typhoon_options(year):
|
|
|
658 |
|
659 |
year_dropdown.change(fn=update_typhoon_options, inputs=year_dropdown, outputs=typhoon_dropdown)
|
660 |
animate_btn.click(
|
661 |
+
fn=generate_track_animation,
|
662 |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
|
663 |
+
outputs=path_animation
|
664 |
)
|
665 |
|
666 |
+
demo.launch(share=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|