Hurricane Ian Flood Replay
The Hurricane Ian Simulation replays a pre-computed GeoAI flood model of Hurricane Ian (2022) over Orlando, Florida. A 34 × 34 grid of cells is animated hour by hour, colored by composite risk score, alongside shelter status and a flood-aware evacuation route.
The entire replay dataset is fetched once and driven client-side, moving the timeline slider or adjusting risk weights requires no further server round-trips.
Enabling the Simulation
In the sidebar under Flood Analysis, click Hurricane Ian Simulation (sidebarFloodReplayBtn). The app:
- Opens the floating Hurricane Ian Simulation panel over the map
- Fetches
/flood-workshop/replay(proxied to the JetStream2 backend) - Builds one Cesium rectangle entity per grid cell plus shelter points and route polylines
- Flies the camera to the study area, offset eastward so the panel does not cover the grid
Panel Controls
| Control | Description |
|---|---|
| Timeline slider | Scrub to any hour of the event; label shows the ISO timestamp and frame n / N |
| Play / Stop | Auto-advance through frames |
| Risk Weights | Sliders for Flood, Population, and Lowland weights |
| Fix Weights | Rescales the three adjustable weights so the total returns to 1.00 |
| Stats grid | Cumulative rain, rain rate, wind gust, recommended shelter, Major+ cells, critical cells, population at risk, unsafe shelters |
| Shelters | Per-shelter safe/unsafe status at the current hour |
| Risk Score legend | Gradient bar from 0 (low) to 100 (high) |
Risk Model
Risk is recomputed in the browser whenever a weight slider moves, using normalized components that the API ships once:
risk[i] = clip(100 * ( w_flood * frame.flood_norm_t[i]
+ w_pop * grid[i].pop_norm_t
+ w_lf * grid[i].lf_t
+ w_lw * grid[i].landuse_weight_norm ), 0, 100)
| Weight | Default | Adjustable |
|---|---|---|
w_flood, flood depth (normalized per frame) | 0.60 | Yes |
w_pop, population density | 0.20 | Yes |
w_lf, lowland factor (terrain-derived) | 0.12 | Yes |
w_lw, land use weight | 0.08 | No (fixed) |
The four weights must sum to 1.00. If they do not, the total turns red, a warning appears, and a Fix Weights button proportionally rescales the adjustable three.
Risk tiers: Critical ≥ 50, High ≥ 35, Moderate ≥ 20.
Flood Classification
Cell flood classes use absolute depth cuts (metres):
| Class | Label | Depth |
|---|---|---|
| 0 | None/Minimal | < 0.02 m |
| 1 | Minor | 0.02 – 0.10 m |
| 2 | Moderate | 0.10 – 0.25 m |
| 3 | Major | 0.25 – 0.50 m |
| 4 | Extreme | > 0.50 m |
Shelters carry a resilience factor of 0.5, they tolerate 50% more flooding than an ordinary cell before being marked unsafe.
Shelters and Routing
Four static shelters surround the Orlando study center (28.5383 °N, −81.3792 °W): North (capacity 450), East (600), South (500), West (350). Each ships with a pre-computed OSM road-network polyline, route distance in miles, travel time, and a GeoAI shelter score.
Every frame also contains:
selected_shelter, the recommended shelter for that hourdyn_centroid, the population-weighted high-risk centroiddyn_route_polyline, a flood-aware route from that centroid to the selected shelter
Shelter points turn from safe to unsafe coloring as flooding reaches them.
Data Source
Browser → GET /flood-workshop/replay (Express proxy, server/server.js)
→ GET https://ucf-urbangeolens-backend.cis251126.projects.jetstream-cloud.org
/flood-workshop/replay (FastAPI on JetStream2, port 8002)
Override the upstream host with the FLOOD_BACKEND_URL environment variable.
Response shape
| Key | Contents |
|---|---|
meta | City, event, center lat/lon, grid_size, cell_deg, n_cells, n_frames, routing source, class labels, depth thresholds, risk tier thresholds, risk_weights_default, risk_formula |
grid | Per-cell static properties: lat, lon, elevation_m, landuse, landuse_label, population, pop_norm_t, lf_t, landuse_weight_norm |
timeline | Per-hour weather: time_iso, cumulative_rain_in, rain_in_hr, gust_mph, wind_factor, drainage |
shelters | Name, lat/lon, capacity, route_miles, travel_time_min, geoai_shelter_score, route_polyline |
frames | Per hour: flood_depths, flood_classes, flood_norm_t, risk_scores, flood_max_m, n_major_cells, n_critical_risk, pop_at_risk, selected_shelter, shelter_status, dyn_centroid, dyn_route_polyline |
How the Model Is Built
The backend computes the full replay once at startup (Flood-Workshop/api_server.py), then serves it from memory:
- Weather, Open-Meteo historical data for the Ian window (falls back to a synthetic Ian-shaped curve)
- Terrain, USGS 3DEP 30 m DEM (falls back to synthetic terrain)
- Hydrology, SCS Curve Number runoff plus D8 flow accumulation and a Topographic Wetness Index
- Classification, a Random Forest flood classifier trained on the grid
- Routing, OSMnx road network shelter routing (falls back to straight-line haversine)
Startup takes several minutes. Until it finishes, GET / and GET /health report status: "starting" and /replay returns 503.
Troubleshooting
| Symptom | Cause |
|---|---|
| Panel stuck on "Fetching simulation data..." | Backend still warming up, or the JetStream2 VM is unreachable |
503 Replay data is still loading | Startup pre-computation has not completed |
500 Failed to fetch flood data | Express could not reach FLOOD_BACKEND_URL |
| Weight total shown in red | Weights do not sum to 1.00, click Fix Weights |
Check the service on the VM with:
sudo systemctl status flood-workshop-api
journalctl -u flood-workshop-api -f
Implementation Notes
- Frontend:
client/public/js/app.js,initFloodReplay,createFloodReplayPanel,renderFloodReplayPanel,computeRiskScores,updateFloodFrame,buildFloodEntities,startFloodReplayPlay/stopFloodReplayPlay,initFloodHoverHandler,clearFloodReplayEntities - State:
floodReplayData,floodReplayGridEntities,floodReplayShelterEntities,floodReplayRouteEntities,floodReplayFrameIndex,floodRiskWeights,floodCurrentRiskScores - Server proxy:
GET /flood-workshop/replayinserver/server.js - Distinct from the bounding-box Flood Level Analysis filter, which reads a static mock CSV and is unrelated to this simulation