Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -678,15 +678,11 @@ class TyphoonAnalyzer:
|
|
| 678 |
|
| 679 |
def create_typhoon_animation(self, year, typhoon_id):
|
| 680 |
"""Create animated visualization of typhoon path"""
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
# Base map settings
|
| 687 |
-
fig.update_layout(
|
| 688 |
-
title=f"Typhoon Path Animation - {storm_data['NAME'].iloc[0]} ({year})",
|
| 689 |
-
showlegend=True,
|
| 690 |
geo=dict(
|
| 691 |
projection_type='mercator',
|
| 692 |
showland=True,
|
|
@@ -702,61 +698,127 @@ class TyphoonAnalyzer:
|
|
| 702 |
)
|
| 703 |
)
|
| 704 |
|
| 705 |
-
#
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
)
|
| 721 |
-
frames.append(frame)
|
| 722 |
|
| 723 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
'args': [[None], {'frame': {'duration': 0, 'redraw': True},
|
| 737 |
-
'mode': 'immediate',
|
| 738 |
-
'transition': {'duration': 0}}],
|
| 739 |
-
'label': 'Pause',
|
| 740 |
-
'method': 'animate'
|
| 741 |
-
}
|
| 742 |
-
],
|
| 743 |
-
'type': 'buttons',
|
| 744 |
-
'showactive': False,
|
| 745 |
-
'x': 0.1,
|
| 746 |
-
'y': 0,
|
| 747 |
-
'xanchor': 'right',
|
| 748 |
-
'yanchor': 'top'
|
| 749 |
-
}]
|
| 750 |
-
)
|
| 751 |
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 760 |
|
| 761 |
return fig, info_text
|
| 762 |
def create_pressure_analysis(self, data):
|
|
|
|
| 678 |
|
| 679 |
def create_typhoon_animation(self, year, typhoon_id):
|
| 680 |
"""Create animated visualization of typhoon path"""
|
| 681 |
+
# Create default empty figure
|
| 682 |
+
empty_fig = go.Figure()
|
| 683 |
+
empty_fig.update_layout(
|
| 684 |
+
title="No Data Available",
|
| 685 |
+
showlegend=False,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
geo=dict(
|
| 687 |
projection_type='mercator',
|
| 688 |
showland=True,
|
|
|
|
| 698 |
)
|
| 699 |
)
|
| 700 |
|
| 701 |
+
# Input validation
|
| 702 |
+
if not typhoon_id:
|
| 703 |
+
return empty_fig, "Please select a typhoon"
|
| 704 |
+
|
| 705 |
+
# Get storm data
|
| 706 |
+
try:
|
| 707 |
+
storm_data = self.typhoon_data[self.typhoon_data['SID'] == typhoon_id]
|
| 708 |
+
if len(storm_data) == 0:
|
| 709 |
+
return empty_fig, "No data available for selected typhoon"
|
| 710 |
+
|
| 711 |
+
storm_data = storm_data.sort_values('ISO_TIME')
|
| 712 |
+
|
| 713 |
+
# Get the name safely
|
| 714 |
+
storm_name = storm_data['NAME'].values[0] if len(storm_data) > 0 else "Unknown"
|
| 715 |
+
|
| 716 |
+
fig = go.Figure()
|
| 717 |
+
|
| 718 |
+
# Base map settings
|
| 719 |
+
fig.update_layout(
|
| 720 |
+
title=f"Typhoon Path Animation - {storm_name}",
|
| 721 |
+
showlegend=True,
|
| 722 |
+
geo=dict(
|
| 723 |
+
projection_type='mercator',
|
| 724 |
+
showland=True,
|
| 725 |
+
showcoastlines=True,
|
| 726 |
+
landcolor='rgb(243, 243, 243)',
|
| 727 |
+
countrycolor='rgb(204, 204, 204)',
|
| 728 |
+
coastlinecolor='rgb(214, 214, 214)',
|
| 729 |
+
showocean=True,
|
| 730 |
+
oceancolor='rgb(230, 250, 255)',
|
| 731 |
+
lataxis=dict(range=[0, 50]),
|
| 732 |
+
lonaxis=dict(range=[100, 180]),
|
| 733 |
+
center=dict(lat=20, lon=140)
|
| 734 |
+
)
|
| 735 |
)
|
|
|
|
| 736 |
|
| 737 |
+
# Create animation frames
|
| 738 |
+
frames = []
|
| 739 |
+
for i in range(len(storm_data)):
|
| 740 |
+
frame = go.Frame(
|
| 741 |
+
data=[
|
| 742 |
+
go.Scattergeo(
|
| 743 |
+
lon=storm_data['LON'].values[:i+1],
|
| 744 |
+
lat=storm_data['LAT'].values[:i+1],
|
| 745 |
+
mode='lines+markers',
|
| 746 |
+
line=dict(width=2, color='red'),
|
| 747 |
+
marker=dict(size=8, color='red'),
|
| 748 |
+
name='Path',
|
| 749 |
+
hovertemplate=(
|
| 750 |
+
f"Time: {pd.to_datetime(storm_data['ISO_TIME'].values[i]).strftime('%Y-%m-%d %H:%M')}<br>" +
|
| 751 |
+
f"Wind: {storm_data['USA_WIND'].values[i]:.1f} kt<br>" +
|
| 752 |
+
f"Pressure: {storm_data['WMO_PRES'].values[i]:.1f} hPa<br>" +
|
| 753 |
+
f"Lat: {storm_data['LAT'].values[i]:.2f}°N<br>" +
|
| 754 |
+
f"Lon: {storm_data['LON'].values[i]:.2f}°E"
|
| 755 |
+
)
|
| 756 |
+
)
|
| 757 |
+
],
|
| 758 |
+
name=f'frame{i}'
|
| 759 |
+
)
|
| 760 |
+
frames.append(frame)
|
| 761 |
+
|
| 762 |
+
fig.frames = frames
|
| 763 |
+
|
| 764 |
+
# Add animation controls
|
| 765 |
+
fig.update_layout(
|
| 766 |
+
updatemenus=[{
|
| 767 |
+
'buttons': [
|
| 768 |
+
{
|
| 769 |
+
'args': [None, {'frame': {'duration': 100, 'redraw': True},
|
| 770 |
+
'fromcurrent': True}],
|
| 771 |
+
'label': 'Play',
|
| 772 |
+
'method': 'animate'
|
| 773 |
+
},
|
| 774 |
+
{
|
| 775 |
+
'args': [[None], {'frame': {'duration': 0, 'redraw': True},
|
| 776 |
+
'mode': 'immediate',
|
| 777 |
+
'transition': {'duration': 0}}],
|
| 778 |
+
'label': 'Pause',
|
| 779 |
+
'method': 'animate'
|
| 780 |
+
}
|
| 781 |
+
],
|
| 782 |
+
'type': 'buttons',
|
| 783 |
+
'showactive': False,
|
| 784 |
+
'x': 0.1,
|
| 785 |
+
'y': 0,
|
| 786 |
+
'xanchor': 'right',
|
| 787 |
+
'yanchor': 'top'
|
| 788 |
+
}]
|
| 789 |
+
)
|
| 790 |
|
| 791 |
+
# Add initial data
|
| 792 |
+
fig.add_trace(
|
| 793 |
+
go.Scattergeo(
|
| 794 |
+
lon=[storm_data['LON'].values[0]],
|
| 795 |
+
lat=[storm_data['LAT'].values[0]],
|
| 796 |
+
mode='markers',
|
| 797 |
+
marker=dict(size=8, color='red'),
|
| 798 |
+
name='Start Point',
|
| 799 |
+
showlegend=True
|
| 800 |
+
)
|
| 801 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 802 |
|
| 803 |
+
start_time = pd.to_datetime(storm_data['ISO_TIME'].values[0])
|
| 804 |
+
end_time = pd.to_datetime(storm_data['ISO_TIME'].values[-1])
|
| 805 |
+
duration = (end_time - start_time).total_seconds() / 3600
|
| 806 |
+
|
| 807 |
+
info_text = f"""
|
| 808 |
+
### Typhoon Information
|
| 809 |
+
- Name: {storm_name}
|
| 810 |
+
- Start Date: {start_time.strftime('%Y-%m-%d %H:%M')}
|
| 811 |
+
- End Date: {end_time.strftime('%Y-%m-%d %H:%M')}
|
| 812 |
+
- Maximum Wind Speed: {storm_data['USA_WIND'].max():.1f} kt
|
| 813 |
+
- Minimum Pressure: {storm_data['WMO_PRES'].min():.1f} hPa
|
| 814 |
+
- Duration: {duration:.1f} hours
|
| 815 |
+
"""
|
| 816 |
+
|
| 817 |
+
return fig, info_text
|
| 818 |
+
|
| 819 |
+
except Exception as e:
|
| 820 |
+
print(f"Error in create_typhoon_animation: {str(e)}")
|
| 821 |
+
return empty_fig, f"Error processing typhoon data: {str(e)}"
|
| 822 |
|
| 823 |
return fig, info_text
|
| 824 |
def create_pressure_analysis(self, data):
|