Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -114,7 +114,7 @@ CACHE_FILE = os.path.join(DATA_PATH, 'ibtracs_cache.pkl')
|
|
| 114 |
CACHE_EXPIRY_DAYS = 1
|
| 115 |
|
| 116 |
# -----------------------------
|
| 117 |
-
# ENHANCED: Color Maps and Standards with TD Support
|
| 118 |
# -----------------------------
|
| 119 |
# Enhanced color mapping with TD support (for Plotly)
|
| 120 |
enhanced_color_map = {
|
|
@@ -140,12 +140,12 @@ matplotlib_color_map = {
|
|
| 140 |
'C5 Super Typhoon': '#FF0000' # Red
|
| 141 |
}
|
| 142 |
|
| 143 |
-
# Taiwan color mapping
|
| 144 |
taiwan_color_map = {
|
| 145 |
-
'Tropical Depression': '#808080',
|
| 146 |
-
'
|
| 147 |
-
'
|
| 148 |
-
'
|
| 149 |
}
|
| 150 |
|
| 151 |
def rgb_string_to_hex(rgb_string):
|
|
@@ -204,11 +204,12 @@ atlantic_standard = {
|
|
| 204 |
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
| 205 |
}
|
| 206 |
|
|
|
|
| 207 |
taiwan_standard = {
|
| 208 |
-
'
|
| 209 |
-
'
|
| 210 |
-
'
|
| 211 |
-
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
| 212 |
}
|
| 213 |
|
| 214 |
# -----------------------------
|
|
@@ -708,7 +709,7 @@ def merge_data(oni_long, typhoon_max):
|
|
| 708 |
return pd.merge(typhoon_max, oni_long, on=['Year','Month'])
|
| 709 |
|
| 710 |
# -----------------------------
|
| 711 |
-
# ENHANCED: Categorization Functions
|
| 712 |
# -----------------------------
|
| 713 |
|
| 714 |
def categorize_typhoon_enhanced(wind_speed):
|
|
@@ -737,20 +738,24 @@ def categorize_typhoon_enhanced(wind_speed):
|
|
| 737 |
return 'C5 Super Typhoon'
|
| 738 |
|
| 739 |
def categorize_typhoon_taiwan(wind_speed):
|
| 740 |
-
"""Taiwan categorization system"""
|
| 741 |
if pd.isna(wind_speed):
|
| 742 |
return 'Tropical Depression'
|
| 743 |
|
| 744 |
-
# Convert to m/s
|
| 745 |
-
wind_speed
|
|
|
|
|
|
|
|
|
|
| 746 |
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
|
|
|
| 754 |
return 'Tropical Depression'
|
| 755 |
|
| 756 |
# Original function for backward compatibility
|
|
@@ -771,6 +776,31 @@ def classify_enso_phases(oni_value):
|
|
| 771 |
else:
|
| 772 |
return 'Neutral'
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
# -----------------------------
|
| 775 |
# FIXED: ADVANCED ML FEATURES WITH ROBUST ERROR HANDLING
|
| 776 |
# -----------------------------
|
|
@@ -1586,20 +1616,6 @@ def create_advanced_prediction_model(typhoon_data):
|
|
| 1586 |
except Exception as e:
|
| 1587 |
return None, f"Error creating prediction model: {str(e)}"
|
| 1588 |
|
| 1589 |
-
def rgb_string_to_hex(rgb_string):
|
| 1590 |
-
"""Convert RGB string like 'rgb(255,0,0)' to hex format '#FF0000'"""
|
| 1591 |
-
try:
|
| 1592 |
-
if rgb_string.startswith('rgb(') and rgb_string.endswith(')'):
|
| 1593 |
-
# Extract numbers from rgb(r,g,b)
|
| 1594 |
-
rgb_values = rgb_string[4:-1].split(',')
|
| 1595 |
-
r, g, b = [int(x.strip()) for x in rgb_values]
|
| 1596 |
-
return f'#{r:02x}{g:02x}{b:02x}'
|
| 1597 |
-
else:
|
| 1598 |
-
# Already in hex or other format
|
| 1599 |
-
return rgb_string
|
| 1600 |
-
except:
|
| 1601 |
-
return '#808080' # Default gray
|
| 1602 |
-
|
| 1603 |
def get_realistic_genesis_locations():
|
| 1604 |
"""Get realistic typhoon genesis regions based on climatology"""
|
| 1605 |
return {
|
|
@@ -1879,485 +1895,324 @@ def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value
|
|
| 1879 |
'model_info': 'Error in prediction'
|
| 1880 |
}
|
| 1881 |
|
| 1882 |
-
|
| 1883 |
-
|
| 1884 |
-
|
| 1885 |
-
|
| 1886 |
-
|
| 1887 |
-
|
| 1888 |
-
|
| 1889 |
-
|
| 1890 |
-
|
| 1891 |
-
|
| 1892 |
-
|
| 1893 |
-
|
| 1894 |
-
|
| 1895 |
-
|
| 1896 |
-
|
| 1897 |
-
|
| 1898 |
-
|
| 1899 |
-
|
| 1900 |
-
|
| 1901 |
-
#
|
| 1902 |
-
|
| 1903 |
-
|
| 1904 |
-
|
| 1905 |
-
|
| 1906 |
-
|
| 1907 |
-
|
| 1908 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1909 |
)
|
| 1910 |
|
| 1911 |
-
|
| 1912 |
-
|
| 1913 |
-
|
| 1914 |
-
|
| 1915 |
-
|
| 1916 |
-
|
| 1917 |
-
|
| 1918 |
-
|
| 1919 |
-
|
| 1920 |
-
|
| 1921 |
-
|
| 1922 |
-
|
| 1923 |
-
|
| 1924 |
-
|
| 1925 |
-
|
| 1926 |
-
|
| 1927 |
-
|
| 1928 |
-
|
| 1929 |
-
|
| 1930 |
-
|
| 1931 |
-
|
| 1932 |
-
|
| 1933 |
-
|
| 1934 |
-
|
| 1935 |
-
|
| 1936 |
-
|
| 1937 |
-
|
| 1938 |
-
|
| 1939 |
-
|
| 1940 |
-
|
| 1941 |
-
|
| 1942 |
-
|
| 1943 |
-
|
| 1944 |
-
|
| 1945 |
-
|
| 1946 |
-
|
| 1947 |
-
|
| 1948 |
-
f"Region: {prediction_results['genesis_info']['description']}<br>"
|
| 1949 |
-
"<extra></extra>"
|
| 1950 |
-
)
|
| 1951 |
-
),
|
| 1952 |
-
row=1, col=1
|
| 1953 |
-
)
|
| 1954 |
-
|
| 1955 |
-
# Create animation frames
|
| 1956 |
-
for i in range(len(route_data)):
|
| 1957 |
-
frame_lons = lons[:i+1]
|
| 1958 |
-
frame_lats = lats[:i+1]
|
| 1959 |
-
frame_intensities = intensities[:i+1]
|
| 1960 |
-
frame_categories = categories[:i+1]
|
| 1961 |
-
frame_hours = hours[:i+1]
|
| 1962 |
-
|
| 1963 |
-
# Current position marker
|
| 1964 |
-
current_color = enhanced_color_map.get(frame_categories[-1], 'rgb(128,128,128)')
|
| 1965 |
-
current_size = 15 + (frame_intensities[-1] / 10)
|
| 1966 |
-
|
| 1967 |
-
frame_data = [
|
| 1968 |
-
# Animated track up to current point
|
| 1969 |
-
go.Scattergeo(
|
| 1970 |
-
lon=frame_lons,
|
| 1971 |
-
lat=frame_lats,
|
| 1972 |
-
mode='lines+markers',
|
| 1973 |
-
line=dict(color='blue', width=4),
|
| 1974 |
-
marker=dict(
|
| 1975 |
-
size=[8 + (intensity/15) for intensity in frame_intensities],
|
| 1976 |
-
color=[enhanced_color_map.get(cat, 'rgb(128,128,128)') for cat in frame_categories],
|
| 1977 |
-
opacity=0.8,
|
| 1978 |
-
line=dict(width=1, color='white')
|
| 1979 |
-
),
|
| 1980 |
-
name='Current Track',
|
| 1981 |
-
showlegend=False
|
| 1982 |
-
),
|
| 1983 |
-
# Current position highlight
|
| 1984 |
go.Scattergeo(
|
| 1985 |
-
lon=[
|
| 1986 |
-
lat=[
|
| 1987 |
mode='markers',
|
| 1988 |
marker=dict(
|
| 1989 |
-
size=
|
| 1990 |
-
color=
|
| 1991 |
-
symbol='
|
| 1992 |
-
line=dict(width=
|
| 1993 |
),
|
| 1994 |
-
name='
|
| 1995 |
-
showlegend=
|
| 1996 |
hovertemplate=(
|
| 1997 |
-
f"<b>
|
| 1998 |
-
f"
|
| 1999 |
-
f"
|
| 2000 |
-
f"Category: {categories[i]}<br>"
|
| 2001 |
-
f"Stage: {stages[i]}<br>"
|
| 2002 |
-
f"Speed: {speeds[i]:.1f} km/h<br>"
|
| 2003 |
-
f"Confidence: {confidences[i]*100:.0f}%<br>"
|
| 2004 |
"<extra></extra>"
|
| 2005 |
)
|
| 2006 |
-
),
|
| 2007 |
-
# Animated wind plot
|
| 2008 |
-
go.Scatter(
|
| 2009 |
-
x=frame_hours,
|
| 2010 |
-
y=frame_intensities,
|
| 2011 |
-
mode='lines+markers',
|
| 2012 |
-
line=dict(color='red', width=3),
|
| 2013 |
-
marker=dict(size=6, color='red'),
|
| 2014 |
-
name='Wind Speed',
|
| 2015 |
-
showlegend=False,
|
| 2016 |
-
yaxis='y2'
|
| 2017 |
-
),
|
| 2018 |
-
# Animated speed plot
|
| 2019 |
-
go.Scatter(
|
| 2020 |
-
x=frame_hours,
|
| 2021 |
-
y=speeds[:i+1],
|
| 2022 |
-
mode='lines+markers',
|
| 2023 |
-
line=dict(color='green', width=2),
|
| 2024 |
-
marker=dict(size=4, color='green'),
|
| 2025 |
-
name='Forward Speed',
|
| 2026 |
-
showlegend=False,
|
| 2027 |
-
yaxis='y3'
|
| 2028 |
-
),
|
| 2029 |
-
# Animated pressure plot
|
| 2030 |
-
go.Scatter(
|
| 2031 |
-
x=frame_hours,
|
| 2032 |
-
y=pressures[:i+1],
|
| 2033 |
-
mode='lines+markers',
|
| 2034 |
-
line=dict(color='purple', width=2),
|
| 2035 |
-
marker=dict(size=4, color='purple'),
|
| 2036 |
-
name='Pressure',
|
| 2037 |
-
showlegend=False,
|
| 2038 |
-
yaxis='y4'
|
| 2039 |
-
)
|
| 2040 |
-
]
|
| 2041 |
-
|
| 2042 |
-
frames.append(go.Frame(
|
| 2043 |
-
data=frame_data,
|
| 2044 |
-
name=str(i),
|
| 2045 |
-
layout=go.Layout(
|
| 2046 |
-
title=f"Storm Development Animation - Hour {route_data[i]['hour']}<br>"
|
| 2047 |
-
f"Intensity: {intensities[i]:.0f} kt | Category: {categories[i]} | Stage: {stages[i]} | Speed: {speeds[i]:.1f} km/h"
|
| 2048 |
)
|
| 2049 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2050 |
|
| 2051 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2052 |
|
| 2053 |
-
|
| 2054 |
-
|
| 2055 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2056 |
{
|
| 2057 |
-
"
|
| 2058 |
-
|
| 2059 |
-
|
| 2060 |
-
|
| 2061 |
-
"label": "โถ๏ธ Play",
|
| 2062 |
-
"method": "animate"
|
| 2063 |
-
},
|
| 2064 |
-
{
|
| 2065 |
-
"args": [[None], {"frame": {"duration": 0, "redraw": True},
|
| 2066 |
-
"mode": "immediate", "transition": {"duration": 0}}],
|
| 2067 |
-
"label": "โธ๏ธ Pause",
|
| 2068 |
-
"method": "animate"
|
| 2069 |
-
},
|
| 2070 |
-
{
|
| 2071 |
-
"args": [None, {"frame": {"duration": 500, "redraw": True},
|
| 2072 |
-
"fromcurrent": True, "transition": {"duration": 300}}],
|
| 2073 |
-
"label": "โฉ Fast",
|
| 2074 |
-
"method": "animate"
|
| 2075 |
-
}
|
| 2076 |
-
],
|
| 2077 |
-
"direction": "left",
|
| 2078 |
-
"pad": {"r": 10, "t": 87},
|
| 2079 |
-
"showactive": False,
|
| 2080 |
-
"type": "buttons",
|
| 2081 |
-
"x": 0.1,
|
| 2082 |
-
"xanchor": "right",
|
| 2083 |
-
"y": 0,
|
| 2084 |
-
"yanchor": "top"
|
| 2085 |
}
|
| 2086 |
],
|
| 2087 |
-
|
| 2088 |
-
|
| 2089 |
-
|
| 2090 |
-
|
| 2091 |
-
|
| 2092 |
-
|
| 2093 |
-
|
| 2094 |
-
|
| 2095 |
-
|
| 2096 |
-
|
| 2097 |
-
|
| 2098 |
-
|
| 2099 |
-
|
| 2100 |
-
|
| 2101 |
-
"
|
| 2102 |
-
"
|
| 2103 |
-
|
| 2104 |
-
|
| 2105 |
-
|
| 2106 |
-
|
| 2107 |
-
|
| 2108 |
-
|
| 2109 |
-
|
| 2110 |
-
|
| 2111 |
-
|
| 2112 |
-
|
| 2113 |
-
|
| 2114 |
-
|
| 2115 |
-
|
| 2116 |
-
|
| 2117 |
-
|
| 2118 |
-
|
| 2119 |
-
|
| 2120 |
-
|
| 2121 |
-
|
| 2122 |
-
|
| 2123 |
-
|
| 2124 |
-
|
| 2125 |
-
|
| 2126 |
-
|
| 2127 |
-
|
| 2128 |
-
name='Genesis',
|
| 2129 |
-
showlegend=True,
|
| 2130 |
-
hovertemplate=(
|
| 2131 |
-
f"<b>GENESIS</b><br>"
|
| 2132 |
-
f"Position: {lats[0]:.1f}ยฐN, {lons[0]:.1f}ยฐE<br>"
|
| 2133 |
-
f"Initial: {intensities[0]:.0f} kt<br>"
|
| 2134 |
-
"<extra></extra>"
|
| 2135 |
-
)
|
| 2136 |
-
),
|
| 2137 |
-
row=1, col=1
|
| 2138 |
-
)
|
| 2139 |
|
| 2140 |
-
|
| 2141 |
-
|
| 2142 |
-
|
| 2143 |
-
|
| 2144 |
-
size = 8 + (point['intensity_kt'] / 12)
|
| 2145 |
|
|
|
|
| 2146 |
fig.add_trace(
|
| 2147 |
go.Scattergeo(
|
| 2148 |
-
lon=
|
| 2149 |
-
lat=
|
| 2150 |
-
mode='markers',
|
|
|
|
| 2151 |
marker=dict(
|
| 2152 |
-
size=
|
| 2153 |
color=color,
|
| 2154 |
-
opacity=
|
| 2155 |
-
line=dict(width=1, color='white')
|
| 2156 |
),
|
| 2157 |
-
name=f
|
| 2158 |
-
showlegend=(i % 10 == 0),
|
| 2159 |
hovertemplate=(
|
| 2160 |
-
f"<b>
|
| 2161 |
-
f"
|
| 2162 |
-
f"
|
| 2163 |
-
f"Category: {point['category']}<br>"
|
| 2164 |
-
f"Stage: {point.get('development_stage', 'Unknown')}<br>"
|
| 2165 |
-
f"Speed: {point.get('forward_speed_kmh', 15):.1f} km/h<br>"
|
| 2166 |
"<extra></extra>"
|
| 2167 |
)
|
| 2168 |
-
)
|
| 2169 |
-
row=1, col=1
|
| 2170 |
)
|
| 2171 |
-
|
| 2172 |
-
|
| 2173 |
-
|
| 2174 |
-
|
| 2175 |
-
|
| 2176 |
-
|
| 2177 |
-
|
| 2178 |
-
|
| 2179 |
-
|
| 2180 |
-
|
| 2181 |
-
|
| 2182 |
-
|
| 2183 |
-
|
| 2184 |
-
|
| 2185 |
-
|
| 2186 |
-
|
| 2187 |
-
|
| 2188 |
-
|
| 2189 |
-
|
| 2190 |
-
|
| 2191 |
-
|
| 2192 |
-
|
| 2193 |
-
marker=dict(size=6, color='red'),
|
| 2194 |
-
name='Wind Speed',
|
| 2195 |
-
showlegend=False
|
| 2196 |
-
),
|
| 2197 |
-
row=2, col=1
|
| 2198 |
-
)
|
| 2199 |
-
|
| 2200 |
-
# Add category threshold lines
|
| 2201 |
-
thresholds = [34, 64, 83, 96, 113, 137]
|
| 2202 |
-
threshold_names = ['TS', 'C1', 'C2', 'C3', 'C4', 'C5']
|
| 2203 |
-
|
| 2204 |
-
for thresh, name in zip(thresholds, threshold_names):
|
| 2205 |
-
fig.add_trace(
|
| 2206 |
-
go.Scatter(
|
| 2207 |
-
x=[min(hours), max(hours)],
|
| 2208 |
-
y=[thresh, thresh],
|
| 2209 |
-
mode='lines',
|
| 2210 |
-
line=dict(color='gray', width=1, dash='dash'),
|
| 2211 |
-
name=name,
|
| 2212 |
-
showlegend=False,
|
| 2213 |
-
hovertemplate=f"{name} Threshold: {thresh} kt<extra></extra>"
|
| 2214 |
-
),
|
| 2215 |
-
row=2, col=1
|
| 2216 |
-
)
|
| 2217 |
-
|
| 2218 |
-
# Forward speed plot
|
| 2219 |
-
fig.add_trace(
|
| 2220 |
-
go.Scatter(
|
| 2221 |
-
x=hours,
|
| 2222 |
-
y=speeds,
|
| 2223 |
-
mode='lines+markers',
|
| 2224 |
-
line=dict(color='green', width=2),
|
| 2225 |
-
marker=dict(size=4, color='green'),
|
| 2226 |
-
name='Forward Speed',
|
| 2227 |
-
showlegend=False
|
| 2228 |
-
),
|
| 2229 |
-
row=2, col=2
|
| 2230 |
-
)
|
| 2231 |
-
|
| 2232 |
-
# Add uncertainty cone if requested
|
| 2233 |
-
if show_uncertainty and len(route_data) > 1:
|
| 2234 |
-
uncertainty_lats_upper = []
|
| 2235 |
-
uncertainty_lats_lower = []
|
| 2236 |
-
uncertainty_lons_upper = []
|
| 2237 |
-
uncertainty_lons_lower = []
|
| 2238 |
-
|
| 2239 |
-
for i, point in enumerate(route_data):
|
| 2240 |
-
# Uncertainty grows with time and decreases with confidence
|
| 2241 |
-
base_uncertainty = 0.4 + (i / len(route_data)) * 1.8
|
| 2242 |
-
confidence_factor = point.get('confidence', 0.8)
|
| 2243 |
-
uncertainty = base_uncertainty / confidence_factor
|
| 2244 |
-
|
| 2245 |
-
uncertainty_lats_upper.append(point['lat'] + uncertainty)
|
| 2246 |
-
uncertainty_lats_lower.append(point['lat'] - uncertainty)
|
| 2247 |
-
uncertainty_lons_upper.append(point['lon'] + uncertainty)
|
| 2248 |
-
uncertainty_lons_lower.append(point['lon'] - uncertainty)
|
| 2249 |
-
|
| 2250 |
-
uncertainty_lats = uncertainty_lats_upper + uncertainty_lats_lower[::-1]
|
| 2251 |
-
uncertainty_lons = uncertainty_lons_upper + uncertainty_lons_lower[::-1]
|
| 2252 |
-
|
| 2253 |
-
fig.add_trace(
|
| 2254 |
-
go.Scattergeo(
|
| 2255 |
-
lon=uncertainty_lons,
|
| 2256 |
-
lat=uncertainty_lats,
|
| 2257 |
-
mode='lines',
|
| 2258 |
-
fill='toself',
|
| 2259 |
-
fillcolor='rgba(128,128,128,0.15)',
|
| 2260 |
-
line=dict(color='rgba(128,128,128,0.4)', width=1),
|
| 2261 |
-
name='Uncertainty Cone',
|
| 2262 |
-
showlegend=True
|
| 2263 |
-
),
|
| 2264 |
-
row=1, col=1
|
| 2265 |
-
)
|
| 2266 |
-
|
| 2267 |
-
# Enhanced layout
|
| 2268 |
-
fig.update_layout(
|
| 2269 |
-
title=f"Comprehensive Storm Development Analysis<br><sub>Starting from {prediction_results['genesis_info']['description']}</sub>",
|
| 2270 |
-
height=1000, # Taller for better subplot visibility
|
| 2271 |
-
width=1400, # Wider
|
| 2272 |
-
showlegend=True
|
| 2273 |
-
)
|
| 2274 |
-
|
| 2275 |
-
# Update geo layout
|
| 2276 |
-
fig.update_geos(
|
| 2277 |
projection_type="natural earth",
|
| 2278 |
showland=True,
|
| 2279 |
landcolor="LightGray",
|
| 2280 |
showocean=True,
|
| 2281 |
oceancolor="LightBlue",
|
| 2282 |
showcoastlines=True,
|
| 2283 |
-
coastlinecolor="
|
| 2284 |
-
|
| 2285 |
-
|
| 2286 |
-
|
| 2287 |
-
|
| 2288 |
-
|
| 2289 |
-
|
| 2290 |
-
|
| 2291 |
-
# Update subplot axes
|
| 2292 |
-
fig.update_xaxes(title_text="Forecast Hour", row=2, col=1)
|
| 2293 |
-
fig.update_yaxes(title_text="Wind Speed (kt)", row=2, col=1)
|
| 2294 |
-
fig.update_xaxes(title_text="Forecast Hour", row=2, col=2)
|
| 2295 |
-
fig.update_yaxes(title_text="Forward Speed (km/h)", row=2, col=2)
|
| 2296 |
-
|
| 2297 |
-
# Generate enhanced forecast text
|
| 2298 |
-
current = prediction_results['current_prediction']
|
| 2299 |
-
genesis_info = prediction_results['genesis_info']
|
| 2300 |
-
|
| 2301 |
-
# Calculate some statistics
|
| 2302 |
-
max_intensity = max(intensities)
|
| 2303 |
-
max_intensity_time = hours[intensities.index(max_intensity)]
|
| 2304 |
-
avg_speed = np.mean(speeds)
|
| 2305 |
-
|
| 2306 |
-
forecast_text = f"""
|
| 2307 |
-
COMPREHENSIVE STORM DEVELOPMENT FORECAST
|
| 2308 |
-
{'='*65}
|
| 2309 |
|
| 2310 |
-
|
| 2311 |
-
|
| 2312 |
-
|
| 2313 |
-
|
| 2314 |
-
|
| 2315 |
-
|
| 2316 |
-
|
| 2317 |
-
|
| 2318 |
-
|
| 2319 |
-
|
| 2320 |
-
|
| 2321 |
-
|
| 2322 |
-
|
| 2323 |
-
|
| 2324 |
-
|
| 2325 |
-
|
| 2326 |
-
|
| 2327 |
-
|
| 2328 |
-
|
| 2329 |
-
|
| 2330 |
-
|
| 2331 |
-
|
| 2332 |
-
|
| 2333 |
-
|
| 2334 |
-
|
| 2335 |
-
|
| 2336 |
-
|
| 2337 |
-
|
| 2338 |
-
|
| 2339 |
-
|
| 2340 |
-
|
| 2341 |
-
|
| 2342 |
-
|
| 2343 |
-
|
| 2344 |
-
|
| 2345 |
-
|
| 2346 |
-
|
| 2347 |
-
|
| 2348 |
-
|
| 2349 |
-
|
| 2350 |
-
|
| 2351 |
-
|
| 2352 |
-
|
| 2353 |
-
|
| 2354 |
-
|
| 2355 |
-
|
| 2356 |
-
|
| 2357 |
-
|
| 2358 |
-
|
| 2359 |
-
|
| 2360 |
-
|
| 2361 |
|
| 2362 |
# -----------------------------
|
| 2363 |
# Regression Functions (Original)
|
|
@@ -2562,41 +2417,6 @@ def get_longitude_analysis(start_year, start_month, end_year, end_month, enso_ph
|
|
| 2562 |
regression = perform_longitude_regression(start_year, start_month, end_year, end_month)
|
| 2563 |
return fig, slopes_text, regression
|
| 2564 |
|
| 2565 |
-
def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
|
| 2566 |
-
"""Categorize typhoon by standard with enhanced TD support - FIXED for matplotlib"""
|
| 2567 |
-
if pd.isna(wind_speed):
|
| 2568 |
-
return 'Tropical Depression', '#808080'
|
| 2569 |
-
|
| 2570 |
-
if standard=='taiwan':
|
| 2571 |
-
# Taiwan standard uses m/s, convert if needed
|
| 2572 |
-
if wind_speed > 50: # Likely in knots, convert to m/s
|
| 2573 |
-
wind_speed_ms = wind_speed * 0.514444
|
| 2574 |
-
else:
|
| 2575 |
-
wind_speed_ms = wind_speed
|
| 2576 |
-
|
| 2577 |
-
if wind_speed_ms >= 51.0:
|
| 2578 |
-
return 'Strong Typhoon', '#FF0000' # Red
|
| 2579 |
-
elif wind_speed_ms >= 33.7:
|
| 2580 |
-
return 'Medium Typhoon', '#FFA500' # Orange
|
| 2581 |
-
elif wind_speed_ms >= 17.2:
|
| 2582 |
-
return 'Mild Typhoon', '#FFFF00' # Yellow
|
| 2583 |
-
return 'Tropical Depression', '#808080' # Gray
|
| 2584 |
-
else:
|
| 2585 |
-
# Atlantic standard in knots
|
| 2586 |
-
if wind_speed >= 137:
|
| 2587 |
-
return 'C5 Super Typhoon', '#FF0000' # Red
|
| 2588 |
-
elif wind_speed >= 113:
|
| 2589 |
-
return 'C4 Very Strong Typhoon', '#FFA500' # Orange
|
| 2590 |
-
elif wind_speed >= 96:
|
| 2591 |
-
return 'C3 Strong Typhoon', '#FFFF00' # Yellow
|
| 2592 |
-
elif wind_speed >= 83:
|
| 2593 |
-
return 'C2 Typhoon', '#00FF00' # Green
|
| 2594 |
-
elif wind_speed >= 64:
|
| 2595 |
-
return 'C1 Typhoon', '#00FFFF' # Cyan
|
| 2596 |
-
elif wind_speed >= 34:
|
| 2597 |
-
return 'Tropical Storm', '#0000FF' # Blue
|
| 2598 |
-
return 'Tropical Depression', '#808080' # Gray
|
| 2599 |
-
|
| 2600 |
# -----------------------------
|
| 2601 |
# ENHANCED: Animation Functions with Taiwan Standard Support
|
| 2602 |
# -----------------------------
|
|
@@ -2755,7 +2575,7 @@ def generate_enhanced_track_video(year, typhoon_selection, standard):
|
|
| 2755 |
legend_elements = []
|
| 2756 |
|
| 2757 |
if standard == 'taiwan':
|
| 2758 |
-
categories = ['Tropical Depression', '
|
| 2759 |
for category in categories:
|
| 2760 |
color = get_taiwan_color(category)
|
| 2761 |
legend_elements.append(plt.Line2D([0], [0], marker='o', color='w',
|
|
@@ -2971,6 +2791,206 @@ def create_interface():
|
|
| 2971 |
"""
|
| 2972 |
gr.Markdown(overview_text)
|
| 2973 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2974 |
with gr.Tab("๐ฌ Advanced ML Clustering"):
|
| 2975 |
gr.Markdown("## ๐ฏ Storm Pattern Analysis with Separate Visualizations")
|
| 2976 |
gr.Markdown("**Four separate plots: Clustering, Routes, Pressure Evolution, and Wind Evolution**")
|
|
@@ -3023,206 +3043,6 @@ def create_interface():
|
|
| 3023 |
inputs=[reduction_method],
|
| 3024 |
outputs=[cluster_plot, routes_plot, pressure_plot, wind_plot, cluster_stats]
|
| 3025 |
)
|
| 3026 |
-
|
| 3027 |
-
cluster_info_text = """
|
| 3028 |
-
### ๐ Enhanced Clustering Features:
|
| 3029 |
-
- **Separate Visualizations**: Four distinct plots for comprehensive analysis
|
| 3030 |
-
- **Multi-dimensional Analysis**: Uses 15+ storm characteristics including intensity, track shape, genesis location
|
| 3031 |
-
- **Route Visualization**: Geographic storm tracks colored by cluster membership
|
| 3032 |
-
- **Temporal Analysis**: Pressure and wind evolution patterns by cluster
|
| 3033 |
-
- **DBSCAN Clustering**: Automatic pattern discovery without predefined cluster count
|
| 3034 |
-
- **Interactive**: Hover over points to see storm details, zoom and pan all plots
|
| 3035 |
-
|
| 3036 |
-
### ๐ฏ How to Interpret:
|
| 3037 |
-
- **Clustering Plot**: Each dot is a storm positioned by similarity (close = similar characteristics)
|
| 3038 |
-
- **Routes Plot**: Actual geographic storm tracks, colored by which cluster they belong to
|
| 3039 |
-
- **Pressure Plot**: Shows how pressure changes over time for storms in each cluster
|
| 3040 |
-
- **Wind Plot**: Shows wind speed evolution patterns for each cluster
|
| 3041 |
-
- **Cluster Colors**: Each cluster gets a unique color across all four visualizations
|
| 3042 |
-
"""
|
| 3043 |
-
gr.Markdown(cluster_info_text)
|
| 3044 |
-
|
| 3045 |
-
with gr.Tab("๐ Realistic Storm Genesis & Prediction"):
|
| 3046 |
-
gr.Markdown("## ๐ Realistic Typhoon Development from Genesis")
|
| 3047 |
-
|
| 3048 |
-
if CNN_AVAILABLE:
|
| 3049 |
-
gr.Markdown("๐ง **Deep Learning models available** - TensorFlow loaded successfully")
|
| 3050 |
-
method_description = "Hybrid CNN-Physics genesis modeling with realistic development cycles"
|
| 3051 |
-
else:
|
| 3052 |
-
gr.Markdown("๐ฌ **Physics-based models available** - Using climatological relationships")
|
| 3053 |
-
method_description = "Advanced physics-based genesis modeling with environmental coupling"
|
| 3054 |
-
|
| 3055 |
-
gr.Markdown(f"**Current Method**: {method_description}")
|
| 3056 |
-
gr.Markdown("**๐ Realistic Genesis**: Select from climatologically accurate development regions")
|
| 3057 |
-
gr.Markdown("**๐ TD Starting Point**: Storms begin at realistic Tropical Depression intensities (25-35 kt)")
|
| 3058 |
-
gr.Markdown("**๐ฌ Animation Support**: Watch storm development unfold over time")
|
| 3059 |
-
|
| 3060 |
-
with gr.Row():
|
| 3061 |
-
with gr.Column(scale=2):
|
| 3062 |
-
gr.Markdown("### ๐ Genesis Configuration")
|
| 3063 |
-
genesis_options = list(get_realistic_genesis_locations().keys())
|
| 3064 |
-
genesis_region = gr.Dropdown(
|
| 3065 |
-
choices=genesis_options,
|
| 3066 |
-
value="Western Pacific Main Development Region",
|
| 3067 |
-
label="Typhoon Genesis Region",
|
| 3068 |
-
info="Select realistic development region based on climatology"
|
| 3069 |
-
)
|
| 3070 |
-
|
| 3071 |
-
# Display selected region info
|
| 3072 |
-
def update_genesis_info(region):
|
| 3073 |
-
locations = get_realistic_genesis_locations()
|
| 3074 |
-
if region in locations:
|
| 3075 |
-
info = locations[region]
|
| 3076 |
-
return f"๐ Location: {info['lat']:.1f}ยฐN, {info['lon']:.1f}ยฐE\n๐ {info['description']}"
|
| 3077 |
-
return "Select a genesis region"
|
| 3078 |
-
|
| 3079 |
-
genesis_info_display = gr.Textbox(
|
| 3080 |
-
label="Selected Region Info",
|
| 3081 |
-
lines=2,
|
| 3082 |
-
interactive=False,
|
| 3083 |
-
value=update_genesis_info("Western Pacific Main Development Region")
|
| 3084 |
-
)
|
| 3085 |
-
|
| 3086 |
-
genesis_region.change(
|
| 3087 |
-
fn=update_genesis_info,
|
| 3088 |
-
inputs=[genesis_region],
|
| 3089 |
-
outputs=[genesis_info_display]
|
| 3090 |
-
)
|
| 3091 |
-
|
| 3092 |
-
with gr.Row():
|
| 3093 |
-
pred_month = gr.Slider(1, 12, label="Month", value=9, info="Peak season: Jul-Oct")
|
| 3094 |
-
pred_oni = gr.Number(label="ONI Value", value=0.0, info="ENSO index (-3 to 3)")
|
| 3095 |
-
with gr.Row():
|
| 3096 |
-
forecast_hours = gr.Number(
|
| 3097 |
-
label="Forecast Length (hours)",
|
| 3098 |
-
value=72,
|
| 3099 |
-
minimum=20,
|
| 3100 |
-
maximum=1000,
|
| 3101 |
-
step=6,
|
| 3102 |
-
info="Extended forecasting: 20-1000 hours (42 days max)"
|
| 3103 |
-
)
|
| 3104 |
-
advanced_physics = gr.Checkbox(
|
| 3105 |
-
label="Advanced Physics",
|
| 3106 |
-
value=True,
|
| 3107 |
-
info="Enhanced environmental modeling"
|
| 3108 |
-
)
|
| 3109 |
-
with gr.Row():
|
| 3110 |
-
show_uncertainty = gr.Checkbox(label="Show Uncertainty Cone", value=True)
|
| 3111 |
-
enable_animation = gr.Checkbox(
|
| 3112 |
-
label="Enable Animation",
|
| 3113 |
-
value=True,
|
| 3114 |
-
info="Animated storm development vs static view"
|
| 3115 |
-
)
|
| 3116 |
-
|
| 3117 |
-
with gr.Column(scale=1):
|
| 3118 |
-
gr.Markdown("### โ๏ธ Prediction Controls")
|
| 3119 |
-
predict_btn = gr.Button("๐ Generate Realistic Storm Forecast", variant="primary", size="lg")
|
| 3120 |
-
|
| 3121 |
-
gr.Markdown("### ๐ Genesis Conditions")
|
| 3122 |
-
current_intensity = gr.Number(label="Genesis Intensity (kt)", interactive=False)
|
| 3123 |
-
current_category = gr.Textbox(label="Initial Category", interactive=False)
|
| 3124 |
-
model_confidence = gr.Textbox(label="Model Info", interactive=False)
|
| 3125 |
-
|
| 3126 |
-
with gr.Row():
|
| 3127 |
-
route_plot = gr.Plot(label="๐บ๏ธ Advanced Route & Intensity Forecast")
|
| 3128 |
-
|
| 3129 |
-
with gr.Row():
|
| 3130 |
-
forecast_details = gr.Textbox(label="๐ Detailed Forecast Summary", lines=20, max_lines=25)
|
| 3131 |
-
|
| 3132 |
-
def run_realistic_prediction(region, month, oni, hours, advanced_phys, uncertainty, animation):
|
| 3133 |
-
try:
|
| 3134 |
-
# Run realistic prediction with genesis region
|
| 3135 |
-
results = predict_storm_route_and_intensity_realistic(
|
| 3136 |
-
region, month, oni,
|
| 3137 |
-
forecast_hours=hours,
|
| 3138 |
-
use_advanced_physics=advanced_phys
|
| 3139 |
-
)
|
| 3140 |
-
|
| 3141 |
-
# Extract genesis conditions
|
| 3142 |
-
current = results['current_prediction']
|
| 3143 |
-
intensity = current['intensity_kt']
|
| 3144 |
-
category = current['category']
|
| 3145 |
-
genesis_info = results.get('genesis_info', {})
|
| 3146 |
-
|
| 3147 |
-
# Create enhanced visualization
|
| 3148 |
-
fig, forecast_text = create_animated_route_visualization(
|
| 3149 |
-
results, uncertainty, animation
|
| 3150 |
-
)
|
| 3151 |
-
|
| 3152 |
-
model_info = f"{results['model_info']}\nGenesis: {genesis_info.get('description', 'Unknown')}"
|
| 3153 |
-
|
| 3154 |
-
return (
|
| 3155 |
-
intensity,
|
| 3156 |
-
category,
|
| 3157 |
-
model_info,
|
| 3158 |
-
fig,
|
| 3159 |
-
forecast_text
|
| 3160 |
-
)
|
| 3161 |
-
except Exception as e:
|
| 3162 |
-
error_msg = f"Realistic prediction failed: {str(e)}"
|
| 3163 |
-
logging.error(error_msg)
|
| 3164 |
-
import traceback
|
| 3165 |
-
traceback.print_exc()
|
| 3166 |
-
return (
|
| 3167 |
-
30, "Tropical Depression", f"Prediction failed: {str(e)}",
|
| 3168 |
-
None, f"Error generating realistic forecast: {str(e)}"
|
| 3169 |
-
)
|
| 3170 |
-
|
| 3171 |
-
predict_btn.click(
|
| 3172 |
-
fn=run_realistic_prediction,
|
| 3173 |
-
inputs=[genesis_region, pred_month, pred_oni, forecast_hours, advanced_physics, show_uncertainty, enable_animation],
|
| 3174 |
-
outputs=[current_intensity, current_category, model_confidence, route_plot, forecast_details]
|
| 3175 |
-
)
|
| 3176 |
-
|
| 3177 |
-
prediction_info_text = """
|
| 3178 |
-
### ๐ Realistic Storm Genesis Features:
|
| 3179 |
-
- **Climatological Genesis Regions**: 10 realistic development zones based on historical data
|
| 3180 |
-
- **Tropical Depression Starting Point**: Storms begin at realistic 25-38 kt intensities
|
| 3181 |
-
- **Realistic Typhoon Speeds**: Forward motion 15-25 km/h (matching observations)
|
| 3182 |
-
- **Extended Time Range**: 20-1000 hours (up to 42 days) with user input control
|
| 3183 |
-
- **Comprehensive Animation**: Watch development with wind/speed/pressure plots
|
| 3184 |
-
- **Environmental Coupling**: Advanced physics with ENSO, SST, shear, ฮฒ-drift, ridge patterns
|
| 3185 |
-
|
| 3186 |
-
### ๐ Enhanced Development Stages:
|
| 3187 |
-
- **Genesis (0-24h)**: Initial cyclogenesis at Tropical Depression level
|
| 3188 |
-
- **Development (24-72h)**: Rapid intensification in favorable environment (3-6 kt/h)
|
| 3189 |
-
- **Mature (72-120h)**: Peak intensity phase with environmental modulation
|
| 3190 |
-
- **Extended (120-240h)**: Continued tracking with realistic weakening
|
| 3191 |
-
- **Long-term (240h+)**: Extended forecasting for research and planning
|
| 3192 |
-
|
| 3193 |
-
### ๐โโ๏ธ Realistic Motion Physics:
|
| 3194 |
-
- **Low Latitude (< 20ยฐN)**: 12-15 km/h typical speeds
|
| 3195 |
-
- **Mid Latitude (20-30ยฐN)**: 18-22 km/h moderate speeds
|
| 3196 |
-
- **High Latitude (> 30ยฐN)**: 25-30 km/h fast extratropical transition
|
| 3197 |
-
- **Intensity Effects**: Stronger storms can move faster in steering flow
|
| 3198 |
-
- **Beta Drift**: Coriolis-induced poleward and westward motion
|
| 3199 |
-
- **Ridge Interaction**: Realistic recurvature patterns
|
| 3200 |
-
|
| 3201 |
-
### ๐ Genesis Region Selection:
|
| 3202 |
-
- **Western Pacific MDR**: Peak activity zone near Guam (12.5ยฐN, 145ยฐE)
|
| 3203 |
-
- **South China Sea**: Secondary development region (15ยฐN, 115ยฐE)
|
| 3204 |
-
- **Philippine Sea**: Recurving storm region (18ยฐN, 135ยฐE)
|
| 3205 |
-
- **Marshall Islands**: Eastern development zone (8ยฐN, 165ยฐE)
|
| 3206 |
-
- **Monsoon Trough**: Monsoon-driven genesis (10ยฐN, 130ยฐE)
|
| 3207 |
-
- **Other Basins**: Bay of Bengal, Eastern Pacific, Atlantic options
|
| 3208 |
-
|
| 3209 |
-
### ๐ฌ Enhanced Animation Features:
|
| 3210 |
-
- **Real-time Development**: Watch TD evolve to typhoon intensity
|
| 3211 |
-
- **Multi-plot Display**: Track + Wind Speed + Forward Speed plots
|
| 3212 |
-
- **Interactive Controls**: Play/pause/fast buttons and time slider
|
| 3213 |
-
- **Stage Tracking**: Visual indicators for development phases
|
| 3214 |
-
- **Speed Analysis**: Forward motion tracking throughout lifecycle
|
| 3215 |
-
- **Performance Optimized**: Smooth animation even for 1000+ hour forecasts
|
| 3216 |
-
|
| 3217 |
-
### ๐ฌ Advanced Physics Model:
|
| 3218 |
-
- **Realistic Intensification**: 3-6 kt/h development rates in favorable conditions
|
| 3219 |
-
- **Environmental Coupling**: SST, wind shear, Coriolis effects
|
| 3220 |
-
- **Steering Flow**: Subtropical ridge position and ENSO modulation
|
| 3221 |
-
- **Motion Variability**: Growing uncertainty with forecast time
|
| 3222 |
-
- **Pressure-Wind Relationship**: Realistic intensity-pressure coupling
|
| 3223 |
-
- **Long-term Climatology**: Extended forecasting using seasonal patterns
|
| 3224 |
-
"""
|
| 3225 |
-
gr.Markdown(prediction_info_text)
|
| 3226 |
|
| 3227 |
with gr.Tab("๐บ๏ธ Track Visualization"):
|
| 3228 |
with gr.Row():
|
|
@@ -3334,25 +3154,6 @@ def create_interface():
|
|
| 3334 |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
|
| 3335 |
outputs=[video_output]
|
| 3336 |
)
|
| 3337 |
-
|
| 3338 |
-
animation_info_text = """
|
| 3339 |
-
### ๐ฌ Enhanced Animation Features:
|
| 3340 |
-
- **Dual Standards**: Full support for both Atlantic and Taiwan classification systems
|
| 3341 |
-
- **Full TD Support**: Now displays Tropical Depressions (< 34 kt) in gray
|
| 3342 |
-
- **2025 Compatibility**: Complete support for current year data
|
| 3343 |
-
- **Enhanced Maps**: Better cartographic projections with terrain features
|
| 3344 |
-
- **Smart Scaling**: Storm symbols scale dynamically with intensity
|
| 3345 |
-
- **Real-time Info**: Live position, time, and meteorological data display
|
| 3346 |
-
- **Professional Styling**: Publication-quality animations with proper legends
|
| 3347 |
-
- **Optimized Export**: Fast rendering with web-compatible video formats
|
| 3348 |
-
|
| 3349 |
-
### ๐ Taiwan Standard Features:
|
| 3350 |
-
- **m/s Display**: Shows both knots and meters per second
|
| 3351 |
-
- **Local Categories**: TD โ Mild โ Medium โ Strong Typhoon
|
| 3352 |
-
- **Color Coding**: Gray โ Yellow โ Orange โ Red
|
| 3353 |
-
- **CWB Compatible**: Matches Central Weather Bureau classifications
|
| 3354 |
-
"""
|
| 3355 |
-
gr.Markdown(animation_info_text)
|
| 3356 |
|
| 3357 |
with gr.Tab("๐ Data Statistics & Insights"):
|
| 3358 |
gr.Markdown("## ๐ Comprehensive Dataset Analysis")
|
|
|
|
| 114 |
CACHE_EXPIRY_DAYS = 1
|
| 115 |
|
| 116 |
# -----------------------------
|
| 117 |
+
# ENHANCED: Color Maps and Standards with TD Support (FIXED TAIWAN CLASSIFICATION)
|
| 118 |
# -----------------------------
|
| 119 |
# Enhanced color mapping with TD support (for Plotly)
|
| 120 |
enhanced_color_map = {
|
|
|
|
| 140 |
'C5 Super Typhoon': '#FF0000' # Red
|
| 141 |
}
|
| 142 |
|
| 143 |
+
# FIXED Taiwan color mapping with correct categories
|
| 144 |
taiwan_color_map = {
|
| 145 |
+
'Tropical Depression': '#808080', # Gray
|
| 146 |
+
'Tropical Storm': '#0000FF', # Blue
|
| 147 |
+
'Moderate Typhoon': '#FFA500', # Orange
|
| 148 |
+
'Intense Typhoon': '#FF0000' # Red
|
| 149 |
}
|
| 150 |
|
| 151 |
def rgb_string_to_hex(rgb_string):
|
|
|
|
| 204 |
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'}
|
| 205 |
}
|
| 206 |
|
| 207 |
+
# FIXED Taiwan standard with correct official CWA thresholds
|
| 208 |
taiwan_standard = {
|
| 209 |
+
'Intense Typhoon': {'wind_speed': 51.0, 'color': 'Red', 'hex': '#FF0000'}, # 100+ knots (51.0+ m/s)
|
| 210 |
+
'Moderate Typhoon': {'wind_speed': 32.7, 'color': 'Orange', 'hex': '#FFA500'}, # 64-99 knots (32.7-50.9 m/s)
|
| 211 |
+
'Tropical Storm': {'wind_speed': 17.2, 'color': 'Blue', 'hex': '#0000FF'}, # 34-63 knots (17.2-32.6 m/s)
|
| 212 |
+
'Tropical Depression': {'wind_speed': 0, 'color': 'Gray', 'hex': '#808080'} # <34 knots (<17.2 m/s)
|
| 213 |
}
|
| 214 |
|
| 215 |
# -----------------------------
|
|
|
|
| 709 |
return pd.merge(typhoon_max, oni_long, on=['Year','Month'])
|
| 710 |
|
| 711 |
# -----------------------------
|
| 712 |
+
# ENHANCED: Categorization Functions (FIXED TAIWAN)
|
| 713 |
# -----------------------------
|
| 714 |
|
| 715 |
def categorize_typhoon_enhanced(wind_speed):
|
|
|
|
| 738 |
return 'C5 Super Typhoon'
|
| 739 |
|
| 740 |
def categorize_typhoon_taiwan(wind_speed):
|
| 741 |
+
"""FIXED Taiwan categorization system according to official CWA standards"""
|
| 742 |
if pd.isna(wind_speed):
|
| 743 |
return 'Tropical Depression'
|
| 744 |
|
| 745 |
+
# Convert from knots to m/s (official CWA uses m/s thresholds)
|
| 746 |
+
if wind_speed > 200: # Likely already in m/s
|
| 747 |
+
wind_speed_ms = wind_speed
|
| 748 |
+
else: # Likely in knots, convert to m/s
|
| 749 |
+
wind_speed_ms = wind_speed * 0.514444
|
| 750 |
|
| 751 |
+
# Official CWA Taiwan classification thresholds
|
| 752 |
+
if wind_speed_ms >= 51.0: # 100+ knots
|
| 753 |
+
return 'Intense Typhoon'
|
| 754 |
+
elif wind_speed_ms >= 32.7: # 64-99 knots
|
| 755 |
+
return 'Moderate Typhoon'
|
| 756 |
+
elif wind_speed_ms >= 17.2: # 34-63 knots
|
| 757 |
+
return 'Tropical Storm'
|
| 758 |
+
else: # <34 knots
|
| 759 |
return 'Tropical Depression'
|
| 760 |
|
| 761 |
# Original function for backward compatibility
|
|
|
|
| 776 |
else:
|
| 777 |
return 'Neutral'
|
| 778 |
|
| 779 |
+
def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
|
| 780 |
+
"""FIXED categorization function with correct Taiwan standards"""
|
| 781 |
+
if pd.isna(wind_speed):
|
| 782 |
+
return 'Tropical Depression', '#808080'
|
| 783 |
+
|
| 784 |
+
if standard == 'taiwan':
|
| 785 |
+
category = categorize_typhoon_taiwan(wind_speed)
|
| 786 |
+
color = taiwan_color_map.get(category, '#808080')
|
| 787 |
+
return category, color
|
| 788 |
+
else:
|
| 789 |
+
# Atlantic/International standard (existing logic is correct)
|
| 790 |
+
if wind_speed >= 137:
|
| 791 |
+
return 'C5 Super Typhoon', '#FF0000' # Red
|
| 792 |
+
elif wind_speed >= 113:
|
| 793 |
+
return 'C4 Very Strong Typhoon', '#FFA500' # Orange
|
| 794 |
+
elif wind_speed >= 96:
|
| 795 |
+
return 'C3 Strong Typhoon', '#FFFF00' # Yellow
|
| 796 |
+
elif wind_speed >= 83:
|
| 797 |
+
return 'C2 Typhoon', '#00FF00' # Green
|
| 798 |
+
elif wind_speed >= 64:
|
| 799 |
+
return 'C1 Typhoon', '#00FFFF' # Cyan
|
| 800 |
+
elif wind_speed >= 34:
|
| 801 |
+
return 'Tropical Storm', '#0000FF' # Blue
|
| 802 |
+
return 'Tropical Depression', '#808080' # Gray
|
| 803 |
+
|
| 804 |
# -----------------------------
|
| 805 |
# FIXED: ADVANCED ML FEATURES WITH ROBUST ERROR HANDLING
|
| 806 |
# -----------------------------
|
|
|
|
| 1616 |
except Exception as e:
|
| 1617 |
return None, f"Error creating prediction model: {str(e)}"
|
| 1618 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1619 |
def get_realistic_genesis_locations():
|
| 1620 |
"""Get realistic typhoon genesis regions based on climatology"""
|
| 1621 |
return {
|
|
|
|
| 1895 |
'model_info': 'Error in prediction'
|
| 1896 |
}
|
| 1897 |
|
| 1898 |
+
# -----------------------------
|
| 1899 |
+
# NEW: SIMPLIFIED MULTI-ROUTE PREDICTION FUNCTIONS
|
| 1900 |
+
# -----------------------------
|
| 1901 |
+
|
| 1902 |
+
def generate_multiple_route_predictions(month, oni_value, num_routes=5):
|
| 1903 |
+
"""Generate multiple realistic route predictions for animation"""
|
| 1904 |
+
|
| 1905 |
+
# Realistic genesis regions for variety
|
| 1906 |
+
genesis_regions = [
|
| 1907 |
+
"Western Pacific Main Development Region",
|
| 1908 |
+
"South China Sea",
|
| 1909 |
+
"Philippine Sea",
|
| 1910 |
+
"Marshall Islands",
|
| 1911 |
+
"Monsoon Trough"
|
| 1912 |
+
]
|
| 1913 |
+
|
| 1914 |
+
all_predictions = []
|
| 1915 |
+
|
| 1916 |
+
for i in range(num_routes):
|
| 1917 |
+
# Use different genesis regions and slight ONI variations for diversity
|
| 1918 |
+
region = genesis_regions[i % len(genesis_regions)]
|
| 1919 |
+
|
| 1920 |
+
# Add small random variation to ONI for route diversity
|
| 1921 |
+
oni_variation = oni_value + np.random.normal(0, 0.2)
|
| 1922 |
+
|
| 1923 |
+
# Generate prediction for this route
|
| 1924 |
+
prediction = predict_storm_route_and_intensity_realistic(
|
| 1925 |
+
region,
|
| 1926 |
+
month,
|
| 1927 |
+
oni_variation,
|
| 1928 |
+
forecast_hours=120, # 5 day forecast
|
| 1929 |
+
use_advanced_physics=True
|
| 1930 |
)
|
| 1931 |
|
| 1932 |
+
prediction['route_id'] = i + 1
|
| 1933 |
+
prediction['genesis_region'] = region
|
| 1934 |
+
prediction['oni_used'] = oni_variation
|
| 1935 |
+
all_predictions.append(prediction)
|
| 1936 |
+
|
| 1937 |
+
return all_predictions
|
| 1938 |
+
|
| 1939 |
+
def create_multi_route_animation(all_predictions, enable_animation=True):
|
| 1940 |
+
"""Create animated visualization showing multiple predicted routes"""
|
| 1941 |
+
|
| 1942 |
+
# Route colors for multiple predictions
|
| 1943 |
+
route_colors = ['#FF0066', '#00FF66', '#6600FF', '#FF6600', '#0066FF']
|
| 1944 |
+
|
| 1945 |
+
if not all_predictions or not any(pred.get('route_forecast') for pred in all_predictions):
|
| 1946 |
+
# Return error plot if no valid predictions
|
| 1947 |
+
fig = go.Figure()
|
| 1948 |
+
fig.add_annotation(
|
| 1949 |
+
text="No valid route predictions generated",
|
| 1950 |
+
xref="paper", yref="paper",
|
| 1951 |
+
x=0.5, y=0.5, xanchor='center', yanchor='middle',
|
| 1952 |
+
showarrow=False, font_size=16
|
| 1953 |
+
)
|
| 1954 |
+
return fig
|
| 1955 |
+
|
| 1956 |
+
fig = go.Figure()
|
| 1957 |
+
|
| 1958 |
+
if enable_animation:
|
| 1959 |
+
# Find max forecast length for consistent animation
|
| 1960 |
+
max_hours = max(len(pred['route_forecast']) for pred in all_predictions if pred.get('route_forecast'))
|
| 1961 |
+
|
| 1962 |
+
# Add genesis points for all routes (static background)
|
| 1963 |
+
for i, prediction in enumerate(all_predictions):
|
| 1964 |
+
if prediction.get('route_forecast'):
|
| 1965 |
+
first_point = prediction['route_forecast'][0]
|
| 1966 |
+
color = route_colors[i % len(route_colors)]
|
| 1967 |
+
|
| 1968 |
+
fig.add_trace(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1969 |
go.Scattergeo(
|
| 1970 |
+
lon=[first_point['lon']],
|
| 1971 |
+
lat=[first_point['lat']],
|
| 1972 |
mode='markers',
|
| 1973 |
marker=dict(
|
| 1974 |
+
size=20,
|
| 1975 |
+
color=color,
|
| 1976 |
+
symbol='star',
|
| 1977 |
+
line=dict(width=2, color='white')
|
| 1978 |
),
|
| 1979 |
+
name=f'Route {prediction["route_id"]} Genesis',
|
| 1980 |
+
showlegend=True,
|
| 1981 |
hovertemplate=(
|
| 1982 |
+
f"<b>Route {prediction['route_id']} Genesis</b><br>"
|
| 1983 |
+
f"Region: {prediction['genesis_region']}<br>"
|
| 1984 |
+
f"ONI: {prediction['oni_used']:.2f}<br>"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1985 |
"<extra></extra>"
|
| 1986 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1987 |
)
|
| 1988 |
+
)
|
| 1989 |
+
|
| 1990 |
+
# Create animation frames
|
| 1991 |
+
frames = []
|
| 1992 |
+
|
| 1993 |
+
for frame_idx in range(max_hours):
|
| 1994 |
+
frame_data = []
|
| 1995 |
|
| 1996 |
+
for i, prediction in enumerate(all_predictions):
|
| 1997 |
+
route_data = prediction.get('route_forecast', [])
|
| 1998 |
+
color = route_colors[i % len(route_colors)]
|
| 1999 |
+
|
| 2000 |
+
if frame_idx < len(route_data):
|
| 2001 |
+
# Get route points up to current frame
|
| 2002 |
+
current_points = route_data[:frame_idx + 1]
|
| 2003 |
+
|
| 2004 |
+
if current_points:
|
| 2005 |
+
lats = [p['lat'] for p in current_points]
|
| 2006 |
+
lons = [p['lon'] for p in current_points]
|
| 2007 |
+
intensities = [p['intensity_kt'] for p in current_points]
|
| 2008 |
+
|
| 2009 |
+
# Add route line
|
| 2010 |
+
frame_data.append(
|
| 2011 |
+
go.Scattergeo(
|
| 2012 |
+
lon=lons,
|
| 2013 |
+
lat=lats,
|
| 2014 |
+
mode='lines+markers',
|
| 2015 |
+
line=dict(color=color, width=3),
|
| 2016 |
+
marker=dict(
|
| 2017 |
+
size=[6 + max(0, int/15) for int in intensities],
|
| 2018 |
+
color=color,
|
| 2019 |
+
opacity=0.8
|
| 2020 |
+
),
|
| 2021 |
+
name=f'Route {prediction["route_id"]}',
|
| 2022 |
+
showlegend=False,
|
| 2023 |
+
hovertemplate=(
|
| 2024 |
+
f"<b>Route {prediction['route_id']}</b><br>"
|
| 2025 |
+
f"Hour {current_points[-1]['hour']}<br>"
|
| 2026 |
+
f"Intensity: {current_points[-1]['intensity_kt']:.0f} kt<br>"
|
| 2027 |
+
f"Category: {current_points[-1]['category']}<br>"
|
| 2028 |
+
"<extra></extra>"
|
| 2029 |
+
)
|
| 2030 |
+
)
|
| 2031 |
+
)
|
| 2032 |
|
| 2033 |
+
frames.append(go.Frame(
|
| 2034 |
+
data=frame_data,
|
| 2035 |
+
name=str(frame_idx),
|
| 2036 |
+
layout=go.Layout(
|
| 2037 |
+
title=f"Multi-Route Storm Development Animation - Hour {frame_idx * 6}"
|
| 2038 |
+
)
|
| 2039 |
+
))
|
| 2040 |
+
|
| 2041 |
+
fig.frames = frames
|
| 2042 |
+
|
| 2043 |
+
# Add animation controls
|
| 2044 |
+
fig.update_layout(
|
| 2045 |
+
updatemenus=[{
|
| 2046 |
+
"buttons": [
|
| 2047 |
+
{
|
| 2048 |
+
"args": [None, {"frame": {"duration": 800, "redraw": True},
|
| 2049 |
+
"fromcurrent": True, "transition": {"duration": 300}}],
|
| 2050 |
+
"label": "โถ๏ธ Play",
|
| 2051 |
+
"method": "animate"
|
| 2052 |
+
},
|
| 2053 |
{
|
| 2054 |
+
"args": [[None], {"frame": {"duration": 0, "redraw": True},
|
| 2055 |
+
"mode": "immediate", "transition": {"duration": 0}}],
|
| 2056 |
+
"label": "โธ๏ธ Pause",
|
| 2057 |
+
"method": "animate"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2058 |
}
|
| 2059 |
],
|
| 2060 |
+
"direction": "left",
|
| 2061 |
+
"pad": {"r": 10, "t": 87},
|
| 2062 |
+
"showactive": False,
|
| 2063 |
+
"type": "buttons",
|
| 2064 |
+
"x": 0.1,
|
| 2065 |
+
"xanchor": "right",
|
| 2066 |
+
"y": 0,
|
| 2067 |
+
"yanchor": "top"
|
| 2068 |
+
}],
|
| 2069 |
+
sliders=[{
|
| 2070 |
+
"active": 0,
|
| 2071 |
+
"yanchor": "top",
|
| 2072 |
+
"xanchor": "left",
|
| 2073 |
+
"currentvalue": {
|
| 2074 |
+
"font": {"size": 16},
|
| 2075 |
+
"prefix": "Hour: ",
|
| 2076 |
+
"visible": True,
|
| 2077 |
+
"xanchor": "right"
|
| 2078 |
+
},
|
| 2079 |
+
"transition": {"duration": 300, "easing": "cubic-in-out"},
|
| 2080 |
+
"pad": {"b": 10, "t": 50},
|
| 2081 |
+
"len": 0.9,
|
| 2082 |
+
"x": 0.1,
|
| 2083 |
+
"y": 0,
|
| 2084 |
+
"steps": [
|
| 2085 |
+
{
|
| 2086 |
+
"args": [[str(i)], {"frame": {"duration": 300, "redraw": True},
|
| 2087 |
+
"mode": "immediate", "transition": {"duration": 300}}],
|
| 2088 |
+
"label": f"H{i*6}",
|
| 2089 |
+
"method": "animate"
|
| 2090 |
+
}
|
| 2091 |
+
for i in range(0, max_hours, max(1, max_hours//20))
|
| 2092 |
+
]
|
| 2093 |
+
}]
|
| 2094 |
+
)
|
| 2095 |
+
|
| 2096 |
+
else:
|
| 2097 |
+
# Static version showing all complete routes
|
| 2098 |
+
for i, prediction in enumerate(all_predictions):
|
| 2099 |
+
route_data = prediction.get('route_forecast', [])
|
| 2100 |
+
color = route_colors[i % len(route_colors)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2101 |
|
| 2102 |
+
if route_data:
|
| 2103 |
+
lats = [p['lat'] for p in route_data]
|
| 2104 |
+
lons = [p['lon'] for p in route_data]
|
| 2105 |
+
intensities = [p['intensity_kt'] for p in route_data]
|
|
|
|
| 2106 |
|
| 2107 |
+
# Add complete route
|
| 2108 |
fig.add_trace(
|
| 2109 |
go.Scattergeo(
|
| 2110 |
+
lon=lons,
|
| 2111 |
+
lat=lats,
|
| 2112 |
+
mode='lines+markers',
|
| 2113 |
+
line=dict(color=color, width=3),
|
| 2114 |
marker=dict(
|
| 2115 |
+
size=[6 + max(0, int/15) for int in intensities],
|
| 2116 |
color=color,
|
| 2117 |
+
opacity=0.8
|
|
|
|
| 2118 |
),
|
| 2119 |
+
name=f'Route {prediction["route_id"]} - {prediction["genesis_region"][:20]}...',
|
|
|
|
| 2120 |
hovertemplate=(
|
| 2121 |
+
f"<b>Route {prediction['route_id']}</b><br>"
|
| 2122 |
+
f"Region: {prediction['genesis_region']}<br>"
|
| 2123 |
+
f"ONI: {prediction['oni_used']:.2f}<br>"
|
|
|
|
|
|
|
|
|
|
| 2124 |
"<extra></extra>"
|
| 2125 |
)
|
| 2126 |
+
)
|
|
|
|
| 2127 |
)
|
| 2128 |
+
|
| 2129 |
+
# Add genesis marker
|
| 2130 |
+
fig.add_trace(
|
| 2131 |
+
go.Scattergeo(
|
| 2132 |
+
lon=[lons[0]],
|
| 2133 |
+
lat=[lats[0]],
|
| 2134 |
+
mode='markers',
|
| 2135 |
+
marker=dict(
|
| 2136 |
+
size=20,
|
| 2137 |
+
color=color,
|
| 2138 |
+
symbol='star',
|
| 2139 |
+
line=dict(width=2, color='white')
|
| 2140 |
+
),
|
| 2141 |
+
name=f'Route {prediction["route_id"]} Genesis',
|
| 2142 |
+
showlegend=False
|
| 2143 |
+
)
|
| 2144 |
+
)
|
| 2145 |
+
|
| 2146 |
+
# Update layout
|
| 2147 |
+
fig.update_layout(
|
| 2148 |
+
title="Multiple Route Storm Predictions" + (" (Animated)" if enable_animation else " (Static)"),
|
| 2149 |
+
geo=dict(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2150 |
projection_type="natural earth",
|
| 2151 |
showland=True,
|
| 2152 |
landcolor="LightGray",
|
| 2153 |
showocean=True,
|
| 2154 |
oceancolor="LightBlue",
|
| 2155 |
showcoastlines=True,
|
| 2156 |
+
coastlinecolor="Gray",
|
| 2157 |
+
center=dict(lat=20, lon=140),
|
| 2158 |
+
projection_scale=2.0
|
| 2159 |
+
),
|
| 2160 |
+
height=800
|
| 2161 |
+
)
|
| 2162 |
+
|
| 2163 |
+
return fig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2164 |
|
| 2165 |
+
def create_intensity_comparison(all_predictions):
|
| 2166 |
+
"""Create intensity comparison plot for multiple routes"""
|
| 2167 |
+
fig = go.Figure()
|
| 2168 |
+
|
| 2169 |
+
route_colors = ['#FF0066', '#00FF66', '#6600FF', '#FF6600', '#0066FF']
|
| 2170 |
+
|
| 2171 |
+
for i, prediction in enumerate(all_predictions):
|
| 2172 |
+
route_data = prediction.get('route_forecast', [])
|
| 2173 |
+
if route_data:
|
| 2174 |
+
hours = [p['hour'] for p in route_data]
|
| 2175 |
+
intensities = [p['intensity_kt'] for p in route_data]
|
| 2176 |
+
color = route_colors[i % len(route_colors)]
|
| 2177 |
+
|
| 2178 |
+
fig.add_trace(
|
| 2179 |
+
go.Scatter(
|
| 2180 |
+
x=hours,
|
| 2181 |
+
y=intensities,
|
| 2182 |
+
mode='lines+markers',
|
| 2183 |
+
line=dict(color=color, width=3),
|
| 2184 |
+
marker=dict(size=6, color=color),
|
| 2185 |
+
name=f'Route {prediction["route_id"]}',
|
| 2186 |
+
hovertemplate=(
|
| 2187 |
+
f"<b>Route {prediction['route_id']}</b><br>"
|
| 2188 |
+
f"Hour: %{{x}}<br>"
|
| 2189 |
+
f"Intensity: %{{y:.0f}} kt<br>"
|
| 2190 |
+
"<extra></extra>"
|
| 2191 |
+
)
|
| 2192 |
+
)
|
| 2193 |
+
)
|
| 2194 |
+
|
| 2195 |
+
# Add category threshold lines
|
| 2196 |
+
thresholds = [34, 64, 83, 96, 113, 137]
|
| 2197 |
+
threshold_names = ['TS', 'C1', 'C2', 'C3', 'C4', 'C5']
|
| 2198 |
+
|
| 2199 |
+
for thresh, name in zip(thresholds, threshold_names):
|
| 2200 |
+
fig.add_hline(
|
| 2201 |
+
y=thresh,
|
| 2202 |
+
line_dash="dash",
|
| 2203 |
+
line_color="gray",
|
| 2204 |
+
annotation_text=name,
|
| 2205 |
+
annotation_position="right"
|
| 2206 |
+
)
|
| 2207 |
+
|
| 2208 |
+
fig.update_layout(
|
| 2209 |
+
title="Intensity Evolution Comparison - All Routes",
|
| 2210 |
+
xaxis_title="Forecast Hour",
|
| 2211 |
+
yaxis_title="Wind Speed (kt)",
|
| 2212 |
+
height=400
|
| 2213 |
+
)
|
| 2214 |
+
|
| 2215 |
+
return fig
|
| 2216 |
|
| 2217 |
# -----------------------------
|
| 2218 |
# Regression Functions (Original)
|
|
|
|
| 2417 |
regression = perform_longitude_regression(start_year, start_month, end_year, end_month)
|
| 2418 |
return fig, slopes_text, regression
|
| 2419 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2420 |
# -----------------------------
|
| 2421 |
# ENHANCED: Animation Functions with Taiwan Standard Support
|
| 2422 |
# -----------------------------
|
|
|
|
| 2575 |
legend_elements = []
|
| 2576 |
|
| 2577 |
if standard == 'taiwan':
|
| 2578 |
+
categories = ['Tropical Depression', 'Tropical Storm', 'Moderate Typhoon', 'Intense Typhoon']
|
| 2579 |
for category in categories:
|
| 2580 |
color = get_taiwan_color(category)
|
| 2581 |
legend_elements.append(plt.Line2D([0], [0], marker='o', color='w',
|
|
|
|
| 2791 |
"""
|
| 2792 |
gr.Markdown(overview_text)
|
| 2793 |
|
| 2794 |
+
with gr.Tab("๐ฏ Simple Multi-Route Prediction"):
|
| 2795 |
+
gr.Markdown("## ๐ฏ Simple Storm Prediction - Just Month & ONI")
|
| 2796 |
+
gr.Markdown("**Enter just the month and ONI value to see 5 different realistic storm development scenarios with prediction uncertainty**")
|
| 2797 |
+
|
| 2798 |
+
with gr.Row():
|
| 2799 |
+
with gr.Column(scale=1):
|
| 2800 |
+
simple_pred_month = gr.Slider(
|
| 2801 |
+
1, 12,
|
| 2802 |
+
label="Prediction Month",
|
| 2803 |
+
value=9,
|
| 2804 |
+
info="Peak typhoon season: Jul-Oct"
|
| 2805 |
+
)
|
| 2806 |
+
simple_pred_oni = gr.Number(
|
| 2807 |
+
label="ONI Value",
|
| 2808 |
+
value=0.0,
|
| 2809 |
+
info="El Niรฑo (+) / La Niรฑa (-) / Neutral (0)"
|
| 2810 |
+
)
|
| 2811 |
+
simple_enable_animation = gr.Checkbox(
|
| 2812 |
+
label="Enable Animation",
|
| 2813 |
+
value=True,
|
| 2814 |
+
info="Watch storms develop over time"
|
| 2815 |
+
)
|
| 2816 |
+
simple_predict_btn = gr.Button("๐ Generate 5 Route Predictions", variant="primary", size="lg")
|
| 2817 |
+
|
| 2818 |
+
with gr.Column(scale=2):
|
| 2819 |
+
gr.Markdown("### ๐ What You'll Get:")
|
| 2820 |
+
gr.Markdown("""
|
| 2821 |
+
- **5 Different Routes**: Various genesis regions and development paths
|
| 2822 |
+
- **Real-time Animation**: Watch all storms develop simultaneously
|
| 2823 |
+
- **Intensity Tracking**: See how each route intensifies differently
|
| 2824 |
+
- **Realistic Physics**: Environmental effects on each route
|
| 2825 |
+
- **Prediction Errors**: Uncertainty estimates and confidence intervals
|
| 2826 |
+
- **Comparative Analysis**: See best/worst case scenarios
|
| 2827 |
+
""")
|
| 2828 |
+
|
| 2829 |
+
with gr.Row():
|
| 2830 |
+
simple_route_animation = gr.Plot(label="๐บ๏ธ Multi-Route Animation")
|
| 2831 |
+
|
| 2832 |
+
with gr.Row():
|
| 2833 |
+
simple_intensity_comparison = gr.Plot(label="๐ Intensity Evolution Comparison")
|
| 2834 |
+
|
| 2835 |
+
with gr.Row():
|
| 2836 |
+
simple_prediction_summary = gr.Textbox(label="๐ Prediction Summary with Error Analysis", lines=20)
|
| 2837 |
+
|
| 2838 |
+
def run_simplified_prediction(month, oni, animation):
|
| 2839 |
+
try:
|
| 2840 |
+
# Generate multiple route predictions
|
| 2841 |
+
all_predictions = generate_multiple_route_predictions(month, oni, num_routes=5)
|
| 2842 |
+
|
| 2843 |
+
# Create multi-route animation
|
| 2844 |
+
route_fig = create_multi_route_animation(all_predictions, animation)
|
| 2845 |
+
|
| 2846 |
+
# Create intensity comparison
|
| 2847 |
+
intensity_fig = create_intensity_comparison(all_predictions)
|
| 2848 |
+
|
| 2849 |
+
# Generate summary text with error analysis
|
| 2850 |
+
summary_text = f"""
|
| 2851 |
+
SIMPLIFIED MULTI-ROUTE PREDICTION SUMMARY WITH ERROR ANALYSIS
|
| 2852 |
+
{'='*65}
|
| 2853 |
+
|
| 2854 |
+
INPUT CONDITIONS:
|
| 2855 |
+
โข Month: {month} {'(Peak Season)' if month in [7,8,9,10] else '(Off Season)'}
|
| 2856 |
+
โข ONI Value: {oni:.2f} {'(El Niรฑo)' if oni > 0.5 else '(La Niรฑa)' if oni < -0.5 else '(Neutral)'}
|
| 2857 |
+
|
| 2858 |
+
ROUTE PREDICTIONS GENERATED: 5 scenarios
|
| 2859 |
+
|
| 2860 |
+
ROUTE BREAKDOWN:
|
| 2861 |
+
"""
|
| 2862 |
+
|
| 2863 |
+
all_max_intensities = []
|
| 2864 |
+
all_final_intensities = []
|
| 2865 |
+
genesis_regions = []
|
| 2866 |
+
|
| 2867 |
+
for i, pred in enumerate(all_predictions):
|
| 2868 |
+
route_data = pred.get('route_forecast', [])
|
| 2869 |
+
if route_data:
|
| 2870 |
+
max_intensity = max(p['intensity_kt'] for p in route_data)
|
| 2871 |
+
final_intensity = route_data[-1]['intensity_kt']
|
| 2872 |
+
max_category = max((categorize_typhoon_enhanced(p['intensity_kt']) for p in route_data),
|
| 2873 |
+
key=lambda x: ['Tropical Depression', 'Tropical Storm', 'C1 Typhoon', 'C2 Typhoon', 'C3 Strong Typhoon', 'C4 Very Strong Typhoon', 'C5 Super Typhoon'].index(x))
|
| 2874 |
+
|
| 2875 |
+
all_max_intensities.append(max_intensity)
|
| 2876 |
+
all_final_intensities.append(final_intensity)
|
| 2877 |
+
genesis_regions.append(pred['genesis_region'])
|
| 2878 |
+
|
| 2879 |
+
summary_text += f"""
|
| 2880 |
+
ROUTE {pred['route_id']}:
|
| 2881 |
+
โข Genesis: {pred['genesis_region']}
|
| 2882 |
+
โข ONI Used: {pred['oni_used']:.2f}
|
| 2883 |
+
โข Peak Intensity: {max_intensity:.0f} kt ({max_category})
|
| 2884 |
+
โข Final Intensity: {final_intensity:.0f} kt
|
| 2885 |
+
โข Duration: {len(route_data)} time steps ({len(route_data)*6} hours)
|
| 2886 |
+
โข Confidence: {pred.get('confidence_scores', {}).get('long_term', 0.5)*100:.0f}%
|
| 2887 |
+
"""
|
| 2888 |
+
|
| 2889 |
+
# Enhanced scenario analysis with prediction errors
|
| 2890 |
+
if all_max_intensities:
|
| 2891 |
+
mean_max = np.mean(all_max_intensities)
|
| 2892 |
+
std_max = np.std(all_max_intensities)
|
| 2893 |
+
mean_final = np.mean(all_final_intensities)
|
| 2894 |
+
std_final = np.std(all_final_intensities)
|
| 2895 |
+
|
| 2896 |
+
# Calculate prediction uncertainty metrics
|
| 2897 |
+
intensity_range = max(all_max_intensities) - min(all_max_intensities)
|
| 2898 |
+
prediction_skill = max(0, 1 - (std_max / max(mean_max, 1)))
|
| 2899 |
+
|
| 2900 |
+
# Environmental predictability
|
| 2901 |
+
if month in [7, 8, 9]: # Peak season
|
| 2902 |
+
environmental_predictability = 0.8
|
| 2903 |
+
elif month in [10, 11, 6]: # Shoulder season
|
| 2904 |
+
environmental_predictability = 0.6
|
| 2905 |
+
else: # Off season
|
| 2906 |
+
environmental_predictability = 0.4
|
| 2907 |
+
|
| 2908 |
+
# ENSO influence on predictability
|
| 2909 |
+
enso_predictability = 0.7 + 0.2 * min(abs(oni), 2.0) / 2.0
|
| 2910 |
+
|
| 2911 |
+
# Overall prediction confidence
|
| 2912 |
+
overall_confidence = (prediction_skill * 0.4 +
|
| 2913 |
+
environmental_predictability * 0.3 +
|
| 2914 |
+
enso_predictability * 0.3)
|
| 2915 |
+
|
| 2916 |
+
summary_text += f"""
|
| 2917 |
+
|
| 2918 |
+
PREDICTION ERROR & UNCERTAINTY ANALYSIS:
|
| 2919 |
+
==========================================
|
| 2920 |
+
|
| 2921 |
+
INTENSITY STATISTICS:
|
| 2922 |
+
โข Peak Intensity Range: {min(all_max_intensities):.0f} - {max(all_max_intensities):.0f} kt
|
| 2923 |
+
โข Mean Peak Intensity: {mean_max:.0f} ยฑ {std_max:.0f} kt
|
| 2924 |
+
โข Final Intensity Range: {min(all_final_intensities):.0f} - {max(all_final_intensities):.0f} kt
|
| 2925 |
+
โข Mean Final Intensity: {mean_final:.0f} ยฑ {std_final:.0f} kt
|
| 2926 |
+
|
| 2927 |
+
PREDICTION UNCERTAINTY METRICS:
|
| 2928 |
+
โข Intensity Spread: {intensity_range:.0f} kt (uncertainty range)
|
| 2929 |
+
โข Prediction Skill: {prediction_skill:.2f} (0=poor, 1=excellent)
|
| 2930 |
+
โข Environmental Predictability: {environmental_predictability:.2f}
|
| 2931 |
+
โข ENSO Influence Factor: {enso_predictability:.2f}
|
| 2932 |
+
โข Overall Confidence: {overall_confidence:.2f} ({overall_confidence*100:.0f}%)
|
| 2933 |
+
|
| 2934 |
+
ERROR SOURCES & LIMITATIONS:
|
| 2935 |
+
โข Genesis Location Uncertainty: ยฑ2-5ยฐ lat/lon
|
| 2936 |
+
โข Steering Flow Variability: ยฑ15-25% track speed
|
| 2937 |
+
โข Intensity Development: ยฑ20-40 kt at 72h forecast
|
| 2938 |
+
โข Environmental Interaction: Variable SST/shear effects
|
| 2939 |
+
โข Model Physics: Simplified compared to full NWP models
|
| 2940 |
+
|
| 2941 |
+
INTERPRETATION GUIDE:
|
| 2942 |
+
{"๐ด HIGH UNCERTAINTY" if overall_confidence < 0.5 else "๐ก MODERATE UNCERTAINTY" if overall_confidence < 0.7 else "๐ข GOOD CONFIDENCE"}
|
| 2943 |
+
โข Spread > 40 kt: High uncertainty scenario
|
| 2944 |
+
โข Spread 20-40 kt: Moderate uncertainty
|
| 2945 |
+
โข Spread < 20 kt: Good agreement between scenarios
|
| 2946 |
+
|
| 2947 |
+
FORECAST SKILL BY TIME:
|
| 2948 |
+
โข 0-24h: ~85% position accuracy, ~75% intensity
|
| 2949 |
+
โข 24-48h: ~75% position accuracy, ~65% intensity
|
| 2950 |
+
โข 48-72h: ~65% position accuracy, ~55% intensity
|
| 2951 |
+
โข 72h+: ~50% position accuracy, ~45% intensity
|
| 2952 |
+
|
| 2953 |
+
GENESIS REGION DIVERSITY:
|
| 2954 |
+
โข Primary Regions: {len(set(genesis_regions))} different zones
|
| 2955 |
+
โข Most Active: {max(set(genesis_regions), key=genesis_regions.count)}
|
| 2956 |
+
โข Least Represented: {min(set(genesis_regions), key=genesis_regions.count)}
|
| 2957 |
+
|
| 2958 |
+
SEASONAL CONTEXT:
|
| 2959 |
+
{"Peak season - higher confidence in development patterns" if month in [7,8,9,10] else "Off-season - increased uncertainty in storm behavior"}
|
| 2960 |
+
|
| 2961 |
+
ENSO INFLUENCE:
|
| 2962 |
+
{"Strong El Niรฑo suppression expected" if oni > 1.0 else "Moderate El Niรฑo effects" if oni > 0.5 else "Strong La Niรฑa enhancement likely" if oni < -1.0 else "Moderate La Niรฑa effects" if oni < -0.5 else "Neutral ENSO - average conditions"}
|
| 2963 |
+
|
| 2964 |
+
ANIMATION FEATURES:
|
| 2965 |
+
{"โ
Real-time development animation enabled" if animation else "๐ Static multi-route comparison"}
|
| 2966 |
+
โ
5 simultaneous storm tracks with uncertainty
|
| 2967 |
+
โ
Color-coded intensity evolution
|
| 2968 |
+
โ
Realistic genesis locations and physics
|
| 2969 |
+
โ
Environmental coupling effects
|
| 2970 |
+
โ
Confidence-weighted predictions
|
| 2971 |
+
|
| 2972 |
+
โ ๏ธ IMPORTANT DISCLAIMERS:
|
| 2973 |
+
โข These are statistical predictions, not operational forecasts
|
| 2974 |
+
โข Actual storms may behave differently due to unmodeled factors
|
| 2975 |
+
โข Use for research/planning only, not for emergency decisions
|
| 2976 |
+
โข Consult official meteorological services for real forecasts
|
| 2977 |
+
โข Model simplified compared to operational NWP systems
|
| 2978 |
+
"""
|
| 2979 |
+
|
| 2980 |
+
return route_fig, intensity_fig, summary_text
|
| 2981 |
+
|
| 2982 |
+
except Exception as e:
|
| 2983 |
+
import traceback
|
| 2984 |
+
error_msg = f"Prediction failed: {str(e)}\n\nDetails:\n{traceback.format_exc()}"
|
| 2985 |
+
logging.error(error_msg)
|
| 2986 |
+
return None, None, error_msg
|
| 2987 |
+
|
| 2988 |
+
simple_predict_btn.click(
|
| 2989 |
+
fn=run_simplified_prediction,
|
| 2990 |
+
inputs=[simple_pred_month, simple_pred_oni, simple_enable_animation],
|
| 2991 |
+
outputs=[simple_route_animation, simple_intensity_comparison, simple_prediction_summary]
|
| 2992 |
+
)
|
| 2993 |
+
|
| 2994 |
with gr.Tab("๐ฌ Advanced ML Clustering"):
|
| 2995 |
gr.Markdown("## ๐ฏ Storm Pattern Analysis with Separate Visualizations")
|
| 2996 |
gr.Markdown("**Four separate plots: Clustering, Routes, Pressure Evolution, and Wind Evolution**")
|
|
|
|
| 3043 |
inputs=[reduction_method],
|
| 3044 |
outputs=[cluster_plot, routes_plot, pressure_plot, wind_plot, cluster_stats]
|
| 3045 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3046 |
|
| 3047 |
with gr.Tab("๐บ๏ธ Track Visualization"):
|
| 3048 |
with gr.Row():
|
|
|
|
| 3154 |
inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
|
| 3155 |
outputs=[video_output]
|
| 3156 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3157 |
|
| 3158 |
with gr.Tab("๐ Data Statistics & Insights"):
|
| 3159 |
gr.Markdown("## ๐ Comprehensive Dataset Analysis")
|