The architectural challenge was making re-runs fast enough that a responder changing a fire date doesn't wait on a federal API. So a rerun is isolated to weather and ignition (sim_slug), while terrain and fuel rasters (geo_slug) are downloaded once and reused across every scenario on that AOI — minutes down to under 15 seconds.
The step that matters is vectorization: burn grids leave the backend as GeoJSON, not rasters. Cell2Fire runs as a subprocess behind FastAPI so the heavy C++ model stays server-side, and the browser only ever receives geometry it can animate at 60fps.
Keeping raster logic entirely server-side means the frontend can't break on a malformed .asc grid or a projection mismatch — the two failure modes that usually take a geospatial web app down. It also keeps payloads to kilobytes, which is why the map redraws in seconds rather than minutes.
Cell2Fire writes dense CSV burn grids in EPSG:5070. Shipping that to a browser would kill frame rate — so every timestep gets unioned, simplified, and reprojected before it ever reaches Mapbox.
# Convert one Cell2Fire burn-grid timestep into a Mapbox-ready polygon def vectorize_timestep(burn_grid, transform, src_crs): # Extract burned cells as raw polygons (rasterio.features) shapes = features.shapes(burn_grid, transform=transform) polygons = [shape(geom) for geom, val in shapes if val == 1] # Union + smooth to remove blocky grid-cell artifacts merged = unary_union(polygons).simplify(tolerance=1.5) # EPSG:5070 → WGS84, truncate to ~1m precision wgs84 = reproject(merged, src_crs, "EPSG:4326") return truncate_coords(wgs84, precision=5) # ~340K raster cells per frame → single-digit KB of GeoJSON, # keeping Mapbox GL JS smooth at 60fps.
Any CONUS location goes from a map click to a validated Cell2Fire run with no manual QGIS steps. Caching terrain and recomputing only weather ships kilobytes instead of megabytes — fast enough for a responder to run mid-event.