euler314 commited on
Commit
611f47d
·
verified ·
1 Parent(s): 6339b5b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -39
app.py CHANGED
@@ -65,6 +65,13 @@ taiwan_standard = {
65
  'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
66
  }
67
 
 
 
 
 
 
 
 
68
  # Data loading and preprocessing functions
69
  def download_oni_file(url, filename):
70
  response = requests.get(url)
@@ -362,9 +369,9 @@ def generate_track_video(year, typhoon, standard):
362
  point, = ax.plot([], [], 'o', markersize=8, transform=ccrs.PlateCarree())
363
  date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=10, bbox=dict(facecolor='white', alpha=0.8))
364
 
365
- # Add sidebar on the right
366
- details_title = fig.text(0.75, 0.95, "Typhoon Details", fontsize=12, fontweight='bold', verticalalignment='top')
367
- details_text = fig.text(0.75, 0.85, '', fontsize=10, verticalalignment='top',
368
  bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))
369
 
370
  # Add color legend
@@ -372,8 +379,8 @@ def generate_track_video(year, typhoon, standard):
372
  legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}",
373
  markerfacecolor=details['hex'], markersize=10)
374
  for cat, details in standard_dict.items()]
375
- fig.legend(handles=legend_elements, title="Color Legend", loc='lower right',
376
- bbox_to_anchor=(0.95, 0.05), fontsize=10)
377
 
378
  def init():
379
  line.set_data([], [])
@@ -445,6 +452,29 @@ def filter_west_pacific_coordinates(lons, lats):
445
  mask = (lons >= 100) & (lons <= 180) & (lats >= 0) & (lats <= 50)
446
  return lons[mask], lats[mask]
447
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  def update_route_clusters(start_year, start_month, end_year, end_month, enso_value, season):
449
  start_date = datetime(int(start_year), int(start_month), 1)
450
  end_date = datetime(int(end_year), int(end_month), 28)
@@ -457,7 +487,18 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
457
  if storm.time[0] >= start_date and storm.time[-1] <= end_date:
458
  lons, lats = filter_west_pacific_coordinates(np.array(storm.lon), np.array(storm.lat))
459
  if len(lons) > 1:
460
- all_storms_data.append((lons, lats, np.array(storm.vmax), np.array(storm.mslp), np.array(storm.time), storm.name))
 
 
 
 
 
 
 
 
 
 
 
461
 
462
  if not all_storms_data:
463
  return go.Figure(), go.Figure(), go.Figure(), "No storms found in the selected period."
@@ -465,7 +506,7 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
465
  # Prepare route vectors for t-SNE
466
  max_length = max(len(st[0]) for st in all_storms_data)
467
  route_vectors = []
468
- for lons, lats, _, _, _, _ in all_storms_data:
469
  interp_lons = np.interp(np.linspace(0, 1, max_length), np.linspace(0, 1, len(lons)), lons)
470
  interp_lats = np.interp(np.linspace(0, 1, max_length), np.linspace(0, 1, len(lats)), lats)
471
  route_vectors.append(np.column_stack((interp_lons, interp_lats)).flatten())
@@ -475,32 +516,7 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
475
  tsne_results = TSNE(n_components=2, random_state=42, perplexity=min(30, len(route_vectors)-1)).fit_transform(route_vectors)
476
 
477
  # Dynamic DBSCAN clustering
