euler314 commited on
Commit
ed262cf
Β·
verified Β·
1 Parent(s): 86de8ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +292 -197
app.py CHANGED
@@ -140,6 +140,14 @@ matplotlib_color_map = {
140
  'C5 Super Typhoon': '#FF0000' # Red
141
  }
142
 
 
 
 
 
 
 
 
 
143
  def rgb_string_to_hex(rgb_string):
144
  """Convert 'rgb(r,g,b)' string to hex color for matplotlib"""
145
  try:
@@ -158,6 +166,10 @@ def get_matplotlib_color(category):
158
  """Get matplotlib-compatible color for a storm category"""
159
  return matplotlib_color_map.get(category, '#808080')
160
 
 
 
 
 
161
  # Cluster colors for route visualization
162
  CLUSTER_COLORS = [
163
  '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
@@ -724,6 +736,24 @@ def categorize_typhoon_enhanced(wind_speed):
724
  else: # 137+ knots = Category 5 Super Typhoon
725
  return 'C5 Super Typhoon'
726
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
  # Original function for backward compatibility
728
  def categorize_typhoon(wind_speed):
729
  """Original categorize typhoon function for backward compatibility"""
@@ -1007,8 +1037,8 @@ def cluster_storms(embedding, method='dbscan', eps=0.5, min_samples=3):
1007
  # Return single cluster as fallback
1008
  return np.array([0] * len(embedding))
1009
 
1010
- def create_advanced_clustering_visualization(storm_features, typhoon_data, method='umap', show_routes=True):
1011
- """Create comprehensive clustering visualization with route display - FIXED VERSION"""
1012
  try:
1013
  # Validate inputs
1014
  if storm_features is None or storm_features.empty:
@@ -1043,115 +1073,71 @@ def create_advanced_clustering_visualization(storm_features, typhoon_data, metho
1043
  storm_features_viz['NAME'] = 'UNNAMED'
1044
  storm_features_viz['SEASON'] = 2000
1045
 
1046
- if show_routes:
1047
- # Create subplot with both scatter plot and route map
1048
- fig = make_subplots(
1049
- rows=1, cols=2,
1050
- subplot_titles=(
1051
- f'Storm Clustering using {method.upper()}',
1052
- 'Clustered Storm Routes'
1053
- ),
1054
- specs=[[{"type": "scatter"}, {"type": "geo"}]],
1055
- column_widths=[0.5, 0.5]
1056
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
 
1058
- # Add clustering scatter plot
1059
- unique_clusters = sorted(storm_features_viz['cluster'].unique())
1060
- for i, cluster in enumerate(unique_clusters):
1061
- cluster_data = storm_features_viz[storm_features_viz['cluster'] == cluster]
1062
- color = CLUSTER_COLORS[i % len(CLUSTER_COLORS)] if cluster != -1 else '#CCCCCC'
1063
- cluster_name = f'Cluster {cluster}' if cluster != -1 else 'Noise'
1064
-
1065
- # FIXED: Add safe access to clustering features
1066
  try:
1067
- max_wind = cluster_data['USA_WIND_max'].fillna(0)
1068
- min_pres = cluster_data['USA_PRES_min'].fillna(1000)
1069
- track_len = cluster_data['track_length'].fillna(0)
1070
- except KeyError as e:
1071
- logging.warning(f"Missing clustering feature: {e}")
1072
- max_wind = pd.Series([0] * len(cluster_data))
1073
- min_pres = pd.Series([1000] * len(cluster_data))
1074
- track_len = pd.Series([0] * len(cluster_data))
1075
-
1076
- fig.add_trace(
1077
- go.Scatter(
1078
- x=cluster_data['dim1'],
1079
- y=cluster_data['dim2'],
1080
- mode='markers',
1081
- marker=dict(color=color, size=8),
1082
- name=cluster_name,
1083
- hovertemplate=(
1084
- '<b>%{customdata[0]}</b><br>'
1085
- 'Season: %{customdata[1]}<br>'
1086
- 'Max Wind: %{customdata[2]:.0f} kt<br>'
1087
- 'Min Pressure: %{customdata[3]:.0f} hPa<br>'
1088
- 'Track Length: %{customdata[4]:.0f} points<br>'
1089
- '<extra></extra>'
1090
- ),
1091
- customdata=np.column_stack((
1092
- cluster_data['NAME'].fillna('UNNAMED'),
1093
- cluster_data['SEASON'].fillna(2000),
1094
- max_wind,
1095
- min_pres,
1096
- track_len
1097
- ))
1098
- ),
1099
- row=1, col=1
1100
- )
1101
-
1102
- # Add route map - FIXED with better error handling
1103
- for i, cluster in enumerate(unique_clusters):
1104
- if cluster == -1: # Skip noise for route visualization
1105
- continue
1106
-
1107
- cluster_storm_ids = storm_features_viz[storm_features_viz['cluster'] == cluster]['SID'].tolist()
1108
- color = CLUSTER_COLORS[i % len(CLUSTER_COLORS)]
1109
-
1110
- tracks_added = 0
1111
- for j, sid in enumerate(cluster_storm_ids[:5]): # Limit to 5 storms per cluster for performance
1112
- try:
1113
- storm_track = typhoon_data[typhoon_data['SID'] == sid].sort_values('ISO_TIME')
1114
  if len(storm_track) > 1:
1115
- # Ensure valid coordinates
1116
- valid_coords = storm_track['LAT'].notna() & storm_track['LON'].notna()
1117
- storm_track = storm_track[valid_coords]
1118
 
1119
- if len(storm_track) > 1:
1120
- storm_name = storm_track['NAME'].iloc[0] if pd.notna(storm_track['NAME'].iloc[0]) else 'UNNAMED'
1121
-
1122
- fig.add_trace(
1123
- go.Scattergeo(
1124
- lon=storm_track['LON'],
1125
- lat=storm_track['LAT'],
1126
- mode='lines+markers',
1127
- line=dict(color=color, width=2),
1128
- marker=dict(color=color, size=4),
1129
- name=f'C{cluster}: {storm_name}' if tracks_added == 0 else None,
1130
- showlegend=(tracks_added == 0),
1131
- hovertemplate=(
1132
- f'<b>{storm_name}</b><br>'
1133
- 'Lat: %{lat:.1f}Β°<br>'
1134
- 'Lon: %{lon:.1f}Β°<br>'
1135
- f'Cluster: {cluster}<br>'
1136
- '<extra></extra>'
1137
- )
1138
- ),
1139
- row=1, col=2
1140
  )
1141
- tracks_added += 1
1142
- except Exception as track_error:
1143
- logging.warning(f"Error adding track for storm {sid}: {track_error}")
1144
- continue
1145
-
1146
- # Update layout
1147
- fig.update_layout(
1148
- title_text="Advanced Storm Clustering Analysis with Route Visualization",
1149
- showlegend=True,
1150
- height=600
1151
- )
1152
-
1153
- # Update geo layout
1154
- fig.update_geos(
1155
  projection_type="natural earth",
1156
  showland=True,
1157
  landcolor="LightGray",
@@ -1159,29 +1145,97 @@ def create_advanced_clustering_visualization(storm_features, typhoon_data, metho
1159
  oceancolor="LightBlue",
1160
  showcoastlines=True,
1161
  coastlinecolor="Gray",
1162
- center=dict(lat=20, lon=140),
1163
- row=1, col=2
1164
  )
 
 
 
 
 
 
 
 
 
 
 
1165
 
1166
- # Update scatter plot axes
1167
- fig.update_xaxes(title_text=f"{method.upper()} Dimension 1", row=1, col=1)
1168
- fig.update_yaxes(title_text=f"{method.upper()} Dimension 2", row=1, col=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1169
 
1170
- else:
1171
- # Simple scatter plot only
1172
- fig = px.scatter(
1173
- storm_features_viz,
1174
- x='dim1',
1175
- y='dim2',
1176
- color='cluster',
1177
- hover_data=['NAME', 'SEASON'],
1178
- title=f'Storm Clustering using {method.upper()}',
1179
- labels={
1180
- 'dim1': f'{method.upper()} Dimension 1',
1181
- 'dim2': f'{method.upper()} Dimension 2',
1182
- 'cluster': 'Cluster'
1183
- }
1184
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1185
 
1186
  # Generate detailed cluster statistics - FIXED
1187
  try:
@@ -1201,11 +1255,6 @@ def create_advanced_clustering_visualization(storm_features, typhoon_data, metho
1201
  existing_cols = {k: v for k, v in available_cols.items() if v in storm_features_viz.columns}
1202
 
1203
  if len(existing_cols) > 1: # Need at least SID + one other column
1204
- cluster_stats = storm_features_viz.groupby('cluster').agg(
1205
- {col: ['mean', 'std', 'count'] if col != 'SID' else 'count'
1206
- for col in existing_cols.values()}
1207
- ).round(2)
1208
-
1209
  stats_text = "ADVANCED CLUSTER ANALYSIS RESULTS\n" + "="*50 + "\n\n"
1210
 
1211
  for cluster in sorted(storm_features_viz['cluster'].unique()):
@@ -1256,7 +1305,7 @@ def create_advanced_clustering_visualization(storm_features, typhoon_data, metho
1256
  logging.error(f"Error generating cluster statistics: {stats_error}")
1257
  stats_text = f"Error generating cluster statistics: {str(stats_error)}"
1258
 
1259
- return fig, stats_text, storm_features_viz
1260
 
1261
  except Exception as e:
1262
  logging.error(f"Error in clustering analysis: {e}")
@@ -1270,7 +1319,7 @@ def create_advanced_clustering_visualization(storm_features, typhoon_data, metho
1270
  x=0.5, y=0.5, xanchor='center', yanchor='middle',
1271
  showarrow=False, font_size=16
1272
  )
1273
- return error_fig, f"Error in clustering: {str(e)}", None
1274
 
1275
  # -----------------------------
1276
  # ENHANCED: Advanced Prediction System with Route Forecasting
@@ -1513,18 +1562,18 @@ def predict_storm_route_and_intensity(lat, lon, month, oni_value, models=None, f
1513
  }
1514
 
1515
  def create_route_visualization(prediction_results, show_uncertainty=True):
1516
- """Create comprehensive route and intensity visualization"""
1517
  try:
1518
  if 'route_forecast' not in prediction_results or not prediction_results['route_forecast']:
1519
  return None, "No route forecast data available"
1520
 
1521
  route_data = prediction_results['route_forecast']
1522
 
1523
- # Create subplot with route map and intensity evolution
1524
  fig = make_subplots(
1525
  rows=1, cols=2,
1526
  subplot_titles=('Forecast Track', 'Intensity Evolution'),
1527
- specs=[[{"type": "geo"}, {"type": "scatter"}]],
1528
  column_widths=[0.6, 0.4]
1529
  )
1530
 
@@ -1619,7 +1668,7 @@ def create_route_visualization(prediction_results, show_uncertainty=True):
1619
  row=1, col=1
1620
  )
1621
 
1622
- # Intensity evolution plot
1623
  fig.add_trace(
1624
  go.Scatter(
1625
  x=hours,
@@ -1658,7 +1707,7 @@ def create_route_visualization(prediction_results, show_uncertainty=True):
1658
  height=600
1659
  )
1660
 
1661
- # Update geo layout
1662
  fig.update_geos(
1663
  projection_type="natural earth",
1664
  showland=True,
@@ -1672,7 +1721,7 @@ def create_route_visualization(prediction_results, show_uncertainty=True):
1672
  row=1, col=1
1673
  )
1674
 
1675
- # Update intensity plot
1676
  fig.update_xaxes(title_text="Forecast Hour", row=1, col=2)
1677
  fig.update_yaxes(title_text="Intensity (kt)", row=1, col=2)
1678
 
@@ -1922,7 +1971,12 @@ def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
1922
  return 'Tropical Depression', '#808080'
1923
 
1924
  if standard=='taiwan':
1925
- wind_speed_ms = wind_speed * 0.514444
 
 
 
 
 
1926
  if wind_speed_ms >= 51.0:
1927
  return 'Strong Typhoon', '#FF0000' # Red
1928
  elif wind_speed_ms >= 33.7:
@@ -1931,6 +1985,7 @@ def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
1931
  return 'Mild Typhoon', '#FFFF00' # Yellow
1932
  return 'Tropical Depression', '#808080' # Gray
1933
  else:
 
1934
  if wind_speed >= 137:
1935
  return 'C5 Super Typhoon', '#FF0000' # Red
1936
  elif wind_speed >= 113:
@@ -1946,7 +2001,7 @@ def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
1946
  return 'Tropical Depression', '#808080' # Gray
1947
 
1948
  # -----------------------------
1949
- # ENHANCED: Animation Functions
1950
  # -----------------------------
1951
 
1952
  def get_available_years(typhoon_data):
@@ -2032,7 +2087,7 @@ def update_typhoon_options_enhanced(year, basin):
2032
  return gr.update(choices=["Error loading storms"], value=None)
2033
 
2034
  def generate_enhanced_track_video(year, typhoon_selection, standard):
2035
- """Enhanced track video generation with TD support and 2025 compatibility - FIXED color handling"""
2036
  if not typhoon_selection or typhoon_selection == "No storms found":
2037
  return None
2038
 
@@ -2063,10 +2118,10 @@ def generate_enhanced_track_video(year, typhoon_selection, standard):
2063
  storm_name = storm_df['NAME'].iloc[0] if pd.notna(storm_df['NAME'].iloc[0]) else "UNNAMED"
2064
  season = storm_df['SEASON'].iloc[0] if 'SEASON' in storm_df.columns else year
2065
 
2066
- print(f"Generating video for {storm_name} ({sid}) with {len(lats)} track points")
2067
 
2068
  # Create figure with enhanced map
2069
- fig, ax = plt.subplots(figsize=(14, 8), subplot_kw={'projection': ccrs.PlateCarree()})
2070
 
2071
  # Enhanced map features
2072
  ax.stock_img()
@@ -2086,28 +2141,37 @@ def generate_enhanced_track_video(year, typhoon_selection, standard):
2086
  gl = ax.gridlines(draw_labels=True, alpha=0.3)
2087
  gl.top_labels = gl.right_labels = False
2088
 
2089
- # Title with enhanced info
2090
- ax.set_title(f"{season} {storm_name} ({sid}) Track Animation", fontsize=16, fontweight='bold')
 
2091
 
2092
  # Animation elements
2093
  line, = ax.plot([], [], 'b-', linewidth=3, alpha=0.7, label='Track')
2094
- point, = ax.plot([], [], 'o', markersize=12)
2095
 
2096
  # Enhanced info display
2097
  info_box = ax.text(0.02, 0.98, '', transform=ax.transAxes,
2098
- fontsize=11, verticalalignment='top',
2099
  bbox=dict(boxstyle="round,pad=0.5", facecolor='white', alpha=0.9))
2100
 
2101
- # Color legend with TD support - FIXED
2102
  legend_elements = []
2103
- for category in ['Tropical Depression', 'Tropical Storm', 'C1 Typhoon', 'C2 Typhoon',
2104
- 'C3 Strong Typhoon', 'C4 Very Strong Typhoon', 'C5 Super Typhoon']:
2105
- if category in matplotlib_color_map:
 
 
 
 
 
 
 
 
2106
  color = get_matplotlib_color(category)
2107
  legend_elements.append(plt.Line2D([0], [0], marker='o', color='w',
2108
- markerfacecolor=color, markersize=8, label=category))
2109
 
2110
- ax.legend(handles=legend_elements, loc='upper right', fontsize=9)
2111
 
2112
  def animate(frame):
2113
  try:
@@ -2117,32 +2181,43 @@ def generate_enhanced_track_video(year, typhoon_selection, standard):
2117
  # Update track line
2118
  line.set_data(lons[:frame+1], lats[:frame+1])
2119
 
2120
- # Update current position
2121
  current_wind = winds[frame]
2122
- category = categorize_typhoon_enhanced(current_wind)
2123
- color = get_matplotlib_color(category) # FIXED: Use matplotlib-compatible color
 
 
 
2124
 
2125
  # Debug print for first few frames
2126
  if frame < 3:
2127
- print(f"Frame {frame}: Wind={current_wind:.1f}kt, Category={category}, Color={color}")
2128
 
2129
  point.set_data([lons[frame]], [lats[frame]])
2130
  point.set_color(color)
2131
- point.set_markersize(8 + current_wind/10) # Size based on intensity
2132
 
2133
- # Enhanced info display
2134
  if 'ISO_TIME' in storm_df.columns and frame < len(storm_df):
2135
  current_time = storm_df.iloc[frame]['ISO_TIME']
2136
  time_str = current_time.strftime('%Y-%m-%d %H:%M UTC') if pd.notna(current_time) else 'Unknown'
2137
  else:
2138
  time_str = f"Step {frame+1}"
2139
 
 
 
 
 
 
 
 
2140
  info_text = (
2141
  f"Storm: {storm_name}\n"
2142
  f"Time: {time_str}\n"
2143
  f"Position: {lats[frame]:.1f}Β°N, {lons[frame]:.1f}Β°E\n"
2144
- f"Max Wind: {current_wind:.0f} kt\n"
2145
  f"Category: {category}\n"
 
2146
  f"Frame: {frame+1}/{len(lats)}"
2147
  )
2148
  info_box.set_text(info_text)
@@ -2156,7 +2231,7 @@ def generate_enhanced_track_video(year, typhoon_selection, standard):
2156
  # Create animation
2157
  anim = animation.FuncAnimation(
2158
  fig, animate, frames=len(lats),
2159
- interval=300, blit=False, repeat=True
2160
  )
2161
 
2162
  # Save animation
@@ -2165,12 +2240,12 @@ def generate_enhanced_track_video(year, typhoon_selection, standard):
2165
 
2166
  # Enhanced writer settings
2167
  writer = animation.FFMpegWriter(
2168
- fps=4, bitrate=2000, codec='libx264',
2169
  extra_args=['-pix_fmt', 'yuv420p'] # Better compatibility
2170
  )
2171
 
2172
  print(f"Saving animation to {temp_file.name}")
2173
- anim.save(temp_file.name, writer=writer, dpi=100)
2174
  plt.close(fig)
2175
 
2176
  print(f"Video generated successfully: {temp_file.name}")
@@ -2236,7 +2311,7 @@ def initialize_data():
2236
  initialize_data()
2237
 
2238
  # -----------------------------
2239
- # ENHANCED: Gradio Interface with Advanced Features
2240
  # -----------------------------
2241
 
2242
  def create_interface():
@@ -2271,11 +2346,12 @@ def create_interface():
2271
  This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
2272
 
2273
  ### πŸš€ Enhanced Features:
2274
- - **Advanced ML Clustering**: UMAP/t-SNE storm pattern analysis with route visualization
2275
  - **Predictive Routing**: Advanced storm track and intensity forecasting with uncertainty quantification
2276
  - **Complete TD Support**: Now includes Tropical Depressions (< 34 kt)
 
2277
  - **2025 Data Ready**: Real-time compatibility with current year data
2278
- - **Enhanced Animations**: High-quality storm track visualizations
2279
 
2280
  ### πŸ“Š Data Status:
2281
  - **ONI Data**: {len(oni_data)} years loaded
@@ -2299,8 +2375,8 @@ def create_interface():
2299
  gr.Markdown(overview_text)
2300
 
2301
  with gr.Tab("πŸ”¬ Advanced ML Clustering"):
2302
- gr.Markdown("## 🎯 Storm Pattern Analysis using UMAP/t-SNE with Route Visualization")
2303
- gr.Markdown("**This tab shows both the dimensional clustering analysis AND the actual storm tracks colored by cluster**")
2304
 
2305
  with gr.Row():
2306
  with gr.Column(scale=2):
@@ -2311,52 +2387,61 @@ def create_interface():
2311
  info="UMAP provides better global structure preservation"
2312
  )
2313
  with gr.Column(scale=1):
2314
- show_routes = gr.Checkbox(
2315
- label="πŸ—ΊοΈ Show Storm Routes on Map",
2316
- value=True,
2317
- info="Display actual storm tracks colored by cluster"
2318
- )
2319
 
2320
- analyze_clusters_btn = gr.Button("πŸš€ Analyze Storm Clusters & Routes", variant="primary", size="lg")
 
 
 
 
2321
 
2322
  with gr.Row():
2323
- cluster_plot = gr.Plot(label="Storm Clustering with Route Visualization")
 
 
 
2324
 
2325
  with gr.Row():
2326
  cluster_stats = gr.Textbox(label="πŸ“ˆ Detailed Cluster Statistics", lines=15, max_lines=20)
2327
 
2328
- def run_advanced_clustering_analysis(method, show_routes):
2329
  try:
2330
  # Extract features for clustering
2331
  storm_features = extract_storm_features(typhoon_data)
2332
  if storm_features is None:
2333
- return None, "Error: Could not extract storm features"
2334
- fig, stats, _ = create_advanced_clustering_visualization(storm_features, typhoon_data, method.lower(), show_routes)
2335
- return fig, stats
 
 
 
2336
  except Exception as e:
2337
  import traceback
2338
  error_details = traceback.format_exc()
2339
- return None, f"Error: {str(e)}\n\nDetails:\n{error_details}"
 
2340
 
2341
  analyze_clusters_btn.click(
2342
- fn=run_advanced_clustering_analysis,
2343
- inputs=[reduction_method, show_routes],
2344
- outputs=[cluster_plot, cluster_stats]
2345
  )
2346
 
2347
  cluster_info_text = """
2348
- ### πŸ“Š Advanced Clustering Features:
 
2349
  - **Multi-dimensional Analysis**: Uses 15+ storm characteristics including intensity, track shape, genesis location
2350
- - **Route Visualization**: Shows actual storm tracks colored by cluster membership
2351
- - **DBSCAN Clustering**: Automatically finds natural groupings without predefined cluster count
2352
- - **Comprehensive Stats**: Detailed cluster analysis including intensity, pressure, track length, curvature
2353
- - **Interactive**: Hover over points to see storm details, zoom and pan the route map
2354
 
2355
  ### 🎯 How to Interpret:
2356
- - **Left Plot**: Each dot is a storm positioned by similarity (close = similar characteristics)
2357
- - **Right Plot**: Actual geographic storm tracks, colored by which cluster they belong to
2358
- - **Cluster Colors**: Each cluster gets a unique color to identify similar storm patterns
2359
- - **Noise Points**: Gray points represent storms that don't fit clear patterns
 
2360
  """
2361
  gr.Markdown(cluster_info_text)
2362
 
@@ -2521,7 +2606,7 @@ def create_interface():
2521
  )
2522
 
2523
  with gr.Tab("🎬 Enhanced Track Animation"):
2524
- gr.Markdown("## πŸŽ₯ High-Quality Storm Track Visualization (All Categories Including TD)")
2525
 
2526
  with gr.Row():
2527
  year_dropdown = gr.Dropdown(
@@ -2538,9 +2623,10 @@ def create_interface():
2538
  with gr.Row():
2539
  typhoon_dropdown = gr.Dropdown(label="Storm Selection (All Categories Including TD)")
2540
  standard_dropdown = gr.Dropdown(
2541
- label="Classification Standard",
2542
  choices=['atlantic', 'taiwan'],
2543
- value='atlantic'
 
2544
  )
2545
 
2546
  generate_video_btn = gr.Button("🎬 Generate Enhanced Animation", variant="primary")
@@ -2563,6 +2649,7 @@ def create_interface():
2563
 
2564
  animation_info_text = """
2565
  ### 🎬 Enhanced Animation Features:
 
2566
  - **Full TD Support**: Now displays Tropical Depressions (< 34 kt) in gray
2567
  - **2025 Compatibility**: Complete support for current year data
2568
  - **Enhanced Maps**: Better cartographic projections with terrain features
@@ -2570,6 +2657,12 @@ def create_interface():
2570
  - **Real-time Info**: Live position, time, and meteorological data display
2571
  - **Professional Styling**: Publication-quality animations with proper legends
2572
  - **Optimized Export**: Fast rendering with web-compatible video formats
 
 
 
 
 
 
2573
  """
2574
  gr.Markdown(animation_info_text)
2575
 
@@ -2694,7 +2787,8 @@ def create_interface():
2694
 
2695
  ### πŸš€ Platform Capabilities:
2696
  - **Complete TD Analysis** - First platform to include comprehensive TD tracking
2697
- - **Advanced ML Clustering** - DBSCAN pattern recognition with route visualization
 
2698
  - **Real-time Predictions** - Physics-based and optional CNN intensity forecasting
2699
  - **2025 Data Ready** - Full compatibility with current season data
2700
  - **Enhanced Animations** - Professional-quality storm track videos
@@ -2706,6 +2800,7 @@ def create_interface():
2706
  - Storm pattern classification
2707
  - ENSO-typhoon relationship analysis
2708
  - Intensity prediction model development
 
2709
  """
2710
  gr.Markdown(stats_text)
2711
 
 
140
  'C5 Super Typhoon': '#FF0000' # Red
141
  }
142
 
143
+ # Taiwan color mapping
144
+ taiwan_color_map = {
145
+ 'Tropical Depression': '#808080', # Gray
146
+ 'Mild Typhoon': '#FFFF00', # Yellow
147
+ 'Medium Typhoon': '#FFA500', # Orange
148
+ 'Strong Typhoon': '#FF0000' # Red
149
+ }
150
+
151
  def rgb_string_to_hex(rgb_string):
152
  """Convert 'rgb(r,g,b)' string to hex color for matplotlib"""
153
  try:
 
166
  """Get matplotlib-compatible color for a storm category"""
167
  return matplotlib_color_map.get(category, '#808080')
168
 
169
+ def get_taiwan_color(category):
170
+ """Get Taiwan standard color for a storm category"""
171
+ return taiwan_color_map.get(category, '#808080')
172
+
173
  # Cluster colors for route visualization
174
  CLUSTER_COLORS = [
175
  '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
 
736
  else: # 137+ knots = Category 5 Super Typhoon
737
  return 'C5 Super Typhoon'
738
 
739
+ def categorize_typhoon_taiwan(wind_speed):
740
+ """Taiwan categorization system"""
741
+ if pd.isna(wind_speed):
742
+ return 'Tropical Depression'
743
+
744
+ # Convert to m/s if in knots
745
+ if wind_speed > 50: # Likely in knots, convert to m/s
746
+ wind_speed = wind_speed * 0.514444
747
+
748
+ if wind_speed >= 51.0:
749
+ return 'Strong Typhoon'
750
+ elif wind_speed >= 33.7:
751
+ return 'Medium Typhoon'
752
+ elif wind_speed >= 17.2:
753
+ return 'Mild Typhoon'
754
+ else:
755
+ return 'Tropical Depression'
756
+
757
  # Original function for backward compatibility
758
  def categorize_typhoon(wind_speed):
759
  """Original categorize typhoon function for backward compatibility"""
 
1037
  # Return single cluster as fallback
1038
  return np.array([0] * len(embedding))
1039
 
1040
+ def create_separate_clustering_plots(storm_features, typhoon_data, method='umap'):
1041
+ """Create separate plots for clustering analysis - FIXED VERSION"""
1042
  try:
1043
  # Validate inputs
1044
  if storm_features is None or storm_features.empty:
 
1073
  storm_features_viz['NAME'] = 'UNNAMED'
1074
  storm_features_viz['SEASON'] = 2000
1075
 
1076
+ # 1. Clustering scatter plot
1077
+ fig_cluster = px.scatter(
1078
+ storm_features_viz,
1079
+ x='dim1',
1080
+ y='dim2',
1081
+ color='cluster',
1082
+ hover_data=['NAME', 'SEASON'],
1083
+ title=f'Storm Clustering using {method.upper()}',
1084
+ labels={
1085
+ 'dim1': f'{method.upper()} Dimension 1',
1086
+ 'dim2': f'{method.upper()} Dimension 2',
1087
+ 'cluster': 'Cluster'
1088
+ }
1089
+ )
1090
+
1091
+ # 2. Route map
1092
+ fig_routes = go.Figure()
1093
+
1094
+ unique_clusters = sorted(storm_features_viz['cluster'].unique())
1095
+ for i, cluster in enumerate(unique_clusters):
1096
+ if cluster == -1: # Skip noise for route visualization
1097
+ continue
1098
+
1099
+ cluster_storm_ids = storm_features_viz[storm_features_viz['cluster'] == cluster]['SID'].tolist()
1100
+ color = CLUSTER_COLORS[i % len(CLUSTER_COLORS)]
1101
 
1102
+ tracks_added = 0
1103
+ for j, sid in enumerate(cluster_storm_ids[:5]): # Limit to 5 storms per cluster for performance
 
 
 
 
 
 
1104
  try:
1105
+ storm_track = typhoon_data[typhoon_data['SID'] == sid].sort_values('ISO_TIME')
1106
+ if len(storm_track) > 1:
1107
+ # Ensure valid coordinates
1108
+ valid_coords = storm_track['LAT'].notna() & storm_track['LON'].notna()
1109
+ storm_track = storm_track[valid_coords]
1110
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1111
  if len(storm_track) > 1:
1112
+ storm_name = storm_track['NAME'].iloc[0] if pd.notna(storm_track['NAME'].iloc[0]) else 'UNNAMED'
 
 
1113
 
1114
+ fig_routes.add_trace(
1115
+ go.Scattergeo(
1116
+ lon=storm_track['LON'],
1117
+ lat=storm_track['LAT'],
1118
+ mode='lines+markers',
1119
+ line=dict(color=color, width=2),
1120
+ marker=dict(color=color, size=4),
1121
+ name=f'C{cluster}: {storm_name}' if tracks_added == 0 else None,
1122
+ showlegend=(tracks_added == 0),
1123
+ hovertemplate=(
1124
+ f'<b>{storm_name}</b><br>'
1125
+ 'Lat: %{lat:.1f}Β°<br>'
1126
+ 'Lon: %{lon:.1f}Β°<br>'
1127
+ f'Cluster: {cluster}<br>'
1128
+ '<extra></extra>'
1129
+ )
 
 
 
 
 
1130
  )
1131
+ )
1132
+ tracks_added += 1
1133
+ except Exception as track_error:
1134
+ logging.warning(f"Error adding track for storm {sid}: {track_error}")
1135
+ continue
1136
+
1137
+ # Update route map layout
1138
+ fig_routes.update_layout(
1139
+ title="Clustered Storm Routes",
1140
+ geo=dict(
 
 
 
 
1141
  projection_type="natural earth",
1142
  showland=True,
1143
  landcolor="LightGray",
 
1145
  oceancolor="LightBlue",
1146
  showcoastlines=True,
1147
  coastlinecolor="Gray",
1148
+ center=dict(lat=20, lon=140)
 
1149
  )
1150
+ )
1151
+
1152
+ # 3. Pressure evolution plot
1153
+ fig_pressure = go.Figure()
1154
+
1155
+ for i, cluster in enumerate(unique_clusters):
1156
+ if cluster == -1:
1157
+ continue
1158
+
1159
+ cluster_storm_ids = storm_features_viz[storm_features_viz['cluster'] == cluster]['SID'].tolist()
1160
+ color = CLUSTER_COLORS[i % len(CLUSTER_COLORS)]
1161
 
1162
+ for j, sid in enumerate(cluster_storm_ids[:3]): # Limit to 3 storms per cluster
1163
+ try:
1164
+ storm_track = typhoon_data[typhoon_data['SID'] == sid].sort_values('ISO_TIME')
1165
+ if len(storm_track) > 1 and 'USA_PRES' in storm_track.columns:
1166
+ pressure_values = pd.to_numeric(storm_track['USA_PRES'], errors='coerce').dropna()
1167
+ if len(pressure_values) > 0:
1168
+ storm_name = storm_track['NAME'].iloc[0] if pd.notna(storm_track['NAME'].iloc[0]) else 'UNNAMED'
1169
+ time_hours = range(len(pressure_values))
1170
+
1171
+ fig_pressure.add_trace(
1172
+ go.Scatter(
1173
+ x=time_hours,
1174
+ y=pressure_values,
1175
+ mode='lines',
1176
+ line=dict(color=color, width=2),
1177
+ name=f'C{cluster}: {storm_name}' if j == 0 else None,
1178
+ showlegend=(j == 0),
1179
+ hovertemplate=(
1180
+ f'<b>{storm_name}</b><br>'
1181
+ 'Hour: %{x}<br>'
1182
+ 'Pressure: %{y:.0f} hPa<br>'
1183
+ '<extra></extra>'
1184
+ )
1185
+ )
1186
+ )
1187
+ except Exception as e:
1188
+ continue
1189
+
1190
+ fig_pressure.update_layout(
1191
+ title="Pressure Evolution by Cluster",
1192
+ xaxis_title="Time (hours)",
1193
+ yaxis_title="Pressure (hPa)"
1194
+ )
1195
+
1196
+ # 4. Wind evolution plot
1197
+ fig_wind = go.Figure()
1198
+
1199
+ for i, cluster in enumerate(unique_clusters):
1200
+ if cluster == -1:
1201
+ continue
1202
+
1203
+ cluster_storm_ids = storm_features_viz[storm_features_viz['cluster'] == cluster]['SID'].tolist()
1204
+ color = CLUSTER_COLORS[i % len(CLUSTER_COLORS)]
1205
 
1206
+ for j, sid in enumerate(cluster_storm_ids[:3]): # Limit to 3 storms per cluster
1207
+ try:
1208
+ storm_track = typhoon_data[typhoon_data['SID'] == sid].sort_values('ISO_TIME')
1209
+ if len(storm_track) > 1 and 'USA_WIND' in storm_track.columns:
1210
+ wind_values = pd.to_numeric(storm_track['USA_WIND'], errors='coerce').dropna()
1211
+ if len(wind_values) > 0:
1212
+ storm_name = storm_track['NAME'].iloc[0] if pd.notna(storm_track['NAME'].iloc[0]) else 'UNNAMED'
1213
+ time_hours = range(len(wind_values))
1214
+
1215
+ fig_wind.add_trace(
1216
+ go.Scatter(
1217
+ x=time_hours,
1218
+ y=wind_values,
1219
+ mode='lines',
1220
+ line=dict(color=color, width=2),
1221
+ name=f'C{cluster}: {storm_name}' if j == 0 else None,
1222
+ showlegend=(j == 0),
1223
+ hovertemplate=(
1224
+ f'<b>{storm_name}</b><br>'
1225
+ 'Hour: %{x}<br>'
1226
+ 'Wind: %{y:.0f} kt<br>'
1227
+ '<extra></extra>'
1228
+ )
1229
+ )
1230
+ )
1231
+ except Exception as e:
1232
+ continue
1233
+
1234
+ fig_wind.update_layout(
1235
+ title="Wind Speed Evolution by Cluster",
1236
+ xaxis_title="Time (hours)",
1237
+ yaxis_title="Wind Speed (kt)"
1238
+ )
1239
 
1240
  # Generate detailed cluster statistics - FIXED
1241
  try:
 
1255
  existing_cols = {k: v for k, v in available_cols.items() if v in storm_features_viz.columns}
1256
 
1257
  if len(existing_cols) > 1: # Need at least SID + one other column
 
 
 
 
 
1258
  stats_text = "ADVANCED CLUSTER ANALYSIS RESULTS\n" + "="*50 + "\n\n"
1259
 
1260
  for cluster in sorted(storm_features_viz['cluster'].unique()):
 
1305
  logging.error(f"Error generating cluster statistics: {stats_error}")
1306
  stats_text = f"Error generating cluster statistics: {str(stats_error)}"
1307
 
1308
+ return fig_cluster, fig_routes, fig_pressure, fig_wind, stats_text
1309
 
1310
  except Exception as e:
1311
  logging.error(f"Error in clustering analysis: {e}")
 
1319
  x=0.5, y=0.5, xanchor='center', yanchor='middle',
1320
  showarrow=False, font_size=16
1321
  )
1322
+ return error_fig, error_fig, error_fig, error_fig, f"Error in clustering: {str(e)}"
1323
 
1324
  # -----------------------------
1325
  # ENHANCED: Advanced Prediction System with Route Forecasting
 
1562
  }
1563
 
1564
  def create_route_visualization(prediction_results, show_uncertainty=True):
1565
+ """Create comprehensive route and intensity visualization - FIXED"""
1566
  try:
1567
  if 'route_forecast' not in prediction_results or not prediction_results['route_forecast']:
1568
  return None, "No route forecast data available"
1569
 
1570
  route_data = prediction_results['route_forecast']
1571
 
1572
+ # Create subplot with route map and intensity evolution - FIXED
1573
  fig = make_subplots(
1574
  rows=1, cols=2,
1575
  subplot_titles=('Forecast Track', 'Intensity Evolution'),
1576
+ specs=[[{"type": "geo"}, {"type": "xy"}]], # Changed to xy for regular plot
1577
  column_widths=[0.6, 0.4]
1578
  )
1579
 
 
1668
  row=1, col=1
1669
  )
1670
 
1671
+ # Intensity evolution plot - FIXED to use regular scatter plot
1672
  fig.add_trace(
1673
  go.Scatter(
1674
  x=hours,
 
1707
  height=600
1708
  )
1709
 
1710
+ # Update geo layout - FIXED: Only update geo subplot
1711
  fig.update_geos(
1712
  projection_type="natural earth",
1713
  showland=True,
 
1721
  row=1, col=1
1722
  )
1723
 
1724
+ # Update intensity plot - FIXED: Use correct method for regular subplot
1725
  fig.update_xaxes(title_text="Forecast Hour", row=1, col=2)
1726
  fig.update_yaxes(title_text="Intensity (kt)", row=1, col=2)
1727
 
 
1971
  return 'Tropical Depression', '#808080'
1972
 
1973
  if standard=='taiwan':
1974
+ # Taiwan standard uses m/s, convert if needed
1975
+ if wind_speed > 50: # Likely in knots, convert to m/s
1976
+ wind_speed_ms = wind_speed * 0.514444
1977
+ else:
1978
+ wind_speed_ms = wind_speed
1979
+
1980
  if wind_speed_ms >= 51.0:
1981
  return 'Strong Typhoon', '#FF0000' # Red
1982
  elif wind_speed_ms >= 33.7:
 
1985
  return 'Mild Typhoon', '#FFFF00' # Yellow
1986
  return 'Tropical Depression', '#808080' # Gray
1987
  else:
1988
+ # Atlantic standard in knots
1989
  if wind_speed >= 137:
1990
  return 'C5 Super Typhoon', '#FF0000' # Red
1991
  elif wind_speed >= 113:
 
2001
  return 'Tropical Depression', '#808080' # Gray
2002
 
2003
  # -----------------------------
2004
+ # ENHANCED: Animation Functions with Taiwan Standard Support
2005
  # -----------------------------
2006
 
2007
  def get_available_years(typhoon_data):
 
2087
  return gr.update(choices=["Error loading storms"], value=None)
2088
 
2089
  def generate_enhanced_track_video(year, typhoon_selection, standard):
2090
+ """Enhanced track video generation with TD support, Taiwan standard, and 2025 compatibility"""
2091
  if not typhoon_selection or typhoon_selection == "No storms found":
2092
  return None
2093
 
 
2118
  storm_name = storm_df['NAME'].iloc[0] if pd.notna(storm_df['NAME'].iloc[0]) else "UNNAMED"
2119
  season = storm_df['SEASON'].iloc[0] if 'SEASON' in storm_df.columns else year
2120
 
2121
+ print(f"Generating video for {storm_name} ({sid}) with {len(lats)} track points using {standard} standard")
2122
 
2123
  # Create figure with enhanced map
2124
+ fig, ax = plt.subplots(figsize=(16, 10), subplot_kw={'projection': ccrs.PlateCarree()})
2125
 
2126
  # Enhanced map features
2127
  ax.stock_img()
 
2141
  gl = ax.gridlines(draw_labels=True, alpha=0.3)
2142
  gl.top_labels = gl.right_labels = False
2143
 
2144
+ # Title with enhanced info and standard
2145
+ ax.set_title(f"{season} {storm_name} ({sid}) Track Animation - {standard.upper()} Standard",
2146
+ fontsize=18, fontweight='bold')
2147
 
2148
  # Animation elements
2149
  line, = ax.plot([], [], 'b-', linewidth=3, alpha=0.7, label='Track')
2150
+ point, = ax.plot([], [], 'o', markersize=15)
2151
 
2152
  # Enhanced info display
2153
  info_box = ax.text(0.02, 0.98, '', transform=ax.transAxes,
2154
+ fontsize=12, verticalalignment='top',
2155
  bbox=dict(boxstyle="round,pad=0.5", facecolor='white', alpha=0.9))
2156
 
2157
+ # Color legend with both standards - ENHANCED
2158
  legend_elements = []
2159
+
2160
+ if standard == 'taiwan':
2161
+ categories = ['Tropical Depression', 'Mild Typhoon', 'Medium Typhoon', 'Strong Typhoon']
2162
+ for category in categories:
2163
+ color = get_taiwan_color(category)
2164
+ legend_elements.append(plt.Line2D([0], [0], marker='o', color='w',
2165
+ markerfacecolor=color, markersize=10, label=category))
2166
+ else:
2167
+ categories = ['Tropical Depression', 'Tropical Storm', 'C1 Typhoon', 'C2 Typhoon',
2168
+ 'C3 Strong Typhoon', 'C4 Very Strong Typhoon', 'C5 Super Typhoon']
2169
+ for category in categories:
2170
  color = get_matplotlib_color(category)
2171
  legend_elements.append(plt.Line2D([0], [0], marker='o', color='w',
2172
+ markerfacecolor=color, markersize=10, label=category))
2173
 
2174
+ ax.legend(handles=legend_elements, loc='upper right', fontsize=10)
2175
 
2176
  def animate(frame):
2177
  try:
 
2181
  # Update track line
2182
  line.set_data(lons[:frame+1], lats[:frame+1])
2183
 
2184
+ # Update current position with appropriate categorization
2185
  current_wind = winds[frame]
2186
+
2187
+ if standard == 'taiwan':
2188
+ category, color = categorize_typhoon_by_standard(current_wind, 'taiwan')
2189
+ else:
2190
+ category, color = categorize_typhoon_by_standard(current_wind, 'atlantic')
2191
 
2192
  # Debug print for first few frames
2193
  if frame < 3:
2194
+ print(f"Frame {frame}: Wind={current_wind:.1f}kt, Category={category}, Color={color}, Standard={standard}")
2195
 
2196
  point.set_data([lons[frame]], [lats[frame]])
2197
  point.set_color(color)
2198
+ point.set_markersize(10 + current_wind/8) # Size based on intensity
2199
 
2200
+ # Enhanced info display with standard information
2201
  if 'ISO_TIME' in storm_df.columns and frame < len(storm_df):
2202
  current_time = storm_df.iloc[frame]['ISO_TIME']
2203
  time_str = current_time.strftime('%Y-%m-%d %H:%M UTC') if pd.notna(current_time) else 'Unknown'
2204
  else:
2205
  time_str = f"Step {frame+1}"
2206
 
2207
+ # Convert wind speed for Taiwan standard display
2208
+ if standard == 'taiwan':
2209
+ wind_ms = current_wind * 0.514444 # Convert to m/s for display
2210
+ wind_display = f"{current_wind:.0f} kt ({wind_ms:.1f} m/s)"
2211
+ else:
2212
+ wind_display = f"{current_wind:.0f} kt"
2213
+
2214
  info_text = (
2215
  f"Storm: {storm_name}\n"
2216
  f"Time: {time_str}\n"
2217
  f"Position: {lats[frame]:.1f}Β°N, {lons[frame]:.1f}Β°E\n"
2218
+ f"Max Wind: {wind_display}\n"
2219
  f"Category: {category}\n"
2220
+ f"Standard: {standard.upper()}\n"
2221
  f"Frame: {frame+1}/{len(lats)}"
2222
  )
2223
  info_box.set_text(info_text)
 
2231
  # Create animation
2232
  anim = animation.FuncAnimation(
2233
  fig, animate, frames=len(lats),
2234
+ interval=400, blit=False, repeat=True # Slightly slower for better viewing
2235
  )
2236
 
2237
  # Save animation
 
2240
 
2241
  # Enhanced writer settings
2242
  writer = animation.FFMpegWriter(
2243
+ fps=3, bitrate=2000, codec='libx264', # Slower FPS for better visibility
2244
  extra_args=['-pix_fmt', 'yuv420p'] # Better compatibility
2245
  )
2246
 
2247
  print(f"Saving animation to {temp_file.name}")
2248
+ anim.save(temp_file.name, writer=writer, dpi=120) # Higher DPI for better quality
2249
  plt.close(fig)
2250
 
2251
  print(f"Video generated successfully: {temp_file.name}")
 
2311
  initialize_data()
2312
 
2313
  # -----------------------------
2314
+ # ENHANCED: Gradio Interface with Fixed Route Visualization and Enhanced Features
2315
  # -----------------------------
2316
 
2317
  def create_interface():
 
2346
  This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
2347
 
2348
  ### πŸš€ Enhanced Features:
2349
+ - **Advanced ML Clustering**: UMAP/t-SNE storm pattern analysis with separate visualizations
2350
  - **Predictive Routing**: Advanced storm track and intensity forecasting with uncertainty quantification
2351
  - **Complete TD Support**: Now includes Tropical Depressions (< 34 kt)
2352
+ - **Taiwan Standard**: Full support for Taiwan meteorological classification system
2353
  - **2025 Data Ready**: Real-time compatibility with current year data
2354
+ - **Enhanced Animations**: High-quality storm track visualizations with both standards
2355
 
2356
  ### πŸ“Š Data Status:
2357
  - **ONI Data**: {len(oni_data)} years loaded
 
2375
  gr.Markdown(overview_text)
2376
 
2377
  with gr.Tab("πŸ”¬ Advanced ML Clustering"):
2378
+ gr.Markdown("## 🎯 Storm Pattern Analysis with Separate Visualizations")
2379
+ gr.Markdown("**Four separate plots: Clustering, Routes, Pressure Evolution, and Wind Evolution**")
2380
 
2381
  with gr.Row():
2382
  with gr.Column(scale=2):
 
2387
  info="UMAP provides better global structure preservation"
2388
  )
2389
  with gr.Column(scale=1):
2390
+ analyze_clusters_btn = gr.Button("πŸš€ Generate All Cluster Analyses", variant="primary", size="lg")
 
 
 
 
2391
 
2392
+ with gr.Row():
2393
+ with gr.Column():
2394
+ cluster_plot = gr.Plot(label="πŸ“Š Storm Clustering Analysis")
2395
+ with gr.Column():
2396
+ routes_plot = gr.Plot(label="πŸ—ΊοΈ Clustered Storm Routes")
2397
 
2398
  with gr.Row():
2399
+ with gr.Column():
2400
+ pressure_plot = gr.Plot(label="🌑️ Pressure Evolution by Cluster")
2401
+ with gr.Column():
2402
+ wind_plot = gr.Plot(label="πŸ’¨ Wind Speed Evolution by Cluster")
2403
 
2404
  with gr.Row():
2405
  cluster_stats = gr.Textbox(label="πŸ“ˆ Detailed Cluster Statistics", lines=15, max_lines=20)
2406
 
2407
+ def run_separate_clustering_analysis(method):
2408
  try:
2409
  # Extract features for clustering
2410
  storm_features = extract_storm_features(typhoon_data)
2411
  if storm_features is None:
2412
+ return None, None, None, None, "Error: Could not extract storm features"
2413
+
2414
+ fig_cluster, fig_routes, fig_pressure, fig_wind, stats = create_separate_clustering_plots(
2415
+ storm_features, typhoon_data, method.lower()
2416
+ )
2417
+ return fig_cluster, fig_routes, fig_pressure, fig_wind, stats
2418
  except Exception as e:
2419
  import traceback
2420
  error_details = traceback.format_exc()
2421
+ error_msg = f"Error: {str(e)}\n\nDetails:\n{error_details}"
2422
+ return None, None, None, None, error_msg
2423
 
2424
  analyze_clusters_btn.click(
2425
+ fn=run_separate_clustering_analysis,
2426
+ inputs=[reduction_method],
2427
+ outputs=[cluster_plot, routes_plot, pressure_plot, wind_plot, cluster_stats]
2428
  )
2429
 
2430
  cluster_info_text = """
2431
+ ### πŸ“Š Enhanced Clustering Features:
2432
+ - **Separate Visualizations**: Four distinct plots for comprehensive analysis
2433
  - **Multi-dimensional Analysis**: Uses 15+ storm characteristics including intensity, track shape, genesis location
2434
+ - **Route Visualization**: Geographic storm tracks colored by cluster membership
2435
+ - **Temporal Analysis**: Pressure and wind evolution patterns by cluster
2436
+ - **DBSCAN Clustering**: Automatic pattern discovery without predefined cluster count
2437
+ - **Interactive**: Hover over points to see storm details, zoom and pan all plots
2438
 
2439
  ### 🎯 How to Interpret:
2440
+ - **Clustering Plot**: Each dot is a storm positioned by similarity (close = similar characteristics)
2441
+ - **Routes Plot**: Actual geographic storm tracks, colored by which cluster they belong to
2442
+ - **Pressure Plot**: Shows how pressure changes over time for storms in each cluster
2443
+ - **Wind Plot**: Shows wind speed evolution patterns for each cluster
2444
+ - **Cluster Colors**: Each cluster gets a unique color across all four visualizations
2445
  """
2446
  gr.Markdown(cluster_info_text)
2447
 
 
2606
  )
2607
 
2608
  with gr.Tab("🎬 Enhanced Track Animation"):
2609
+ gr.Markdown("## πŸŽ₯ High-Quality Storm Track Visualization (Atlantic & Taiwan Standards)")
2610
 
2611
  with gr.Row():
2612
  year_dropdown = gr.Dropdown(
 
2623
  with gr.Row():
2624
  typhoon_dropdown = gr.Dropdown(label="Storm Selection (All Categories Including TD)")
2625
  standard_dropdown = gr.Dropdown(
2626
+ label="🎌 Classification Standard",
2627
  choices=['atlantic', 'taiwan'],
2628
+ value='atlantic',
2629
+ info="Atlantic: International standard | Taiwan: Local meteorological standard"
2630
  )
2631
 
2632
  generate_video_btn = gr.Button("🎬 Generate Enhanced Animation", variant="primary")
 
2649
 
2650
  animation_info_text = """
2651
  ### 🎬 Enhanced Animation Features:
2652
+ - **Dual Standards**: Full support for both Atlantic and Taiwan classification systems
2653
  - **Full TD Support**: Now displays Tropical Depressions (< 34 kt) in gray
2654
  - **2025 Compatibility**: Complete support for current year data
2655
  - **Enhanced Maps**: Better cartographic projections with terrain features
 
2657
  - **Real-time Info**: Live position, time, and meteorological data display
2658
  - **Professional Styling**: Publication-quality animations with proper legends
2659
  - **Optimized Export**: Fast rendering with web-compatible video formats
2660
+
2661
+ ### 🎌 Taiwan Standard Features:
2662
+ - **m/s Display**: Shows both knots and meters per second
2663
+ - **Local Categories**: TD β†’ Mild β†’ Medium β†’ Strong Typhoon
2664
+ - **Color Coding**: Gray β†’ Yellow β†’ Orange β†’ Red
2665
+ - **CWB Compatible**: Matches Central Weather Bureau classifications
2666
  """
2667
  gr.Markdown(animation_info_text)
2668
 
 
2787
 
2788
  ### πŸš€ Platform Capabilities:
2789
  - **Complete TD Analysis** - First platform to include comprehensive TD tracking
2790
+ - **Dual Classification Systems** - Both Atlantic and Taiwan standards supported
2791
+ - **Advanced ML Clustering** - DBSCAN pattern recognition with separate visualizations
2792
  - **Real-time Predictions** - Physics-based and optional CNN intensity forecasting
2793
  - **2025 Data Ready** - Full compatibility with current season data
2794
  - **Enhanced Animations** - Professional-quality storm track videos
 
2800
  - Storm pattern classification
2801
  - ENSO-typhoon relationship analysis
2802
  - Intensity prediction model development
2803
+ - Cross-regional classification comparisons
2804
  """
2805
  gr.Markdown(stats_text)
2806