euler314 commited on
Commit
9d9f4d9
·
verified ·
1 Parent(s): c222226

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -65
app.py CHANGED
@@ -1157,32 +1157,46 @@ def create_genesis_animation(prediction_data, enable_animation=True):
1157
  month = prediction_data['month']
1158
  oni_value = prediction_data['oni_value']
1159
  year = prediction_data.get('year', 2025)
1160
-
1161
- # ---- Build animation frames ----
1162
- frames = []
1163
-
1164
- # Determine map bounds from all storm tracks
1165
- all_lats, all_lons = [], []
1166
  for storm in storm_predictions:
1167
  track = storm.get('track', [])
1168
- for pt in track:
1169
- all_lats.append(pt['lat'])
1170
- all_lons.append(pt['lon'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1171
  map_bounds = {
1172
  'lat_min': min(5, min(all_lats) - 5) if all_lats else 5,
1173
  'lat_max': max(35, max(all_lats) + 5) if all_lats else 35,
1174
  'lon_min': min(110, min(all_lons) - 10) if all_lons else 110,
1175
  'lon_max': max(180, max(all_lons) + 10) if all_lons else 180
1176
  }
1177
-
1178
  for day_idx, day_data in enumerate(daily_maps):
1179
  day = day_data['day']
1180
  gpi = day_data['gpi_field']
1181
  lats = day_data['lat_range']
1182
  lons = day_data['lon_range']
1183
-
1184
  traces = []
1185
- # 1) Genesis Potential dots
1186
  traces.append(go.Scattergeo(
1187
  lat=np.repeat(lats, len(lons)),
1188
  lon=np.tile(lons, len(lats)),
@@ -1194,10 +1208,7 @@ def create_genesis_animation(prediction_data, enable_animation=True):
1194
  cmin=0, cmax=3, opacity=0.6,
1195
  showscale=(day_idx == 0),
1196
  colorbar=(dict(
1197
- title=dict(
1198
- text="Genesis<br>Potential<br>Index",
1199
- side="right"
1200
- )
1201
  ) if day_idx == 0 else None)
1202
  ),
1203
  name='Genesis Potential',
@@ -1209,38 +1220,38 @@ def create_genesis_animation(prediction_data, enable_animation=True):
1209
  f'Day {day} of {month:02d}/{year}<extra></extra>'
1210
  )
1211
  ))
1212
-
1213
- # 2) Storm tracks up to this day
1214
  for storm in storm_predictions:
1215
- track = [pt for pt in storm.get('track', []) if pt['day'] <= day]
1216
- if not track:
1217
  continue
1218
- track_lats = [pt['lat'] for pt in track]
1219
- track_lons = [pt['lon'] for pt in track]
1220
- intensities = [pt['intensity'] for pt in track]
1221
- categories = [pt['category'] for pt in track]
1222
-
1223
- # a) historical line
1224
  traces.append(go.Scattergeo(
1225
- lat=track_lats, lon=track_lons, mode='lines',
1226
  line=dict(width=2, color='gray'),
1227
  name=f"{storm['storm_id']} Track",
1228
  showlegend=(day_idx == 0),
1229
  hoverinfo='skip'
1230
  ))
1231
- # b) current position
1232
  traces.append(go.Scattergeo(
1233
- lat=[track_lats[-1]], lon=[track_lons[-1]], mode='markers',
1234
  marker=dict(size=10, symbol='circle', color='red'),
1235
  name=f"{storm['storm_id']} Position",
1236
  showlegend=(day_idx == 0),
1237
  hovertemplate=(
1238
  f"{storm['storm_id']}<br>"
1239
- f"Intensity: {intensities[-1]} kt<br>"
1240
- f"Category: {categories[-1]}<extra></extra>"
1241
  )
1242
  ))
1243
-
1244
  frames.append(go.Frame(
1245
  data=traces,
1246
  name=str(day),
@@ -1261,49 +1272,56 @@ def create_genesis_animation(prediction_data, enable_animation=True):
1261
  title=f"Day {day} of {month:02d}/{year} ONI: {oni_value:.2f}"
1262
  )
1263
  ))
1264
-
1265
- # ---- Initialize Figure with first frame’s data ----
 
 
 
 
1266
  if enable_animation and len(frames) > 1:
