diff --git "a/app.py" "b/app.py"
--- "a/app.py"
+++ "b/app.py"
@@ -34,16 +34,12 @@ from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
-from scipy.interpolate import interp1d, RBFInterpolator, griddata
-from scipy.ndimage import gaussian_filter
+from scipy.interpolate import interp1d, RBFInterpolator
import statsmodels.api as sm
import requests
import tempfile
import shutil
import xarray as xr
-import urllib.request
-from urllib.error import URLError
-import ssl
# NEW: Advanced ML imports
try:
@@ -76,9 +72,6 @@ except ImportError:
import tropycal.tracks as tracks
-# Suppress SSL warnings for oceanic data downloads
-ssl._create_default_https_context = ssl._create_unverified_context
-
# -----------------------------
# Configuration and Setup
# -----------------------------
@@ -121,7 +114,7 @@ CACHE_FILE = os.path.join(DATA_PATH, 'ibtracs_cache.pkl')
CACHE_EXPIRY_DAYS = 1
# -----------------------------
-# ENHANCED: Color Maps and Standards with TD Support - FIXED TAIWAN CLASSIFICATION
+# ENHANCED: Color Maps and Standards with TD Support (FIXED TAIWAN CLASSIFICATION)
# -----------------------------
# Enhanced color mapping with TD support (for Plotly)
enhanced_color_map = {
@@ -147,14 +140,12 @@ matplotlib_color_map = {
'C5 Super Typhoon': '#FF0000' # Red
}
-# FIXED: Taiwan color mapping with correct CMA 2006 standards
-taiwan_color_map_fixed = {
- 'Tropical Depression': '#808080', # Gray
- 'Tropical Storm': '#0000FF', # Blue
- 'Severe Tropical Storm': '#00FFFF', # Cyan
- 'Typhoon': '#FFFF00', # Yellow
- 'Severe Typhoon': '#FFA500', # Orange
- 'Super Typhoon': '#FF0000' # Red
+# FIXED Taiwan color mapping with correct categories
+taiwan_color_map = {
+ 'Tropical Depression': '#808080', # Gray
+ 'Tropical Storm': '#0000FF', # Blue
+ 'Moderate Typhoon': '#FFA500', # Orange
+ 'Intense Typhoon': '#FF0000' # Red
}
def rgb_string_to_hex(rgb_string):
@@ -175,9 +166,9 @@ def get_matplotlib_color(category):
"""Get matplotlib-compatible color for a storm category"""
return matplotlib_color_map.get(category, '#808080')
-def get_taiwan_color_fixed(category):
- """Get corrected Taiwan standard color"""
- return taiwan_color_map_fixed.get(category, '#808080')
+def get_taiwan_color(category):
+ """Get Taiwan standard color for a storm category"""
+ return taiwan_color_map.get(category, '#808080')
# Cluster colors for route visualization
CLUSTER_COLORS = [
@@ -213,269 +204,14 @@ atlantic_standard = {
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
}
-# FIXED: Taiwan standard with correct CMA 2006 thresholds
-taiwan_standard_fixed = {
- 'Super Typhoon': {'wind_speed_ms': 51.0, 'wind_speed_kt': 99.2, 'color': 'Red', 'hex': '#FF0000'},
- 'Severe Typhoon': {'wind_speed_ms': 41.5, 'wind_speed_kt': 80.7, 'color': 'Orange', 'hex': '#FFA500'},
- 'Typhoon': {'wind_speed_ms': 32.7, 'wind_speed_kt': 63.6, 'color': 'Yellow', 'hex': '#FFFF00'},
- 'Severe Tropical Storm': {'wind_speed_ms': 24.5, 'wind_speed_kt': 47.6, 'color': 'Cyan', 'hex': '#00FFFF'},
- 'Tropical Storm': {'wind_speed_ms': 17.2, 'wind_speed_kt': 33.4, 'color': 'Blue', 'hex': '#0000FF'},
- 'Tropical Depression': {'wind_speed_ms': 0, 'wind_speed_kt': 0, 'color': 'Gray', 'hex': '#808080'}
+# FIXED Taiwan standard with correct official CWA thresholds
+taiwan_standard = {
+ 'Intense Typhoon': {'wind_speed': 51.0, 'color': 'Red', 'hex': '#FF0000'}, # 100+ knots (51.0+ m/s)
+ 'Moderate Typhoon': {'wind_speed': 32.7, 'color': 'Orange', 'hex': '#FFA500'}, # 64-99 knots (32.7-50.9 m/s)
+ 'Tropical Storm': {'wind_speed': 17.2, 'color': 'Blue', 'hex': '#0000FF'}, # 34-63 knots (17.2-32.6 m/s)
+ 'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'} # <34 knots (<17.2 m/s)
}
-# -----------------------------
-# ENHANCED: Oceanic Data Integration
-# -----------------------------
-
-class OceanicDataManager:
- """Manages real-time oceanic data for enhanced typhoon prediction"""
-
- def __init__(self):
- self.sst_base_url = "https://www.ncei.noaa.gov/erddap/griddap/NOAA_OISST_V2.nc"
- self.slp_base_url = "https://psl.noaa.gov/thredds/dodsC/Datasets/ncep.reanalysis.dailyavgs/surface/slp.nc"
- self.cache_dir = os.path.join(DATA_PATH, 'oceanic_cache')
- self.create_cache_directory()
-
- def create_cache_directory(self):
- """Create cache directory for oceanic data"""
- try:
- os.makedirs(self.cache_dir, exist_ok=True)
- except Exception as e:
- logging.warning(f"Could not create cache directory: {e}")
- self.cache_dir = tempfile.mkdtemp()
-
- def get_sst_data(self, lat_min, lat_max, lon_min, lon_max, date_start, date_end=None):
- """
- Fetch Sea Surface Temperature data from NOAA OISST v2
-
- Parameters:
- lat_min, lat_max: Latitude bounds
- lon_min, lon_max: Longitude bounds
- date_start: Start date (datetime or string)
- date_end: End date (datetime or string, optional)
- """
- try:
- if date_end is None:
- date_end = date_start
-
- # Convert dates to strings if needed
- if isinstance(date_start, datetime):
- date_start_str = date_start.strftime('%Y-%m-%d')
- else:
- date_start_str = str(date_start)
-
- if isinstance(date_end, datetime):
- date_end_str = date_end.strftime('%Y-%m-%d')
- else:
- date_end_str = str(date_end)
-
- # Construct ERDDAP URL with parameters
- url_params = (
- f"?sst[({date_start_str}):1:({date_end_str})]"
- f"[({lat_min}):1:({lat_max})]"
- f"[({lon_min}):1:({lon_max})]"
- )
- full_url = self.sst_base_url + url_params
-
- logging.info(f"Fetching SST data from: {full_url}")
-
- # Use xarray to open the remote dataset
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- ds = xr.open_dataset(full_url)
-
- # Extract SST data
- sst_data = ds['sst'].values
- lats = ds['latitude'].values
- lons = ds['longitude'].values
- times = ds['time'].values
-
- ds.close()
-
- return {
- 'sst': sst_data,
- 'latitude': lats,
- 'longitude': lons,
- 'time': times,
- 'success': True
- }
-
- except Exception as e:
- logging.error(f"Error fetching SST data: {e}")
- return self._get_fallback_sst_data(lat_min, lat_max, lon_min, lon_max)
-
- def get_slp_data(self, lat_min, lat_max, lon_min, lon_max, date_start, date_end=None):
- """
- Fetch Sea Level Pressure data from NCEP/NCAR Reanalysis
-
- Parameters similar to get_sst_data
- """
- try:
- if date_end is None:
- date_end = date_start
-
- # Convert dates for OPeNDAP access
- if isinstance(date_start, datetime):
- # NCEP uses different time indexing, may need adjustment
- date_start_str = date_start.strftime('%Y-%m-%d')
- else:
- date_start_str = str(date_start)
-
- if isinstance(date_end, datetime):
- date_end_str = date_end.strftime('%Y-%m-%d')
- else:
- date_end_str = str(date_end)
-
- logging.info(f"Fetching SLP data for {date_start_str} to {date_end_str}")
-
- # Use xarray to open OPeNDAP dataset
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
-
- # Open the full dataset (this might be large, so we'll subset)
- ds = xr.open_dataset(self.slp_base_url)
-
- # Subset by time and location
- # Note: Coordinate names might vary, adjust as needed
- lat_coord = 'lat' if 'lat' in ds.dims else 'latitude'
- lon_coord = 'lon' if 'lon' in ds.dims else 'longitude'
-
- # Subset the data
- subset = ds.sel(
- time=slice(date_start_str, date_end_str),
- **{lat_coord: slice(lat_min, lat_max),
- lon_coord: slice(lon_min, lon_max)}
- )
-
- # Extract SLP data
- slp_data = subset['slp'].values
- lats = subset[lat_coord].values
- lons = subset[lon_coord].values
- times = subset['time'].values
-
- ds.close()
-
- return {
- 'slp': slp_data,
- 'latitude': lats,
- 'longitude': lons,
- 'time': times,
- 'success': True
- }
-
- except Exception as e:
- logging.error(f"Error fetching SLP data: {e}")
- return self._get_fallback_slp_data(lat_min, lat_max, lon_min, lon_max)
-
- def _get_fallback_sst_data(self, lat_min, lat_max, lon_min, lon_max):
- """Generate realistic fallback SST data based on climatology"""
- # Create a reasonable grid
- lats = np.linspace(lat_min, lat_max, 20)
- lons = np.linspace(lon_min, lon_max, 20)
-
- # Generate climatological SST values for Western Pacific
- sst_values = np.zeros((1, len(lats), len(lons)))
-
- for i, lat in enumerate(lats):
- for j, lon in enumerate(lons):
- # Climatological SST estimation for Western Pacific
- if lat < 10: # Tropical
- base_sst = 29.0
- elif lat < 20: # Subtropical
- base_sst = 28.0 - (lat - 10) * 0.3
- elif lat < 30: # Temperate
- base_sst = 25.0 - (lat - 20) * 0.5
- else: # Cool waters
- base_sst = 20.0 - (lat - 30) * 0.3
-
- # Add some realistic variation
- sst_values[0, i, j] = base_sst + np.random.normal(0, 0.5)
-
- return {
- 'sst': sst_values,
- 'latitude': lats,
- 'longitude': lons,
- 'time': [datetime.now()],
- 'success': False,
- 'note': 'Using climatological fallback data'
- }
-
- def _get_fallback_slp_data(self, lat_min, lat_max, lon_min, lon_max):
- """Generate realistic fallback SLP data"""
- lats = np.linspace(lat_min, lat_max, 20)
- lons = np.linspace(lon_min, lon_max, 20)
-
- slp_values = np.zeros((1, len(lats), len(lons)))
-
- for i, lat in enumerate(lats):
- for j, lon in enumerate(lons):
- # Climatological SLP estimation
- if lat < 30: # Subtropical high influence
- base_slp = 1013 + 3 * np.cos(np.radians(lat * 6))
- else: # Mid-latitude
- base_slp = 1010 - (lat - 30) * 0.2
-
- slp_values[0, i, j] = base_slp + np.random.normal(0, 2)
-
- return {
- 'slp': slp_values,
- 'latitude': lats,
- 'longitude': lons,
- 'time': [datetime.now()],
- 'success': False,
- 'note': 'Using climatological fallback data'
- }
-
- def interpolate_data_to_point(self, data_dict, target_lat, target_lon, variable='sst'):
- """Interpolate gridded data to a specific point"""
- try:
- data = data_dict[variable]
- lats = data_dict['latitude']
- lons = data_dict['longitude']
-
- # Take most recent time if multiple times available
- if len(data.shape) == 3: # time, lat, lon
- data_2d = data[-1, :, :]
- else: # lat, lon
- data_2d = data
-
- # Create coordinate grids
- lon_grid, lat_grid = np.meshgrid(lons, lats)
-
- # Flatten for interpolation
- points = np.column_stack((lat_grid.flatten(), lon_grid.flatten()))
- values = data_2d.flatten()
-
- # Remove NaN values
- valid_mask = ~np.isnan(values)
- points = points[valid_mask]
- values = values[valid_mask]
-
- if len(values) == 0:
- return np.nan
-
- # Interpolate to target point
- interpolated_value = griddata(
- points, values, (target_lat, target_lon),
- method='linear', fill_value=np.nan
- )
-
- # If linear interpolation fails, try nearest neighbor
- if np.isnan(interpolated_value):
- interpolated_value = griddata(
- points, values, (target_lat, target_lon),
- method='nearest'
- )
-
- return interpolated_value
-
- except Exception as e:
- logging.error(f"Error interpolating {variable} data: {e}")
- return np.nan
-
-# Global oceanic data manager
-oceanic_manager = None
-
# -----------------------------
# Utility Functions for HF Spaces
# -----------------------------
@@ -973,7 +709,7 @@ def merge_data(oni_long, typhoon_max):
return pd.merge(typhoon_max, oni_long, on=['Year','Month'])
# -----------------------------
-# ENHANCED: Categorization Functions - FIXED TAIWAN CLASSIFICATION
+# ENHANCED: Categorization Functions (FIXED TAIWAN)
# -----------------------------
def categorize_typhoon_enhanced(wind_speed):
@@ -1001,33 +737,26 @@ def categorize_typhoon_enhanced(wind_speed):
else: # 137+ knots = Category 5 Super Typhoon
return 'C5 Super Typhoon'
-def categorize_typhoon_taiwan_fixed(wind_speed):
- """
- FIXED Taiwan categorization system based on CMA 2006 standards
- Reference: CMA Tropical Cyclone Data Center official classification
- """
+def categorize_typhoon_taiwan(wind_speed):
+ """FIXED Taiwan categorization system according to official CWA standards"""
if pd.isna(wind_speed):
return 'Tropical Depression'
- # Convert from knots to m/s if input appears to be in knots
- if wind_speed > 50: # Likely in knots, convert to m/s
- wind_speed_ms = wind_speed * 0.514444
- else:
+ # Convert from knots to m/s (official CWA uses m/s thresholds)
+ if wind_speed > 200: # Likely already in m/s
wind_speed_ms = wind_speed
+ else: # Likely in knots, convert to m/s
+ wind_speed_ms = wind_speed * 0.514444
- # CMA 2006 Classification Standards (used by Taiwan CWA)
- if wind_speed_ms >= 51.0:
- return 'Super Typhoon' # ≥51.0 m/s (≥99.2 kt)
- elif wind_speed_ms >= 41.5:
- return 'Severe Typhoon' # 41.5–50.9 m/s (80.7–99.1 kt)
- elif wind_speed_ms >= 32.7:
- return 'Typhoon' # 32.7–41.4 m/s (63.6–80.6 kt)
- elif wind_speed_ms >= 24.5:
- return 'Severe Tropical Storm' # 24.5–32.6 m/s (47.6–63.5 kt)
- elif wind_speed_ms >= 17.2:
- return 'Tropical Storm' # 17.2–24.4 m/s (33.4–47.5 kt)
- else:
- return 'Tropical Depression' # < 17.2 m/s (< 33.4 kt)
+ # Official CWA Taiwan classification thresholds
+ if wind_speed_ms >= 51.0: # 100+ knots
+ return 'Intense Typhoon'
+ elif wind_speed_ms >= 32.7: # 64-99 knots
+ return 'Moderate Typhoon'
+ elif wind_speed_ms >= 17.2: # 34-63 knots
+ return 'Tropical Storm'
+ else: # <34 knots
+ return 'Tropical Depression'
# Original function for backward compatibility
def categorize_typhoon(wind_speed):
@@ -1047,673 +776,912 @@ def classify_enso_phases(oni_value):
else:
return 'Neutral'
-# FIXED: Combined categorization function
-def categorize_typhoon_by_standard_fixed(wind_speed, standard='atlantic'):
- """FIXED categorization function supporting both standards with correct Taiwan thresholds"""
+def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
+ """FIXED categorization function with correct Taiwan standards"""
if pd.isna(wind_speed):
return 'Tropical Depression', '#808080'
if standard == 'taiwan':
- category = categorize_typhoon_taiwan_fixed(wind_speed)
- color = taiwan_color_map_fixed.get(category, '#808080')
+ category = categorize_typhoon_taiwan(wind_speed)
+ color = taiwan_color_map.get(category, '#808080')
return category, color
-
else:
- # Atlantic/International standard (unchanged)
+ # Atlantic/International standard (existing logic is correct)
if wind_speed >= 137:
- return 'C5 Super Typhoon', '#FF0000'
+ return 'C5 Super Typhoon', '#FF0000' # Red
elif wind_speed >= 113:
- return 'C4 Very Strong Typhoon', '#FFA500'
+ return 'C4 Very Strong Typhoon', '#FFA500' # Orange
elif wind_speed >= 96:
- return 'C3 Strong Typhoon', '#FFFF00'
+ return 'C3 Strong Typhoon', '#FFFF00' # Yellow
elif wind_speed >= 83:
- return 'C2 Typhoon', '#00FF00'
+ return 'C2 Typhoon', '#00FF00' # Green
elif wind_speed >= 64:
- return 'C1 Typhoon', '#00FFFF'
+ return 'C1 Typhoon', '#00FFFF' # Cyan
elif wind_speed >= 34:
- return 'Tropical Storm', '#0000FF'
- else:
- return 'Tropical Depression', '#808080'
+ return 'Tropical Storm', '#0000FF' # Blue
+ return 'Tropical Depression', '#808080' # Gray
# -----------------------------
-# ENHANCED: Historical Environmental Analysis
+# FIXED: Genesis Potential Index (GPI) Based Prediction System
# -----------------------------
-def analyze_historical_environment(typhoon_data, oni_data):
- """Analyze historical environmental conditions for better predictions"""
+def calculate_genesis_potential_index(sst, rh, vorticity, wind_shear, lat, lon, month, oni_value):
+ """
+ Calculate Genesis Potential Index based on environmental parameters
+ Following Emanuel and Nolan (2004) formulation with modifications for monthly predictions
+ """
try:
- logging.info("Analyzing historical environmental patterns...")
-
- # Get historical storm data with environmental conditions
- historical_analysis = {
- 'sst_patterns': {},
- 'slp_patterns': {},
- 'oni_relationships': {},
- 'seasonal_variations': {},
- 'intensity_predictors': {}
+ # Base environmental parameters
+
+ # SST factor - optimal range 26-30°C
+ sst_factor = max(0, (sst - 26.5) / 4.0) if sst > 26.5 else 0
+
+ # Humidity factor - mid-level relative humidity (600 hPa)
+ rh_factor = max(0, (rh - 40) / 50.0) # Normalized 40-90%
+
+ # Vorticity factor - low-level absolute vorticity (850 hPa)
+ vort_factor = max(0, min(vorticity / 5e-5, 3.0)) # Cap at reasonable max
+
+ # Wind shear factor - vertical wind shear (inverse relationship)
+ shear_factor = max(0, (20 - wind_shear) / 15.0) if wind_shear < 20 else 0
+
+ # Coriolis factor - latitude dependency
+ coriolis_factor = max(0, min(abs(lat) / 20.0, 1.0)) if abs(lat) > 5 else 0
+
+ # Seasonal factor
+ seasonal_weights = {
+ 1: 0.3, 2: 0.2, 3: 0.4, 4: 0.6, 5: 0.8, 6: 1.0,
+ 7: 1.2, 8: 1.4, 9: 1.5, 10: 1.3, 11: 0.9, 12: 0.5
}
+ seasonal_factor = seasonal_weights.get(month, 1.0)
- # Analyze by storm intensity categories
- for category in ['Tropical Depression', 'Tropical Storm', 'C1 Typhoon',
- 'C2 Typhoon', 'C3 Strong Typhoon', 'C4 Very Strong Typhoon', 'C5 Super Typhoon']:
-
- # Filter storms by category
- if 'USA_WIND' in typhoon_data.columns:
- category_storms = typhoon_data[
- typhoon_data['USA_WIND'].apply(categorize_typhoon_enhanced) == category
- ]
-
- if len(category_storms) > 0:
- historical_analysis['intensity_predictors'][category] = {
- 'avg_genesis_lat': category_storms['LAT'].mean(),
- 'avg_genesis_lon': category_storms['LON'].mean(),
- 'count': len(category_storms['SID'].unique()),
- 'seasonal_distribution': category_storms['ISO_TIME'].dt.month.value_counts().to_dict() if 'ISO_TIME' in category_storms.columns else {}
- }
-
- # Analyze ENSO relationships
- if len(oni_data) > 0:
- for phase in ['El Nino', 'La Nina', 'Neutral']:
- # This would be enhanced with actual storm-ENSO matching
- historical_analysis['oni_relationships'][phase] = {
- 'storm_frequency_modifier': 1.0, # Will be calculated from real data
- 'intensity_modifier': 0.0,
- 'track_shift': {'lat': 0.0, 'lon': 0.0}
- }
-
- logging.info("Historical environmental analysis complete")
- return historical_analysis
+ # ENSO modulation
+ if oni_value > 0.5: # El Niño
+ enso_factor = 0.6 if lon > 140 else 0.8 # Suppress in WP
+ elif oni_value < -0.5: # La Niña
+ enso_factor = 1.4 if lon > 140 else 1.1 # Enhance in WP
+ else: # Neutral
+ enso_factor = 1.0
+
+ # Regional modulation (Western Pacific specifics)
+ if 10 <= lat <= 25 and 120 <= lon <= 160: # Main Development Region
+ regional_factor = 1.3
+ elif 5 <= lat <= 15 and 130 <= lon <= 150: # Prime genesis zone
+ regional_factor = 1.5
+ else:
+ regional_factor = 0.8
+
+ # Calculate GPI
+ gpi = (sst_factor * rh_factor * vort_factor * shear_factor *
+ coriolis_factor * seasonal_factor * enso_factor * regional_factor)
+
+ return max(0, min(gpi, 5.0)) # Cap at reasonable maximum
except Exception as e:
- logging.error(f"Error in historical environmental analysis: {e}")
- return {}
+ logging.error(f"Error calculating GPI: {e}")
+ return 0.0
-# -----------------------------
-# ENHANCED: Environmental Intensity Prediction
-# -----------------------------
+def get_environmental_conditions(lat, lon, month, oni_value):
+ """
+ Get realistic environmental conditions for a given location and time
+ Based on climatological patterns and ENSO modulation
+ """
+ try:
+ # Base SST calculation (climatological)
+ base_sst = 28.5 - 0.15 * abs(lat - 15) # Peak at 15°N
+ seasonal_sst_adj = 2.0 * np.cos(2 * np.pi * (month - 9) / 12) # Peak in Sep
+ enso_sst_adj = oni_value * 0.8 if lon > 140 else oni_value * 0.4
+ sst = base_sst + seasonal_sst_adj + enso_sst_adj
+
+ # Relative humidity (600 hPa)
+ base_rh = 75 - 0.5 * abs(lat - 12) # Peak around 12°N
+ seasonal_rh_adj = 10 * np.cos(2 * np.pi * (month - 8) / 12) # Peak in Aug
+ monsoon_effect = 5 if 100 <= lon <= 120 and month in [6,7,8,9] else 0
+ rh = max(40, min(90, base_rh + seasonal_rh_adj + monsoon_effect))
+
+ # Low-level vorticity (850 hPa)
+ base_vort = 2e-5 * (1 + 0.1 * np.sin(2 * np.pi * lat / 30))
+ seasonal_vort_adj = 1e-5 * np.cos(2 * np.pi * (month - 8) / 12)
+ itcz_effect = 1.5e-5 if 5 <= lat <= 15 else 0
+ vorticity = max(0, base_vort + seasonal_vort_adj + itcz_effect)
+
+ # Vertical wind shear (200-850 hPa)
+ base_shear = 8 + 0.3 * abs(lat - 20) # Lower near 20°N
+ seasonal_shear_adj = 4 * np.cos(2 * np.pi * (month - 2) / 12) # Low in Aug-Sep
+ enso_shear_adj = oni_value * 3 if lon > 140 else 0 # El Niño increases shear
+ wind_shear = max(2, base_shear + seasonal_shear_adj + enso_shear_adj)
+
+ return {
+ 'sst': sst,
+ 'relative_humidity': rh,
+ 'vorticity': vorticity,
+ 'wind_shear': wind_shear
+ }
+
+ except Exception as e:
+ logging.error(f"Error getting environmental conditions: {e}")
+ return {
+ 'sst': 28.0,
+ 'relative_humidity': 70.0,
+ 'vorticity': 2e-5,
+ 'wind_shear': 10.0
+ }
-def calculate_environmental_intensity_potential(lat, lon, month, oni_value, sst_data=None, slp_data=None):
+def generate_genesis_prediction_monthly(month, oni_value, year=2025):
"""
- Calculate environmental intensity potential based on oceanic conditions
-
- This function integrates multiple environmental factors to estimate
- the maximum potential intensity a storm could achieve in given conditions.
+ Generate realistic typhoon genesis prediction for a given month using GPI
+ Returns day-by-day genesis potential and storm development scenarios
"""
try:
- # Base intensity potential from climatology
- base_potential = 45 # kt - baseline for tropical storm formation
+ logging.info(f"Generating GPI-based prediction for month {month}, ONI {oni_value}")
- # SST contribution (most important factor)
- if sst_data and sst_data['success']:
- try:
- sst_value = oceanic_manager.interpolate_data_to_point(
- sst_data, lat, lon, 'sst'
- )
-
- if not np.isnan(sst_value):
- # Convert to Celsius if needed (OISST is in Celsius)
- sst_celsius = sst_value if sst_value < 50 else sst_value - 273.15
+ # Define the Western Pacific domain
+ lat_range = np.arange(5, 35, 2.5) # 5°N to 35°N
+ lon_range = np.arange(110, 180, 2.5) # 110°E to 180°E
+
+ # Number of days in the month
+ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]
+ if month == 2 and year % 4 == 0: # Leap year
+ days_in_month = 29
+
+ # Daily GPI evolution
+ daily_gpi_maps = []
+ genesis_events = []
+
+ for day in range(1, days_in_month + 1):
+ # Calculate GPI for each grid point
+ gpi_field = np.zeros((len(lat_range), len(lon_range)))
+
+ for i, lat in enumerate(lat_range):
+ for j, lon in enumerate(lon_range):
+ # Get environmental conditions
+ env_conditions = get_environmental_conditions(lat, lon, month, oni_value)
- # Enhanced SST-intensity relationship based on research
- if sst_celsius >= 30.0: # Very warm - super typhoon potential
- sst_contribution = 80 + (sst_celsius - 30) * 10
- elif sst_celsius >= 28.5: # Warm - typhoon potential
- sst_contribution = 40 + (sst_celsius - 28.5) * 26.7
- elif sst_celsius >= 26.5: # Marginal - tropical storm potential
- sst_contribution = 0 + (sst_celsius - 26.5) * 20
- else: # Too cool for significant development
- sst_contribution = -30
+ # Add daily variability
+ daily_variation = 0.1 * np.sin(2 * np.pi * day / 30) + np.random.normal(0, 0.05)
- base_potential += sst_contribution
- logging.debug(f"SST: {sst_celsius:.1f}°C, contribution: {sst_contribution:.1f}kt")
- else:
- # Use climatological SST
- clim_sst = get_climatological_sst(lat, lon, month)
- base_potential += max(0, (clim_sst - 26.5) * 15)
+ # Calculate GPI
+ gpi = calculate_genesis_potential_index(
+ sst=env_conditions['sst'] + daily_variation,
+ rh=env_conditions['relative_humidity'],
+ vorticity=env_conditions['vorticity'],
+ wind_shear=env_conditions['wind_shear'],
+ lat=lat,
+ lon=lon,
+ month=month,
+ oni_value=oni_value
+ )
- except Exception as e:
- logging.warning(f"Error processing SST data: {e}")
- clim_sst = get_climatological_sst(lat, lon, month)
- base_potential += max(0, (clim_sst - 26.5) * 15)
- else:
- # Use climatological SST if real data unavailable
- clim_sst = get_climatological_sst(lat, lon, month)
- base_potential += max(0, (clim_sst - 26.5) * 15)
-
- # SLP contribution (atmospheric environment)
- if slp_data and slp_data['success']:
- try:
- slp_value = oceanic_manager.interpolate_data_to_point(
- slp_data, lat, lon, 'slp'
- )
-
- if not np.isnan(slp_value):
- # Convert from Pa to hPa if needed
- slp_hpa = slp_value if slp_value > 500 else slp_value / 100
+ gpi_field[i, j] = gpi
+
+ daily_gpi_maps.append({
+ 'day': day,
+ 'gpi_field': gpi_field,
+ 'lat_range': lat_range,
+ 'lon_range': lon_range
+ })
+
+ # Check for genesis events (GPI > threshold)
+ genesis_threshold = 1.5 # Adjusted threshold
+ if np.max(gpi_field) > genesis_threshold:
+ # Find peak genesis locations
+ peak_indices = np.where(gpi_field > genesis_threshold)
+ if len(peak_indices[0]) > 0:
+ # Select strongest genesis point
+ max_idx = np.argmax(gpi_field)
+ max_i, max_j = np.unravel_index(max_idx, gpi_field.shape)
- # Lower pressure = better environment for intensification
- if slp_hpa < 1008: # Low pressure environment
- slp_contribution = (1008 - slp_hpa) * 3
- elif slp_hpa > 1015: # High pressure - suppressed development
- slp_contribution = (1015 - slp_hpa) * 2
- else: # Neutral
- slp_contribution = 0
+ genesis_lat = lat_range[max_i]
+ genesis_lon = lon_range[max_j]
+ genesis_gpi = gpi_field[max_i, max_j]
- base_potential += slp_contribution
- logging.debug(f"SLP: {slp_hpa:.1f}hPa, contribution: {slp_contribution:.1f}kt")
+ # Determine probability of actual genesis
+ genesis_prob = min(0.8, genesis_gpi / 3.0)
- except Exception as e:
- logging.warning(f"Error processing SLP data: {e}")
-
- # ENSO modulation
- if oni_value > 1.0: # Strong El Niño
- enso_modifier = -15 # Suppressed development
- elif oni_value > 0.5: # Moderate El Niño
- enso_modifier = -8
- elif oni_value < -1.0: # Strong La Niña
- enso_modifier = +12 # Enhanced development
- elif oni_value < -0.5: # Moderate La Niña
- enso_modifier = +6
- else: # Neutral
- enso_modifier = oni_value * 2
-
- base_potential += enso_modifier
-
- # Seasonal modulation
- seasonal_factors = {
- 1: -12, 2: -10, 3: -8, 4: -5, 5: 0, 6: 5,
- 7: 12, 8: 15, 9: 18, 10: 12, 11: 5, 12: -8
- }
- seasonal_modifier = seasonal_factors.get(month, 0)
- base_potential += seasonal_modifier
-
- # Latitude effects
- if lat < 8: # Too close to equator - weak Coriolis
- lat_modifier = -20
- elif lat < 12: # Good for development
- lat_modifier = 5
- elif lat < 25: # Prime development zone
- lat_modifier = 10
- elif lat < 35: # Marginal
- lat_modifier = -5
- else: # Too far north
- lat_modifier = -25
-
- base_potential += lat_modifier
-
- # Wind shear estimation (simplified)
- shear_factor = estimate_wind_shear(lat, lon, month, oni_value)
- base_potential -= shear_factor
-
- # Apply realistic bounds
- environmental_potential = max(25, min(185, base_potential))
+ if np.random.random() < genesis_prob:
+ genesis_events.append({
+ 'day': day,
+ 'lat': genesis_lat,
+ 'lon': genesis_lon,
+ 'gpi': genesis_gpi,
+ 'probability': genesis_prob,
+ 'date': f"{year}-{month:02d}-{day:02d}"
+ })
+
+ # Generate storm tracks for genesis events
+ storm_predictions = []
+ for i, genesis in enumerate(genesis_events):
+ storm_track = generate_storm_track_from_genesis(
+ genesis['lat'],
+ genesis['lon'],
+ genesis['day'],
+ month,
+ oni_value,
+ storm_id=i+1
+ )
+
+ if storm_track:
+ storm_predictions.append({
+ 'storm_id': i + 1,
+ 'genesis_event': genesis,
+ 'track': storm_track,
+ 'uncertainty': calculate_track_uncertainty(storm_track)
+ })
return {
- 'potential_intensity': environmental_potential,
- 'sst_contribution': sst_contribution if 'sst_contribution' in locals() else 0,
- 'slp_contribution': slp_contribution if 'slp_contribution' in locals() else 0,
- 'enso_modifier': enso_modifier,
- 'seasonal_modifier': seasonal_modifier,
- 'latitude_modifier': lat_modifier,
- 'shear_factor': shear_factor
+ 'month': month,
+ 'year': year,
+ 'oni_value': oni_value,
+ 'daily_gpi_maps': daily_gpi_maps,
+ 'genesis_events': genesis_events,
+ 'storm_predictions': storm_predictions,
+ 'summary': {
+ 'total_genesis_events': len(genesis_events),
+ 'total_storm_predictions': len(storm_predictions),
+ 'peak_gpi_day': max(daily_gpi_maps, key=lambda x: np.max(x['gpi_field']))['day'],
+ 'peak_gpi_value': max(np.max(day_data['gpi_field']) for day_data in daily_gpi_maps)
+ }
}
except Exception as e:
- logging.error(f"Error calculating environmental potential: {e}")
+ logging.error(f"Error in genesis prediction: {e}")
+ import traceback
+ traceback.print_exc()
return {
- 'potential_intensity': 50,
- 'error': str(e)
+ 'error': str(e),
+ 'month': month,
+ 'oni_value': oni_value,
+ 'storm_predictions': []
}
-def get_climatological_sst(lat, lon, month):
- """Get climatological SST for a location and month"""
- # Simplified climatological SST model for Western Pacific
- base_sst = 28.0 # Base warm pool temperature
-
- # Latitude effect
- if lat < 5:
- lat_effect = 0.5 # Warm near equator
- elif lat < 15:
- lat_effect = 1.0 # Peak warm pool
- elif lat < 25:
- lat_effect = 0.0 - (lat - 15) * 0.3 # Cooling northward
- else:
- lat_effect = -3.0 - (lat - 25) * 0.2 # Much cooler
-
- # Seasonal effect
- seasonal_cycle = {
- 1: -1.0, 2: -1.2, 3: -0.8, 4: 0.0, 5: 0.5, 6: 0.8,
- 7: 1.0, 8: 1.2, 9: 1.0, 10: 0.5, 11: 0.0, 12: -0.5
- }
- seasonal_effect = seasonal_cycle.get(month, 0)
-
- return base_sst + lat_effect + seasonal_effect
-
-def estimate_wind_shear(lat, lon, month, oni_value):
- """Estimate wind shear based on location, season, and ENSO state"""
- # Base shear climatology
- if 5 <= lat <= 20 and 120 <= lon <= 160: # Low shear region
- base_shear = 5 # kt equivalent intensity reduction
- elif lat > 25: # Higher latitude - more shear
- base_shear = 15 + (lat - 25) * 2
- else: # Marginal regions
- base_shear = 10
+def generate_storm_track_from_genesis(genesis_lat, genesis_lon, genesis_day, month, oni_value, storm_id=1):
+ """
+ Generate a realistic storm track from a genesis location
+ """
+ try:
+ track_points = []
+ current_lat = genesis_lat
+ current_lon = genesis_lon
+ current_intensity = 25 # Start as TD
+
+ # Track duration (3-10 days typically)
+ track_duration_hours = np.random.randint(72, 240)
+
+ for hour in range(0, track_duration_hours + 6, 6):
+ # Calculate storm motion
+ # Base motion patterns for Western Pacific
+ if current_lat < 20: # Low latitude - westward motion
+ lat_speed = 0.1 + np.random.normal(0, 0.05) # Slight poleward
+ lon_speed = -0.3 + np.random.normal(0, 0.1) # Westward
+ elif current_lat < 25: # Mid latitude - WNW motion
+ lat_speed = 0.15 + np.random.normal(0, 0.05)
+ lon_speed = -0.2 + np.random.normal(0, 0.1)
+ else: # High latitude - recurvature
+ lat_speed = 0.2 + np.random.normal(0, 0.05)
+ lon_speed = 0.1 + np.random.normal(0, 0.1) # Eastward
+
+ # ENSO effects on motion
+ if oni_value > 0.5: # El Niño - more eastward
+ lon_speed += 0.05
+ elif oni_value < -0.5: # La Niña - more westward
+ lon_speed -= 0.05
+
+ # Update position
+ current_lat += lat_speed
+ current_lon += lon_speed
+
+ # Intensity evolution
+ # Get environmental conditions for intensity change
+ env_conditions = get_environmental_conditions(current_lat, current_lon, month, oni_value)
+
+ # Intensity change factors
+ sst_factor = max(0, env_conditions['sst'] - 26.5)
+ shear_factor = max(0, (15 - env_conditions['wind_shear']) / 10)
+
+ # Basic intensity change
+ if hour < 48: # Development phase
+ intensity_change = 3 + sst_factor + shear_factor + np.random.normal(0, 2)
+ elif hour < 120: # Mature phase
+ intensity_change = 1 + sst_factor * 0.5 + np.random.normal(0, 1.5)
+ else: # Weakening phase
+ intensity_change = -2 + sst_factor * 0.3 + np.random.normal(0, 1)
+
+ # Environmental limits
+ if current_lat > 30: # Cool waters
+ intensity_change -= 5
+ if current_lon < 120: # Land interaction
+ intensity_change -= 8
+
+ current_intensity += intensity_change
+ current_intensity = max(15, min(180, current_intensity)) # Realistic bounds
+
+ # Calculate pressure
+ pressure = max(900, 1013 - (current_intensity - 25) * 0.9)
+
+ # Add uncertainty
+ position_uncertainty = 0.5 + (hour / 120) * 1.5 # Growing uncertainty
+ intensity_uncertainty = 5 + (hour / 120) * 15
+
+ track_points.append({
+ 'hour': hour,
+ 'day': genesis_day + hour / 24.0,
+ 'lat': current_lat,
+ 'lon': current_lon,
+ 'intensity': current_intensity,
+ 'pressure': pressure,
+ 'category': categorize_typhoon_enhanced(current_intensity),
+ 'position_uncertainty': position_uncertainty,
+ 'intensity_uncertainty': intensity_uncertainty
+ })
+
+ # Stop if storm moves too far or weakens significantly
+ if current_lat > 40 or current_lat < 0 or current_lon < 100 or current_intensity < 20:
+ break
+
+ return track_points
+
+ except Exception as e:
+ logging.error(f"Error generating storm track: {e}")
+ return None
+
+def calculate_track_uncertainty(track_points):
+ """Calculate uncertainty metrics for a storm track"""
+ if not track_points:
+ return {'position': 0, 'intensity': 0}
- # Seasonal modulation
- if month in [12, 1, 2, 3]: # Winter - high shear
- seasonal_shear = 8
- elif month in [6, 7, 8, 9]: # Summer - low shear
- seasonal_shear = -3
- else: # Transition seasons
- seasonal_shear = 2
+ # Position uncertainty grows with time
+ position_uncertainty = [point['position_uncertainty'] for point in track_points]
- # ENSO modulation
- if oni_value > 0.5: # El Niño - increased shear
- enso_shear = 5 + oni_value * 3
- elif oni_value < -0.5: # La Niña - decreased shear
- enso_shear = oni_value * 2
- else:
- enso_shear = 0
+ # Intensity uncertainty
+ intensity_uncertainty = [point['intensity_uncertainty'] for point in track_points]
- total_shear = base_shear + seasonal_shear + enso_shear
- return max(0, total_shear)
-
-# -----------------------------
-# ENHANCED: Realistic Storm Prediction with Oceanic Data
-# -----------------------------
-
-def get_realistic_genesis_locations():
- """Get realistic typhoon genesis regions based on climatology"""
return {
- "Western Pacific Main Development Region": {"lat": 12.5, "lon": 145.0, "description": "Peak activity zone (Guam area)"},
- "South China Sea": {"lat": 15.0, "lon": 115.0, "description": "Secondary development region"},
- "Philippine Sea": {"lat": 18.0, "lon": 135.0, "description": "Recurving storm region"},
- "Marshall Islands": {"lat": 8.0, "lon": 165.0, "description": "Eastern development zone"},
- "Monsoon Trough": {"lat": 10.0, "lon": 130.0, "description": "Monsoon-driven genesis"},
- "ITCZ Region": {"lat": 6.0, "lon": 140.0, "description": "Near-equatorial development"},
- "Subtropical Region": {"lat": 22.0, "lon": 125.0, "description": "Late season development"},
- "Bay of Bengal": {"lat": 15.0, "lon": 88.0, "description": "Indian Ocean cyclones"},
- "Eastern Pacific": {"lat": 12.0, "lon": -105.0, "description": "Hurricane development zone"},
- "Atlantic MDR": {"lat": 12.0, "lon": -45.0, "description": "Main Development Region"}
+ 'position_mean': np.mean(position_uncertainty),
+ 'position_max': np.max(position_uncertainty),
+ 'intensity_mean': np.mean(intensity_uncertainty),
+ 'intensity_max': np.max(intensity_uncertainty),
+ 'track_length': len(track_points)
}
-
-def predict_storm_route_and_intensity_with_oceanic_data(
- genesis_region, month, oni_value,
- forecast_hours=72, use_real_data=True,
- models=None, enable_animation=True
-):
+def create_predict_animation(prediction_data, enable_animation=True):
"""
- Enhanced prediction system integrating real-time oceanic data
-
- This function provides the most realistic storm development prediction
- by incorporating current SST and SLP conditions from global datasets.
+ Typhoon genesis PREDICT tab animation:
+ shows monthly genesis-potential + progressive storm positions
"""
try:
- genesis_locations = get_realistic_genesis_locations()
-
- if genesis_region not in genesis_locations:
- genesis_region = "Western Pacific Main Development Region"
-
- genesis_info = genesis_locations[genesis_region]
- start_lat = genesis_info["lat"]
- start_lon = genesis_info["lon"]
-
- logging.info(f"Starting enhanced prediction for {genesis_region}")
-
- # Determine data bounds for oceanic data fetch
- lat_buffer = 10 # degrees
- lon_buffer = 15 # degrees
- lat_min = start_lat - lat_buffer
- lat_max = start_lat + lat_buffer
- lon_min = start_lon - lon_buffer
- lon_max = start_lon + lon_buffer
-
- # Fetch current oceanic conditions
- current_date = datetime.now()
- sst_data = None
- slp_data = None
-
- if use_real_data:
- try:
- logging.info("Fetching real-time oceanic data...")
-
- # Fetch SST data
- sst_data = oceanic_manager.get_sst_data(
- lat_min, lat_max, lon_min, lon_max,
- current_date - timedelta(days=1), # Yesterday's data (most recent available)
- current_date
- )
-
- # Fetch SLP data
- slp_data = oceanic_manager.get_slp_data(
- lat_min, lat_max, lon_min, lon_max,
- current_date - timedelta(days=1),
- current_date
+ daily_maps = prediction_data.get('daily_gpi_maps', [])
+ if not daily_maps:
+ return create_error_plot("No GPI data for prediction")
+
+ storms = prediction_data.get('storm_predictions', [])
+ month = prediction_data['month']
+ oni = prediction_data['oni_value']
+ year = prediction_data.get('year', 2025)
+
+ # -- 1) static underlay: full storm routes (dashed gray lines)
+ static_routes = []
+ for s in storms:
+ track = s.get('track', [])
+ if not track: continue
+ lats = [pt['lat'] for pt in track]
+ lons = [pt['lon'] for pt in track]
+ static_routes.append(
+ go.Scattergeo(
+ lat=lats, lon=lons,
+ mode='lines',
+ line=dict(width=2, dash='dash', color='gray'),
+ showlegend=False, hoverinfo='skip'
)
-
- logging.info(f"SST fetch: {'Success' if sst_data['success'] else 'Failed'}")
- logging.info(f"SLP fetch: {'Success' if slp_data['success'] else 'Failed'}")
-
- except Exception as e:
- logging.warning(f"Error fetching real-time data, using climatology: {e}")
- use_real_data = False
-
- # Initialize results structure
- results = {
- 'current_prediction': {},
- 'route_forecast': [],
- 'confidence_scores': {},
- 'environmental_data': {
- 'sst_source': 'Real-time NOAA OISST' if (sst_data and sst_data['success']) else 'Climatological',
- 'slp_source': 'Real-time NCEP/NCAR' if (slp_data and slp_data['success']) else 'Climatological',
- 'use_real_data': use_real_data
- },
- 'model_info': 'Enhanced Oceanic-Coupled Model',
- 'genesis_info': genesis_info
- }
-
- # Calculate initial environmental potential
- env_potential = calculate_environmental_intensity_potential(
- start_lat, start_lon, month, oni_value, sst_data, slp_data
- )
-
- # Realistic starting intensity (TD level) with environmental modulation
- base_intensity = 30 # Base TD intensity
- environmental_boost = min(8, max(-5, env_potential['potential_intensity'] - 50) * 0.15)
- predicted_intensity = base_intensity + environmental_boost
- predicted_intensity = max(25, min(45, predicted_intensity)) # Keep in TD-weak TS range
-
- # Enhanced genesis conditions
- results['current_prediction'] = {
- 'intensity_kt': predicted_intensity,
- 'pressure_hpa': 1008 - (predicted_intensity - 25) * 0.8,
- 'category': categorize_typhoon_enhanced(predicted_intensity),
- 'genesis_region': genesis_region,
- 'environmental_potential': env_potential['potential_intensity'],
- 'sst_contribution': env_potential.get('sst_contribution', 0),
- 'environmental_favorability': 'High' if env_potential['potential_intensity'] > 80 else
- ('Moderate' if env_potential['potential_intensity'] > 50 else 'Low')
+ )
+
+ # figure out map bounds
+ all_lats = [pt['lat'] for s in storms for pt in s.get('track',[])]
+ all_lons = [pt['lon'] for s in storms for pt in s.get('track',[])]
+ mb = {
+ 'lat_min': min(5, min(all_lats)-5) if all_lats else 5,
+ 'lat_max': max(35, max(all_lats)+5) if all_lats else 35,
+ 'lon_min': min(110, min(all_lons)-10) if all_lons else 110,
+ 'lon_max': max(180, max(all_lons)+10) if all_lons else 180
}
-
- # Enhanced route prediction with environmental coupling
- current_lat = start_lat
- current_lon = start_lon
- current_intensity = predicted_intensity
-
- route_points = []
-
- # Historical environmental analysis for better predictions
- historical_patterns = analyze_historical_environment(typhoon_data, oni_data)
-
- # Track storm development with oceanic data integration
- for hour in range(0, forecast_hours + 6, 6):
-
- # Dynamic oceanic conditions along track
- if use_real_data and sst_data and slp_data:
- # Get current environmental conditions
- current_env = calculate_environmental_intensity_potential(
- current_lat, current_lon, month, oni_value, sst_data, slp_data
+
+ # -- 2) build frames
+ frames = []
+ for idx, day_data in enumerate(daily_maps):
+ day = day_data['day']
+ gpi = day_data['gpi_field']
+ lats = day_data['lat_range']
+ lons = day_data['lon_range']
+
+ traces = []
+ # genesis‐potential scatter
+ traces.append(go.Scattergeo(
+ lat=np.repeat(lats, len(lons)),
+ lon=np.tile(lons, len(lats)),
+ mode='markers',
+ marker=dict(
+ size=6, color=gpi.flatten(),
+ colorscale='Viridis', cmin=0, cmax=3, opacity=0.6,
+ showscale=(idx==0),
+ colorbar=(dict(
+ title=dict(text="Genesis
Potential
Index", side="right")
+ ) if idx==0 else None)
+ ),
+ name='GPI',
+ showlegend=(idx==0),
+ hovertemplate=(
+ 'GPI: %{marker.color:.2f}
'
+ 'Lat: %{lat:.1f}°N
'
+ 'Lon: %{lon:.1f}°E
'
+ f'Day {day} of {month:02d}/{year}'
)
- environmental_limit = current_env['potential_intensity']
- else:
- # Use climatological estimates
- current_env = calculate_environmental_intensity_potential(
- current_lat, current_lon, month, oni_value, None, None
+ ))
+
+ # storm positions up to this day
+ for s in storms:
+ past = [pt for pt in s.get('track',[]) if pt['day'] <= day]
+ if not past: continue
+ lats_p = [pt['lat'] for pt in past]
+ lons_p = [pt['lon'] for pt in past]
+ intens = [pt['intensity'] for pt in past]
+ cats = [pt['category'] for pt in past]
+
+ # line history
+ traces.append(go.Scattergeo(
+ lat=lats_p, lon=lons_p, mode='lines',
+ line=dict(width=2, color='gray'),
+ showlegend=(idx==0), hoverinfo='skip'
+ ))
+ # current position
+ traces.append(go.Scattergeo(
+ lat=[lats_p[-1]], lon=[lons_p[-1]],
+ mode='markers',
+ marker=dict(size=10, symbol='circle', color='red'),
+ showlegend=(idx==0),
+ hovertemplate=(
+ f"{s['storm_id']}
"
+ f"Intensity: {intens[-1]} kt
"
+ f"Category: {cats[-1]}"
+ )
+ ))
+
+ frames.append(go.Frame(
+ data=traces,
+ name=str(day), # ← name is REQUIRED as string :contentReference[oaicite:1]{index=1}
+ layout=go.Layout(
+ geo=dict(
+ projection_type="natural earth",
+ showland=True, landcolor="lightgray",
+ showocean=True, oceancolor="lightblue",
+ showcoastlines=True, coastlinecolor="darkgray",
+ center=dict(lat=(mb['lat_min']+mb['lat_max'])/2,
+ lon=(mb['lon_min']+mb['lon_max'])/2),
+ lonaxis_range=[mb['lon_min'], mb['lon_max']],
+ lataxis_range=[mb['lat_min'], mb['lat_max']],
+ resolution=50
+ ),
+ title=f"Day {day} of {month:02d}/{year} | ONI: {oni:.2f}"
)
- environmental_limit = current_env['potential_intensity']
-
- # Enhanced storm motion with environmental steering
- base_speed = calculate_environmental_steering_speed(
- current_lat, current_lon, month, oni_value, slp_data
- )
-
- # Motion vectors with environmental influences
- lat_tendency, lon_tendency = calculate_motion_tendency(
- current_lat, current_lon, month, oni_value, hour, slp_data
+ ))
+
+ # -- 3) initial Figure (static + first frame)
+ init_data = static_routes + list(frames[0].data)
+ fig = go.Figure(data=init_data, frames=frames)
+
+ # -- 4) play/pause + slider (redraw=True!)
+ if enable_animation and len(frames)>1:
+ steps = [
+ dict(method="animate",
+ args=[[fr.name],
+ {"mode":"immediate",
+ "frame":{"duration":600,"redraw":True},
+ "transition":{"duration":0}}],
+ label=fr.name)
+ for fr in frames
+ ]
+
+ fig.update_layout(
+ updatemenus=[dict(
+ type="buttons", showactive=False,
+ x=1.05, y=0.05, xanchor="right", yanchor="bottom",
+ buttons=[
+ dict(label="▶ Play",
+ method="animate",
+ args=[None, # None=all frames
+ {"frame":{"duration":600,"redraw":True}, # ← redraw fixes dead ▶
+ "fromcurrent":True,"transition":{"duration":0}}]),
+ dict(label="⏸ Pause",
+ method="animate",
+ args=[[None],
+ {"frame":{"duration":0,"redraw":False},
+ "mode":"immediate"}])
+ ]
+ )],
+ sliders=[dict(active=0, pad=dict(t=50), steps=steps)]
)
-
- # Update position
- current_lat += lat_tendency
- current_lon += lon_tendency
-
- # Enhanced intensity evolution with environmental limits
- intensity_tendency = calculate_environmental_intensity_change(
- current_intensity, environmental_limit, hour, current_lat, current_lon,
- month, oni_value, sst_data
+ else:
+ # fallback: show only final day + static routes
+ final = static_routes + list(frames[-1].data)
+ fig = go.Figure(data=final)
+
+ # -- 5) shared layout styling
+ fig.update_layout(
+ title={
+ 'text': f"🌊 Typhoon Prediction — {month:02d}/{year} | ONI: {oni:.2f}",
+ 'x':0.5,'font':{'size':18}
+ },
+ geo=dict(
+ projection_type="natural earth",
+ showland=True, landcolor="lightgray",
+ showocean=True, oceancolor="lightblue",
+ showcoastlines=True, coastlinecolor="darkgray",
+ showlakes=True, lakecolor="lightblue",
+ showcountries=True, countrycolor="gray",
+ resolution=50,
+ center=dict(lat=20, lon=140),
+ lonaxis_range=[110,180], lataxis_range=[5,35]
+ ),
+ width=1100, height=750,
+ showlegend=True,
+ legend=dict(
+ x=0.02,y=0.98,
+ bgcolor="rgba(255,255,255,0.7)",
+ bordercolor="gray",borderwidth=1
)
-
- # Update intensity with environmental constraints
- current_intensity += intensity_tendency
- current_intensity = max(20, min(environmental_limit, current_intensity))
-
- # Enhanced confidence calculation
- confidence = calculate_dynamic_confidence(
- hour, current_lat, current_lon, use_real_data,
- sst_data['success'] if sst_data else False,
- slp_data['success'] if slp_data else False
+ )
+
+ return fig
+
+ except Exception as e:
+ logging.error(f"Error in predict animation: {e}")
+ import traceback; traceback.print_exc()
+ return create_error_plot(f"Animation error: {e}")
+def create_genesis_animation(prediction_data, enable_animation=True):
+ """
+ Create professional typhoon track animation showing daily genesis potential and storm development
+ Following NHC/JTWC visualization standards with proper geographic map and time controls
+ """
+ try:
+ daily_maps = prediction_data.get('daily_gpi_maps', [])
+ if not daily_maps:
+ return create_error_plot("No GPI data available for animation")
+
+ storm_predictions = prediction_data.get('storm_predictions', [])
+ month = prediction_data['month']
+ oni_value = prediction_data['oni_value']
+ year = prediction_data.get('year', 2025)
+
+ # ---- 1) Prepare static full-track routes ----
+ static_routes = []
+ for storm in storm_predictions:
+ track = storm.get('track', [])
+ if not track:
+ continue
+ lats = [pt['lat'] for pt in track]
+ lons = [pt['lon'] for pt in track]
+ static_routes.append(
+ go.Scattergeo(
+ lat=lats,
+ lon=lons,
+ mode='lines',
+ line=dict(width=2, dash='dash', color='gray'),
+ showlegend=False,
+ hoverinfo='skip'
+ )
)
-
- # Determine development stage with environmental context
- stage = get_environmental_development_stage(hour, current_intensity, environmental_limit)
-
- # Environmental metadata
- if sst_data and sst_data['success']:
- current_sst = oceanic_manager.interpolate_data_to_point(
- sst_data, current_lat, current_lon, 'sst'
+
+ # ---- 2) Build animation frames ----
+ frames = []
+ # determine map bounds from all storm tracks
+ all_lats = [pt['lat'] for storm in storm_predictions for pt in storm.get('track', [])]
+ all_lons = [pt['lon'] for storm in storm_predictions for pt in storm.get('track', [])]
+ map_bounds = {
+ 'lat_min': min(5, min(all_lats) - 5) if all_lats else 5,
+ 'lat_max': max(35, max(all_lats) + 5) if all_lats else 35,
+ 'lon_min': min(110, min(all_lons) - 10) if all_lons else 110,
+ 'lon_max': max(180, max(all_lons) + 10) if all_lons else 180
+ }
+
+ for day_idx, day_data in enumerate(daily_maps):
+ day = day_data['day']
+ gpi = day_data['gpi_field']
+ lats = day_data['lat_range']
+ lons = day_data['lon_range']
+
+ traces = []
+ # Genesis potential dots
+ traces.append(go.Scattergeo(
+ lat=np.repeat(lats, len(lons)),
+ lon=np.tile(lons, len(lats)),
+ mode='markers',
+ marker=dict(
+ size=6,
+ color=gpi.flatten(),
+ colorscale='Viridis',
+ cmin=0, cmax=3, opacity=0.6,
+ showscale=(day_idx == 0),
+ colorbar=(dict(
+ title=dict(text="Genesis
Potential
Index", side="right")
+ ) if day_idx == 0 else None)
+ ),
+ name='Genesis Potential',
+ showlegend=(day_idx == 0),
+ hovertemplate=(
+ 'GPI: %{marker.color:.2f}
' +
+ 'Lat: %{lat:.1f}°N
' +
+ 'Lon: %{lon:.1f}°E
' +
+ f'Day {day} of {month:02d}/{year}'
)
- else:
- current_sst = get_climatological_sst(current_lat, current_lon, month)
-
- if slp_data and slp_data['success']:
- current_slp = oceanic_manager.interpolate_data_to_point(
- slp_data, current_lat, current_lon, 'slp'
+ ))
+
+ # Storm positions up to this day
+ for storm in storm_predictions:
+ past = [pt for pt in storm.get('track', []) if pt['day'] <= day]
+ if not past:
+ continue
+ lats_p = [pt['lat'] for pt in past]
+ lons_p = [pt['lon'] for pt in past]
+ intens = [pt['intensity'] for pt in past]
+ cats = [pt['category'] for pt in past]
+
+ # historical line
+ traces.append(go.Scattergeo(
+ lat=lats_p, lon=lons_p, mode='lines',
+ line=dict(width=2, color='gray'),
+ name=f"{storm['storm_id']} Track",
+ showlegend=(day_idx == 0),
+ hoverinfo='skip'
+ ))
+ # current position
+ traces.append(go.Scattergeo(
+ lat=[lats_p[-1]], lon=[lons_p[-1]], mode='markers',
+ marker=dict(size=10, symbol='circle', color='red'),
+ name=f"{storm['storm_id']} Position",
+ showlegend=(day_idx == 0),
+ hovertemplate=(
+ f"{storm['storm_id']}
"
+ f"Intensity: {intens[-1]} kt
"
+ f"Category: {cats[-1]}"
+ )
+ ))
+
+ frames.append(go.Frame(
+ data=traces,
+ name=str(day),
+ layout=go.Layout(
+ geo=dict(
+ projection_type="natural earth",
+ showland=True, landcolor="lightgray",
+ showocean=True, oceancolor="lightblue",
+ showcoastlines=True, coastlinecolor="darkgray",
+ center=dict(
+ lat=(map_bounds['lat_min'] + map_bounds['lat_max'])/2,
+ lon=(map_bounds['lon_min'] + map_bounds['lon_max'])/2
+ ),
+ lonaxis_range=[map_bounds['lon_min'], map_bounds['lon_max']],
+ lataxis_range=[map_bounds['lat_min'], map_bounds['lat_max']],
+ resolution=50
+ ),
+ title=f"Day {day} of {month:02d}/{year} ONI: {oni_value:.2f}"
)
- current_slp = current_slp if current_slp > 500 else current_slp / 100 # Convert to hPa
- else:
- current_slp = 1013 # Standard atmosphere
-
- route_points.append({
- 'hour': hour,
- 'lat': current_lat,
- 'lon': current_lon,
- 'intensity_kt': current_intensity,
- 'category': categorize_typhoon_enhanced(current_intensity),
- 'confidence': confidence,
- 'development_stage': stage,
- 'forward_speed_kmh': base_speed * 111, # Convert to km/h
- 'pressure_hpa': max(900, 1013 - (current_intensity - 25) * 0.9)
- })
+ ))
+
+ # ---- 3) Initialize figure with static routes + first frame ----
+ initial_data = static_routes + list(frames[0].data)
+ fig = go.Figure(data=initial_data, frames=frames)
+
+ # ---- 4) Add play/pause buttons with redraw=True ----
+ if enable_animation and len(frames) > 1:
+ # slider steps
+ steps = [
+ dict(method="animate",
+ args=[[fr.name],
+ {"mode": "immediate",
+ "frame": {"duration": 600, "redraw": True},
+ "transition": {"duration": 0}}],
+ label=fr.name)
+ for fr in frames
+ ]
+
+ fig.update_layout(
+ updatemenus=[dict(
+ type="buttons", showactive=False,
+ x=1.05, y=0.05, xanchor="right", yanchor="bottom",
+ buttons=[
+ dict(label="▶ Play",
+ method="animate",
+ args=[None, # None means “all frames”
+ {"frame": {"duration": 600, "redraw": True},
+ "fromcurrent": True,
+ "transition": {"duration": 0}}
+ ]), # redraw=True fixes the dead play button :contentReference[oaicite:1]{index=1}
+ dict(label="⏸ Pause",
+ method="animate",
+ args=[[None],
+ {"frame": {"duration": 0, "redraw": False},
+ "mode": "immediate"}])
+ ]
+ )],
+ sliders=[dict(active=0, pad=dict(t=50), steps=steps)]
+ )
+ # No-animation fallback: just show final day + routes
+ else:
+ final = static_routes + list(frames[-1].data)
+ fig = go.Figure(data=final)
+
+ # ---- 5) Common layout styling ----
+ fig.update_layout(
+ title={
+ 'text': f"🌊 Typhoon Genesis & Development Forecast
"
+ f"{month:02d}/{year} | ONI: {oni_value:.2f}",
+ 'x': 0.5, 'font': {'size': 18}
+ },
+ geo=dict(
+ projection_type="natural earth",
+ showland=True, landcolor="lightgray",
+ showocean=True, oceancolor="lightblue",
+ showcoastlines=True, coastlinecolor="darkgray",
+ showlakes=True, lakecolor="lightblue",
+ showcountries=True, countrycolor="gray",
+ resolution=50,
+ center=dict(lat=20, lon=140),
+ lonaxis_range=[110, 180], lataxis_range=[5, 35]
+ ),
+ width=1100, height=750,
+ showlegend=True,
+ legend=dict(x=0.02, y=0.98,
+ bgcolor="rgba(255,255,255,0.7)",
+ bordercolor="gray", borderwidth=1)
+ )
+
+ return fig
+
+ except Exception as e:
+ logging.error(f"Error creating professional genesis animation: {e}")
+ import traceback; traceback.print_exc()
+ return create_error_plot(f"Animation error: {e}")
+
+
+def create_error_plot(error_message):
+ """Create a simple error plot"""
+ fig = go.Figure()
+ fig.add_annotation(
+ text=error_message,
+ xref="paper", yref="paper",
+ x=0.5, y=0.5, xanchor='center', yanchor='middle',
+ showarrow=False, font_size=16
+ )
+ fig.update_layout(title="Error in Visualization")
+ return fig
+
+def create_prediction_summary(prediction_data):
+ """Create a comprehensive summary of the prediction"""
+ try:
+ if 'error' in prediction_data:
+ return f"Error in prediction: {prediction_data['error']}"
+
+ month = prediction_data['month']
+ oni_value = prediction_data['oni_value']
+ summary = prediction_data.get('summary', {})
+ genesis_events = prediction_data.get('genesis_events', [])
+ storm_predictions = prediction_data.get('storm_predictions', [])
+
+ month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+ month_name = month_names[month - 1]
+
+ summary_text = f"""
+TYPHOON GENESIS PREDICTION SUMMARY - {month_name.upper()} 2025
+{'='*70}
+
+🌊 ENVIRONMENTAL CONDITIONS:
+• Month: {month_name} (Month {month})
+• ONI Value: {oni_value:.2f} {'(El Niño)' if oni_value > 0.5 else '(La Niña)' if oni_value < -0.5 else '(Neutral)'}
+• Season Phase: {'Peak Season' if month in [7,8,9,10] else 'Off Season' if month in [1,2,3,4,11,12] else 'Transition Season'}
+
+📊 GENESIS POTENTIAL ANALYSIS:
+• Peak GPI Day: Day {summary.get('peak_gpi_day', 'Unknown')}
+• Peak GPI Value: {summary.get('peak_gpi_value', 0):.2f}
+• Total Genesis Events: {summary.get('total_genesis_events', 0)}
+• Storm Development Success: {summary.get('total_storm_predictions', 0)}/{summary.get('total_genesis_events', 0)} events
+
+🎯 GENESIS EVENTS BREAKDOWN:
+"""
- results['route_forecast'] = route_points
-
- # Realistic confidence scores
- results['confidence_scores'] = {
- 'genesis': 0.88,
- 'early_development': 0.82,
- 'position_24h': 0.85,
- 'position_48h': 0.78,
- 'position_72h': 0.68,
- 'intensity_24h': 0.75,
- 'intensity_48h': 0.65,
- 'intensity_72h': 0.55,
- 'long_term': max(0.3, 0.8 - (forecast_hours / 240) * 0.5)
- }
+ if genesis_events:
+ for i, event in enumerate(genesis_events, 1):
+ summary_text += f"""
+Event {i}:
+• Date: {event['date']}
+• Location: {event['lat']:.1f}°N, {event['lon']:.1f}°E
+• GPI Value: {event['gpi']:.2f}
+• Genesis Probability: {event['probability']*100:.0f}%
+"""
+ else:
+ summary_text += "\n• No significant genesis events predicted for this month\n"
- # Model information
- results['model_info'] = f"Enhanced Realistic Model - {genesis_region}"
+ summary_text += f"""
+
+🌪️ STORM TRACK PREDICTIONS:
+"""
+
+ if storm_predictions:
+ for storm in storm_predictions:
+ track = storm['track']
+ if track:
+ genesis = storm['genesis_event']
+ max_intensity = max(pt['intensity'] for pt in track)
+ max_category = categorize_typhoon_enhanced(max_intensity)
+ track_duration = len(track) * 6 # hours
+ final_pos = track[-1]
+
+ summary_text += f"""
+Storm {storm['storm_id']}:
+• Genesis: Day {genesis['day']}, {genesis['lat']:.1f}°N {genesis['lon']:.1f}°E
+• Peak Intensity: {max_intensity:.0f} kt ({max_category})
+• Track Duration: {track_duration} hours ({track_duration/24:.1f} days)
+• Final Position: {final_pos['lat']:.1f}°N, {final_pos['lon']:.1f}°E
+• Uncertainty: ±{storm['uncertainty']['position_mean']:.1f}° position, ±{storm['uncertainty']['intensity_mean']:.0f} kt intensity
+"""
+ else:
+ summary_text += "\n• No storm development predicted from genesis events\n"
+
+ # Add climatological context
+ summary_text += f"""
+
+📈 CLIMATOLOGICAL CONTEXT:
+• {month_name} Typical Activity: {'Very High' if month in [8,9] else 'High' if month in [7,10] else 'Moderate' if month in [6,11] else 'Low'}
+• ENSO Influence: {'Strong suppression expected' if oni_value > 1.0 else 'Moderate suppression' if oni_value > 0.5 else 'Strong enhancement likely' if oni_value < -1.0 else 'Moderate enhancement' if oni_value < -0.5 else 'Near-normal activity'}
+• Regional Focus: Western Pacific Main Development Region (10-25°N, 120-160°E)
+
+🔧 METHODOLOGY DETAILS:
+• Genesis Potential Index: Emanuel & Nolan (2004) formulation
+• Environmental Factors: SST, humidity, vorticity, wind shear, Coriolis effect
+• Temporal Resolution: Daily evolution throughout month
+• Spatial Resolution: 2.5° grid spacing
+• ENSO Modulation: Integrated ONI effects on environmental parameters
+• Track Prediction: Physics-based storm motion and intensity evolution
+
+⚠️ UNCERTAINTY & LIMITATIONS:
+• Genesis timing: ±2-3 days typical uncertainty
+• Track position: Growing uncertainty with time (±0.5° to ±2°)
+• Intensity prediction: ±5-15 kt uncertainty range
+• Environmental assumptions: Based on climatological patterns
+• Model limitations: Simplified compared to operational NWP systems
+
+🎯 FORECAST CONFIDENCE:
+• Genesis Location: {'High' if summary.get('peak_gpi_value', 0) > 2 else 'Moderate' if summary.get('peak_gpi_value', 0) > 1 else 'Low'}
+• Genesis Timing: {'High' if month in [7,8,9] else 'Moderate' if month in [6,10] else 'Low'}
+• Track Prediction: Moderate (physics-based motion patterns)
+• Intensity Evolution: Moderate (environmental constraints applied)
+
+📋 OPERATIONAL IMPLICATIONS:
+• Monitor Days {', '.join([str(event['day']) for event in genesis_events[:3]])} for potential development
+• Focus regions: {', '.join([f"{event['lat']:.0f}°N {event['lon']:.0f}°E" for event in genesis_events[:3]])}
+• Preparedness level: {'High' if len(storm_predictions) > 2 else 'Moderate' if len(storm_predictions) > 0 else 'Routine'}
+
+🔬 RESEARCH APPLICATIONS:
+• Suitable for seasonal planning and climate studies
+• Genesis mechanism investigation
+• ENSO-typhoon relationship analysis
+• Environmental parameter sensitivity studies
+
+⚠️ IMPORTANT DISCLAIMERS:
+• This is a research prediction system, not operational forecast
+• Use official meteorological services for real-time warnings
+• Actual conditions may differ from climatological assumptions
+• Model simplified compared to operational prediction systems
+• Uncertainty grows significantly beyond 5-7 day lead times
+"""
- return results
+ return summary_text
except Exception as e:
- logging.error(f"Realistic prediction error: {str(e)}")
- return {
- 'error': f"Prediction error: {str(e)}",
- 'current_prediction': {'intensity_kt': 30, 'category': 'Tropical Depression'},
- 'route_forecast': [],
- 'confidence_scores': {},
- 'model_info': 'Error in prediction'
- }
+ logging.error(f"Error creating prediction summary: {e}")
+ return f"Error generating summary: {str(e)}"
# -----------------------------
# FIXED: ADVANCED ML FEATURES WITH ROBUST ERROR HANDLING
# -----------------------------
-def calculate_environmental_steering_speed(lat, lon, month, oni_value, slp_data):
- """Calculate storm forward speed based on environmental steering"""
- base_speed = 0.15 # Default speed in degrees/hour
-
- # Latitude effects
- if lat < 20:
- speed_factor = 0.8 # Slower in tropics
- elif lat < 30:
- speed_factor = 1.2 # Faster in subtropics
- else:
- speed_factor = 1.5 # Fast in mid-latitudes
-
- # Pressure gradient effects (if SLP data available)
- if slp_data and slp_data['success']:
- try:
- # Calculate approximate pressure gradient (simplified)
- slp_value = oceanic_manager.interpolate_data_to_point(slp_data, lat, lon, 'slp')
- if not np.isnan(slp_value):
- slp_hpa = slp_value if slp_value > 500 else slp_value / 100
- if slp_hpa < 1008: # Low pressure - faster motion
- speed_factor *= 1.2
- elif slp_hpa > 1015: # High pressure - slower motion
- speed_factor *= 0.8
- except:
- pass
-
- return base_speed * speed_factor
-
-def calculate_motion_tendency(lat, lon, month, oni_value, hour, slp_data):
- """Calculate motion tendency with environmental steering"""
- # Base climatological motion
- ridge_position = 32 + 4 * np.sin(2 * np.pi * (month - 6) / 4)
-
- if lat < ridge_position - 10:
- base_lat_tendency = 0.05 # Poleward
- base_lon_tendency = -0.12 # Westward
- elif lat > ridge_position - 3:
- base_lat_tendency = 0.15 # Strong poleward (recurvature)
- base_lon_tendency = 0.08 # Eastward
- else:
- base_lat_tendency = 0.08 # Moderate poleward
- base_lon_tendency = -0.06 # Moderate westward
-
- # ENSO steering effects
- if oni_value > 0.5: # El Niño
- base_lon_tendency += 0.03 # More eastward
- base_lat_tendency += 0.01 # Slightly more poleward
- elif oni_value < -0.5: # La Niña
- base_lon_tendency -= 0.04 # More westward
-
- # Add realistic motion uncertainty
- motion_uncertainty = 0.02 + (hour / 120) * 0.03
- lat_noise = np.random.normal(0, motion_uncertainty)
- lon_noise = np.random.normal(0, motion_uncertainty)
-
- return base_lat_tendency + lat_noise, base_lon_tendency + lon_noise
-
-def calculate_environmental_intensity_change(
- current_intensity, environmental_limit, hour, lat, lon, month, oni_value, sst_data
-):
- """Calculate intensity change based on environmental conditions"""
-
- # Base intensity tendency based on development stage
- if hour <= 48: # Development phase
- if current_intensity < environmental_limit * 0.6:
- base_tendency = 3.5 # Rapid development possible
- elif current_intensity < environmental_limit * 0.8:
- base_tendency = 2.0 # Moderate development
- else:
- base_tendency = 0.5 # Near limit
- elif hour <= 120: # Mature phase
- if current_intensity < environmental_limit:
- base_tendency = 1.0 # Slow intensification
- else:
- base_tendency = -0.5 # Slight weakening
- else: # Extended phase
- base_tendency = -2.0 # General weakening trend
-
- # Environmental limit constraint
- if current_intensity >= environmental_limit:
- base_tendency = min(base_tendency, -1.0) # Force weakening if over limit
-
- # SST effects on development rate
- if sst_data and sst_data['success']:
- try:
- sst_value = oceanic_manager.interpolate_data_to_point(sst_data, lat, lon, 'sst')
- if not np.isnan(sst_value):
- sst_celsius = sst_value if sst_value < 50 else sst_value - 273.15
- if sst_celsius >= 29.5: # Very warm - enhanced development
- base_tendency += 1.5
- elif sst_celsius >= 28.0: # Warm - normal development
- base_tendency += 0.5
- elif sst_celsius < 26.5: # Cool - inhibited development
- base_tendency -= 2.0
- except:
- pass
-
- # Land interaction
- if lon < 110 or (120 < lon < 125 and lat > 20): # Near land masses
- base_tendency -= 8.0
-
- # High latitude weakening
- if lat > 35:
- base_tendency -= 10.0
- elif lat > 30:
- base_tendency -= 4.0
-
- # Add realistic intensity uncertainty
- intensity_noise = np.random.normal(0, 1.0)
-
- return base_tendency + intensity_noise
-
-def calculate_dynamic_confidence(hour, lat, lon, use_real_data, sst_success, slp_success):
- """Calculate dynamic confidence based on data availability and conditions"""
- base_confidence = 0.92
-
- # Time penalty
- time_penalty = (hour / 120) * 0.35
-
- # Data quality bonus
- data_bonus = 0.0
- if use_real_data:
- if sst_success:
- data_bonus += 0.08
- if slp_success:
- data_bonus += 0.05
-
- # Environmental uncertainty
- environment_penalty = 0.0
- if lat > 30 or lon < 115: # Challenging forecast regions
- environment_penalty = 0.12
- elif lat > 25:
- environment_penalty = 0.06
-
- final_confidence = base_confidence + data_bonus - time_penalty - environment_penalty
- return max(0.25, min(0.95, final_confidence))
-def get_environmental_development_stage(hour, intensity, environmental_limit):
- """Determine development stage based on time and environmental context"""
- intensity_fraction = intensity / max(environmental_limit, 50)
-
- if hour <= 24:
- return 'Genesis'
- elif hour <= 72:
- if intensity_fraction < 0.3:
- return 'Early Development'
- elif intensity_fraction < 0.6:
- return 'Active Development'
- else:
- return 'Rapid Development'
- elif hour <= 120:
- if intensity_fraction > 0.8:
- return 'Peak Intensity'
- else:
- return 'Mature Stage'
- else:
- return 'Extended Forecast'
def extract_storm_features(typhoon_data):
"""Extract comprehensive features for clustering analysis - FIXED VERSION"""
try:
@@ -2417,594 +2385,6 @@ def create_separate_clustering_plots(storm_features, typhoon_data, method='umap'
)
return error_fig, error_fig, error_fig, error_fig, f"Error in clustering: {str(e)}"
-# -----------------------------
-# ENHANCED: Advanced Prediction System with Route Forecasting
-# -----------------------------
-
-def create_advanced_prediction_model(typhoon_data):
- """Create advanced ML model for intensity and route prediction"""
- try:
- if typhoon_data is None or typhoon_data.empty:
- return None, "No data available for model training"
-
- # Prepare training data
- features = []
- targets = []
-
- for sid in typhoon_data['SID'].unique():
- storm_data = typhoon_data[typhoon_data['SID'] == sid].sort_values('ISO_TIME')
-
- if len(storm_data) < 3: # Need at least 3 points for prediction
- continue
-
- for i in range(len(storm_data) - 1):
- current = storm_data.iloc[i]
- next_point = storm_data.iloc[i + 1]
-
- # Extract features (current state)
- feature_row = []
-
- # Current position
- feature_row.extend([
- current.get('LAT', 20),
- current.get('LON', 140)
- ])
-
- # Current intensity
- feature_row.extend([
- current.get('USA_WIND', 30),
- current.get('USA_PRES', 1000)
- ])
-
- # Time features
- if 'ISO_TIME' in current and pd.notna(current['ISO_TIME']):
- month = current['ISO_TIME'].month
- day_of_year = current['ISO_TIME'].dayofyear
- else:
- month = 9 # Peak season default
- day_of_year = 250
-
- feature_row.extend([month, day_of_year])
-
- # Motion features (if previous point exists)
- if i > 0:
- prev = storm_data.iloc[i - 1]
- dlat = current.get('LAT', 20) - prev.get('LAT', 20)
- dlon = current.get('LON', 140) - prev.get('LON', 140)
- speed = np.sqrt(dlat**2 + dlon**2)
- bearing = np.arctan2(dlat, dlon)
- else:
- speed = 0
- bearing = 0
-
- feature_row.extend([speed, bearing])
-
- features.append(feature_row)
-
- # Target: next position and intensity
- targets.append([
- next_point.get('LAT', 20),
- next_point.get('LON', 140),
- next_point.get('USA_WIND', 30)
- ])
-
- if len(features) < 10: # Need sufficient training data
- return None, "Insufficient data for model training"
-
- # Train model
- X = np.array(features)
- y = np.array(targets)
-
- # Split data
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
-
- # Create separate models for position and intensity
- models = {}
-
- # Position model (lat, lon)
- pos_model = RandomForestRegressor(n_estimators=100, random_state=42)
- pos_model.fit(X_train, y_train[:, :2])
- models['position'] = pos_model
-
- # Intensity model (wind speed)
- int_model = RandomForestRegressor(n_estimators=100, random_state=42)
- int_model.fit(X_train, y_train[:, 2])
- models['intensity'] = int_model
-
- # Calculate model performance
- pos_pred = pos_model.predict(X_test)
- int_pred = int_model.predict(X_test)
-
- pos_mae = mean_absolute_error(y_test[:, :2], pos_pred)
- int_mae = mean_absolute_error(y_test[:, 2], int_pred)
-
- model_info = f"Position MAE: {pos_mae:.2f}°, Intensity MAE: {int_mae:.2f} kt"
-
- return models, model_info
-
- except Exception as e:
- return None, f"Error creating prediction model: {str(e)}"
-
-def create_animated_route_visualization(prediction_results, show_uncertainty=True, enable_animation=True):
- """Create comprehensive animated route visualization with intensity plots"""
- try:
- if 'route_forecast' not in prediction_results or not prediction_results['route_forecast']:
- return None, "No route forecast data available"
-
- route_data = prediction_results['route_forecast']
-
- # Extract data for plotting
- hours = [point['hour'] for point in route_data]
- lats = [point['lat'] for point in route_data]
- lons = [point['lon'] for point in route_data]
- intensities = [point['intensity_kt'] for point in route_data]
- categories = [point['category'] for point in route_data]
- confidences = [point.get('confidence', 0.8) for point in route_data]
- stages = [point.get('development_stage', 'Unknown') for point in route_data]
- speeds = [point.get('forward_speed_kmh', 15) for point in route_data]
- pressures = [point.get('pressure_hpa', 1013) for point in route_data]
-
- # Create subplot layout with map and intensity plot
- fig = make_subplots(
- rows=2, cols=2,
- subplot_titles=('Storm Track Animation', 'Wind Speed vs Time', 'Forward Speed vs Time', 'Pressure vs Time'),
- specs=[[{"type": "geo", "colspan": 2}, None],
- [{"type": "xy"}, {"type": "xy"}]],
- vertical_spacing=0.15,
- row_heights=[0.7, 0.3]
- )
-
- if enable_animation:
- # Add frames for animation
- frames = []
-
- # Static background elements first
- # Add complete track as background
- fig.add_trace(
- go.Scattergeo(
- lon=lons,
- lat=lats,
- mode='lines',
- line=dict(color='lightgray', width=2, dash='dot'),
- name='Complete Track',
- showlegend=True,
- opacity=0.4
- ),
- row=1, col=1
- )
-
- # Genesis marker (always visible)
- fig.add_trace(
- go.Scattergeo(
- lon=[lons[0]],
- lat=[lats[0]],
- mode='markers',
- marker=dict(
- size=25,
- color='gold',
- symbol='star',
- line=dict(width=3, color='black')
- ),
- name='Genesis',
- showlegend=True,
- hovertemplate=(
- f"GENESIS
"
- f"Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E
"
- f"Initial: {intensities[0]:.0f} kt
"
- f"Region: {prediction_results['genesis_info']['description']}
"
- ""
- )
- ),
- row=1, col=1
- )
-
- # Create animation frames
- for i in range(len(route_data)):
- frame_lons = lons[:i+1]
- frame_lats = lats[:i+1]
- frame_intensities = intensities[:i+1]
- frame_categories = categories[:i+1]
- frame_hours = hours[:i+1]
-
- # Current position marker
- current_color = enhanced_color_map.get(frame_categories[-1], 'rgb(128,128,128)')
- current_size = 15 + (frame_intensities[-1] / 10)
-
- frame_data = [
- # Animated track up to current point
- go.Scattergeo(
- lon=frame_lons,
- lat=frame_lats,
- mode='lines+markers',
- line=dict(color='blue', width=4),
- marker=dict(
- size=[8 + (intensity/15) for intensity in frame_intensities],
- color=[enhanced_color_map.get(cat, 'rgb(128,128,128)') for cat in frame_categories],
- opacity=0.8,
- line=dict(width=1, color='white')
- ),
- name='Current Track',
- showlegend=False
- ),
- # Current position highlight
- go.Scattergeo(
- lon=[frame_lons[-1]],
- lat=[frame_lats[-1]],
- mode='markers',
- marker=dict(
- size=current_size,
- color=current_color,
- symbol='circle',
- line=dict(width=3, color='white')
- ),
- name='Current Position',
- showlegend=False,
- hovertemplate=(
- f"Hour {route_data[i]['hour']}
"
- f"Position: {lats[i]:.1f}°N, {lons[i]:.1f}°E
"
- f"Intensity: {intensities[i]:.0f} kt
"
- f"Category: {categories[i]}
"
- f"Stage: {stages[i]}
"
- f"Speed: {speeds[i]:.1f} km/h
"
- f"Confidence: {confidences[i]*100:.0f}%
"
- ""
- )
- ),
- # Animated wind plot
- go.Scatter(
- x=frame_hours,
- y=frame_intensities,
- mode='lines+markers',
- line=dict(color='red', width=3),
- marker=dict(size=6, color='red'),
- name='Wind Speed',
- showlegend=False,
- yaxis='y2'
- ),
- # Animated speed plot
- go.Scatter(
- x=frame_hours,
- y=speeds[:i+1],
- mode='lines+markers',
- line=dict(color='green', width=2),
- marker=dict(size=4, color='green'),
- name='Forward Speed',
- showlegend=False,
- yaxis='y3'
- ),
- # Animated pressure plot
- go.Scatter(
- x=frame_hours,
- y=pressures[:i+1],
- mode='lines+markers',
- line=dict(color='purple', width=2),
- marker=dict(size=4, color='purple'),
- name='Pressure',
- showlegend=False,
- yaxis='y4'
- )
- ]
-
- frames.append(go.Frame(
- data=frame_data,
- name=str(i),
- layout=go.Layout(
- title=f"Storm Development Animation - Hour {route_data[i]['hour']}
"
- f"Intensity: {intensities[i]:.0f} kt | Category: {categories[i]} | Stage: {stages[i]} | Speed: {speeds[i]:.1f} km/h"
- )
- ))
-
- fig.frames = frames
-
- # Add play/pause controls
- fig.update_layout(
- updatemenus=[
- {
- "buttons": [
- {
- "args": [None, {"frame": {"duration": 1000, "redraw": True},
- "fromcurrent": True, "transition": {"duration": 300}}],
- "label": "▶️ Play",
- "method": "animate"
- },
- {
- "args": [[None], {"frame": {"duration": 0, "redraw": True},
- "mode": "immediate", "transition": {"duration": 0}}],
- "label": "⏸️ Pause",
- "method": "animate"
- },
- {
- "args": [None, {"frame": {"duration": 500, "redraw": True},
- "fromcurrent": True, "transition": {"duration": 300}}],
- "label": "⏩ Fast",
- "method": "animate"
- }
- ],
- "direction": "left",
- "pad": {"r": 10, "t": 87},
- "showactive": False,
- "type": "buttons",
- "x": 0.1,
- "xanchor": "right",
- "y": 0,
- "yanchor": "top"
- }
- ],
- sliders=[{
- "active": 0,
- "yanchor": "top",
- "xanchor": "left",
- "currentvalue": {
- "font": {"size": 16},
- "prefix": "Hour: ",
- "visible": True,
- "xanchor": "right"
- },
- "transition": {"duration": 300, "easing": "cubic-in-out"},
- "pad": {"b": 10, "t": 50},
- "len": 0.9,
- "x": 0.1,
- "y": 0,
- "steps": [
- {
- "args": [[str(i)], {"frame": {"duration": 300, "redraw": True},
- "mode": "immediate", "transition": {"duration": 300}}],
- "label": f"H{route_data[i]['hour']}",
- "method": "animate"
- }
- for i in range(0, len(route_data), max(1, len(route_data)//20)) # Limit slider steps
- ]
- }]
- )
-
- else:
- # Static view with all points
- # Add genesis marker
- fig.add_trace(
- go.Scattergeo(
- lon=[lons[0]],
- lat=[lats[0]],
- mode='markers',
- marker=dict(
- size=25,
- color='gold',
- symbol='star',
- line=dict(width=3, color='black')
- ),
- name='Genesis',
- showlegend=True,
- hovertemplate=(
- f"GENESIS
"
- f"Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E
"
- f"Initial: {intensities[0]:.0f} kt
"
- ""
- )
- ),
- row=1, col=1
- )
-
- # Add full track with intensity coloring
- for i in range(0, len(route_data), max(1, len(route_data)//50)): # Sample points for performance
- point = route_data[i]
- color = enhanced_color_map.get(point['category'], 'rgb(128,128,128)')
- size = 8 + (point['intensity_kt'] / 12)
-
- fig.add_trace(
- go.Scattergeo(
- lon=[point['lon']],
- lat=[point['lat']],
- mode='markers',
- marker=dict(
- size=size,
- color=color,
- opacity=point.get('confidence', 0.8),
- line=dict(width=1, color='white')
- ),
- name=f"Hour {point['hour']}" if i % 10 == 0 else None,
- showlegend=(i % 10 == 0),
- hovertemplate=(
- f"Hour {point['hour']}
"
- f"Position: {point['lat']:.1f}°N, {point['lon']:.1f}°E
"
- f"Intensity: {point['intensity_kt']:.0f} kt
"
- f"Category: {point['category']}
"
- f"Stage: {point.get('development_stage', 'Unknown')}
"
- f"Speed: {point.get('forward_speed_kmh', 15):.1f} km/h
"
- ""
- )
- ),
- row=1, col=1
- )
-
- # Connect points with track line
- fig.add_trace(
- go.Scattergeo(
- lon=lons,
- lat=lats,
- mode='lines',
- line=dict(color='black', width=3),
- name='Forecast Track',
- showlegend=True
- ),
- row=1, col=1
- )
-
- # Add static intensity, speed, and pressure plots
- # Wind speed plot
- fig.add_trace(
- go.Scatter(
- x=hours,
- y=intensities,
- mode='lines+markers',
- line=dict(color='red', width=3),
- marker=dict(size=6, color='red'),
- name='Wind Speed',
- showlegend=False
- ),
- row=2, col=1
- )
-
- # Add category threshold lines
- thresholds = [34, 64, 83, 96, 113, 137]
- threshold_names = ['TS', 'C1', 'C2', 'C3', 'C4', 'C5']
-
- for thresh, name in zip(thresholds, threshold_names):
- fig.add_trace(
- go.Scatter(
- x=[min(hours), max(hours)],
- y=[thresh, thresh],
- mode='lines',
- line=dict(color='gray', width=1, dash='dash'),
- name=name,
- showlegend=False,
- hovertemplate=f"{name} Threshold: {thresh} kt"
- ),
- row=2, col=1
- )
-
- # Forward speed plot
- fig.add_trace(
- go.Scatter(
- x=hours,
- y=speeds,
- mode='lines+markers',
- line=dict(color='green', width=2),
- marker=dict(size=4, color='green'),
- name='Forward Speed',
- showlegend=False
- ),
- row=2, col=2
- )
-
- # Add uncertainty cone if requested
- if show_uncertainty and len(route_data) > 1:
- uncertainty_lats_upper = []
- uncertainty_lats_lower = []
- uncertainty_lons_upper = []
- uncertainty_lons_lower = []
-
- for i, point in enumerate(route_data):
- # Uncertainty grows with time and decreases with confidence
- base_uncertainty = 0.4 + (i / len(route_data)) * 1.8
- confidence_factor = point.get('confidence', 0.8)
- uncertainty = base_uncertainty / confidence_factor
-
- uncertainty_lats_upper.append(point['lat'] + uncertainty)
- uncertainty_lats_lower.append(point['lat'] - uncertainty)
- uncertainty_lons_upper.append(point['lon'] + uncertainty)
- uncertainty_lons_lower.append(point['lon'] - uncertainty)
-
- uncertainty_lats = uncertainty_lats_upper + uncertainty_lats_lower[::-1]
- uncertainty_lons = uncertainty_lons_upper + uncertainty_lons_lower[::-1]
-
- fig.add_trace(
- go.Scattergeo(
- lon=uncertainty_lons,
- lat=uncertainty_lats,
- mode='lines',
- fill='toself',
- fillcolor='rgba(128,128,128,0.15)',
- line=dict(color='rgba(128,128,128,0.4)', width=1),
- name='Uncertainty Cone',
- showlegend=True
- ),
- row=1, col=1
- )
-
- # Enhanced layout
- fig.update_layout(
- title=f"Comprehensive Storm Development Analysis
Starting from {prediction_results['genesis_info']['description']}",
- height=1000, # Taller for better subplot visibility
- width=1400, # Wider
- showlegend=True
- )
-
- # Update geo layout
- fig.update_geos(
- projection_type="natural earth",
- showland=True,
- landcolor="LightGray",
- showocean=True,
- oceancolor="LightBlue",
- showcoastlines=True,
- coastlinecolor="DarkGray",
- showlakes=True,
- lakecolor="LightBlue",
- center=dict(lat=np.mean(lats), lon=np.mean(lons)),
- projection_scale=2.0,
- row=1, col=1
- )
-
- # Update subplot axes
- fig.update_xaxes(title_text="Forecast Hour", row=2, col=1)
- fig.update_yaxes(title_text="Wind Speed (kt)", row=2, col=1)
- fig.update_xaxes(title_text="Forecast Hour", row=2, col=2)
- fig.update_yaxes(title_text="Forward Speed (km/h)", row=2, col=2)
-
- # Generate enhanced forecast text
- current = prediction_results['current_prediction']
- genesis_info = prediction_results['genesis_info']
-
- # Calculate some statistics
- max_intensity = max(intensities)
- max_intensity_time = hours[intensities.index(max_intensity)]
- avg_speed = np.mean(speeds)
-
- forecast_text = f"""
-COMPREHENSIVE STORM DEVELOPMENT FORECAST
-{'='*65}
-
-GENESIS CONDITIONS:
-• Region: {current.get('genesis_region', 'Unknown')}
-• Description: {genesis_info['description']}
-• Starting Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E
-• Initial Intensity: {current['intensity_kt']:.0f} kt (Tropical Depression)
-• Genesis Pressure: {current.get('pressure_hpa', 1008):.0f} hPa
-
-STORM CHARACTERISTICS:
-• Peak Intensity: {max_intensity:.0f} kt at Hour {max_intensity_time}
-• Average Forward Speed: {avg_speed:.1f} km/h
-• Total Distance: {sum([speeds[i]/6 for i in range(len(speeds))]):.0f} km
-• Final Position: {lats[-1]:.1f}°N, {lons[-1]:.1f}°E
-• Forecast Duration: {hours[-1]} hours ({hours[-1]/24:.1f} days)
-
-DEVELOPMENT TIMELINE:
-• Hour 0 (Genesis): {intensities[0]:.0f} kt - {categories[0]}
-• Hour 24: {intensities[min(4, len(intensities)-1)]:.0f} kt - {categories[min(4, len(categories)-1)]}
-• Hour 48: {intensities[min(8, len(intensities)-1)]:.0f} kt - {categories[min(8, len(categories)-1)]}
-• Hour 72: {intensities[min(12, len(intensities)-1)]:.0f} kt - {categories[min(12, len(categories)-1)]}
-• Final: {intensities[-1]:.0f} kt - {categories[-1]}
-
-MOTION ANALYSIS:
-• Initial Motion: {speeds[0]:.1f} km/h
-• Peak Speed: {max(speeds):.1f} km/h at Hour {hours[speeds.index(max(speeds))]}
-• Final Motion: {speeds[-1]:.1f} km/h
-
-CONFIDENCE ASSESSMENT:
-• Genesis Likelihood: {prediction_results['confidence_scores'].get('genesis', 0.85)*100:.0f}%
-• 24-hour Track: {prediction_results['confidence_scores'].get('position_24h', 0.85)*100:.0f}%
-• 48-hour Track: {prediction_results['confidence_scores'].get('position_48h', 0.75)*100:.0f}%
-• 72-hour Track: {prediction_results['confidence_scores'].get('position_72h', 0.65)*100:.0f}%
-• Long-term: {prediction_results['confidence_scores'].get('long_term', 0.50)*100:.0f}%
-
-FEATURES:
-{"✅ Animation Enabled - Use controls to watch development" if enable_animation else "📊 Static Analysis - All time steps displayed"}
-✅ Realistic Forward Speeds (15-25 km/h typical)
-✅ Environmental Coupling (ENSO, SST, Shear)
-✅ Multi-stage Development Cycle
-✅ Uncertainty Quantification
-
-MODEL: {prediction_results['model_info']}
- """
-
- return fig, forecast_text.strip()
-
- except Exception as e:
- error_msg = f"Error creating comprehensive visualization: {str(e)}"
- logging.error(error_msg)
- import traceback
- traceback.print_exc()
- return None, error_msg
-
# -----------------------------
# Regression Functions (Original)
# -----------------------------
@@ -3209,7 +2589,7 @@ def get_longitude_analysis(start_year, start_month, end_year, end_month, enso_ph
return fig, slopes_text, regression
# -----------------------------
-# ENHANCED: Animation Functions with Taiwan Standard Support - FIXED VERSION
+# ENHANCED: Animation Functions with Taiwan Standard Support
# -----------------------------
def get_available_years(typhoon_data):
@@ -3294,8 +2674,8 @@ def update_typhoon_options_enhanced(year, basin):
print(f"Error in update_typhoon_options_enhanced: {e}")
return gr.update(choices=["Error loading storms"], value=None)
-def generate_enhanced_track_video_fixed(year, typhoon_selection, standard):
- """FIXED: Enhanced track video generation with working animation display"""
+def generate_enhanced_track_video(year, typhoon_selection, standard):
+ """Enhanced track video generation with TD support, Taiwan standard, and 2025 compatibility"""
if not typhoon_selection or typhoon_selection == "No storms found":
return None
@@ -3320,17 +2700,16 @@ def generate_enhanced_track_video_fixed(year, typhoon_selection, standard):
if 'USA_WIND' in storm_df.columns:
winds = pd.to_numeric(storm_df['USA_WIND'], errors='coerce').fillna(0).values
else:
- winds = np.full(len(lats), 30)
+ winds = np.full(len(lats), 30) # Default TD strength
# Enhanced metadata
storm_name = storm_df['NAME'].iloc[0] if pd.notna(storm_df['NAME'].iloc[0]) else "UNNAMED"
season = storm_df['SEASON'].iloc[0] if 'SEASON' in storm_df.columns else year
- print(f"Generating FIXED video for {storm_name} ({sid}) with {len(lats)} track points using {standard} standard")
+ print(f"Generating video for {storm_name} ({sid}) with {len(lats)} track points using {standard} standard")
- # FIXED: Create figure with proper cartopy setup
- fig = plt.figure(figsize=(16, 10))
- ax = plt.axes(projection=ccrs.PlateCarree())
+ # Create figure with enhanced map
+ fig, ax = plt.subplots(figsize=(16, 10), subplot_kw={'projection': ccrs.PlateCarree()})
# Enhanced map features
ax.stock_img()
@@ -3350,35 +2729,26 @@ def generate_enhanced_track_video_fixed(year, typhoon_selection, standard):
gl = ax.gridlines(draw_labels=True, alpha=0.3)
gl.top_labels = gl.right_labels = False
- # Title
+ # Title with enhanced info and standard
ax.set_title(f"{season} {storm_name} ({sid}) Track Animation - {standard.upper()} Standard",
fontsize=18, fontweight='bold')
- # FIXED: Animation elements - proper initialization with cartopy transforms
- # Initialize empty line for track with correct transform
- track_line, = ax.plot([], [], 'b-', linewidth=3, alpha=0.7,
- label='Track', transform=ccrs.PlateCarree())
-
- # Initialize current position marker
- current_point, = ax.plot([], [], 'o', markersize=15,
- transform=ccrs.PlateCarree())
-
- # Historical track points (to show path traversed)
- history_points, = ax.plot([], [], 'o', markersize=6, alpha=0.4, color='blue',
- transform=ccrs.PlateCarree())
+ # Animation elements
+ line, = ax.plot([], [], 'b-', linewidth=3, alpha=0.7, label='Track')
+ point, = ax.plot([], [], 'o', markersize=15)
- # Info text box
+ # Enhanced info display
info_box = ax.text(0.02, 0.98, '', transform=ax.transAxes,
fontsize=12, verticalalignment='top',
bbox=dict(boxstyle="round,pad=0.5", facecolor='white', alpha=0.9))
- # FIXED: Color legend with proper categories for both standards
+ # Color legend with both standards - ENHANCED
legend_elements = []
+
if standard == 'taiwan':
- categories = ['Tropical Depression', 'Tropical Storm', 'Severe Tropical Storm',
- 'Typhoon', 'Severe Typhoon', 'Super Typhoon']
+ categories = ['Tropical Depression', 'Tropical Storm', 'Moderate Typhoon', 'Intense Typhoon']
for category in categories:
- color = get_taiwan_color_fixed(category)
+ color = get_taiwan_color(category)
legend_elements.append(plt.Line2D([0], [0], marker='o', color='w',
markerfacecolor=color, markersize=10, label=category))
else:
@@ -3391,51 +2761,40 @@ def generate_enhanced_track_video_fixed(year, typhoon_selection, standard):
ax.legend(handles=legend_elements, loc='upper right', fontsize=10)
- # FIXED: Animation function with proper artist updates and cartopy compatibility
- def animate_fixed(frame):
- """Fixed animation function that properly updates tracks with cartopy"""
+ def animate(frame):
try:
if frame >= len(lats):
- return track_line, current_point, history_points, info_box
-
- # FIXED: Update track line up to current frame
- current_lons = lons[:frame+1]
- current_lats = lats[:frame+1]
+ return line, point, info_box
- # Update the track line data (this is the key fix!)
- track_line.set_data(current_lons, current_lats)
+ # Update track line
+ line.set_data(lons[:frame+1], lats[:frame+1])
- # FIXED: Update historical points (smaller markers showing traversed path)
- if frame > 0:
- history_points.set_data(current_lons[:-1], current_lats[:-1])
-
- # FIXED: Update current position with correct categorization
+ # Update current position with appropriate categorization
current_wind = winds[frame]
if standard == 'taiwan':
- category, color = categorize_typhoon_by_standard_fixed(current_wind, 'taiwan')
+ category, color = categorize_typhoon_by_standard(current_wind, 'taiwan')
else:
- category, color = categorize_typhoon_by_standard_fixed(current_wind, 'atlantic')
+ category, color = categorize_typhoon_by_standard(current_wind, 'atlantic')
- # Debug for first few frames
+ # Debug print for first few frames
if frame < 3:
- print(f"FIXED Frame {frame}: Wind={current_wind:.1f}kt, Category={category}, Color={color}")
+ print(f"Frame {frame}: Wind={current_wind:.1f}kt, Category={category}, Color={color}, Standard={standard}")
- # Update current position marker
- current_point.set_data([lons[frame]], [lats[frame]])
- current_point.set_color(color)
- current_point.set_markersize(12 + current_wind/8)
+ point.set_data([lons[frame]], [lats[frame]])
+ point.set_color(color)
+ point.set_markersize(10 + current_wind/8) # Size based on intensity
- # FIXED: Enhanced info display with correct Taiwan wind speed conversion
+ # Enhanced info display with standard information
if 'ISO_TIME' in storm_df.columns and frame < len(storm_df):
current_time = storm_df.iloc[frame]['ISO_TIME']
time_str = current_time.strftime('%Y-%m-%d %H:%M UTC') if pd.notna(current_time) else 'Unknown'
else:
time_str = f"Step {frame+1}"
- # Corrected wind speed display for Taiwan standard
+ # Convert wind speed for Taiwan standard display
if standard == 'taiwan':
- wind_ms = current_wind * 0.514444
+ wind_ms = current_wind * 0.514444 # Convert to m/s for display
wind_display = f"{current_wind:.0f} kt ({wind_ms:.1f} m/s)"
else:
wind_display = f"{current_wind:.0f} kt"
@@ -3451,116 +2810,47 @@ def generate_enhanced_track_video_fixed(year, typhoon_selection, standard):
)
info_box.set_text(info_text)
- # FIXED: Return all modified artists (crucial for proper display)
- return track_line, current_point, history_points, info_box
+ return line, point, info_box
except Exception as e:
print(f"Error in animate frame {frame}: {e}")
- return track_line, current_point, history_points, info_box
+ return line, point, info_box
- # FIXED: Create animation with cartopy-compatible settings
- # Key fixes: blit=False (crucial for cartopy), proper interval
+ # Create animation
anim = animation.FuncAnimation(
- fig, animate_fixed, frames=len(lats),
- interval=600, blit=False, repeat=True # blit=False is essential for cartopy!
+ fig, animate, frames=len(lats),
+ interval=400, blit=False, repeat=True # Slightly slower for better viewing
)
- # Save animation with optimized settings
+ # Save animation
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4',
dir=tempfile.gettempdir())
- # FIXED: Writer settings optimized for track visibility
+ # Enhanced writer settings
writer = animation.FFMpegWriter(
- fps=2, bitrate=3000, codec='libx264', # Slower FPS for better track visibility
- extra_args=['-pix_fmt', 'yuv420p']
+ fps=3, bitrate=2000, codec='libx264', # Slower FPS for better visibility
+ extra_args=['-pix_fmt', 'yuv420p'] # Better compatibility
)
- print(f"Saving FIXED animation to {temp_file.name}")
- anim.save(temp_file.name, writer=writer, dpi=120)
+ print(f"Saving animation to {temp_file.name}")
+ anim.save(temp_file.name, writer=writer, dpi=120) # Higher DPI for better quality
plt.close(fig)
- print(f"FIXED video generated successfully: {temp_file.name}")
+ print(f"Video generated successfully: {temp_file.name}")
return temp_file.name
except Exception as e:
- print(f"Error generating FIXED video: {e}")
+ print(f"Error generating video: {e}")
import traceback
traceback.print_exc()
return None
-# FIXED: Update the simplified wrapper function
-def simplified_track_video_fixed(year, basin, typhoon, standard):
- """Simplified track video function with FIXED animation and Taiwan classification"""
+# Simplified wrapper for backward compatibility - FIXED
+def simplified_track_video(year, basin, typhoon, standard):
+ """Simplified track video function with fixed color handling"""
if not typhoon:
return None
- return generate_enhanced_track_video_fixed(year, typhoon, standard)
-
-# -----------------------------
-# Enhanced Gradio Interface with Oceanic Data Integration
-# -----------------------------
-
-def generate_enhanced_environmental_forecast_text(results, base_forecast_text):
- """Generate enhanced forecast text with environmental details"""
- try:
- current = results['current_prediction']
- env_data = results['environmental_data']
- route_forecast = results['route_forecast']
-
- # Environmental analysis
- env_analysis_text = f"""
-
-ENHANCED ENVIRONMENTAL ANALYSIS
-{'='*65}
-
-REAL-TIME OCEANIC CONDITIONS:
-• SST Data Source: {env_data.get('sst_source', 'Unknown')}
-• SLP Data Source: {env_data.get('slp_source', 'Unknown')}
-• Real-time Integration: {'✅ Active' if env_data.get('use_real_data', False) else '❌ Climatological Fallback'}
-
-ENVIRONMENTAL POTENTIAL ANALYSIS:
-• Genesis Potential: {current.get('environmental_potential', 'Unknown')} kt
-• Environmental Favorability: {current.get('environmental_favorability', 'Unknown')}
-• SST Contribution: {current.get('sst_contribution', 0):+.1f} kt
-• Current Environmental Limit: {current.get('environmental_potential', 50):.0f} kt
-
-TRACK-POINT ENVIRONMENTAL CONDITIONS:
-"""
-
- # Add sample of environmental conditions along track
- if route_forecast and len(route_forecast) > 0:
- sample_points = [0, len(route_forecast)//4, len(route_forecast)//2,
- 3*len(route_forecast)//4, len(route_forecast)-1]
-
- for i in sample_points:
- if i < len(route_forecast):
- point = route_forecast[i]
- env_analysis_text += f"""
-• Hour {point['hour']}:
- - Position: {point['lat']:.1f}°N, {point['lon']:.1f}°E
- - Intensity: {point['intensity_kt']:.0f} kt (Limit: {point.get('environmental_limit', 'N/A')} kt)
- - SST: {point.get('sst_celsius', 'N/A'):.1f}°C | SLP: {point.get('slp_hpa', 'N/A'):.0f} hPa
- - Development Stage: {point['development_stage']}
- - Tendency: {point.get('intensity_tendency', 0):+.1f} kt/6hr"""
-
- env_analysis_text += f"""
-
-OCEANIC DATA QUALITY ASSESSMENT:
-• Position Confidence: {results['confidence_scores'].get('position_72h', 0.5)*100:.0f}% (72hr)
-• Intensity Confidence: {results['confidence_scores'].get('intensity_72h', 0.5)*100:.0f}% (72hr)
-• Environmental Coupling: {results['confidence_scores'].get('environmental_coupling', 0.5)*100:.0f}%
-
-TECHNICAL IMPLEMENTATION:
-• Model: {results['model_info']}
-• Data Protocols: ERDDAP (SST) + OPeNDAP (SLP)
-• Spatial Interpolation: Linear with nearest-neighbor fallback
-• Physics: Emanuel potential intensity + environmental coupling
- """
-
- return base_forecast_text + env_analysis_text
-
- except Exception as e:
- logging.error(f"Error generating enhanced forecast text: {e}")
- return base_forecast_text + f"\n\nError in environmental analysis: {str(e)}"
+ return generate_enhanced_track_video(year, typhoon, standard)
# -----------------------------
# Load & Process Data
@@ -3573,13 +2863,9 @@ merged_data = None
def initialize_data():
"""Initialize all data safely"""
- global oni_data, typhoon_data, merged_data, oceanic_manager
+ global oni_data, typhoon_data, merged_data
try:
logging.info("Starting data loading process...")
-
- # Initialize oceanic manager
- oceanic_manager = OceanicDataManager()
-
update_oni_data()
oni_data, typhoon_data = load_data_fixed(ONI_DATA_PATH, TYPHOON_DATA_PATH)
@@ -3609,8 +2895,15 @@ def initialize_data():
typhoon_max = process_typhoon_data(typhoon_data)
merged_data = merge_data(oni_long, typhoon_max)
+# Initialize data
+initialize_data()
+
+# -----------------------------
+# ENHANCED: Gradio Interface with Fixed Route Visualization and Enhanced Features
+# -----------------------------
+
def create_interface():
- """Create the enhanced Gradio interface with oceanic data integration"""
+ """Create the enhanced Gradio interface with robust error handling"""
try:
# Ensure data is available
if oni_data is None or typhoon_data is None or merged_data is None:
@@ -3630,302 +2923,111 @@ def create_interface():
year_range_display = "Unknown"
available_years = [str(year) for year in range(2000, 2026)]
- with gr.Blocks(title="Enhanced Typhoon Analysis Platform with Oceanic Data", theme=gr.themes.Soft()) as demo:
- gr.Markdown("# 🌊 Enhanced Typhoon Analysis Platform with Real-time Oceanic Data")
- gr.Markdown("**Advanced ML clustering, real-time SST/SLP integration, route predictions, and comprehensive tropical cyclone analysis**")
+ with gr.Blocks(title="Enhanced Typhoon Analysis Platform", theme=gr.themes.Soft()) as demo:
+ gr.Markdown("# 🌪️ Enhanced Typhoon Analysis Platform")
+ gr.Markdown("**Advanced ML clustering, route predictions, and comprehensive tropical cyclone analysis including Tropical Depressions**")
with gr.Tab("🏠 Overview"):
overview_text = f"""
- ## 🌊 Welcome to the Enhanced Typhoon Analysis Dashboard with Oceanic Coupling
-
- This dashboard provides comprehensive analysis of typhoon data with **real-time oceanic data integration** for unprecedented forecast accuracy.
-
- ### 🚀 NEW Oceanic Data Features:
- - **🌊 Real-time SST Data**: NOAA OISST v2 Sea Surface Temperature via ERDDAP
- - **🌡️ Real-time SLP Data**: NCEP/NCAR Sea Level Pressure via OPeNDAP
- - **🔄 Dynamic Environmental Coupling**: Live oceanic conditions drive intensity predictions
- - **📊 Historical Environmental Analysis**: Past storm-environment relationships inform predictions
- - **🎯 Environmental Potential Index**: Real-time calculation of maximum possible intensity
- - **🌍 Global Data Coverage**: Automatic fallback to climatology when real-time data unavailable
-
- ### 📊 Enhanced Capabilities:
- - **Environmental Intensity Modeling**: SST-driven maximum potential intensity calculations
- - **Dynamic Steering**: SLP-based atmospheric steering patterns
- - **ENSO-Environment Coupling**: Combined ENSO and oceanic state influences
- - **Uncertainty Quantification**: Data quality-based confidence scoring
- - **Multi-source Integration**: Seamless blending of real-time and climatological data
+ ## Welcome to the Enhanced Typhoon Analysis Dashboard
+
+ This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
+
+ ### 🚀 Enhanced Features:
+ - **Advanced ML Clustering**: UMAP/t-SNE storm pattern analysis with separate visualizations
+ - **Predictive Routing**: Advanced storm track and intensity forecasting with uncertainty quantification
+ - **Complete TD Support**: Now includes Tropical Depressions (< 34 kt)
+ - **Taiwan Standard**: Full support for Taiwan meteorological classification system
+ - **2025 Data Ready**: Real-time compatibility with current year data
+ - **Enhanced Animations**: High-quality storm track visualizations with both standards
### 📊 Data Status:
- **ONI Data**: {len(oni_data)} years loaded
- - **Typhoon Data**: {total_records:,} records loaded
- - **Oceanic Data Sources**: NOAA OISST v2 + NCEP/NCAR Reanalysis
+ - **Typhoon Data**: {total_records:,} records loaded
+ - **Merged Data**: {len(merged_data):,} typhoons with ONI values
- **Available Years**: {year_range_display}
- ### 🔧 Technical Infrastructure:
- - **Real-time Data Access**: xarray + OPeNDAP + ERDDAP protocols
- - **Environmental Interpolation**: Spatial interpolation to storm locations
- - **Physics-based Modeling**: Emanuel potential intensity theory implementation
- - **Fallback Systems**: Robust climatological backup when real-time data unavailable
+ ### 🔧 Technical Capabilities:
+ - **UMAP Clustering**: {"✅ Available" if UMAP_AVAILABLE else "⚠️ Limited to t-SNE/PCA"}
+ - **AI Predictions**: {"🧠 Deep Learning" if CNN_AVAILABLE else "🔬 Physics-based"}
+ - **Enhanced Categorization**: Tropical Depression to Super Typhoon
+ - **Platform**: Optimized for Hugging Face Spaces
- ### 🔬 Scientific Accuracy:
- - **SST-Intensity Relationship**: Based on latest tropical cyclone research
- - **Shear Parameterization**: ENSO and seasonal wind shear modeling
- - **Genesis Climatology**: Realistic development regions and frequencies
- - **Track Forecasting**: Environmental steering with oceanic state dependencies
+ ### 📈 Research Applications:
+ - Climate change impact studies
+ - Seasonal forecasting research
+ - Storm pattern classification
+ - ENSO-typhoon relationship analysis
+ - Intensity prediction model development
"""
gr.Markdown(overview_text)
- with gr.Tab("🌊 Real-time Oceanic Storm Prediction"):
- gr.Markdown("## 🌊 Advanced Storm Development with Live Oceanic Data")
-
- gr.Markdown("""
- ### 🔥 Revolutionary Features:
- - **🌊 Live SST Integration**: Current sea surface temperatures from NOAA satellites
- - **🌡️ Real-time SLP Data**: Current atmospheric pressure from global reanalysis
- - **🎯 Environmental Potential**: Real-time calculation of maximum storm intensity
- - **📈 Historical Learning**: Past storm-environment relationships guide predictions
- - **🌍 Global Coverage**: Automatic data fetching with intelligent fallbacks
- """)
+ with gr.Tab("🌊 Monthly Typhoon Genesis Prediction"):
+ gr.Markdown("## 🌊 Monthly Typhoon Genesis Prediction")
+ gr.Markdown("**Enter month (1-12) and ONI value to see realistic typhoon development throughout the month using Genesis Potential Index**")
with gr.Row():
- with gr.Column(scale=2):
- gr.Markdown("### 🌊 Genesis & Environmental Configuration")
-
- genesis_options = list(get_realistic_genesis_locations().keys())
- genesis_region = gr.Dropdown(
- choices=genesis_options,
- value="Western Pacific Main Development Region",
- label="🌊 Typhoon Genesis Region",
- info="Climatologically realistic development regions"
- )
-
- # Enhanced environmental controls
- with gr.Row():
- use_real_oceanic = gr.Checkbox(
- label="🌊 Use Real-time Oceanic Data",
- value=True,
- info="Fetch live SST/SLP data (may take 10-30 seconds)"
- )
- show_environmental_details = gr.Checkbox(
- label="📊 Show Environmental Analysis",
- value=True,
- info="Display detailed environmental breakdown"
- )
-
- # Display selected region info with real-time data status
- def update_genesis_info_enhanced(region):
- locations = get_realistic_genesis_locations()
- if region in locations:
- info = locations[region]
- base_info = f"📍 Location: {info['lat']:.1f}°N, {info['lon']:.1f}°E\n📝 {info['description']}"
-
- # Add climatological information
- clim_sst = get_climatological_sst(info['lat'], info['lon'], 9) # September
- env_potential = calculate_environmental_intensity_potential(
- info['lat'], info['lon'], 9, 0.0, None, None
- )
-
- enhanced_info = (
- f"{base_info}\n"
- f"🌡️ Climatological SST: {clim_sst:.1f}°C\n"
- f"⚡ Environmental Potential: {env_potential['potential_intensity']:.0f} kt"
- )
- return enhanced_info
- return "Select a genesis region"
-
- genesis_info_display = gr.Textbox(
- label="Selected Region Analysis",
- lines=4,
- interactive=False,
- value=update_genesis_info_enhanced("Western Pacific Main Development Region")
+ with gr.Column(scale=1):
+ genesis_month = gr.Slider(
+ 1, 12,
+ label="Month",
+ value=9,
+ step=1,
+ info="1=Jan, 2=Feb, ..., 12=Dec"
)
-
- genesis_region.change(
- fn=update_genesis_info_enhanced,
- inputs=[genesis_region],
- outputs=[genesis_info_display]
+ genesis_oni = gr.Number(
+ label="ONI Value",
+ value=0.0,
+ info="El Niño (+) / La Niña (-) / Neutral (0)"
)
-
- with gr.Row():
- pred_month = gr.Slider(
- 1, 12, label="Month", value=9,
- info="Peak season: Jul-Oct (affects SST/shear patterns)"
- )
- pred_oni = gr.Number(
- label="ONI Value", value=0.0,
- info="Current ENSO state (-3 to 3, affects oceanic patterns)"
- )
-
- with gr.Row():
- forecast_hours = gr.Number(
- label="Forecast Length (hours)",
- value=72,
- minimum=24,
- maximum=240,
- step=6,
- info="Extended forecasting with environmental evolution"
- )
- advanced_physics = gr.Checkbox(
- label="Advanced Environmental Physics",
- value=True,
- info="Full SST-intensity coupling and wind shear modeling"
- )
-
- with gr.Row():
- show_uncertainty = gr.Checkbox(
- label="Environmental Uncertainty Cone",
- value=True,
- info="Uncertainty based on data quality and environmental variability"
- )
- enable_animation = gr.Checkbox(
- label="Animated Development",
- value=True,
- info="Watch storm-environment interaction evolve"
- )
-
- with gr.Column(scale=1):
- gr.Markdown("### ⚙️ Oceanic Prediction Controls")
- predict_oceanic_btn = gr.Button(
- "🌊 Generate Enhanced Oceanic Forecast",
- variant="primary",
- size="lg"
+ enable_genesis_animation = gr.Checkbox(
+ label="Enable Animation",
+ value=True,
+ info="Watch daily genesis potential evolution"
)
-
- gr.Markdown("### 📊 Environmental Conditions")
- current_intensity = gr.Number(label="Genesis Intensity (kt)", interactive=False)
- current_category = gr.Textbox(label="Initial Category", interactive=False)
- environmental_potential = gr.Number(label="Environmental Potential (kt)", interactive=False)
- environmental_favorability = gr.Textbox(label="Environmental Favorability", interactive=False)
-
- gr.Markdown("### 🔧 Data Sources")
- sst_data_source = gr.Textbox(label="SST Data Source", interactive=False)
- slp_data_source = gr.Textbox(label="SLP Data Source", interactive=False)
- model_confidence = gr.Textbox(label="Model Info", interactive=False)
+ generate_genesis_btn = gr.Button("🌊 Generate Monthly Genesis Prediction", variant="primary", size="lg")
+
+ with gr.Column(scale=2):
+ gr.Markdown("### 🌊 What You'll Get:")
+ gr.Markdown("""
+ - **Daily GPI Evolution**: See genesis potential change day-by-day throughout the month
+ - **Genesis Event Detection**: Automatic identification of likely cyclogenesis times/locations
+ - **Storm Track Development**: Physics-based tracks from each genesis point
+ - **Real-time Animation**: Watch storms develop and move with uncertainty visualization
+ - **Environmental Analysis**: SST, humidity, wind shear, and vorticity effects
+ - **ENSO Modulation**: How El Niño/La Niña affects monthly patterns
+ """)
with gr.Row():
- route_plot = gr.Plot(label="🗺️ Advanced Oceanic-Coupled Forecast")
+ genesis_animation = gr.Plot(label="🗺️ Daily Genesis Potential & Storm Development")
with gr.Row():
- forecast_details = gr.Textbox(
- label="📋 Comprehensive Environmental Forecast",
- lines=25,
- max_lines=30
- )
+ genesis_summary = gr.Textbox(label="📋 Monthly Genesis Analysis Summary", lines=25)
- def run_oceanic_prediction(
- region, month, oni, hours, advanced_phys, uncertainty,
- animation, use_real_data, show_env_details
- ):
+ def run_genesis_prediction(month, oni, animation):
try:
- # Run enhanced oceanic prediction
- results = predict_storm_route_and_intensity_with_oceanic_data(
- region, month, oni, hours,
- use_real_data=use_real_data,
- models=None,
- enable_animation=animation
- )
-
- # Extract enhanced conditions
- current = results['current_prediction']
- env_data = results['environmental_data']
-
- intensity = current['intensity_kt']
- category = current['category']
- env_potential = current.get('environmental_potential', 50)
- env_favorability = current.get('environmental_favorability', 'Unknown')
-
- # Data source information
- sst_source = env_data.get('sst_source', 'Unknown')
- slp_source = env_data.get('slp_source', 'Unknown')
-
- # Create enhanced visualization
- fig, forecast_text = create_animated_route_visualization(
- results, uncertainty, animation
- )
+ # Generate monthly prediction using GPI
+ prediction_data = generate_genesis_prediction_monthly(month, oni, year=2025)
- # Enhanced forecast text with environmental details
- if show_env_details:
- enhanced_forecast_text = generate_enhanced_environmental_forecast_text(
- results, forecast_text
- )
- else:
- enhanced_forecast_text = forecast_text
+ # Create animation
+ genesis_fig = create_genesis_animation(prediction_data, animation)
- model_info = f"{results['model_info']}\nReal-time Data: {'Yes' if use_real_data else 'No'}"
+ # Generate summary
+ summary_text = create_prediction_summary(prediction_data)
- return (
- intensity,
- category,
- env_potential,
- env_favorability,
- sst_source,
- slp_source,
- model_info,
- fig,
- enhanced_forecast_text
- )
+ return genesis_fig, summary_text
except Exception as e:
- error_msg = f"Enhanced oceanic prediction failed: {str(e)}"
- logging.error(error_msg)
import traceback
- traceback.print_exc()
-
- return (
- 30, "Tropical Depression", 50, "Unknown",
- "Error", "Error", f"Prediction failed: {str(e)}",
- None, f"Error generating enhanced forecast: {str(e)}"
- )
+ error_msg = f"Genesis prediction failed: {str(e)}\n\nDetails:\n{traceback.format_exc()}"
+ logging.error(error_msg)
+ return create_error_plot(error_msg), error_msg
- predict_oceanic_btn.click(
- fn=run_oceanic_prediction,
- inputs=[
- genesis_region, pred_month, pred_oni, forecast_hours,
- advanced_physics, show_uncertainty, enable_animation,
- use_real_oceanic, show_environmental_details
- ],
- outputs=[
- current_intensity, current_category, environmental_potential,
- environmental_favorability, sst_data_source, slp_data_source,
- model_confidence, route_plot, forecast_details
- ]
+ generate_genesis_btn.click(
+ fn=run_genesis_prediction,
+ inputs=[genesis_month, genesis_oni, enable_genesis_animation],
+ outputs=[genesis_animation, genesis_summary]
)
-
- # Enhanced information section
- oceanic_info_text = """
- ### 🌊 Oceanic Data Integration Features:
-
- #### 🔥 Real-time Data Sources:
- - **SST**: NOAA OISST v2 - Daily 0.25° resolution satellite-based sea surface temperatures
- - **SLP**: NCEP/NCAR Reanalysis - 6-hourly 2.5° resolution atmospheric pressure fields
- - **Coverage**: Global oceans with 1-2 day latency for most recent conditions
- - **Protocols**: ERDDAP and OPeNDAP for standardized data access
-
- #### 🧠 Environmental Physics:
- - **Emanuel Potential Intensity**: Theoretical maximum intensity based on thermodynamics
- - **SST-Intensity Coupling**: Non-linear relationship between sea surface temperature and storm intensity
- - **Atmospheric Steering**: Sea level pressure gradients drive storm motion patterns
- - **Wind Shear Modeling**: Vertical wind shear estimation from pressure patterns and ENSO state
-
- #### 🎯 Enhanced Accuracy:
- - **Real-time Environmental Limits**: Current oceanic conditions set maximum achievable intensity
- - **Dynamic Development**: Storm intensification rate depends on real SST and atmospheric conditions
- - **Track Steering**: Motion influenced by current pressure patterns rather than climatology alone
- - **Confidence Scoring**: Higher confidence when real-time data successfully integrated
-
- #### 🔄 Fallback Systems:
- - **Automatic Degradation**: Seamlessly switches to climatology if real-time data unavailable
- - **Quality Assessment**: Evaluates data completeness and provides appropriate confidence levels
- - **Hybrid Approach**: Combines real-time data with climatological patterns for optimal accuracy
- - **Error Handling**: Robust system continues operation even with partial data failures
-
- #### 📊 Output Enhancements:
- - **Environmental Metadata**: Track-point SST, SLP, and environmental limits
- - **Data Source Tracking**: Clear indication of real-time vs climatological data usage
- - **Uncertainty Quantification**: Confidence intervals based on data availability and environmental complexity
- - **Detailed Analysis**: Comprehensive breakdown of environmental factors affecting development
- """
- gr.Markdown(oceanic_info_text)
with gr.Tab("🔬 Advanced ML Clustering"):
gr.Markdown("## 🎯 Storm Pattern Analysis with Separate Visualizations")
@@ -4084,9 +3186,9 @@ def create_interface():
outputs=[typhoon_dropdown]
)
- # Generate video with fixed function
+ # Generate video
generate_video_btn.click(
- fn=generate_enhanced_track_video_fixed,
+ fn=generate_enhanced_track_video,
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
outputs=[video_output]
)
@@ -4153,7 +3255,7 @@ def create_interface():
except Exception as e:
gr.Markdown(f"Visualization error: {str(e)}")
- # Enhanced statistics
+ # Enhanced statistics - FIXED formatting
total_storms = len(typhoon_data['SID'].unique()) if 'SID' in typhoon_data.columns else 0
total_records = len(typhoon_data)
@@ -4214,7 +3316,7 @@ def create_interface():
- **Complete TD Analysis** - First platform to include comprehensive TD tracking
- **Dual Classification Systems** - Both Atlantic and Taiwan standards supported
- **Advanced ML Clustering** - DBSCAN pattern recognition with separate visualizations
- - **Real-time Oceanic Predictions** - Physics-based with SST/SLP integration
+ - **Real-time Predictions** - Physics-based and optional CNN intensity forecasting
- **2025 Data Ready** - Full compatibility with current season data
- **Enhanced Animations** - Professional-quality storm track videos
- **Multi-basin Analysis** - Comprehensive Pacific and Atlantic coverage
@@ -4224,13 +3326,12 @@ def create_interface():
- Seasonal forecasting research
- Storm pattern classification
- ENSO-typhoon relationship analysis
- - Oceanic-atmospheric coupling research
+ - Intensity prediction model development
- Cross-regional classification comparisons
"""
gr.Markdown(stats_text)
return demo
-
except Exception as e:
logging.error(f"Error creating Gradio interface: {e}")
import traceback
@@ -4294,12 +3395,8 @@ def create_minimal_fallback_interface():
return demo
-# Initialize data
-initialize_data()
-
# Create and launch the interface
demo = create_interface()
if __name__ == "__main__":
- demo.launch(share=True)
-
+ demo.launch(share=True) # Enable sharing with public link
\ No newline at end of file