Spaces:
Sleeping
Sleeping
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|