1267
- fig = go.Figure(
1268
- data=frames[0].data,
1269
- frames=frames
1270
- )
1271
-
1272
- # Build slider steps
1273
- steps = []
1274
- for fr in frames:
1275
- steps.append(dict(
1276
- method="animate",
1277
- args=[[fr.name], {"mode":"immediate","frame":{"duration":600,"redraw":True},"transition":{"duration":0}}],
1278
- label=fr.name
1279
- ))
1280
-
1281
  fig.update_layout(
1282
  updatemenus=[dict(
1283
  type="buttons", showactive=False,
1284
  x=1.05, y=0.05, xanchor="right", yanchor="bottom",
1285
  buttons=[
1286
- dict(label="▶ Play", method="animate",
1287
- args=[None, {"frame":{"duration":600,"redraw":True},"fromcurrent":True,"transition":{"duration":0}}]),
1288
- dict(label="⏸ Pause", method="animate",
1289
- args=[[None], {"frame":{"duration":0,"redraw":False},"mode":"immediate"}])
 
 
 
 
 
 
 
 
1290
  ]
1291
  )],
1292
- sliders=[dict(
1293
- active=0, pad=dict(t=50),
1294
- steps=steps
1295
- )]
1296
  )
 
1297
  else:
1298
- # STATIC fallback: show only final day
1299
- final = frames[-1].data
1300
  fig = go.Figure(data=final)
1301
-
1302
- # ---- Shared final layout styling ----
1303
  fig.update_layout(
1304
  title={
1305
- 'text': f"🌊 Typhoon Genesis & Development Forecast<br><sub>{month:02d}/{year} | ONI: {oni_value:.2f}</sub>",
1306
- 'x':0.5, 'font':{'size':18}
 
1307
  },
1308
  geo=dict(
1309
  projection_type="natural earth",
@@ -1314,19 +1332,23 @@ def create_genesis_animation(prediction_data, enable_animation=True):
1314
  showcountries=True, countrycolor="gray",
1315
  resolution=50,
1316
  center=dict(lat=20, lon=140),
1317
- lonaxis_range=[110,180], lataxis_range=[5,35]
1318
  ),
1319
  width=1100, height=750,
1320
  showlegend=True,
1321
- legend=dict(x=0.02, y=0.98, bgcolor="rgba(255,255,255,0.7)", bordercolor="gray", borderwidth=1)
 
 
1322
  )
1323
-
1324
  return fig
1325
 
1326
  except Exception as e:
1327
  logging.error(f"Error creating professional genesis animation: {e}")
1328
  import traceback; traceback.print_exc()
1329
  return create_error_plot(f"Animation error: {e}")
 
 
1330
  def create_error_plot(error_message):
1331
  """Create a simple error plot"""
1332
  fig = go.Figure()
 
1157
  month = prediction_data['month']
1158
  oni_value = prediction_data['oni_value']
1159
  year = prediction_data.get('year', 2025)
1160
+
1161
+ # ---- 1) Prepare static full-track routes ----
1162
+ static_routes = []
 
 
 
1163
  for storm in storm_predictions:
1164
  track = storm.get('track', [])
1165
+ if not track:
1166
+ continue
1167
+ lats = [pt['lat'] for pt in track]
1168
+ lons = [pt['lon'] for pt in track]
1169
+ static_routes.append(
1170
+ go.Scattergeo(
1171
+ lat=lats,
1172
+ lon=lons,
1173
+ mode='lines',
1174
+ line=dict(width=2, dash='dash', color='gray'),
1175
+ showlegend=False,
1176
+ hoverinfo='skip'
1177
+ )
1178
+ )
1179
+
1180
+ # ---- 2) Build animation frames ----
1181
+ frames = []
1182
+ # determine map bounds from all storm tracks
1183
+ all_lats = [pt['lat'] for storm in storm_predictions for pt in storm.get('track', [])]
1184
+ all_lons = [pt['lon'] for storm in storm_predictions for pt in storm.get('track', [])]
1185
  map_bounds = {
1186
  'lat_min': min(5, min(all_lats) - 5) if all_lats else 5,
1187
  'lat_max': max(35, max(all_lats) + 5) if all_lats else 35,
1188
  'lon_min': min(110, min(all_lons) - 10) if all_lons else 110,
1189
  'lon_max': max(180, max(all_lons) + 10) if all_lons else 180
1190
  }
1191
+
1192
  for day_idx, day_data in enumerate(daily_maps):
1193
  day = day_data['day']
1194
  gpi = day_data['gpi_field']
1195
  lats = day_data['lat_range']
1196
  lons = day_data['lon_range']
1197
+
1198
  traces = []
1199
+ # Genesis potential dots
1200
  traces.append(go.Scattergeo(
1201
  lat=np.repeat(lats, len(lons)),
1202
  lon=np.tile(lons, len(lats)),
 
1208
  cmin=0, cmax=3, opacity=0.6,
1209
  showscale=(day_idx == 0),
1210
  colorbar=(dict(
1211
+ title=dict(text="Genesis<br>Potential<br>Index", side="right")
 
 
 
1212
  ) if day_idx == 0 else None)
1213
  ),
1214
  name='Genesis Potential',
 
1220
  f'Day {day} of {month:02d}/{year}<extra></extra>'
1221
  )
1222
  ))
1223
+
1224
+ # Storm positions up to this day
1225
  for storm in storm_predictions:
