test_time = "2024-07-01 00:00:00"
with engine.connect() as conn:
tracks = gpd.read_postgis("""
SELECT storm_id, valid_time, leadtime, wind_speed,
nature, geometry
FROM storms.nhc_tracks_geo
WHERE issued_time = %s
ORDER BY storm_id, leadtime
""", conn, geom_col="geometry", params=(test_time,))
wsp = gpd.read_postgis("""
SELECT percentage, geometry,
round(ST_Area(geography(geometry))/1e6) as area_km2
FROM storms.nhc_wsp_polygon
WHERE issued_time = %s AND wind_threshold_kt = 34
AND geometry IS NOT NULL
ORDER BY percentage
""", conn, geom_col="geometry", params=(test_time,))
fig = go.Figure()
for _, row in wsp.iterrows():
pct = row["percentage"]
opacity = 0.08 + (pct / 90) * 0.35
fill = f"rgba(30,80,180,{opacity:.2f})"
line_c = f"rgba(30,80,180,{min(opacity*2, 0.6):.2f})"
geom = row["geometry"]
parts = (
list(geom.geoms) if geom.geom_type == "MultiPolygon" else [geom]
)
for i, part in enumerate(parts):
lons, lats = part.exterior.xy
fig.add_trace(go.Scattermap(
lat=list(lats), lon=list(lons),
mode="lines", fill="toself", fillcolor=fill,
line={"width": 0.5, "color": line_c},
name=f"34kt {pct}% band" if i == 0 else None,
showlegend=(i == 0),
hoverinfo="text",
text=f"34kt >= {pct}% prob | {row['area_km2']:,.0f} km2",
))
storm_colors = {"beryl_na_2024": "#e63946", "chris_na_2024": "#2a9d8f"}
for storm_id in tracks["storm_id"].unique():
sub = tracks[tracks["storm_id"] == storm_id].sort_values("leadtime")
color = storm_colors.get(storm_id, "#4444ff")
lats = [pt.y for pt in sub.geometry]
lons = [pt.x for pt in sub.geometry]
hovers = [
f"<b>{storm_id}</b><br>Valid: {r['valid_time']}<br>"
f"Lead: +{r['leadtime']}h | {r['wind_speed']}kt | {r['nature']}"
for _, r in sub.iterrows()
]
sizes = [16 if lt == 0 else 9 for lt in sub["leadtime"]]
fig.add_trace(go.Scattermap(
lat=lats, lon=lons, mode="lines",
line={"width": 3, "color": color},
name=storm_id, hoverinfo="skip",
))
fig.add_trace(go.Scattermap(
lat=lats, lon=lons, mode="markers",
marker={"size": sizes, "color": color},
text=hovers, hoverinfo="text", showlegend=False,
))
fig.update_layout(
title=dict(
text=(
"<b>BERYL + CHRIS: 34kt WSP Bands</b><br>"
"<span style='font-size:12px'>"
f"Issuance: {test_time}</span>"
),
font_size=15,
),
map={"style": "carto-positron",
"center": {"lat": 18, "lon": -75}, "zoom": 3},
height=700,
margin={"l": 20, "r": 20, "t": 70, "b": 20},
legend={"yanchor": "top", "y": 0.98, "x": 0.01,
"bgcolor": "rgba(255,255,255,0.85)"},
)
fig.show()