using Elmfire
using PlotsCrown Fire
Modeling passive and active crown fire
Crown fire occurs when fire spreads through the canopy layer of trees. This tutorial covers crown fire modeling in Elmfire.jl.
Crown Fire Basics
Crown fire is classified as: - Passive (torching): Individual trees or groups torch, but fire doesn’t spread continuously through canopy - Active: Fire spreads continuously through the canopy, driven by wind
Canopy Properties
Crown fire requires canopy data:
ncols, nrows = 100, 100
cellsize = 30.0
# Create canopy grid with typical forest values
canopy = CanopyGrid{Float64}(
fill(0.15, ncols, nrows), # cbd: Canopy bulk density (kg/m³)
fill(2.0, ncols, nrows), # cbh: Canopy base height (m)
fill(0.6, ncols, nrows), # cc: Canopy cover (fraction)
fill(20.0, ncols, nrows) # ch: Canopy height (m)
)
println("Canopy properties:")
println(" Bulk density: 0.15 kg/m³")
println(" Base height: 2.0 m")
println(" Cover: 60%")
println(" Height: 20 m")Canopy properties:
Bulk density: 0.15 kg/m³
Base height: 2.0 m
Cover: 60%
Height: 20 m
Enabling Crown Fire
Configure simulation for crown fire:
config = SimulationConfig{Float64}(
enable_crown_fire = true,
enable_spotting = false,
crown_fire_adj = 1.0,
critical_canopy_cover = 0.4,
foliar_moisture = 100.0
)SimulationConfig{Float64}(true, false, 1.0, 0.4, 100.0, nothing, false)
Surface Fire vs Crown Fire
Compare simulations with and without crown fire:
fuel_table = create_standard_fuel_table(Float64)
fuel_ids = fill(10, ncols, nrows) # FBFM10: Timber understory
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.50, MLW = 0.80
)
# Surface fire only
state_surface = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state_surface, 50, 50, 0.0)
config_surface = SimulationConfig{Float64}(enable_crown_fire = false)
weather_interp = create_constant_interpolator(weather, ncols, nrows, cellsize)
simulate_full!(state_surface, fuel_ids, fuel_table, weather_interp, slope, aspect,
0.0, 30.0; config = config_surface)
# Crown fire enabled
state_crown = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state_crown, 50, 50, 0.0)
config_crown = SimulationConfig{Float64}(
enable_crown_fire = true,
critical_canopy_cover = 0.4,
foliar_moisture = 100.0
)
simulate_full!(state_crown, fuel_ids, fuel_table, weather_interp, slope, aspect,
0.0, 30.0; canopy = canopy, config = config_crown)
# Compare
p1 = heatmap(state_surface.burned',
title = "Surface Fire Only\n$(round(get_burned_area_acres(state_surface), digits=1)) acres",
color = :YlOrRd, aspect_ratio = 1)
p2 = heatmap(state_crown.burned',
title = "Crown Fire Enabled\n$(round(get_burned_area_acres(state_crown), digits=1)) acres",
color = :YlOrRd, aspect_ratio = 1)
plot(p1, p2, layout = (1, 2), size = (800, 400))Critical Fireline Intensity
Crown fire initiation depends on fireline intensity reaching the critical threshold:
# Calculate critical intensity for different canopy base heights
cbh_values = 1.0:0.5:10.0 # meters
foliar_moisture = 100.0 # percent
i_crit = [critical_fireline_intensity(cbh, foliar_moisture) for cbh in cbh_values]
plot(cbh_values, i_crit,
xlabel = "Canopy Base Height (m)",
ylabel = "Critical Fireline Intensity (kW/m)",
title = "Crown Fire Initiation Threshold",
linewidth = 2,
legend = false
)Higher canopy base heights require greater fireline intensity to initiate crown fire.
Canopy Bulk Density Effect
cbd_values = [0.05, 0.10, 0.15, 0.20]
plots = []
for cbd in cbd_values
canopy_var = CanopyGrid{Float64}(
fill(cbd, ncols, nrows),
fill(2.0, ncols, nrows),
fill(0.6, ncols, nrows),
fill(20.0, ncols, nrows)
)
state = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state, 50, 50, 0.0)
simulate_full!(state, fuel_ids, fuel_table, weather_interp, slope, aspect,
0.0, 25.0; canopy = canopy_var, config = config_crown)
push!(plots, heatmap(state.burned',
title = "CBD = $cbd kg/m³",
color = :YlOrRd, aspect_ratio = 1, colorbar = false
))
end
plot(plots..., layout = (2, 2), size = (700, 700),
plot_title = "Crown Fire vs Canopy Bulk Density")Foliar Moisture Effect
fm_values = [80, 100, 120, 140]
plots = []
for fm in fm_values
config_fm = SimulationConfig{Float64}(
enable_crown_fire = true,
foliar_moisture = Float64(fm)
)
state = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state, 50, 50, 0.0)
simulate_full!(state, fuel_ids, fuel_table, weather_interp, slope, aspect,
0.0, 25.0; canopy = canopy, config = config_fm)
push!(plots, heatmap(state.burned',
title = "FM = $fm%",
color = :YlOrRd, aspect_ratio = 1, colorbar = false
))
end
plot(plots..., layout = (2, 2), size = (700, 700),
plot_title = "Crown Fire vs Foliar Moisture")Combined Spread Rate
The total spread rate combines surface and crown fire components:
# Get canopy properties
canopy_props = CanopyProperties{Float64}(0.15, 2.0, 0.6, 20.0)
# Range of surface fireline intensities
flin_range = 100:100:5000
surface_ros = 50.0 # ft/min
combined_ros = Float64[]
for flin in flin_range
cr = crown_spread_rate(canopy_props, Float64(flin), 20.0, 0.06, surface_ros)
# SpreadResult fields: velocity, vs0, ir, hpua, flin, phiw, phis
sr = SpreadResult{Float64}(surface_ros, surface_ros, 500.0, 1000.0, Float64(flin), 1.0, 0.0)
cros = combined_spread_rate(sr, cr)
push!(combined_ros, cros)
end
plot(flin_range, combined_ros,
xlabel = "Surface Fireline Intensity (kW/m)",
ylabel = "Combined Spread Rate (ft/min)",
title = "Crown Fire Contribution to Spread Rate",
linewidth = 2,
legend = false
)
hline!([surface_ros], linestyle = :dash, label = "Surface only")