1226
+ past = [pt for pt in storm.get('track', []) if pt['day'] <= day]
1227
+ if not past:
1228
  continue
1229
+ lats_p = [pt['lat'] for pt in past]
1230
+ lons_p = [pt['lon'] for pt in past]
1231
+ intens = [pt['intensity'] for pt in past]
1232
+ cats = [pt['category'] for pt in past]
1233
+
1234
+ # historical line
1235
  traces.append(go.Scattergeo(
1236
+ lat=lats_p, lon=lons_p, mode='lines',
1237
  line=dict(width=2, color='gray'),
1238
  name=f"{storm['storm_id']} Track",
1239
  showlegend=(day_idx == 0),
1240
  hoverinfo='skip'
1241
  ))
1242
+ # current position
1243
  traces.append(go.Scattergeo(
1244
+ lat=[lats_p[-1]], lon=[lons_p[-1]], mode='markers',
1245
  marker=dict(size=10, symbol='circle', color='red'),
1246
  name=f"{storm['storm_id']} Position",
1247
  showlegend=(day_idx == 0),
1248
  hovertemplate=(
1249
  f"{storm['storm_id']}<br>"
1250
+ f"Intensity: {intens[-1]} kt<br>"
1251
+ f"Category: {cats[-1]}<extra></extra>"
1252
  )
1253
  ))
1254
+
1255
  frames.append(go.Frame(
1256
  data=traces,
1257
  name=str(day),
 
1272
  title=f"Day {day} of {month:02d}/{year} ONI: {oni_value:.2f}"
1273
  )
1274
  ))
1275
+
1276
+ # ---- 3) Initialize figure with static routes + first frame ----
1277
+ initial_data = static_routes + list(frames[0].data)
1278
+ fig = go.Figure(data=initial_data, frames=frames)
1279
+
1280
+ # ---- 4) Add play/pause buttons with redraw=True ----
1281
  if enable_animation and len(frames) > 1:
1282
+ # slider steps
1283
+ steps = [
1284
+ dict(method="animate",
1285
+ args=[[fr.name],
1286
+ {"mode": "immediate",
1287
+ "frame": {"duration": 600, "redraw": True},
1288
+ "transition": {"duration": 0}}],
1289
+ label=fr.name)
1290
+ for fr in frames
1291
+ ]
1292
+
 
 
 
1293
  fig.update_layout(
1294
  updatemenus=[dict(
1295
  type="buttons", showactive=False,
1296
  x=1.05, y=0.05, xanchor="right", yanchor="bottom",
1297
  buttons=[
1298
+ dict(label="▶ Play",
1299
+ method="animate",
1300
+ args=[None, # None means “all frames”
1301
+ {"frame": {"duration": 600, "redraw": True},
1302
+ "fromcurrent": True,
1303
+ "transition": {"duration": 0}}
1304
+ ]), # redraw=True fixes the dead play button :contentReference[oaicite:1]{index=1}
1305
+ dict(label="⏸ Pause",
1306
+ method="animate",
1307
+ args=[[None],
1308
+ {"frame": {"duration": 0, "redraw": False},
1309
+ "mode": "immediate"}])
1310
  ]
1311
  )],
1312
+ sliders=[dict(active=0, pad=dict(t=50), steps=steps)]
 
 
 
1313
  )
1314
+ # No-animation fallback: just show final day + routes
1315
  else:
1316
+ final = static_routes + list(frames[-1].data)
 
1317
  fig = go.Figure(data=final)
1318
+
1319
+ # ---- 5) Common layout styling ----
1320
  fig.update_layout(
1321
  title={
1322
+ 'text': f"🌊 Typhoon Genesis & Development Forecast<br>"
1323
+ f"<sub>{month:02d}/{year} | ONI: {oni_value:.2f}</sub>",
1324
+ 'x': 0.5, 'font': {'size': 18}
1325
  },
1326
  geo=dict(
1327
  projection_type="natural earth",
 
1332
  showcountries=True, countrycolor="gray",
1333
  resolution=50,
1334
  center=dict(lat=20, lon=140),
1335
+ lonaxis_range=[110, 180], lataxis_range=[5, 35]
1336
  ),
1337
  width=1100, height=750,
1338
  showlegend=True,
1339
+ legend=dict(x=0.02, y=0.98,
1340
+ bgcolor="rgba(255,255,255,0.7)",
1341
+ bordercolor="gray", borderwidth=1)
1342
  )
1343
+
1344
  return fig
1345
 
1346
  except Exception as e:
1347
  logging.error(f"Error creating professional genesis animation: {e}")
1348
  import traceback; traceback.print_exc()
1349
  return create_error_plot(f"Animation error: {e}")
1350
+
1351
+
1352
  def create_error_plot(error_message):
1353
  """Create a simple error plot"""
1354
  fig = go.Figure()