Spotting

Ember generation and transport

Spotting (ember transport) creates new ignitions downwind of the main fire. This tutorial covers Elmfire.jl’s spotting capabilities.

using Elmfire
using Plots
using Random
Random.seed!(42)
TaskLocalRNG()

Spotting Mechanism

Embers (firebrands) are generated by: - Burning vegetation lofted by convection - Higher intensity fires produce more embers - Crown fires generate significantly more embers

Configuring Spotting

# Create spotting parameters
spotting_params = SpottingParameters{Float64}(
    mean_distance = 100.0,              # Mean spotting distance (m)
    normalized_variance = 0.5,          # Normalized variance
    ws_exponent = 1.0,                  # Wind speed exponent
    flin_exponent = 0.5,                # Fireline intensity exponent
    nembers_max = 10,                   # Max embers per cell per timestep
    surface_spotting_percent = 1.0,     # Surface fire spotting probability (%)
    crown_spotting_percent = 10.0,      # Crown fire spotting probability (%)
    pign = 50.0,                        # Ignition probability (%)
    min_distance = 10.0,                # Min distance (m)
    max_distance = 2000.0               # Max distance (m)
)
SpottingParameters{Float64}(100.0, 0.5, 1.0, 0.5, 10, 1.0, 10.0, 50.0, 10.0, 2000.0)

Ember Transport

Embers are transported downwind with distances that scale with wind speed and fireline intensity. The SpottingParameters control:

  • mean_distance: Base spotting distance (m)
  • ws_exponent: How much wind speed increases distance
  • flin_exponent: How much intensity increases distance
  • normalized_variance: Randomness in landing location
  • min_distance / max_distance: Distance bounds (m)

Simulation with Spotting

ncols, nrows = 150, 150
cellsize = 30.0

state = FireState{Float64}(ncols, nrows, cellsize)
fuel_table = create_standard_fuel_table(Float64)
fuel_ids = fill(4, ncols, nrows)  # Chaparral (high intensity)

slope = zeros(Float64, ncols, nrows)
aspect = zeros(Float64, ncols, nrows)

weather = ConstantWeather{Float64}(
    wind_speed_mph = 20.0,
    wind_direction = 270.0,
    M1 = 0.04, M10 = 0.06, M100 = 0.08,
    MLH = 0.40, MLW = 0.70
)

config = SimulationConfig{Float64}(
    enable_crown_fire = false,
    enable_spotting = true,
    spotting_params = spotting_params
)

ignite!(state, 40, 75, 0.0)
weather_interp = create_constant_interpolator(weather, ncols, nrows, cellsize)

simulate_full!(state, fuel_ids, fuel_table, weather_interp, slope, aspect,
    0.0, 45.0; config = config, rng = MersenneTwister(42))

# Visualize
toa = copy(state.time_of_arrival)
toa[toa .< 0] .= NaN

heatmap(toa',
    title = "Fire Spread with Spotting Enabled",
    xlabel = "X (cells)",
    ylabel = "Y (cells)",
    color = :viridis,
    aspect_ratio = 1,
    size = (600, 500)
)

With vs Without Spotting

# Without spotting
state_no_spot = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state_no_spot, 40, 75, 0.0)
config_no_spot = SimulationConfig{Float64}(enable_spotting = false)

simulate_full!(state_no_spot, fuel_ids, fuel_table, weather_interp, slope, aspect,
    0.0, 45.0; config = config_no_spot)

# With spotting
state_spot = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state_spot, 40, 75, 0.0)

simulate_full!(state_spot, fuel_ids, fuel_table, weather_interp, slope, aspect,
    0.0, 45.0; config = config, rng = MersenneTwister(42))

p1 = heatmap(state_no_spot.burned',
    title = "Without Spotting\n$(round(get_burned_area_acres(state_no_spot), digits=1)) acres",
    color = :YlOrRd, aspect_ratio = 1)

p2 = heatmap(state_spot.burned',
    title = "With Spotting\n$(round(get_burned_area_acres(state_spot), digits=1)) acres",
    color = :YlOrRd, aspect_ratio = 1)

plot(p1, p2, layout = (1, 2), size = (800, 400))

Ember Generation

The number of embers depends on:

  • Fireline intensity: Higher intensity fires loft more embers
  • Fire type: Crown fires generate more embers than surface fires
  • spotting_params.nembers_max: Upper limit on embers per cell
  • spotting_params.surface_spotting_percent / crown_spotting_percent: Probability of spotting

Long-Range Spotting

Extreme fires can spot miles ahead:

# High-intensity scenario
ncols_big, nrows_big = 300, 200

state_big = FireState{Float64}(ncols_big, nrows_big, cellsize)
fuel_ids_big = fill(4, ncols_big, nrows_big)
slope_big = zeros(Float64, ncols_big, nrows_big)
aspect_big = zeros(Float64, ncols_big, nrows_big)

extreme_weather = ConstantWeather{Float64}(
    wind_speed_mph = 35.0,
    wind_direction = 270.0,
    M1 = 0.03, M10 = 0.05, M100 = 0.07,
    MLH = 0.35, MLW = 0.60
)

extreme_spotting = SpottingParameters{Float64}(
    mean_distance = 200.0,              # Longer mean distance (m)
    normalized_variance = 0.5,
    ws_exponent = 1.0,
    flin_exponent = 0.5,
    nembers_max = 20,                   # More embers
    surface_spotting_percent = 2.0,
    crown_spotting_percent = 15.0,
    pign = 60.0,                        # Higher ignition probability
    min_distance = 10.0,
    max_distance = 5000.0               # Longer max distance (m)
)

config_extreme = SimulationConfig{Float64}(
    enable_spotting = true,
    spotting_params = extreme_spotting
)

ignite!(state_big, 50, 100, 0.0)
weather_interp_big = create_constant_interpolator(extreme_weather, ncols_big, nrows_big, cellsize)

simulate_full!(state_big, fuel_ids_big, fuel_table, weather_interp_big,
    slope_big, aspect_big, 0.0, 60.0;
    config = config_extreme, rng = MersenneTwister(42))

toa = copy(state_big.time_of_arrival)
toa[toa .< 0] .= NaN

heatmap(toa',
    title = "Extreme Fire with Long-Range Spotting",
    color = :viridis,
    aspect_ratio = 1,
    size = (800, 500)
)