Inspiration

Wildfire information is scattered. Fire detections live on one map, weather on another, terrain somewhere else, and road closures and evacuation notices somewhere else again. Nothing shows how those layers affect each other: how wind pushes a fire toward a ridge, which road that fire cuts off, and where people would have to go instead.

What it does

EMBER simulates how a wildfire spreads across terrain and reroutes evacuation as roads become threatened.

  • Fire propagation: a cellular model spreads fire across a 72×72 terrain grid, driven by wind, slope, fuel type, humidity and temperature.
  • Spot fires: embers can jump downwind and start new, separate fire fronts.
  • Road status: every road segment moves through SAFE → WATCH → THREATENED → BLOCKED based on projected fire proximity, with a time-to-threat estimate.
  • Adaptive evacuation routing: a Dijkstra router recomputes the least-risk route to safety the moment a road's status changes, and explains why a route changed.
  • What-if controls: shift the wind, close a road, or draw a firebreak and watch fire, threatened roads and routes update.
  • Scenario comparison: capture a baseline, change conditions, and compare fire fronts, blocked roads, affected communities and active routes side by side.
  • Live data, honestly labeled: wind, temperature and humidity come from Open-Meteo, and fire detections come from NASA FIRMS. Every data source is marked LIVE or CACHED. If a live request fails, EMBER falls back to a cached scenario and says so.

EMBER is a decision-support simulation, not an operational wildfire forecast, and the app says that on screen at all times.

How we built it

EMBER is a single dependency-free web page using vanilla JavaScript and layered HTML canvases, with the simulation engine in its own module. The data flows through one pipeline:

real data → normalizer → environmental state → fire model + road graph → impact/risk engine → router → interface

Because everything passes through the normalizer, a real data source can be swapped in without touching the fire model, the router or the UI.

Each minute of simulated time, every burning cell tries to ignite each of its 8 neighbors. The spread score is a product of factors:

$$ s = r_0 \cdot f_{\text{fuel}} \cdot f_{\text{wind}} \cdot f_{\text{slope}} \cdot f_{\text{dry}} \cdot f_{\text{temp}} \cdot f_{\text{break}} \cdot m $$

and the ignition probability is

$$ p = 1 - e^{-s} $$

This keeps \( p \) a proper probability between 0 and 1 no matter how many factors stack up, so no ad hoc clamping is needed. Firebreaks work by multiplying spread into a cell by about 0.03.

For the fire edge, I ran marching squares over a smoothed, slightly warped field instead of the raw cells, so the perimeter reads as an organic curve rather than a grid of squares.

NASA FIRMS is called through a small Vercel serverless function that holds the API key as a server-side environment variable. The key never reaches the browser, and edge caching lets many users share one upstream request. The site is deployed on Vercel with the code on GitHub.

Challenges we ran into

  • The all-black map. At one point the terrain rendered completely black. The cause was a color-format mismatch: one helper returned rgb(...) strings and the next only understood #rrggbb. That made parseInt return NaN, which silently became black for every cell. The tests I had didn't inspect the actual canvas colors, so it passed everything. The fix was one shared color parser, plus new tests that check real fill colors and a pixel check in a real browser. I also found terrain was being painted twice on startup and made resizing skip identical repaints.
  • Wind direction is easy to get backwards. Meteorological "wind from the NW" means the fire travels toward the SE. I pinned this with an explicit downwind-versus-upwind test so a later edit can't invert it.
  • Staying honest about data. Only two of the five data sources (Open-Meteo and NASA FIRMS) are live. Terrain, land cover and roads are procedural stand-ins. Every cached or procedural source is labeled as such, and the app never presents cached data as live.
  • Keeping the API key secret on a static site. Hardcoding it or using a client-side variable would have published it, which is why it lives in the serverless function.

Accomplishments that we're proud of

  • A simulation where fire, roads and evacuation routes are actually coupled, so changing one changes the others.
  • Honest labeling throughout: LIVE versus CACHED, and "simulation, not a forecast".
  • Nearly 100 automated checks covering the fire model, routing, rendering colors and the API proxy.
  • A secure deployment where the FIRMS key never touches the browser.
  • A UI with full keyboard control and baseline accessibility support (reduced motion, focus states, ARIA live regions).

What we learned

  • A test suite that passes doesn't prove a visual bug is fixed, and I had to check the actual canvas pixels.
  • Silent failures are dangerous. A helper that fails loudly on bad input beats one that quietly produces black.
  • "Live" only means something when the app tells the truth about when it isn't.
  • Static sites can't hide secrets. Anything shipped to the browser is public, so a small server-side function is the right tool.

What's next for EMBER — Wildfire Spread & Evacuation Simulator

  • Replace the procedural terrain, land cover and road network with real USGS 3DEP elevation, ESA WorldCover and OpenStreetMap data. The architecture is built for these to drop in.
  • Calibrate and validate the fire model against historical fires before it is used for anything more than exploration.
  • Add more prepared locations and shareable scenarios.
  • Model evacuation demand and road capacity, not just road status.

AI disclosure

I used Claude (Anthropic) as a coding and technical-writing assistant. The concept and the initial framework of the project were mine. Claude did most of the iterative development, including writing and revising code, the automated tests and the technical documentation, and I directed the work throughout. I ran and tested the app locally in my own browser, reported bugs, made manual edits, and handled deployment to GitHub and Vercel. All design decisions, direction and final review were mine.

Built With

Share this project:

Updates

Submission history