478
- target_clusters = min(5, len(all_storms_data) // 3)
479
- eps_range = np.arange(5.0, 50.0, 5.0)
480
- min_samples = max(3, len(all_storms_data) // 20)
481
- best_labels = None
482
- best_eps = None
483
- best_n_clusters = 0
484
- best_noise_ratio = 1.0
485
-
486
- for eps in eps_range:
487
- dbscan = DBSCAN(eps=eps, min_samples=min_samples)
488
- labels = dbscan.fit_predict(tsne_results)
489
- n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
490
- noise_points = np.sum(labels == -1)
491
- noise_ratio = noise_points / len(labels)
492
-
493
- if n_clusters >= target_clusters and noise_ratio < 0.3 and (n_clusters > best_n_clusters or (n_clusters == best_n_clusters and noise_ratio < best_noise_ratio)):
494
- best_labels = labels
495
- best_eps = eps
496
- best_n_clusters = n_clusters
497
- best_noise_ratio = noise_ratio
498
-
499
- if best_labels is None:
500
- dbscan = DBSCAN(eps=5.0, min_samples=min_samples)
501
- best_labels = dbscan.fit_predict(tsne_results)
502
- best_eps = 5.0
503
- best_n_clusters = len(set(best_labels)) - (1 if -1 in best_labels else 0)
504
 
505
  # t-SNE Scatter Plot
506
  fig_tsne = go.Figure()
@@ -516,7 +532,7 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
516
 
517
  # Typhoon Routes Plot
518
  fig_routes = go.Figure()
519
- for i, (lons, lats, _, _, _, name) in enumerate(all_storms_data):
520
  cluster = best_labels[i]
521
  color = 'gray' if cluster == -1 else px.colors.qualitative.Plotly[cluster % len(px.colors.qualitative.Plotly)]
522
  fig_routes.add_trace(go.Scattergeo(
@@ -541,13 +557,17 @@ def update_route_clusters(start_year, start_month, end_year, end_month, enso_val
541
  'Mean Pressure': np.mean(pressures)
542
  })
543
  stats_df = pd.DataFrame(cluster_stats)
544
- fig_stats = px.bar(stats_df, x='Cluster', y=['Mean Wind', 'Mean Pressure'], barmode='group',
545
- title="Cluster Statistics (Mean Wind Speed and Pressure)")
 
 
 
546
 
547
  # Cluster Information
548
- cluster_info = f"Number of Clusters: {best_n_clusters}\nBest EPS: {best_eps}\nNoise Points: {best_noise_ratio*100:.1f}%"
 
549
  for stat in cluster_stats:
550
- cluster_info += f"\nCluster {stat['Cluster']}: {stat['Count']} storms, Mean Wind: {stat['Mean Wind']:.1f} kt, Mean Pressure: {stat['Mean Pressure']:.1f} hPa"
551
 
552
  return fig_tsne, fig_routes, fig_stats, cluster_info
553
 
@@ -737,7 +757,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
737
  5. The animation shows the typhoon track growing over a world map, with:
738
  - Date on the bottom left
739
  - Sidebar on the right showing typhoon details (name, date, wind speed, category) as it moves
740
- - Color legend with colored markers on the bottom right
741
  """)
742
 
743
  def update_typhoon_options(year):
 
65
  'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
66
  }
67
 
68
+ # Season months mapping
69
+ season_months = {
70
+ 'all': list(range(1, 13)),
71
+ 'summer': [6, 7, 8],
72
+ 'winter': [12, 1, 2]
73
+ }
74
+
75
  # Data loading and preprocessing functions
76
  def download_oni_file(url, filename):
77
  response = requests.get(url)
 
369
  point, = ax.plot([], [], 'o', markersize=8, transform=ccrs.PlateCarree())
370
  date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=10, bbox=dict(facecolor='white', alpha=0.8))
371
 
372
+ # Add sidebar on the right with adjusted positions
373
+ details_title = fig.text(0.7, 0.95, "Typhoon Details", fontsize=12, fontweight='bold', verticalalignment='top')
374
+ details_text = fig.text(0.7, 0.85, '', fontsize=12, verticalalignment='top',
375
  bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))
376
 
377
  # Add color legend
 
379
  legend_elements = [plt.Line2D([0], [0], marker='o', color='w', label=f"{cat}",
380
  markerfacecolor=details['hex'], markersize=10)
381
  for cat, details in standard_dict.items()]
382
+ fig.legend(handles=legend_elements, title="Color Legend", loc='center right',
383
+ bbox_to_anchor=(0.95, 0.5), fontsize=10)
384
 
385
  def init():
386
  line.set_data([], [])
 
452
  mask = (lons >= 100) & (lons <= 180) & (lats >= 0) & (lats <= 50)
453
  return lons[mask], lats[mask]
454
 
455
+ def dynamic_dbscan(tsne_results, min_clusters=10, max_clusters=20, eps_values=np.arange(0.1, 5.0, 0.1)):
456
+ best_labels = None
457
+ best_n_clusters = 0
458
+ best_n_noise = len(tsne_results)
459
+ best_eps = None
460
+ for eps in eps_values:
461
+ dbscan = DBSCAN(eps=eps, min_samples=3)
462
+ labels = dbscan.fit_predict(tsne_results)
463
+ n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
464
+ n_noise = np.sum(labels == -1)
465
+ if min_clusters <= n_clusters <= max_clusters and n_noise < best_n_noise:
466
+ best_labels = labels
467
+ best_n_clusters = n_clusters
468
+ best_n_noise = n_noise
469
+ best_eps = eps
470
+ if best_labels is None:
471
+ dbscan = DBSCAN(eps=eps_values[0], min_samples=3)
472
+ best_labels = dbscan.fit_predict(tsne_results)
473
+ best_n_clusters = len(set(best_labels)) - (1 if -1 in best_labels else 0)
474
+ best_n_noise = np.sum(best_labels == -1)
475
+ best_eps = eps_values[0]
476
+ return best_labels, best_n_clusters, best_n_noise, best_eps
477
+
478
  def update_route_clusters(start_year, start_month, end_year, end_month, enso_value, season):
479
  start_date = datetime(int(start_year), int(start_month), 1)
480
  end_date = datetime(int(end_year), int(end_month), 28)
 
487
  if storm.time[0] >= start_date and storm.time[-1] <= end_date:
488
  lons, lats = filter_west_pacific_coordinates(np.array(storm.lon), np.array(storm.lat))
489
  if len(lons) > 1:
490
+ start_time = storm.time[0]
491
+ start_year_storm = start_time.year
492
+ start_month_storm = start_time.month
493
+ oni_row = oni_long[(oni_long['Year'] == start_year_storm) & (oni_long['Month'] == f'{start_month_storm:02d}')]
494
+ if not oni_row.empty:
495
+ oni_value_storm = oni_row['ONI'].iloc[0]
496
+ enso_phase_storm = classify_enso_phases(oni_value_storm)
497
+ if enso_value == 'all' or enso_phase_storm == enso_value.capitalize():
498
+ all_storms_data.append((lons, lats, np.array(storm.vmax), np.array(storm.mslp), np.array(storm.time), storm.name, enso_phase_storm))
499
+
500
+ if season != 'all':
501
+ all_storms_data = [storm for storm in all_storms_data if storm[4][0].month in season_months[season]]
502
 
503
  if not all_storms_data:
504
  return go.Figure(), go.Figure(), go.Figure(), "No storms found in the selected period."
 
506
  # Prepare route vectors for t-SNE
507
  max_length = max(len(st[0]) for st in all_storms_data)
508
  route_vectors = []
509
+ for lons, lats, _, _, _, _, _ in all_storms_data:
510
  interp_lons = np.interp(np.linspace(0, 1, max_length), np.linspace(0, 1, len(lons)), lons)
511
  interp_lats = np.interp(np.linspace(0, 1, max_length), np.linspace(0, 1, len(lats)), lats)
512
  route_vectors.append(np.column_stack((interp_lons, interp_lats)).flatten())
 
516
  tsne_results = TSNE(n_components=2, random_state=42, perplexity=min(30, len(route_vectors)-1)).fit_transform(route_vectors)
517
 
518
  # Dynamic DBSCAN clustering
519
+ best_labels, best_n_clusters, best_n_noise, best_eps = dynamic_dbscan(tsne_results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
 
521
  # t-SNE Scatter Plot
522
  fig_tsne = go.Figure()
 
532
 
533
  # Typhoon Routes Plot
534
  fig_routes = go.Figure()
535
+ for i, (lons, lats, _, _, _, name, _) in enumerate(all_storms_data):
536
  cluster = best_labels[i]
537
  color = 'gray' if cluster == -1 else px.colors.qualitative.Plotly[cluster % len(px.colors.qualitative.Plotly)]
538
  fig_routes.add_trace(go.Scattergeo(
 
557
  'Mean Pressure': np.mean(pressures)
558
  })
559
  stats_df = pd.DataFrame(cluster_stats)
560
+ fig_stats = go.Figure()
561
+ fig_stats.add_trace(go.Bar(x=stats_df['Cluster'], y=stats_df['Count'], name='Storm Count'))
562
+ fig_stats.add_trace(go.Bar(x=stats_df['Cluster'], y=stats_df['Mean Wind'], name='Mean Max Wind Speed'))
563
+ fig_stats.add_trace(go.Bar(x=stats_df['Cluster'], y=stats_df['Mean Pressure'], name='Mean Min Pressure'))
564
+ fig_stats.update_layout(barmode='group', title="Cluster Statistics")
565
 
566
  # Cluster Information
567
+ cluster_info = f"Date Range: {start_year}-{start_month} to {end_year}-{end_month}\nENSO Phase: {enso_value}\nSeason: {season}\n\n"
568
+ cluster_info += f"Selected EPS: {best_eps}\nNumber of Clusters: {best_n_clusters}\nNoise Points: {best_n_noise} ({(best_n_noise / len(best_labels))*100:.1f}%)\n"
569
  for stat in cluster_stats:
570
+ cluster_info += f"Cluster {stat['Cluster']}: {stat['Count']} storms, Mean Max Wind: {stat['Mean Wind']:.1f} kt, Mean Min Pressure: {stat['Mean Pressure']:.1f} hPa\n"
571
 
572
  return fig_tsne, fig_routes, fig_stats, cluster_info
573
 
 
757
  5. The animation shows the typhoon track growing over a world map, with:
758
  - Date on the bottom left
759
  - Sidebar on the right showing typhoon details (name, date, wind speed, category) as it moves
760
+ - Color legend with colored markers centered on the right
761
  """)
762
 
763
  def update_typhoon_options(year):