euler314 commited on
Commit
c7943cc
·
verified ·
1 Parent(s): 80709af

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +329 -88
app.py CHANGED
@@ -354,29 +354,95 @@ class TyphoonAnalyzer:
354
  }
355
 
356
  def create_tracks_plot(self, data):
 
357
  fig = go.Figure()
358
 
359
- for _, storm in data.groupby('SID'):
360
- fig.add_trace(go.Scattergeo(
361
- lon=storm['LON'],
362
- lat=storm['LAT'],
363
- mode='lines',
364
- name=storm['NAME'].iloc[0],
365
- line=dict(
366
- width=2,
367
- color=COLOR_MAP[storm['Category'].iloc[0]]
368
- ),
369
- hovertemplate=(
370
- f"Name: {storm['NAME'].iloc[0]}<br>"
371
- f"Category: {storm['Category'].iloc[0]}<br>"
372
- f"Wind Speed: {storm['USA_WIND'].iloc[0]:.1f} kt<br>"
373
- f"Pressure: {storm['WMO_PRES'].iloc[0]:.1f} hPa<br>"
374
- f"Date: {storm['ISO_TIME'].iloc[0]:%Y-%m-%d}"
375
- )
376
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
 
378
  fig.update_layout(
379
- title='Typhoon Tracks',
380
  showlegend=True,
381
  geo=dict(
382
  projection_type='mercator',
@@ -385,43 +451,71 @@ class TyphoonAnalyzer:
385
  landcolor='rgb(243, 243, 243)',
386
  countrycolor='rgb(204, 204, 204)',
387
  coastlinecolor='rgb(214, 214, 214)',
 
 
388
  lataxis=dict(range=[0, 50]),
389
  lonaxis=dict(range=[100, 180]),
 
390
  )
391
  )
392
-
393
- return fig
394
 
395
- def create_wind_analysis(self, data):
396
- fig = px.scatter(data,
397
- x='ONI',
398
- y='USA_WIND',
399
- color='Category',
400
- color_discrete_map=COLOR_MAP,
401
- title='Wind Speed vs ONI Index',
402
- labels={
403
- 'ONI': 'Oceanic Niño Index',
404
- 'USA_WIND': 'Maximum Wind Speed (kt)'
405
- },
406
- hover_data=['NAME', 'ISO_TIME']
407
- )
408
-
409
- # Add regression line
410
- x = data['ONI']
411
- y = data['USA_WIND']
412
- slope, intercept = np.polyfit(x, y, 1)
413
- fig.add_trace(
414
- go.Scatter(
415
- x=x,
416
- y=slope * x + intercept,
417
- mode='lines',
418
- name=f'Regression (slope={slope:.2f})',
419
- line=dict(color='black', dash='dash')
420
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  )
422
-
423
- return fig
424
 
 
 
 
 
 
 
 
 
 
 
425
  def create_pressure_analysis(self, data):
426
  fig = px.scatter(data,
427
  x='ONI',
@@ -513,7 +607,100 @@ class TyphoonAnalyzer:
513
  )
514
 
515
  return fig
 
 
 
 
 
 
 
 
 
516
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  def generate_statistics(self, data):
518
  stats = {
519
  'total_typhoons': len(data['SID'].unique()),
@@ -566,69 +753,123 @@ def create_interface():
566
 
567
  analyze_btn = gr.Button("Analyze")
568
 
569
- plots_tabs = gr.Tabs()
570
- with plots_tabs:
571
- with gr.Tab("Tracks"):
572
- tracks_plot = gr.Plot()
573
- with gr.Tab("Wind Analysis"):
574
- wind_plot = gr.Plot()
575
- with gr.Tab("Pressure Analysis"):
576
- pressure_plot = gr.Plot()
577
- with gr.Tab("Clusters"):
578
- cluster_plot = gr.Plot()
579
 
580
  stats_text = gr.Markdown()
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  # Search Tab
583
  with gr.Tab("Typhoon Search"):
584
  with gr.Row():
585
- search_input = gr.Textbox(label="Search Typhoon Name")
586
- search_btn = gr.Button("Search")
587
- search_results = gr.Plot()
588
- typhoon_info = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
 
 
590
  def analyze_callback(start_y, start_m, end_y, end_m, enso):
591
  results = analyzer.analyze_typhoon(start_y, start_m, end_y, end_m, enso)
592
  return [
593
  results['tracks'],
594
  results['wind'],
595
  results['pressure'],
596
- results['clusters'],
597
  results['stats']
598
  ]
599
 
 
 
 
 
 
 
 
 
600
  analyze_btn.click(
601
  analyze_callback,
602
  inputs=[start_year, start_month, end_year, end_month, enso_dropdown],
603
- outputs=[tracks_plot, wind_plot, pressure_plot, cluster_plot, stats_text]
604
  )
605
 
606
- def search_callback(query):
607
- if not query:
608
- return None, "Please enter a typhoon name to search."
609
-
610
- matches = analyzer.merged_data[
611
- analyzer.merged_data['NAME'].str.contains(query, case=False, na=False)
612
- ]
613
-
614
- if matches.empty:
615
- return None, "No typhoons found matching your search."
616
-
617
- fig = analyzer.create_tracks_plot(matches)
618
-
619
- info = f"### Found {len(matches['SID'].unique())} matching typhoons:\n\n"
620
- for _, storm in matches.groupby('SID'):
621
- info += (f"- {storm['NAME'].iloc[0]} ({storm['ISO_TIME'].iloc[0]:%Y-%m-%d})\n"
622
- f" - Category: {storm['Category'].iloc[0]}\n"
623
- f" - Max Wind: {storm['USA_WIND'].iloc[0]:.1f} kt\n"
624
- f" - Min Pressure: {storm['WMO_PRES'].iloc[0]:.1f} hPa\n")
625
-
626
- return fig, info
 
 
 
 
 
627
 
628
  search_btn.click(
629
- search_callback,
630
- inputs=[search_input],
631
- outputs=[search_results, typhoon_info]
632
  )
633
 
634
  return demo
 
354
  }
355
 
356
  def create_tracks_plot(self, data):
357
+ """Create typhoon tracks visualization"""
358
  fig = go.Figure()
359
 
360
+ fig.update_layout(
361
+ title={
362
+ 'text': 'Typhoon Tracks',
363
+ 'y':0.95,
364
+ 'x':0.5,
365
+ 'xanchor': 'center',
366
+ 'yanchor': 'top'
367
+ },
368
+ showlegend=True,
369
+ legend=dict(
370
+ yanchor="top",
371
+ y=0.99,
372
+ xanchor="left",
373
+ x=0.01,
374
+ bgcolor='rgba(255, 255, 255, 0.8)'
375
+ ),
376
+ geo=dict(
377
+ projection_type='mercator',
378
+ showland=True,
379
+ showcoastlines=True,
380
+ landcolor='rgb(243, 243, 243)',
381
+ countrycolor='rgb(204, 204, 204)',
382
+ coastlinecolor='rgb(214, 214, 214)',
383
+ showocean=True,
384
+ oceancolor='rgb(230, 250, 255)',
385
+ showlakes=True,
386
+ lakecolor='rgb(230, 250, 255)',
387
+ lataxis=dict(range=[0, 50]),
388
+ lonaxis=dict(range=[100, 180]),
389
+ center=dict(lat=20, lon=140),
390
+ bgcolor='rgba(255, 255, 255, 0.5)'
391
+ ),
392
+ paper_bgcolor='rgba(255, 255, 255, 0.5)',
393
+ plot_bgcolor='rgba(255, 255, 255, 0.5)'
394
+ )
395
+
396
+ for category in COLOR_MAP.keys():
397
+ category_data = data[data['Category'] == category]
398
+
399
+ # Group by SID to get individual typhoon tracks
400
+ for _, storm in category_data.groupby('SID'):
401
+ # Create single track for each typhoon
402
+ track_data = self.typhoon_data[self.typhoon_data['SID'] == storm['SID'].iloc[0]]
403
+ track_data = track_data.sort_values('ISO_TIME')
404
+
405
+ fig.add_trace(go.Scattergeo(
406
+ lon=track_data['LON'],
407
+ lat=track_data['LAT'],
408
+ mode='lines',
409
+ line=dict(
410
+ width=2,
411
+ color=COLOR_MAP[category]
412
+ ),
413
+ name=category,
414
+ legendgroup=category,
415
+ showlegend=True if storm.iloc[0]['SID'] == category_data.iloc[0]['SID'] else False,
416
+ hovertemplate=(
417
+ f"Name: {storm['NAME'].iloc[0]}<br>" +
418
+ f"Category: {category}<br>" +
419
+ f"Wind Speed: {storm['USA_WIND'].iloc[0]:.1f} kt<br>" +
420
+ f"Pressure: {storm['WMO_PRES'].iloc[0]:.1f} hPa<br>" +
421
+ f"Date: {track_data['ISO_TIME'].dt.strftime('%Y-%m-%d %H:%M').iloc[0]}<br>" +
422
+ f"Lat: %{lat:.2f}°N<br>" +
423
+ f"Lon: %{lon:.2f}°E<br>" +
424
+ "<extra></extra>"
425
+ )
426
+ ))
427
+
428
+ return fig
429
+ def get_typhoons_for_year(self, year):
430
+ """Get list of typhoons for a specific year"""
431
+ year_data = self.typhoon_data[self.typhoon_data['ISO_TIME'].dt.year == year]
432
+ typhoons = year_data.groupby('SID').first()
433
+ return [{'label': f"{row['NAME']} ({row.name})", 'value': row.name}
434
+ for _, row in typhoons.iterrows()]
435
+
436
+ def create_typhoon_animation(self, year, typhoon_id):
437
+ """Create animated visualization of typhoon path"""
438
+ storm_data = self.typhoon_data[self.typhoon_data['SID'] == typhoon_id]
439
+ storm_data = storm_data.sort_values('ISO_TIME')
440
+
441
+ fig = go.Figure()
442
 
443
+ # Base map settings
444
  fig.update_layout(
445
+ title=f"Typhoon Path Animation - {storm_data['NAME'].iloc[0]} ({year})",
446
  showlegend=True,
447
  geo=dict(
448
  projection_type='mercator',
 
451
  landcolor='rgb(243, 243, 243)',
452
  countrycolor='rgb(204, 204, 204)',
453
  coastlinecolor='rgb(214, 214, 214)',
454
+ showocean=True,
455
+ oceancolor='rgb(230, 250, 255)',
456
  lataxis=dict(range=[0, 50]),
457
  lonaxis=dict(range=[100, 180]),
458
+ center=dict(lat=20, lon=140)
459
  )
460
  )
 
 
461
 
462
+ # Create animation frames
463
+ frames = []
464
+ for i in range(len(storm_data)):
465
+ frame = go.Frame(
466
+ data=[
467
+ go.Scattergeo(
468
+ lon=storm_data['LON'].iloc[:i+1],
469
+ lat=storm_data['LAT'].iloc[:i+1],
470
+ mode='lines+markers',
471
+ line=dict(width=2, color='red'),
472
+ marker=dict(size=8, color='red'),
473
+ name='Path'
474
+ )
475
+ ],
476
+ name=f'frame{i}'
 
 
 
 
 
 
 
 
 
 
477
  )
478
+ frames.append(frame)
479
+
480
+ fig.frames = frames
481
+
482
+ # Add animation controls
483
+ fig.update_layout(
484
+ updatemenus=[{
485
+ 'buttons': [
486
+ {
487
+ 'args': [None, {'frame': {'duration': 100, 'redraw': True},
488
+ 'fromcurrent': True}],
489
+ 'label': 'Play',
490
+ 'method': 'animate'
491
+ },
492
+ {
493
+ 'args': [[None], {'frame': {'duration': 0, 'redraw': True},
494
+ 'mode': 'immediate',
495
+ 'transition': {'duration': 0}}],
496
+ 'label': 'Pause',
497
+ 'method': 'animate'
498
+ }
499
+ ],
500
+ 'type': 'buttons',
501
+ 'showactive': False,
502
+ 'x': 0.1,
503
+ 'y': 0,
504
+ 'xanchor': 'right',
505
+ 'yanchor': 'top'
506
+ }]
507
  )
 
 
508
 
509
+ info_text = f"""
510
+ ### Typhoon Information
511
+ - Name: {storm_data['NAME'].iloc[0]}
512
+ - Start Date: {storm_data['ISO_TIME'].iloc[0]:%Y-%m-%d %H:%M}
513
+ - End Date: {storm_data['ISO_TIME'].iloc[-1]:%Y-%m-%d %H:%M}
514
+ - Maximum Wind Speed: {storm_data['USA_WIND'].max():.1f} kt
515
+ - Minimum Pressure: {storm_data['WMO_PRES'].min():.1f} hPa
516
+ """
517
+
518
+ return fig, info_text
519
  def create_pressure_analysis(self, data):
520
  fig = px.scatter(data,
521
  x='ONI',
 
607
  )
608
 
609
  return fig
610
+ def get_typhoons_for_year(self, year):
611
+ """Get list of typhoons for a specific year"""
612
+ year_data = self.typhoon_data[self.typhoon_data['SEASON'] == year]
613
+ unique_typhoons = year_data.groupby('SID').first().reset_index()
614
+ return [
615
+ {'label': f"{row['NAME']} ({row['ISO_TIME'].strftime('%Y-%m-%d')})",
616
+ 'value': row['SID']}
617
+ for _, row in unique_typhoons.iterrows()
618
+ ]
619
 
620
+ def search_typhoon_details(self, year, typhoon_id):
621
+ """Get detailed information for a specific typhoon"""
622
+ if not typhoon_id:
623
+ return None, "Please select a typhoon"
624
+
625
+ storm_data = self.typhoon_data[self.typhoon_data['SID'] == typhoon_id]
626
+ storm_data = storm_data.sort_values('ISO_TIME')
627
+
628
+ # Create track plot
629
+ fig = self.create_single_typhoon_plot(storm_data)
630
+
631
+ # Create detailed information text
632
+ info = self.create_typhoon_info_text(storm_data)
633
+
634
+ return fig, info
635
+
636
+ def create_single_typhoon_plot(self, storm_data):
637
+ """Create a detailed plot for a single typhoon"""
638
+ fig = go.Figure()
639
+
640
+ fig.update_layout(
641
+ title=f"Typhoon Track - {storm_data['NAME'].iloc[0]} ({storm_data['SEASON'].iloc[0]})",
642
+ showlegend=True,
643
+ geo=dict(
644
+ projection_type='mercator',
645
+ showland=True,
646
+ showcoastlines=True,
647
+ landcolor='rgb(243, 243, 243)',
648
+ countrycolor='rgb(204, 204, 204)',
649
+ coastlinecolor='rgb(214, 214, 214)',
650
+ showocean=True,
651
+ oceancolor='rgb(230, 250, 255)',
652
+ lataxis=dict(range=[0, 50]),
653
+ lonaxis=dict(range=[100, 180]),
654
+ center=dict(lat=20, lon=140)
655
+ )
656
+ )
657
+
658
+ # Add main track
659
+ fig.add_trace(go.Scattergeo(
660
+ lon=storm_data['LON'],
661
+ lat=storm_data['LAT'],
662
+ mode='lines+markers',
663
+ line=dict(width=2, color='red'),
664
+ marker=dict(
665
+ size=8,
666
+ color=storm_data['USA_WIND'],
667
+ colorscale='Viridis',
668
+ showscale=True,
669
+ colorbar=dict(title='Wind Speed (kt)')
670
+ ),
671
+ text=[f"Time: {time:%Y-%m-%d %H:%M}<br>Wind: {wind:.1f} kt<br>Pressure: {pres:.1f} hPa"
672
+ for time, wind, pres in zip(storm_data['ISO_TIME'],
673
+ storm_data['USA_WIND'],
674
+ storm_data['WMO_PRES'])],
675
+ hoverinfo='text'
676
+ ))
677
+
678
+ return fig
679
+
680
+ def create_typhoon_info_text(self, storm_data):
681
+ """Create detailed information text for a typhoon"""
682
+ max_wind = storm_data['USA_WIND'].max()
683
+ min_pressure = storm_data['WMO_PRES'].min()
684
+ duration = (storm_data['ISO_TIME'].max() - storm_data['ISO_TIME'].min()).total_seconds() / 3600 # hours
685
+
686
+ return f"""
687
+ ### Typhoon Details: {storm_data['NAME'].iloc[0]}
688
+
689
+ **Timing Information:**
690
+ - Start: {storm_data['ISO_TIME'].min():%Y-%m-%d %H:%M}
691
+ - End: {storm_data['ISO_TIME'].max():%Y-%m-%d %H:%M}
692
+ - Duration: {duration:.1f} hours
693
+
694
+ **Intensity Metrics:**
695
+ - Maximum Wind Speed: {max_wind:.1f} kt
696
+ - Minimum Pressure: {min_pressure:.1f} hPa
697
+ - Maximum Category: {self.categorize_typhoon(max_wind)}
698
+
699
+ **Track Information:**
700
+ - Starting Position: {storm_data['LAT'].iloc[0]:.1f}°N, {storm_data['LON'].iloc[0]:.1f}°E
701
+ - Ending Position: {storm_data['LAT'].iloc[-1]:.1f}°N, {storm_data['LON'].iloc[-1]:.1f}°E
702
+ - Total Track Points: {len(storm_data)}
703
+ """
704
  def generate_statistics(self, data):
705
  stats = {
706
  'total_typhoons': len(data['SID'].unique()),
 
753
 
754
  analyze_btn = gr.Button("Analyze")
755
 
756
+ with gr.Row():
757
+ tracks_plot = gr.Plot()
758
+ with gr.Row():
759
+ wind_plot = gr.Plot()
760
+ pressure_plot = gr.Plot()
 
 
 
 
 
761
 
762
  stats_text = gr.Markdown()
763
 
764
+ # Clustering Analysis Tab
765
+ with gr.Tab("Clustering Analysis"):
766
+ with gr.Row():
767
+ cluster_year = gr.Slider(1900, 2024, 2000, label="Year")
768
+ n_clusters = gr.Slider(2, 20, 5, label="Number of Clusters")
769
+
770
+ cluster_btn = gr.Button("Analyze Clusters")
771
+ cluster_plot = gr.Plot()
772
+ cluster_stats = gr.Markdown()
773
+
774
+ # Animation Tab
775
+ with gr.Tab("Typhoon Animation"):
776
+ with gr.Row():
777
+ animation_year = gr.Slider(
778
+ minimum=1900,
779
+ maximum=2024,
780
+ value=2024,
781
+ step=1,
782
+ label="Select Year"
783
+ )
784
+
785
+ with gr.Row():
786
+ animation_typhoon = gr.Dropdown(
787
+ choices=[],
788
+ label="Select Typhoon",
789
+ interactive=True
790
+ )
791
+
792
+ animation_btn = gr.Button("Animate Typhoon Path", variant="primary")
793
+ animation_plot = gr.Plot()
794
+ animation_info = gr.Markdown()
795
+
796
  # Search Tab
797
  with gr.Tab("Typhoon Search"):
798
  with gr.Row():
799
+ search_year = gr.Slider(
800
+ minimum=1900,
801
+ maximum=2024,
802
+ value=2024,
803
+ step=1,
804
+ label="Select Year"
805
+ )
806
+
807
+ with gr.Row():
808
+ search_typhoon = gr.Dropdown(
809
+ choices=[],
810
+ label="Select Typhoon",
811
+ interactive=True
812
+ )
813
+
814
+ search_btn = gr.Button("Show Typhoon Details", variant="primary")
815
+ search_plot = gr.Plot()
816
+ search_info = gr.Markdown()
817
 
818
+ # Event handlers
819
  def analyze_callback(start_y, start_m, end_y, end_m, enso):
820
  results = analyzer.analyze_typhoon(start_y, start_m, end_y, end_m, enso)
821
  return [
822
  results['tracks'],
823
  results['wind'],
824
  results['pressure'],
 
825
  results['stats']
826
  ]
827
 
828
+ def cluster_callback(year, n_clusters):
829
+ return analyzer.analyze_clusters(year, n_clusters)
830
+
831
+ def update_typhoon_choices(year):
832
+ typhoons = analyzer.get_typhoons_for_year(year)
833
+ return gr.Dropdown.update(choices=typhoons, value=None)
834
+
835
+ # Connect events for main analysis
836
  analyze_btn.click(
837
  analyze_callback,
838
  inputs=[start_year, start_month, end_year, end_month, enso_dropdown],
839
+ outputs=[tracks_plot, wind_plot, pressure_plot, stats_text]
840
  )
841
 
842
+ # Connect events for clustering
843
+ cluster_btn.click(
844
+ cluster_callback,
845
+ inputs=[cluster_year, n_clusters],
846
+ outputs=[cluster_plot, cluster_stats]
847
+ )
848
+
849
+ # Connect events for Animation tab
850
+ animation_year.change(
851
+ update_typhoon_choices,
852
+ inputs=[animation_year],
853
+ outputs=[animation_typhoon]
854
+ )
855
+
856
+ animation_btn.click(
857
+ analyzer.create_typhoon_animation,
858
+ inputs=[animation_year, animation_typhoon],
859
+ outputs=[animation_plot, animation_info]
860
+ )
861
+
862
+ # Connect events for Search tab
863
+ search_year.change(
864
+ update_typhoon_choices,
865
+ inputs=[search_year],
866
+ outputs=[search_typhoon]
867
+ )
868
 
869
  search_btn.click(
870
+ analyzer.search_typhoon_details,
871
+ inputs=[search_year, search_typhoon],
872
+ outputs=[search_plot, search_info]
873
  )
874
 
875
  return demo