Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1587,6 +1587,20 @@ def create_advanced_prediction_model(typhoon_data):
|
|
1587 |
except Exception as e:
|
1588 |
return None, f"Error creating prediction model: {str(e)}"
|
1589 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1590 |
def get_realistic_genesis_locations():
|
1591 |
"""Get realistic typhoon genesis regions based on climatology"""
|
1592 |
return {
|
@@ -1603,7 +1617,7 @@ def get_realistic_genesis_locations():
|
|
1603 |
}
|
1604 |
|
1605 |
def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value, models=None, forecast_hours=72, use_advanced_physics=True):
|
1606 |
-
"""Realistic prediction
|
1607 |
try:
|
1608 |
genesis_locations = get_realistic_genesis_locations()
|
1609 |
|
@@ -1623,11 +1637,11 @@ def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value
|
|
1623 |
}
|
1624 |
|
1625 |
# REALISTIC starting intensity - Tropical Depression level
|
1626 |
-
base_intensity =
|
1627 |
|
1628 |
-
# Environmental factors for genesis
|
1629 |
if oni_value > 1.0: # Strong El Niño - suppressed development
|
1630 |
-
intensity_modifier = -
|
1631 |
elif oni_value > 0.5: # Moderate El Niño
|
1632 |
intensity_modifier = -3
|
1633 |
elif oni_value < -1.0: # Strong La Niña - enhanced development
|
@@ -1637,142 +1651,193 @@ def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value
|
|
1637 |
else: # Neutral
|
1638 |
intensity_modifier = oni_value * 2
|
1639 |
|
1640 |
-
# Seasonal genesis effects
|
1641 |
seasonal_factors = {
|
1642 |
-
1: -8, 2: -6, 3: -4, 4: -2, 5: 2, 6:
|
1643 |
-
7:
|
1644 |
}
|
1645 |
seasonal_modifier = seasonal_factors.get(month, 0)
|
1646 |
|
1647 |
# Genesis region favorability
|
1648 |
region_factors = {
|
1649 |
-
"Western Pacific Main Development Region":
|
1650 |
-
"South China Sea":
|
1651 |
-
"Philippine Sea":
|
1652 |
-
"Marshall Islands":
|
1653 |
-
"Monsoon Trough":
|
1654 |
-
"ITCZ Region":
|
1655 |
-
"Subtropical Region":
|
1656 |
-
"Bay of Bengal":
|
1657 |
-
"Eastern Pacific":
|
1658 |
-
"Atlantic MDR":
|
1659 |
}
|
1660 |
region_modifier = region_factors.get(genesis_region, 0)
|
1661 |
|
1662 |
# Calculate realistic starting intensity (TD level)
|
1663 |
predicted_intensity = base_intensity + intensity_modifier + seasonal_modifier + region_modifier
|
1664 |
-
predicted_intensity = max(25, min(40, predicted_intensity)) #
|
1665 |
|
1666 |
# Add realistic uncertainty for genesis
|
1667 |
intensity_uncertainty = np.random.normal(0, 2)
|
1668 |
predicted_intensity += intensity_uncertainty
|
1669 |
-
predicted_intensity = max(25, min(38, predicted_intensity)) #
|
1670 |
|
1671 |
results['current_prediction'] = {
|
1672 |
'intensity_kt': predicted_intensity,
|
1673 |
-
'pressure_hpa': 1008 - (predicted_intensity - 25) * 0.
|
1674 |
'category': categorize_typhoon_enhanced(predicted_intensity),
|
1675 |
'genesis_region': genesis_region
|
1676 |
}
|
1677 |
|
1678 |
-
#
|
1679 |
current_lat = lat
|
1680 |
current_lon = lon
|
1681 |
current_intensity = predicted_intensity
|
1682 |
|
1683 |
route_points = []
|
1684 |
|
1685 |
-
# Track storm development over time
|
1686 |
for hour in range(0, forecast_hours + 6, 6):
|
1687 |
|
1688 |
-
#
|
1689 |
-
#
|
1690 |
-
beta_drift_lat = 0.03 * np.sin(np.radians(current_lat))
|
1691 |
-
beta_drift_lon = -0.08 * np.cos(np.radians(current_lat))
|
1692 |
|
1693 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1694 |
if month in [6, 7, 8, 9]: # Peak season
|
1695 |
-
ridge_strength = 1.
|
1696 |
-
ridge_position =
|
1697 |
else: # Off season
|
1698 |
-
ridge_strength = 0.
|
1699 |
-
ridge_position =
|
1700 |
|
1701 |
-
#
|
1702 |
-
if current_lat < ridge_position -
|
1703 |
-
lat_tendency = 0.
|
1704 |
-
lon_tendency = -0.
|
1705 |
elif current_lat > ridge_position - 3: # Near ridge - recurvature
|
1706 |
-
lat_tendency = 0.
|
1707 |
-
lon_tendency = 0.
|
1708 |
-
else: # In between
|
1709 |
-
lat_tendency = 0.
|
1710 |
-
lon_tendency = -0.
|
1711 |
-
|
1712 |
-
# ENSO steering modulation
|
1713 |
-
if oni_value > 0.5: # El Niño - more eastward motion
|
1714 |
-
lon_tendency += 0.
|
1715 |
-
|
|
|
1716 |
lon_tendency -= 0.08
|
|
|
1717 |
|
1718 |
-
# Add motion uncertainty that grows with time
|
1719 |
-
motion_uncertainty = 0.02 + (hour /
|
1720 |
lat_noise = np.random.normal(0, motion_uncertainty)
|
1721 |
lon_noise = np.random.normal(0, motion_uncertainty)
|
1722 |
|
1723 |
-
# Update position
|
1724 |
current_lat += lat_tendency + lat_noise
|
1725 |
current_lon += lon_tendency + lon_noise
|
1726 |
|
1727 |
-
# REALISTIC intensity evolution
|
1728 |
|
1729 |
-
# Development phase (first 48-72 hours)
|
1730 |
if hour <= 48:
|
1731 |
-
if current_intensity <
|
1732 |
-
if
|
1733 |
-
intensity_tendency =
|
|
|
|
|
1734 |
else:
|
1735 |
-
intensity_tendency =
|
|
|
|
|
1736 |
else: # Already strong
|
1737 |
-
intensity_tendency = 0
|
1738 |
-
|
1739 |
-
|
1740 |
-
|
1741 |
-
|
|
|
|
|
|
|
|
|
1742 |
else:
|
1743 |
-
intensity_tendency = -1.
|
1744 |
-
|
|
|
1745 |
else:
|
1746 |
-
|
|
|
|
|
|
|
1747 |
|
1748 |
-
# Environmental modulation
|
1749 |
if current_lat > 35: # High latitude - rapid weakening
|
1750 |
-
intensity_tendency -=
|
1751 |
elif current_lat > 30: # Moderate latitude
|
1752 |
-
intensity_tendency -=
|
1753 |
-
elif current_lon <
|
1754 |
-
intensity_tendency -=
|
1755 |
elif 125 <= current_lon <= 155 and 10 <= current_lat <= 25: # Warm pool
|
|
|
|
|
1756 |
intensity_tendency += 1
|
1757 |
|
1758 |
-
# SST effects (realistic)
|
1759 |
-
if current_lat < 8: # Very warm but
|
1760 |
intensity_tendency += 0.5
|
1761 |
-
elif 8 <= current_lat <= 20: # Sweet spot
|
1762 |
-
intensity_tendency +=
|
1763 |
-
elif current_lat
|
1764 |
-
intensity_tendency -=
|
1765 |
-
|
1766 |
-
|
1767 |
-
|
1768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1769 |
|
1770 |
# Calculate confidence based on forecast time and environment
|
1771 |
-
base_confidence = 0.
|
1772 |
-
time_penalty = (hour /
|
1773 |
-
environment_penalty = 0.
|
1774 |
confidence = max(0.25, base_confidence - time_penalty - environment_penalty)
|
1775 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1776 |
route_points.append({
|
1777 |
'hour': hour,
|
1778 |
'lat': current_lat,
|
@@ -1780,26 +1845,28 @@ def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value
|
|
1780 |
'intensity_kt': current_intensity,
|
1781 |
'category': categorize_typhoon_enhanced(current_intensity),
|
1782 |
'confidence': confidence,
|
1783 |
-
'development_stage':
|
|
|
|
|
1784 |
})
|
1785 |
|
1786 |
results['route_forecast'] = route_points
|
1787 |
|
1788 |
# Realistic confidence scores
|
1789 |
results['confidence_scores'] = {
|
1790 |
-
'genesis': 0.
|
1791 |
-
'early_development': 0.
|
1792 |
'position_24h': 0.85,
|
1793 |
-
'position_48h': 0.
|
1794 |
-
'position_72h': 0.
|
1795 |
-
'intensity_24h': 0.
|
1796 |
-
'intensity_48h': 0.
|
1797 |
-
'intensity_72h': 0.
|
1798 |
-
'long_term': max(0.3, 0.8 - (forecast_hours /
|
1799 |
}
|
1800 |
|
1801 |
# Model information
|
1802 |
-
results['model_info'] = f"Realistic
|
1803 |
|
1804 |
return results
|
1805 |
|
@@ -1814,7 +1881,7 @@ def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value
|
|
1814 |
}
|
1815 |
|
1816 |
def create_animated_route_visualization(prediction_results, show_uncertainty=True, enable_animation=True):
|
1817 |
-
"""Create animated
|
1818 |
try:
|
1819 |
if 'route_forecast' not in prediction_results or not prediction_results['route_forecast']:
|
1820 |
return None, "No route forecast data available"
|
@@ -1829,11 +1896,24 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1829 |
categories = [point['category'] for point in route_data]
|
1830 |
confidences = [point.get('confidence', 0.8) for point in route_data]
|
1831 |
stages = [point.get('development_stage', 'Unknown') for point in route_data]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1832 |
|
1833 |
if enable_animation:
|
1834 |
-
#
|
1835 |
-
|
1836 |
|
|
|
1837 |
# Add complete track as background
|
1838 |
fig.add_trace(
|
1839 |
go.Scattergeo(
|
@@ -1843,35 +1923,62 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1843 |
line=dict(color='lightgray', width=2, dash='dot'),
|
1844 |
name='Complete Track',
|
1845 |
showlegend=True,
|
1846 |
-
opacity=0.
|
1847 |
-
)
|
|
|
1848 |
)
|
1849 |
|
1850 |
-
#
|
1851 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1852 |
for i in range(len(route_data)):
|
1853 |
frame_lons = lons[:i+1]
|
1854 |
frame_lats = lats[:i+1]
|
1855 |
frame_intensities = intensities[:i+1]
|
1856 |
frame_categories = categories[:i+1]
|
|
|
1857 |
|
1858 |
# Current position marker
|
1859 |
current_color = enhanced_color_map.get(frame_categories[-1], 'rgb(128,128,128)')
|
1860 |
current_size = 15 + (frame_intensities[-1] / 10)
|
1861 |
|
1862 |
frame_data = [
|
1863 |
-
#
|
1864 |
go.Scattergeo(
|
1865 |
lon=frame_lons,
|
1866 |
lat=frame_lats,
|
1867 |
mode='lines+markers',
|
1868 |
-
line=dict(color='blue', width=
|
1869 |
marker=dict(
|
1870 |
size=[8 + (intensity/15) for intensity in frame_intensities],
|
1871 |
color=[enhanced_color_map.get(cat, 'rgb(128,128,128)') for cat in frame_categories],
|
1872 |
-
opacity=0.8
|
|
|
1873 |
),
|
1874 |
-
name='Track',
|
1875 |
showlegend=False
|
1876 |
),
|
1877 |
# Current position highlight
|
@@ -1882,8 +1989,8 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1882 |
marker=dict(
|
1883 |
size=current_size,
|
1884 |
color=current_color,
|
1885 |
-
symbol='
|
1886 |
-
line=dict(width=
|
1887 |
),
|
1888 |
name='Current Position',
|
1889 |
showlegend=False,
|
@@ -1893,9 +2000,43 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1893 |
f"Intensity: {intensities[i]:.0f} kt<br>"
|
1894 |
f"Category: {categories[i]}<br>"
|
1895 |
f"Stage: {stages[i]}<br>"
|
|
|
1896 |
f"Confidence: {confidences[i]*100:.0f}%<br>"
|
1897 |
"<extra></extra>"
|
1898 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1899 |
)
|
1900 |
]
|
1901 |
|
@@ -1903,20 +2044,20 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1903 |
data=frame_data,
|
1904 |
name=str(i),
|
1905 |
layout=go.Layout(
|
1906 |
-
title=f"Storm
|
1907 |
-
f"Intensity: {intensities[i]:.0f} kt | Category: {categories[i]} | Stage: {stages[i]}"
|
1908 |
)
|
1909 |
))
|
1910 |
|
1911 |
fig.frames = frames
|
1912 |
|
1913 |
-
# Add play/pause
|
1914 |
fig.update_layout(
|
1915 |
updatemenus=[
|
1916 |
{
|
1917 |
"buttons": [
|
1918 |
{
|
1919 |
-
"args": [None, {"frame": {"duration":
|
1920 |
"fromcurrent": True, "transition": {"duration": 300}}],
|
1921 |
"label": "▶️ Play",
|
1922 |
"method": "animate"
|
@@ -1926,6 +2067,12 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1926 |
"mode": "immediate", "transition": {"duration": 0}}],
|
1927 |
"label": "⏸️ Pause",
|
1928 |
"method": "animate"
|
|
|
|
|
|
|
|
|
|
|
|
|
1929 |
}
|
1930 |
],
|
1931 |
"direction": "left",
|
@@ -1943,8 +2090,8 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1943 |
"yanchor": "top",
|
1944 |
"xanchor": "left",
|
1945 |
"currentvalue": {
|
1946 |
-
"font": {"size":
|
1947 |
-
"prefix": "Hour:",
|
1948 |
"visible": True,
|
1949 |
"xanchor": "right"
|
1950 |
},
|
@@ -1957,50 +2104,46 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
1957 |
{
|
1958 |
"args": [[str(i)], {"frame": {"duration": 300, "redraw": True},
|
1959 |
"mode": "immediate", "transition": {"duration": 300}}],
|
1960 |
-
"label": f"
|
1961 |
"method": "animate"
|
1962 |
}
|
1963 |
-
for i in range(len(route_data))
|
1964 |
]
|
1965 |
}]
|
1966 |
)
|
1967 |
|
1968 |
else:
|
1969 |
-
#
|
1970 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1971 |
|
1972 |
# Add full track with intensity coloring
|
1973 |
-
for i in range(len(route_data)):
|
1974 |
point = route_data[i]
|
1975 |
color = enhanced_color_map.get(point['category'], 'rgb(128,128,128)')
|
1976 |
size = 8 + (point['intensity_kt'] / 12)
|
1977 |
|
1978 |
-
# Genesis point special marker
|
1979 |
-
if i == 0:
|
1980 |
-
fig.add_trace(
|
1981 |
-
go.Scattergeo(
|
1982 |
-
lon=[point['lon']],
|
1983 |
-
lat=[point['lat']],
|
1984 |
-
mode='markers',
|
1985 |
-
marker=dict(
|
1986 |
-
size=20,
|
1987 |
-
color='yellow',
|
1988 |
-
symbol='star',
|
1989 |
-
line=dict(width=3, color='black')
|
1990 |
-
),
|
1991 |
-
name='Genesis',
|
1992 |
-
showlegend=True,
|
1993 |
-
hovertemplate=(
|
1994 |
-
f"<b>GENESIS - {prediction_results['genesis_info']['description']}</b><br>"
|
1995 |
-
f"Position: {point['lat']:.1f}°N, {point['lon']:.1f}°E<br>"
|
1996 |
-
f"Initial Intensity: {point['intensity_kt']:.0f} kt<br>"
|
1997 |
-
f"Category: {point['category']}<br>"
|
1998 |
-
"<extra></extra>"
|
1999 |
-
)
|
2000 |
-
)
|
2001 |
-
)
|
2002 |
-
|
2003 |
-
# Regular track points
|
2004 |
fig.add_trace(
|
2005 |
go.Scattergeo(
|
2006 |
lon=[point['lon']],
|
@@ -2012,18 +2155,19 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
2012 |
opacity=point.get('confidence', 0.8),
|
2013 |
line=dict(width=1, color='white')
|
2014 |
),
|
2015 |
-
name=f"Hour {point['hour']}" if i %
|
2016 |
-
showlegend=(i %
|
2017 |
hovertemplate=(
|
2018 |
f"<b>Hour {point['hour']}</b><br>"
|
2019 |
f"Position: {point['lat']:.1f}°N, {point['lon']:.1f}°E<br>"
|
2020 |
f"Intensity: {point['intensity_kt']:.0f} kt<br>"
|
2021 |
f"Category: {point['category']}<br>"
|
2022 |
f"Stage: {point.get('development_stage', 'Unknown')}<br>"
|
2023 |
-
f"
|
2024 |
"<extra></extra>"
|
2025 |
)
|
2026 |
-
)
|
|
|
2027 |
)
|
2028 |
|
2029 |
# Connect points with track line
|
@@ -2035,9 +2179,57 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
2035 |
line=dict(color='black', width=3),
|
2036 |
name='Forecast Track',
|
2037 |
showlegend=True
|
2038 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2039 |
)
|
2040 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2041 |
# Add uncertainty cone if requested
|
2042 |
if show_uncertainty and len(route_data) > 1:
|
2043 |
uncertainty_lats_upper = []
|
@@ -2047,7 +2239,7 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
2047 |
|
2048 |
for i, point in enumerate(route_data):
|
2049 |
# Uncertainty grows with time and decreases with confidence
|
2050 |
-
base_uncertainty = 0.
|
2051 |
confidence_factor = point.get('confidence', 0.8)
|
2052 |
uncertainty = base_uncertainty / confidence_factor
|
2053 |
|
@@ -2069,37 +2261,52 @@ def create_animated_route_visualization(prediction_results, show_uncertainty=Tru
|
|
2069 |
line=dict(color='rgba(128,128,128,0.4)', width=1),
|
2070 |
name='Uncertainty Cone',
|
2071 |
showlegend=True
|
2072 |
-
)
|
|
|
2073 |
)
|
2074 |
|
2075 |
-
# Enhanced layout
|
2076 |
fig.update_layout(
|
2077 |
-
title=f"
|
2078 |
-
|
2079 |
-
|
2080 |
-
showland=True,
|
2081 |
-
landcolor="LightGray",
|
2082 |
-
showocean=True,
|
2083 |
-
oceancolor="LightBlue",
|
2084 |
-
showcoastlines=True,
|
2085 |
-
coastlinecolor="DarkGray",
|
2086 |
-
showlakes=True,
|
2087 |
-
lakecolor="LightBlue",
|
2088 |
-
center=dict(lat=np.mean(lats), lon=np.mean(lons)),
|
2089 |
-
projection_scale=2.5
|
2090 |
-
),
|
2091 |
-
height=900, # Much larger
|
2092 |
-
width=1400, # Much wider
|
2093 |
showlegend=True
|
2094 |
)
|
2095 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2096 |
# Generate enhanced forecast text
|
2097 |
current = prediction_results['current_prediction']
|
2098 |
genesis_info = prediction_results['genesis_info']
|
2099 |
|
|
|
|
|
|
|
|
|
|
|
2100 |
forecast_text = f"""
|
2101 |
-
|
2102 |
-
{'='*
|
2103 |
|
2104 |
GENESIS CONDITIONS:
|
2105 |
• Region: {current.get('genesis_region', 'Unknown')}
|
@@ -2107,42 +2314,47 @@ GENESIS CONDITIONS:
|
|
2107 |
• Starting Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E
|
2108 |
• Initial Intensity: {current['intensity_kt']:.0f} kt (Tropical Depression)
|
2109 |
• Genesis Pressure: {current.get('pressure_hpa', 1008):.0f} hPa
|
2110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
2111 |
|
2112 |
DEVELOPMENT TIMELINE:
|
2113 |
• Hour 0 (Genesis): {intensities[0]:.0f} kt - {categories[0]}
|
2114 |
-
• Hour 24
|
2115 |
-
• Hour 48
|
2116 |
-
• Hour 72
|
2117 |
-
|
2118 |
-
|
2119 |
-
|
2120 |
-
•
|
2121 |
-
•
|
2122 |
-
•
|
2123 |
-
• Final: {lats[-1]:.1f}°N, {lons[-1]:.1f}°E
|
2124 |
-
|
2125 |
-
DEVELOPMENT STAGES:
|
2126 |
-
• Genesis (0-24h): Initial cyclogenesis and organization
|
2127 |
-
• Development (24-72h): Intensification in favorable environment
|
2128 |
-
• Mature (72-120h): Peak intensity and steady motion
|
2129 |
-
• Decay (120h+): Gradual weakening due to environmental factors
|
2130 |
|
2131 |
CONFIDENCE ASSESSMENT:
|
2132 |
• Genesis Likelihood: {prediction_results['confidence_scores'].get('genesis', 0.85)*100:.0f}%
|
2133 |
-
• Early Development: {prediction_results['confidence_scores'].get('early_development', 0.80)*100:.0f}%
|
2134 |
• 24-hour Track: {prediction_results['confidence_scores'].get('position_24h', 0.85)*100:.0f}%
|
2135 |
• 48-hour Track: {prediction_results['confidence_scores'].get('position_48h', 0.75)*100:.0f}%
|
2136 |
• 72-hour Track: {prediction_results['confidence_scores'].get('position_72h', 0.65)*100:.0f}%
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2137 |
|
2138 |
MODEL: {prediction_results['model_info']}
|
2139 |
-
ANIMATION: {"Enabled - Use controls below map" if enable_animation else "Static view - All time steps shown"}
|
2140 |
"""
|
2141 |
|
2142 |
return fig, forecast_text.strip()
|
2143 |
|
2144 |
except Exception as e:
|
2145 |
-
error_msg = f"Error creating
|
2146 |
logging.error(error_msg)
|
2147 |
import traceback
|
2148 |
traceback.print_exc()
|
@@ -2882,12 +3094,13 @@ def create_interface():
|
|
2882 |
pred_month = gr.Slider(1, 12, label="Month", value=9, info="Peak season: Jul-Oct")
|
2883 |
pred_oni = gr.Number(label="ONI Value", value=0.0, info="ENSO index (-3 to 3)")
|
2884 |
with gr.Row():
|
2885 |
-
forecast_hours = gr.
|
2886 |
-
6, 240,
|
2887 |
label="Forecast Length (hours)",
|
2888 |
value=72,
|
|
|
|
|
2889 |
step=6,
|
2890 |
-
info="Extended forecasting
|
2891 |
)
|
2892 |
advanced_physics = gr.Checkbox(
|
2893 |
label="Advanced Physics",
|
@@ -2965,17 +3178,26 @@ def create_interface():
|
|
2965 |
prediction_info_text = """
|
2966 |
### 🌊 Realistic Storm Genesis Features:
|
2967 |
- **Climatological Genesis Regions**: 10 realistic development zones based on historical data
|
2968 |
-
- **Tropical Depression Starting Point**: Storms begin at realistic 25-
|
2969 |
-
- **
|
2970 |
-
- **Extended
|
|
|
2971 |
- **Environmental Coupling**: Advanced physics with ENSO, SST, shear, β-drift, ridge patterns
|
2972 |
-
- **Realistic Intensity Evolution**: Natural intensification/weakening cycles, not just decay
|
2973 |
|
2974 |
### 📊 Enhanced Development Stages:
|
2975 |
- **Genesis (0-24h)**: Initial cyclogenesis at Tropical Depression level
|
2976 |
-
- **Development (24-72h)**:
|
2977 |
- **Mature (72-120h)**: Peak intensity phase with environmental modulation
|
2978 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2979 |
|
2980 |
### 🌍 Genesis Region Selection:
|
2981 |
- **Western Pacific MDR**: Peak activity zone near Guam (12.5°N, 145°E)
|
@@ -2985,20 +3207,21 @@ def create_interface():
|
|
2985 |
- **Monsoon Trough**: Monsoon-driven genesis (10°N, 130°E)
|
2986 |
- **Other Basins**: Bay of Bengal, Eastern Pacific, Atlantic options
|
2987 |
|
2988 |
-
###
|
2989 |
-
- **Beta Drift**: Coriolis-induced storm motion (latitude-dependent)
|
2990 |
-
- **Ridge Interaction**: Seasonal subtropical ridge position effects
|
2991 |
-
- **ENSO Modulation**: Non-linear intensity and steering effects
|
2992 |
-
- **SST Coupling**: Ocean temperature impact on intensification
|
2993 |
-
- **Shear Analysis**: Environmental wind shear impact assessment
|
2994 |
-
- **Recurvature Physics**: Advanced extratropical transition modeling
|
2995 |
-
|
2996 |
-
### 🎬 Animation Features:
|
2997 |
- **Real-time Development**: Watch TD evolve to typhoon intensity
|
2998 |
-
- **
|
|
|
2999 |
- **Stage Tracking**: Visual indicators for development phases
|
3000 |
-
- **
|
3001 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3002 |
"""
|
3003 |
gr.Markdown(prediction_info_text)
|
3004 |
|
|
|
1587 |
except Exception as e:
|
1588 |
return None, f"Error creating prediction model: {str(e)}"
|
1589 |
|
1590 |
+
def rgb_string_to_hex(rgb_string):
|
1591 |
+
"""Convert RGB string like 'rgb(255,0,0)' to hex format '#FF0000'"""
|
1592 |
+
try:
|
1593 |
+
if rgb_string.startswith('rgb(') and rgb_string.endswith(')'):
|
1594 |
+
# Extract numbers from rgb(r,g,b)
|
1595 |
+
rgb_values = rgb_string[4:-1].split(',')
|
1596 |
+
r, g, b = [int(x.strip()) for x in rgb_values]
|
1597 |
+
return f'#{r:02x}{g:02x}{b:02x}'
|
1598 |
+
else:
|
1599 |
+
# Already in hex or other format
|
1600 |
+
return rgb_string
|
1601 |
+
except:
|
1602 |
+
return '#808080' # Default gray
|
1603 |
+
|
1604 |
def get_realistic_genesis_locations():
|
1605 |
"""Get realistic typhoon genesis regions based on climatology"""
|
1606 |
return {
|
|
|
1617 |
}
|
1618 |
|
1619 |
def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value, models=None, forecast_hours=72, use_advanced_physics=True):
|
1620 |
+
"""Realistic prediction with proper typhoon speeds and development"""
|
1621 |
try:
|
1622 |
genesis_locations = get_realistic_genesis_locations()
|
1623 |
|
|
|
1637 |
}
|
1638 |
|
1639 |
# REALISTIC starting intensity - Tropical Depression level
|
1640 |
+
base_intensity = 30 # Start at TD level (25-35 kt)
|
1641 |
|
1642 |
+
# Environmental factors for genesis
|
1643 |
if oni_value > 1.0: # Strong El Niño - suppressed development
|
1644 |
+
intensity_modifier = -6
|
1645 |
elif oni_value > 0.5: # Moderate El Niño
|
1646 |
intensity_modifier = -3
|
1647 |
elif oni_value < -1.0: # Strong La Niña - enhanced development
|
|
|
1651 |
else: # Neutral
|
1652 |
intensity_modifier = oni_value * 2
|
1653 |
|
1654 |
+
# Seasonal genesis effects
|
1655 |
seasonal_factors = {
|
1656 |
+
1: -8, 2: -6, 3: -4, 4: -2, 5: 2, 6: 6,
|
1657 |
+
7: 10, 8: 12, 9: 15, 10: 10, 11: 4, 12: -5
|
1658 |
}
|
1659 |
seasonal_modifier = seasonal_factors.get(month, 0)
|
1660 |
|
1661 |
# Genesis region favorability
|
1662 |
region_factors = {
|
1663 |
+
"Western Pacific Main Development Region": 8,
|
1664 |
+
"South China Sea": 4,
|
1665 |
+
"Philippine Sea": 5,
|
1666 |
+
"Marshall Islands": 7,
|
1667 |
+
"Monsoon Trough": 6,
|
1668 |
+
"ITCZ Region": 3,
|
1669 |
+
"Subtropical Region": 2,
|
1670 |
+
"Bay of Bengal": 4,
|
1671 |
+
"Eastern Pacific": 6,
|
1672 |
+
"Atlantic MDR": 5
|
1673 |
}
|
1674 |
region_modifier = region_factors.get(genesis_region, 0)
|
1675 |
|
1676 |
# Calculate realistic starting intensity (TD level)
|
1677 |
predicted_intensity = base_intensity + intensity_modifier + seasonal_modifier + region_modifier
|
1678 |
+
predicted_intensity = max(25, min(40, predicted_intensity)) # Keep in TD-weak TS range
|
1679 |
|
1680 |
# Add realistic uncertainty for genesis
|
1681 |
intensity_uncertainty = np.random.normal(0, 2)
|
1682 |
predicted_intensity += intensity_uncertainty
|
1683 |
+
predicted_intensity = max(25, min(38, predicted_intensity)) # TD range
|
1684 |
|
1685 |
results['current_prediction'] = {
|
1686 |
'intensity_kt': predicted_intensity,
|
1687 |
+
'pressure_hpa': 1008 - (predicted_intensity - 25) * 0.6, # Realistic TD pressure
|
1688 |
'category': categorize_typhoon_enhanced(predicted_intensity),
|
1689 |
'genesis_region': genesis_region
|
1690 |
}
|
1691 |
|
1692 |
+
# REALISTIC route prediction with proper typhoon speeds
|
1693 |
current_lat = lat
|
1694 |
current_lon = lon
|
1695 |
current_intensity = predicted_intensity
|
1696 |
|
1697 |
route_points = []
|
1698 |
|
1699 |
+
# Track storm development over time with REALISTIC SPEEDS
|
1700 |
for hour in range(0, forecast_hours + 6, 6):
|
1701 |
|
1702 |
+
# REALISTIC typhoon motion - much faster speeds
|
1703 |
+
# Typical typhoon forward speed: 15-25 km/h (0.14-0.23°/hour)
|
|
|
|
|
1704 |
|
1705 |
+
# Base forward speed depends on latitude and storm intensity
|
1706 |
+
if current_lat < 20: # Low latitude - slower
|
1707 |
+
base_speed = 0.12 # ~13 km/h
|
1708 |
+
elif current_lat < 30: # Mid latitude - moderate
|
1709 |
+
base_speed = 0.18 # ~20 km/h
|
1710 |
+
else: # High latitude - faster
|
1711 |
+
base_speed = 0.25 # ~28 km/h
|
1712 |
+
|
1713 |
+
# Intensity affects speed (stronger storms can move faster)
|
1714 |
+
intensity_speed_factor = 1.0 + (current_intensity - 50) / 200
|
1715 |
+
base_speed *= max(0.8, min(1.4, intensity_speed_factor))
|
1716 |
+
|
1717 |
+
# Beta drift (Coriolis effect) - realistic values
|
1718 |
+
beta_drift_lat = 0.02 * np.sin(np.radians(current_lat))
|
1719 |
+
beta_drift_lon = -0.05 * np.cos(np.radians(current_lat))
|
1720 |
+
|
1721 |
+
# Seasonal steering patterns with realistic speeds
|
1722 |
if month in [6, 7, 8, 9]: # Peak season
|
1723 |
+
ridge_strength = 1.2
|
1724 |
+
ridge_position = 32 + 4 * np.sin(2 * np.pi * (month - 6) / 4)
|
1725 |
else: # Off season
|
1726 |
+
ridge_strength = 0.9
|
1727 |
+
ridge_position = 28
|
1728 |
|
1729 |
+
# REALISTIC motion based on position relative to subtropical ridge
|
1730 |
+
if current_lat < ridge_position - 10: # Well south of ridge - westward movement
|
1731 |
+
lat_tendency = base_speed * 0.3 + beta_drift_lat # Slight poleward
|
1732 |
+
lon_tendency = -base_speed * 0.9 + beta_drift_lon # Strong westward
|
1733 |
elif current_lat > ridge_position - 3: # Near ridge - recurvature
|
1734 |
+
lat_tendency = base_speed * 0.8 + beta_drift_lat # Strong poleward
|
1735 |
+
lon_tendency = base_speed * 0.4 + beta_drift_lon # Eastward
|
1736 |
+
else: # In between - normal WNW motion
|
1737 |
+
lat_tendency = base_speed * 0.4 + beta_drift_lat # Moderate poleward
|
1738 |
+
lon_tendency = -base_speed * 0.7 + beta_drift_lon # Moderate westward
|
1739 |
+
|
1740 |
+
# ENSO steering modulation (realistic effects)
|
1741 |
+
if oni_value > 0.5: # El Niño - more eastward/poleward motion
|
1742 |
+
lon_tendency += 0.05
|
1743 |
+
lat_tendency += 0.02
|
1744 |
+
elif oni_value < -0.5: # La Niña - more westward motion
|
1745 |
lon_tendency -= 0.08
|
1746 |
+
lat_tendency -= 0.01
|
1747 |
|
1748 |
+
# Add motion uncertainty that grows with time (realistic error growth)
|
1749 |
+
motion_uncertainty = 0.02 + (hour / 120) * 0.04
|
1750 |
lat_noise = np.random.normal(0, motion_uncertainty)
|
1751 |
lon_noise = np.random.normal(0, motion_uncertainty)
|
1752 |
|
1753 |
+
# Update position with realistic speeds
|
1754 |
current_lat += lat_tendency + lat_noise
|
1755 |
current_lon += lon_tendency + lon_noise
|
1756 |
|
1757 |
+
# REALISTIC intensity evolution with proper development cycles
|
1758 |
|
1759 |
+
# Development phase (first 48-72 hours) - realistic intensification
|
1760 |
if hour <= 48:
|
1761 |
+
if current_intensity < 50: # Still weak - rapid development possible
|
1762 |
+
if 10 <= current_lat <= 25 and 115 <= current_lon <= 165: # Favorable environment
|
1763 |
+
intensity_tendency = 4.5 if current_intensity < 35 else 3.0
|
1764 |
+
elif 120 <= current_lon <= 155 and 15 <= current_lat <= 20: # Best environment
|
1765 |
+
intensity_tendency = 6.0 if current_intensity < 40 else 4.0
|
1766 |
else:
|
1767 |
+
intensity_tendency = 2.0
|
1768 |
+
elif current_intensity < 80: # Moderate intensity
|
1769 |
+
intensity_tendency = 2.5 if (120 <= current_lon <= 155 and 10 <= current_lat <= 25) else 1.0
|
1770 |
else: # Already strong
|
1771 |
+
intensity_tendency = 1.0
|
1772 |
+
|
1773 |
+
# Mature phase (48-120 hours) - peak intensity maintenance
|
1774 |
+
elif hour <= 120:
|
1775 |
+
if current_lat < 25 and current_lon > 120: # Still in favorable waters
|
1776 |
+
if current_intensity < 120:
|
1777 |
+
intensity_tendency = 1.5
|
1778 |
+
else:
|
1779 |
+
intensity_tendency = 0.0 # Maintain intensity
|
1780 |
else:
|
1781 |
+
intensity_tendency = -1.5
|
1782 |
+
|
1783 |
+
# Extended phase (120+ hours) - gradual weakening
|
1784 |
else:
|
1785 |
+
if current_lat < 30 and current_lon > 115:
|
1786 |
+
intensity_tendency = -2.0 # Slow weakening
|
1787 |
+
else:
|
1788 |
+
intensity_tendency = -3.5 # Faster weakening
|
1789 |
|
1790 |
+
# Environmental modulation (realistic effects)
|
1791 |
if current_lat > 35: # High latitude - rapid weakening
|
1792 |
+
intensity_tendency -= 12
|
1793 |
elif current_lat > 30: # Moderate latitude
|
1794 |
+
intensity_tendency -= 5
|
1795 |
+
elif current_lon < 110: # Land interaction
|
1796 |
+
intensity_tendency -= 15
|
1797 |
elif 125 <= current_lon <= 155 and 10 <= current_lat <= 25: # Warm pool
|
1798 |
+
intensity_tendency += 2
|
1799 |
+
elif 160 <= current_lon <= 180 and 15 <= current_lat <= 30: # Still warm
|
1800 |
intensity_tendency += 1
|
1801 |
|
1802 |
+
# SST effects (realistic temperature impact)
|
1803 |
+
if current_lat < 8: # Very warm but weak Coriolis
|
1804 |
intensity_tendency += 0.5
|
1805 |
+
elif 8 <= current_lat <= 20: # Sweet spot for development
|
1806 |
+
intensity_tendency += 2.0
|
1807 |
+
elif 20 < current_lat <= 30: # Marginal
|
1808 |
+
intensity_tendency -= 1.0
|
1809 |
+
elif current_lat > 30: # Cool waters
|
1810 |
+
intensity_tendency -= 4.0
|
1811 |
+
|
1812 |
+
# Shear effects (simplified but realistic)
|
1813 |
+
if month in [12, 1, 2, 3]: # High shear season
|
1814 |
+
intensity_tendency -= 2.0
|
1815 |
+
elif month in [7, 8, 9]: # Low shear season
|
1816 |
+
intensity_tendency += 1.0
|
1817 |
+
|
1818 |
+
# Update intensity with realistic bounds and variability
|
1819 |
+
intensity_noise = np.random.normal(0, 1.5) # Small random fluctuations
|
1820 |
+
current_intensity += intensity_tendency + intensity_noise
|
1821 |
+
current_intensity = max(20, min(185, current_intensity)) # Realistic range
|
1822 |
|
1823 |
# Calculate confidence based on forecast time and environment
|
1824 |
+
base_confidence = 0.92
|
1825 |
+
time_penalty = (hour / 120) * 0.45
|
1826 |
+
environment_penalty = 0.15 if current_lat > 30 or current_lon < 115 else 0
|
1827 |
confidence = max(0.25, base_confidence - time_penalty - environment_penalty)
|
1828 |
|
1829 |
+
# Determine development stage
|
1830 |
+
if hour <= 24:
|
1831 |
+
stage = 'Genesis'
|
1832 |
+
elif hour <= 72:
|
1833 |
+
stage = 'Development'
|
1834 |
+
elif hour <= 120:
|
1835 |
+
stage = 'Mature'
|
1836 |
+
elif hour <= 240:
|
1837 |
+
stage = 'Extended'
|
1838 |
+
else:
|
1839 |
+
stage = 'Long-term'
|
1840 |
+
|
1841 |
route_points.append({
|
1842 |
'hour': hour,
|
1843 |
'lat': current_lat,
|
|
|
1845 |
'intensity_kt': current_intensity,
|
1846 |
'category': categorize_typhoon_enhanced(current_intensity),
|
1847 |
'confidence': confidence,
|
1848 |
+
'development_stage': stage,
|
1849 |
+
'forward_speed_kmh': base_speed * 111, # Convert to km/h
|
1850 |
+
'pressure_hpa': max(900, 1013 - (current_intensity - 25) * 0.9)
|
1851 |
})
|
1852 |
|
1853 |
results['route_forecast'] = route_points
|
1854 |
|
1855 |
# Realistic confidence scores
|
1856 |
results['confidence_scores'] = {
|
1857 |
+
'genesis': 0.88,
|
1858 |
+
'early_development': 0.82,
|
1859 |
'position_24h': 0.85,
|
1860 |
+
'position_48h': 0.78,
|
1861 |
+
'position_72h': 0.68,
|
1862 |
+
'intensity_24h': 0.75,
|
1863 |
+
'intensity_48h': 0.65,
|
1864 |
+
'intensity_72h': 0.55,
|
1865 |
+
'long_term': max(0.3, 0.8 - (forecast_hours / 240) * 0.5)
|
1866 |
}
|
1867 |
|
1868 |
# Model information
|
1869 |
+
results['model_info'] = f"Enhanced Realistic Model - {genesis_region}"
|
1870 |
|
1871 |
return results
|
1872 |
|
|
|
1881 |
}
|
1882 |
|
1883 |
def create_animated_route_visualization(prediction_results, show_uncertainty=True, enable_animation=True):
|
1884 |
+
"""Create comprehensive animated route visualization with intensity plots"""
|
1885 |
try:
|
1886 |
if 'route_forecast' not in prediction_results or not prediction_results['route_forecast']:
|
1887 |
return None, "No route forecast data available"
|
|
|
1896 |
categories = [point['category'] for point in route_data]
|
1897 |
confidences = [point.get('confidence', 0.8) for point in route_data]
|
1898 |
stages = [point.get('development_stage', 'Unknown') for point in route_data]
|
1899 |
+
speeds = [point.get('forward_speed_kmh', 15) for point in route_data]
|
1900 |
+
pressures = [point.get('pressure_hpa', 1013) for point in route_data]
|
1901 |
+
|
1902 |
+
# Create subplot layout with map and intensity plot
|
1903 |
+
fig = make_subplots(
|
1904 |
+
rows=2, cols=2,
|
1905 |
+
subplot_titles=('Storm Track Animation', 'Wind Speed vs Time', 'Forward Speed vs Time', 'Pressure vs Time'),
|
1906 |
+
specs=[[{"type": "geo", "colspan": 2}, None],
|
1907 |
+
[{"type": "xy"}, {"type": "xy"}]],
|
1908 |
+
vertical_spacing=0.15,
|
1909 |
+
row_heights=[0.7, 0.3]
|
1910 |
+
)
|
1911 |
|
1912 |
if enable_animation:
|
1913 |
+
# Add frames for animation
|
1914 |
+
frames = []
|
1915 |
|
1916 |
+
# Static background elements first
|
1917 |
# Add complete track as background
|
1918 |
fig.add_trace(
|
1919 |
go.Scattergeo(
|
|
|
1923 |
line=dict(color='lightgray', width=2, dash='dot'),
|
1924 |
name='Complete Track',
|
1925 |
showlegend=True,
|
1926 |
+
opacity=0.4
|
1927 |
+
),
|
1928 |
+
row=1, col=1
|
1929 |
)
|
1930 |
|
1931 |
+
# Genesis marker (always visible)
|
1932 |
+
fig.add_trace(
|
1933 |
+
go.Scattergeo(
|
1934 |
+
lon=[lons[0]],
|
1935 |
+
lat=[lats[0]],
|
1936 |
+
mode='markers',
|
1937 |
+
marker=dict(
|
1938 |
+
size=25,
|
1939 |
+
color='gold',
|
1940 |
+
symbol='star',
|
1941 |
+
line=dict(width=3, color='black')
|
1942 |
+
),
|
1943 |
+
name='Genesis',
|
1944 |
+
showlegend=True,
|
1945 |
+
hovertemplate=(
|
1946 |
+
f"<b>GENESIS</b><br>"
|
1947 |
+
f"Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E<br>"
|
1948 |
+
f"Initial: {intensities[0]:.0f} kt<br>"
|
1949 |
+
f"Region: {prediction_results['genesis_info']['description']}<br>"
|
1950 |
+
"<extra></extra>"
|
1951 |
+
)
|
1952 |
+
),
|
1953 |
+
row=1, col=1
|
1954 |
+
)
|
1955 |
+
|
1956 |
+
# Create animation frames
|
1957 |
for i in range(len(route_data)):
|
1958 |
frame_lons = lons[:i+1]
|
1959 |
frame_lats = lats[:i+1]
|
1960 |
frame_intensities = intensities[:i+1]
|
1961 |
frame_categories = categories[:i+1]
|
1962 |
+
frame_hours = hours[:i+1]
|
1963 |
|
1964 |
# Current position marker
|
1965 |
current_color = enhanced_color_map.get(frame_categories[-1], 'rgb(128,128,128)')
|
1966 |
current_size = 15 + (frame_intensities[-1] / 10)
|
1967 |
|
1968 |
frame_data = [
|
1969 |
+
# Animated track up to current point
|
1970 |
go.Scattergeo(
|
1971 |
lon=frame_lons,
|
1972 |
lat=frame_lats,
|
1973 |
mode='lines+markers',
|
1974 |
+
line=dict(color='blue', width=4),
|
1975 |
marker=dict(
|
1976 |
size=[8 + (intensity/15) for intensity in frame_intensities],
|
1977 |
color=[enhanced_color_map.get(cat, 'rgb(128,128,128)') for cat in frame_categories],
|
1978 |
+
opacity=0.8,
|
1979 |
+
line=dict(width=1, color='white')
|
1980 |
),
|
1981 |
+
name='Current Track',
|
1982 |
showlegend=False
|
1983 |
),
|
1984 |
# Current position highlight
|
|
|
1989 |
marker=dict(
|
1990 |
size=current_size,
|
1991 |
color=current_color,
|
1992 |
+
symbol='circle',
|
1993 |
+
line=dict(width=3, color='white')
|
1994 |
),
|
1995 |
name='Current Position',
|
1996 |
showlegend=False,
|
|
|
2000 |
f"Intensity: {intensities[i]:.0f} kt<br>"
|
2001 |
f"Category: {categories[i]}<br>"
|
2002 |
f"Stage: {stages[i]}<br>"
|
2003 |
+
f"Speed: {speeds[i]:.1f} km/h<br>"
|
2004 |
f"Confidence: {confidences[i]*100:.0f}%<br>"
|
2005 |
"<extra></extra>"
|
2006 |
)
|
2007 |
+
),
|
2008 |
+
# Animated wind plot
|
2009 |
+
go.Scatter(
|
2010 |
+
x=frame_hours,
|
2011 |
+
y=frame_intensities,
|
2012 |
+
mode='lines+markers',
|
2013 |
+
line=dict(color='red', width=3),
|
2014 |
+
marker=dict(size=6, color='red'),
|
2015 |
+
name='Wind Speed',
|
2016 |
+
showlegend=False,
|
2017 |
+
yaxis='y2'
|
2018 |
+
),
|
2019 |
+
# Animated speed plot
|
2020 |
+
go.Scatter(
|
2021 |
+
x=frame_hours,
|
2022 |
+
y=speeds[:i+1],
|
2023 |
+
mode='lines+markers',
|
2024 |
+
line=dict(color='green', width=2),
|
2025 |
+
marker=dict(size=4, color='green'),
|
2026 |
+
name='Forward Speed',
|
2027 |
+
showlegend=False,
|
2028 |
+
yaxis='y3'
|
2029 |
+
),
|
2030 |
+
# Animated pressure plot
|
2031 |
+
go.Scatter(
|
2032 |
+
x=frame_hours,
|
2033 |
+
y=pressures[:i+1],
|
2034 |
+
mode='lines+markers',
|
2035 |
+
line=dict(color='purple', width=2),
|
2036 |
+
marker=dict(size=4, color='purple'),
|
2037 |
+
name='Pressure',
|
2038 |
+
showlegend=False,
|
2039 |
+
yaxis='y4'
|
2040 |
)
|
2041 |
]
|
2042 |
|
|
|
2044 |
data=frame_data,
|
2045 |
name=str(i),
|
2046 |
layout=go.Layout(
|
2047 |
+
title=f"Storm Development Animation - Hour {route_data[i]['hour']}<br>"
|
2048 |
+
f"Intensity: {intensities[i]:.0f} kt | Category: {categories[i]} | Stage: {stages[i]} | Speed: {speeds[i]:.1f} km/h"
|
2049 |
)
|
2050 |
))
|
2051 |
|
2052 |
fig.frames = frames
|
2053 |
|
2054 |
+
# Add play/pause controls
|
2055 |
fig.update_layout(
|
2056 |
updatemenus=[
|
2057 |
{
|
2058 |
"buttons": [
|
2059 |
{
|
2060 |
+
"args": [None, {"frame": {"duration": 1000, "redraw": True},
|
2061 |
"fromcurrent": True, "transition": {"duration": 300}}],
|
2062 |
"label": "▶️ Play",
|
2063 |
"method": "animate"
|
|
|
2067 |
"mode": "immediate", "transition": {"duration": 0}}],
|
2068 |
"label": "⏸️ Pause",
|
2069 |
"method": "animate"
|
2070 |
+
},
|
2071 |
+
{
|
2072 |
+
"args": [None, {"frame": {"duration": 500, "redraw": True},
|
2073 |
+
"fromcurrent": True, "transition": {"duration": 300}}],
|
2074 |
+
"label": "⏩ Fast",
|
2075 |
+
"method": "animate"
|
2076 |
}
|
2077 |
],
|
2078 |
"direction": "left",
|
|
|
2090 |
"yanchor": "top",
|
2091 |
"xanchor": "left",
|
2092 |
"currentvalue": {
|
2093 |
+
"font": {"size": 16},
|
2094 |
+
"prefix": "Hour: ",
|
2095 |
"visible": True,
|
2096 |
"xanchor": "right"
|
2097 |
},
|
|
|
2104 |
{
|
2105 |
"args": [[str(i)], {"frame": {"duration": 300, "redraw": True},
|
2106 |
"mode": "immediate", "transition": {"duration": 300}}],
|
2107 |
+
"label": f"H{route_data[i]['hour']}",
|
2108 |
"method": "animate"
|
2109 |
}
|
2110 |
+
for i in range(0, len(route_data), max(1, len(route_data)//20)) # Limit slider steps
|
2111 |
]
|
2112 |
}]
|
2113 |
)
|
2114 |
|
2115 |
else:
|
2116 |
+
# Static view with all points
|
2117 |
+
# Add genesis marker
|
2118 |
+
fig.add_trace(
|
2119 |
+
go.Scattergeo(
|
2120 |
+
lon=[lons[0]],
|
2121 |
+
lat=[lats[0]],
|
2122 |
+
mode='markers',
|
2123 |
+
marker=dict(
|
2124 |
+
size=25,
|
2125 |
+
color='gold',
|
2126 |
+
symbol='star',
|
2127 |
+
line=dict(width=3, color='black')
|
2128 |
+
),
|
2129 |
+
name='Genesis',
|
2130 |
+
showlegend=True,
|
2131 |
+
hovertemplate=(
|
2132 |
+
f"<b>GENESIS</b><br>"
|
2133 |
+
f"Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E<br>"
|
2134 |
+
f"Initial: {intensities[0]:.0f} kt<br>"
|
2135 |
+
"<extra></extra>"
|
2136 |
+
)
|
2137 |
+
),
|
2138 |
+
row=1, col=1
|
2139 |
+
)
|
2140 |
|
2141 |
# Add full track with intensity coloring
|
2142 |
+
for i in range(0, len(route_data), max(1, len(route_data)//50)): # Sample points for performance
|
2143 |
point = route_data[i]
|
2144 |
color = enhanced_color_map.get(point['category'], 'rgb(128,128,128)')
|
2145 |
size = 8 + (point['intensity_kt'] / 12)
|
2146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2147 |
fig.add_trace(
|
2148 |
go.Scattergeo(
|
2149 |
lon=[point['lon']],
|
|
|
2155 |
opacity=point.get('confidence', 0.8),
|
2156 |
line=dict(width=1, color='white')
|
2157 |
),
|
2158 |
+
name=f"Hour {point['hour']}" if i % 10 == 0 else None,
|
2159 |
+
showlegend=(i % 10 == 0),
|
2160 |
hovertemplate=(
|
2161 |
f"<b>Hour {point['hour']}</b><br>"
|
2162 |
f"Position: {point['lat']:.1f}°N, {point['lon']:.1f}°E<br>"
|
2163 |
f"Intensity: {point['intensity_kt']:.0f} kt<br>"
|
2164 |
f"Category: {point['category']}<br>"
|
2165 |
f"Stage: {point.get('development_stage', 'Unknown')}<br>"
|
2166 |
+
f"Speed: {point.get('forward_speed_kmh', 15):.1f} km/h<br>"
|
2167 |
"<extra></extra>"
|
2168 |
)
|
2169 |
+
),
|
2170 |
+
row=1, col=1
|
2171 |
)
|
2172 |
|
2173 |
# Connect points with track line
|
|
|
2179 |
line=dict(color='black', width=3),
|
2180 |
name='Forecast Track',
|
2181 |
showlegend=True
|
2182 |
+
),
|
2183 |
+
row=1, col=1
|
2184 |
+
)
|
2185 |
+
|
2186 |
+
# Add static intensity, speed, and pressure plots
|
2187 |
+
# Wind speed plot
|
2188 |
+
fig.add_trace(
|
2189 |
+
go.Scatter(
|
2190 |
+
x=hours,
|
2191 |
+
y=intensities,
|
2192 |
+
mode='lines+markers',
|
2193 |
+
line=dict(color='red', width=3),
|
2194 |
+
marker=dict(size=6, color='red'),
|
2195 |
+
name='Wind Speed',
|
2196 |
+
showlegend=False
|
2197 |
+
),
|
2198 |
+
row=2, col=1
|
2199 |
+
)
|
2200 |
+
|
2201 |
+
# Add category threshold lines
|
2202 |
+
thresholds = [34, 64, 83, 96, 113, 137]
|
2203 |
+
threshold_names = ['TS', 'C1', 'C2', 'C3', 'C4', 'C5']
|
2204 |
+
|
2205 |
+
for thresh, name in zip(thresholds, threshold_names):
|
2206 |
+
fig.add_trace(
|
2207 |
+
go.Scatter(
|
2208 |
+
x=[min(hours), max(hours)],
|
2209 |
+
y=[thresh, thresh],
|
2210 |
+
mode='lines',
|
2211 |
+
line=dict(color='gray', width=1, dash='dash'),
|
2212 |
+
name=name,
|
2213 |
+
showlegend=False,
|
2214 |
+
hovertemplate=f"{name} Threshold: {thresh} kt<extra></extra>"
|
2215 |
+
),
|
2216 |
+
row=2, col=1
|
2217 |
)
|
2218 |
|
2219 |
+
# Forward speed plot
|
2220 |
+
fig.add_trace(
|
2221 |
+
go.Scatter(
|
2222 |
+
x=hours,
|
2223 |
+
y=speeds,
|
2224 |
+
mode='lines+markers',
|
2225 |
+
line=dict(color='green', width=2),
|
2226 |
+
marker=dict(size=4, color='green'),
|
2227 |
+
name='Forward Speed',
|
2228 |
+
showlegend=False
|
2229 |
+
),
|
2230 |
+
row=2, col=2
|
2231 |
+
)
|
2232 |
+
|
2233 |
# Add uncertainty cone if requested
|
2234 |
if show_uncertainty and len(route_data) > 1:
|
2235 |
uncertainty_lats_upper = []
|
|
|
2239 |
|
2240 |
for i, point in enumerate(route_data):
|
2241 |
# Uncertainty grows with time and decreases with confidence
|
2242 |
+
base_uncertainty = 0.4 + (i / len(route_data)) * 1.8
|
2243 |
confidence_factor = point.get('confidence', 0.8)
|
2244 |
uncertainty = base_uncertainty / confidence_factor
|
2245 |
|
|
|
2261 |
line=dict(color='rgba(128,128,128,0.4)', width=1),
|
2262 |
name='Uncertainty Cone',
|
2263 |
showlegend=True
|
2264 |
+
),
|
2265 |
+
row=1, col=1
|
2266 |
)
|
2267 |
|
2268 |
+
# Enhanced layout
|
2269 |
fig.update_layout(
|
2270 |
+
title=f"Comprehensive Storm Development Analysis<br><sub>Starting from {prediction_results['genesis_info']['description']}</sub>",
|
2271 |
+
height=1000, # Taller for better subplot visibility
|
2272 |
+
width=1400, # Wider
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2273 |
showlegend=True
|
2274 |
)
|
2275 |
|
2276 |
+
# Update geo layout
|
2277 |
+
fig.update_geos(
|
2278 |
+
projection_type="natural earth",
|
2279 |
+
showland=True,
|
2280 |
+
landcolor="LightGray",
|
2281 |
+
showocean=True,
|
2282 |
+
oceancolor="LightBlue",
|
2283 |
+
showcoastlines=True,
|
2284 |
+
coastlinecolor="DarkGray",
|
2285 |
+
showlakes=True,
|
2286 |
+
lakecolor="LightBlue",
|
2287 |
+
center=dict(lat=np.mean(lats), lon=np.mean(lons)),
|
2288 |
+
projection_scale=2.0,
|
2289 |
+
row=1, col=1
|
2290 |
+
)
|
2291 |
+
|
2292 |
+
# Update subplot axes
|
2293 |
+
fig.update_xaxes(title_text="Forecast Hour", row=2, col=1)
|
2294 |
+
fig.update_yaxes(title_text="Wind Speed (kt)", row=2, col=1)
|
2295 |
+
fig.update_xaxes(title_text="Forecast Hour", row=2, col=2)
|
2296 |
+
fig.update_yaxes(title_text="Forward Speed (km/h)", row=2, col=2)
|
2297 |
+
|
2298 |
# Generate enhanced forecast text
|
2299 |
current = prediction_results['current_prediction']
|
2300 |
genesis_info = prediction_results['genesis_info']
|
2301 |
|
2302 |
+
# Calculate some statistics
|
2303 |
+
max_intensity = max(intensities)
|
2304 |
+
max_intensity_time = hours[intensities.index(max_intensity)]
|
2305 |
+
avg_speed = np.mean(speeds)
|
2306 |
+
|
2307 |
forecast_text = f"""
|
2308 |
+
COMPREHENSIVE STORM DEVELOPMENT FORECAST
|
2309 |
+
{'='*65}
|
2310 |
|
2311 |
GENESIS CONDITIONS:
|
2312 |
• Region: {current.get('genesis_region', 'Unknown')}
|
|
|
2314 |
• Starting Position: {lats[0]:.1f}°N, {lons[0]:.1f}°E
|
2315 |
• Initial Intensity: {current['intensity_kt']:.0f} kt (Tropical Depression)
|
2316 |
• Genesis Pressure: {current.get('pressure_hpa', 1008):.0f} hPa
|
2317 |
+
|
2318 |
+
STORM CHARACTERISTICS:
|
2319 |
+
• Peak Intensity: {max_intensity:.0f} kt at Hour {max_intensity_time}
|
2320 |
+
• Average Forward Speed: {avg_speed:.1f} km/h
|
2321 |
+
• Total Distance: {sum([speeds[i]/6 for i in range(len(speeds))]):.0f} km
|
2322 |
+
• Final Position: {lats[-1]:.1f}°N, {lons[-1]:.1f}°E
|
2323 |
+
• Forecast Duration: {hours[-1]} hours ({hours[-1]/24:.1f} days)
|
2324 |
|
2325 |
DEVELOPMENT TIMELINE:
|
2326 |
• Hour 0 (Genesis): {intensities[0]:.0f} kt - {categories[0]}
|
2327 |
+
• Hour 24: {intensities[min(4, len(intensities)-1)]:.0f} kt - {categories[min(4, len(categories)-1)]}
|
2328 |
+
• Hour 48: {intensities[min(8, len(intensities)-1)]:.0f} kt - {categories[min(8, len(categories)-1)]}
|
2329 |
+
• Hour 72: {intensities[min(12, len(intensities)-1)]:.0f} kt - {categories[min(12, len(categories)-1)]}
|
2330 |
+
• Final: {intensities[-1]:.0f} kt - {categories[-1]}
|
2331 |
+
|
2332 |
+
MOTION ANALYSIS:
|
2333 |
+
• Initial Motion: {speeds[0]:.1f} km/h
|
2334 |
+
• Peak Speed: {max(speeds):.1f} km/h at Hour {hours[speeds.index(max(speeds))]}
|
2335 |
+
• Final Motion: {speeds[-1]:.1f} km/h
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2336 |
|
2337 |
CONFIDENCE ASSESSMENT:
|
2338 |
• Genesis Likelihood: {prediction_results['confidence_scores'].get('genesis', 0.85)*100:.0f}%
|
|
|
2339 |
• 24-hour Track: {prediction_results['confidence_scores'].get('position_24h', 0.85)*100:.0f}%
|
2340 |
• 48-hour Track: {prediction_results['confidence_scores'].get('position_48h', 0.75)*100:.0f}%
|
2341 |
• 72-hour Track: {prediction_results['confidence_scores'].get('position_72h', 0.65)*100:.0f}%
|
2342 |
+
• Long-term: {prediction_results['confidence_scores'].get('long_term', 0.50)*100:.0f}%
|
2343 |
+
|
2344 |
+
FEATURES:
|
2345 |
+
{"✅ Animation Enabled - Use controls to watch development" if enable_animation else "📊 Static Analysis - All time steps displayed"}
|
2346 |
+
✅ Realistic Forward Speeds (15-25 km/h typical)
|
2347 |
+
✅ Environmental Coupling (ENSO, SST, Shear)
|
2348 |
+
✅ Multi-stage Development Cycle
|
2349 |
+
✅ Uncertainty Quantification
|
2350 |
|
2351 |
MODEL: {prediction_results['model_info']}
|
|
|
2352 |
"""
|
2353 |
|
2354 |
return fig, forecast_text.strip()
|
2355 |
|
2356 |
except Exception as e:
|
2357 |
+
error_msg = f"Error creating comprehensive visualization: {str(e)}"
|
2358 |
logging.error(error_msg)
|
2359 |
import traceback
|
2360 |
traceback.print_exc()
|
|
|
3094 |
pred_month = gr.Slider(1, 12, label="Month", value=9, info="Peak season: Jul-Oct")
|
3095 |
pred_oni = gr.Number(label="ONI Value", value=0.0, info="ENSO index (-3 to 3)")
|
3096 |
with gr.Row():
|
3097 |
+
forecast_hours = gr.Number(
|
|
|
3098 |
label="Forecast Length (hours)",
|
3099 |
value=72,
|
3100 |
+
minimum=20,
|
3101 |
+
maximum=1000,
|
3102 |
step=6,
|
3103 |
+
info="Extended forecasting: 20-1000 hours (42 days max)"
|
3104 |
)
|
3105 |
advanced_physics = gr.Checkbox(
|
3106 |
label="Advanced Physics",
|
|
|
3178 |
prediction_info_text = """
|
3179 |
### 🌊 Realistic Storm Genesis Features:
|
3180 |
- **Climatological Genesis Regions**: 10 realistic development zones based on historical data
|
3181 |
+
- **Tropical Depression Starting Point**: Storms begin at realistic 25-38 kt intensities
|
3182 |
+
- **Realistic Typhoon Speeds**: Forward motion 15-25 km/h (matching observations)
|
3183 |
+
- **Extended Time Range**: 20-1000 hours (up to 42 days) with user input control
|
3184 |
+
- **Comprehensive Animation**: Watch development with wind/speed/pressure plots
|
3185 |
- **Environmental Coupling**: Advanced physics with ENSO, SST, shear, β-drift, ridge patterns
|
|
|
3186 |
|
3187 |
### 📊 Enhanced Development Stages:
|
3188 |
- **Genesis (0-24h)**: Initial cyclogenesis at Tropical Depression level
|
3189 |
+
- **Development (24-72h)**: Rapid intensification in favorable environment (3-6 kt/h)
|
3190 |
- **Mature (72-120h)**: Peak intensity phase with environmental modulation
|
3191 |
+
- **Extended (120-240h)**: Continued tracking with realistic weakening
|
3192 |
+
- **Long-term (240h+)**: Extended forecasting for research and planning
|
3193 |
+
|
3194 |
+
### 🏃♂️ Realistic Motion Physics:
|
3195 |
+
- **Low Latitude (< 20°N)**: 12-15 km/h typical speeds
|
3196 |
+
- **Mid Latitude (20-30°N)**: 18-22 km/h moderate speeds
|
3197 |
+
- **High Latitude (> 30°N)**: 25-30 km/h fast extratropical transition
|
3198 |
+
- **Intensity Effects**: Stronger storms can move faster in steering flow
|
3199 |
+
- **Beta Drift**: Coriolis-induced poleward and westward motion
|
3200 |
+
- **Ridge Interaction**: Realistic recurvature patterns
|
3201 |
|
3202 |
### 🌍 Genesis Region Selection:
|
3203 |
- **Western Pacific MDR**: Peak activity zone near Guam (12.5°N, 145°E)
|
|
|
3207 |
- **Monsoon Trough**: Monsoon-driven genesis (10°N, 130°E)
|
3208 |
- **Other Basins**: Bay of Bengal, Eastern Pacific, Atlantic options
|
3209 |
|
3210 |
+
### 🎬 Enhanced Animation Features:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3211 |
- **Real-time Development**: Watch TD evolve to typhoon intensity
|
3212 |
+
- **Multi-plot Display**: Track + Wind Speed + Forward Speed plots
|
3213 |
+
- **Interactive Controls**: Play/pause/fast buttons and time slider
|
3214 |
- **Stage Tracking**: Visual indicators for development phases
|
3215 |
+
- **Speed Analysis**: Forward motion tracking throughout lifecycle
|
3216 |
+
- **Performance Optimized**: Smooth animation even for 1000+ hour forecasts
|
3217 |
+
|
3218 |
+
### 🔬 Advanced Physics Model:
|
3219 |
+
- **Realistic Intensification**: 3-6 kt/h development rates in favorable conditions
|
3220 |
+
- **Environmental Coupling**: SST, wind shear, Coriolis effects
|
3221 |
+
- **Steering Flow**: Subtropical ridge position and ENSO modulation
|
3222 |
+
- **Motion Variability**: Growing uncertainty with forecast time
|
3223 |
+
- **Pressure-Wind Relationship**: Realistic intensity-pressure coupling
|
3224 |
+
- **Long-term Climatology**: Extended forecasting using seasonal patterns
|
3225 |
"""
|
3226 |
gr.Markdown(prediction_info_text)
|
3227 |
|