using Elmfire
using Plots
using Random
Random.seed!(42)TaskLocalRNG()
Wildland-urban interface and building ignition
The Wildland-Urban Interface (WUI) is where homes and structures meet wildland vegetation. This tutorial covers modeling building ignition during wildfires.
Buildings in the WUI can ignite from:
Define individual buildings:
# Create a wood-frame building
building = WUIBuilding{Float64}(
1, # ID
30, 30; # Grid coordinates
construction_type = :wood,
combustible_fraction = 0.7,
ignition_temperature = 300.0
)
println("Building $(building.id) at ($(building.ix), $(building.iy))")
println(" Type: $(building.construction_type)")
println(" Combustible fraction: $(building.combustible_fraction)")Building 1 at (30, 30)
Type: wood
Combustible fraction: 0.7
Different construction materials have different vulnerability:
construction_types = [:wood, :masonry, :mixed]
for ctype in construction_types
b = WUIBuilding{Float64}(1, 10, 10; construction_type = ctype)
# Test ignition probability at same heat flux
prob = building_ignition_probability(b, 30.0, 10.0)
println("$ctype: Ignition probability = $(round(prob * 100, digits=1))%")
endwood: Ignition probability = 100.0%
masonry: Ignition probability = 83.2%
mixed: Ignition probability = 99.8%
For simulations, create a grid of buildings:
ncols, nrows = 100, 100
# Create a grid of buildings
buildings = create_building_grid(
Float64,
ncols, nrows,
10, # Building spacing (cells)
3, # Building footprint (cells)
20, 20; # Starting position
construction_type = :wood
)
# Create WUI grid
wui_grid = WUIGrid{Float64}(buildings, ncols, nrows)
println("Created $(length(buildings)) buildings")
# Visualize building locations
building_map = zeros(ncols, nrows)
for b in buildings
building_map[b.ix, b.iy] = 1
end
heatmap(building_map',
title = "Building Locations",
color = [:white, :blue],
aspect_ratio = 1,
colorbar = false
)Created 64 buildings
Buildings ignite from radiant heat when nearby fire intensities are high:
# Heat flux vs distance for different fireline intensities
distances = 1:1:50 # meters
intensities = [500, 1000, 2000, 4000] # kW/m
p = plot(xlabel = "Distance (m)", ylabel = "Heat Flux (kW/m²)",
title = "Radiative Heat Flux from Fire", legend = :topright)
for I in intensities
fluxes = [compute_radiative_heat_flux(Float64(I), Float64(d), 5.0) for d in distances]
plot!(p, distances, fluxes, label = "I = $I kW/m", linewidth = 2)
end
# Add ignition threshold
hline!(p, [12.5], linestyle = :dash, color = :red, label = "Wood ignition threshold")
pIgnition probability depends on heat flux and exposure time:
wood_building = WUIBuilding{Float64}(1, 10, 10; construction_type = :wood)
masonry_building = WUIBuilding{Float64}(2, 10, 10; construction_type = :masonry)
# Heat flux range
heat_fluxes = 5:5:50 # kW/m²
exposure_time = 10.0 # minutes
p = plot(xlabel = "Heat Flux (kW/m²)", ylabel = "Ignition Probability",
title = "Building Ignition Probability (10 min exposure)",
legend = :bottomright)
wood_probs = [building_ignition_probability(wood_building, Float64(q), exposure_time)
for q in heat_fluxes]
masonry_probs = [building_ignition_probability(masonry_building, Float64(q), exposure_time)
for q in heat_fluxes]
plot!(p, heat_fluxes, wood_probs, label = "Wood", linewidth = 2)
plot!(p, heat_fluxes, masonry_probs, label = "Masonry", linewidth = 2)
pexposure_times = [1, 5, 10, 20, 30] # minutes
heat_flux = 25.0 # kW/m²
probs = [building_ignition_probability(wood_building, heat_flux, Float64(t))
for t in exposure_times]
bar(string.(exposure_times),
probs .* 100,
xlabel = "Exposure Time (min)",
ylabel = "Ignition Probability (%)",
title = "Ignition Probability vs Exposure Time\n(Wood building, 25 kW/m² heat flux)",
legend = false,
color = :orange
)The Hamada model describes fire spread between buildings:
source = WUIBuilding{Float64}(1, 10, 10; construction_type = :wood)
params = HamadaParameters{Float64}(
critical_separation = 15.0, # meters
wind_spread_factor = 1.5,
ember_generation_rate = 0.1,
base_spread_rate = 1.0
)
# Probability vs distance
separations = 1:1:20 # cells
cellsize = 1.0 # feet (small for test)
probs = Float64[]
for sep in separations
target = WUIBuilding{Float64}(2, 10 + sep, 10; construction_type = :wood)
prob = hamada_spread_probability(source, target, params, 10.0, 270.0, cellsize)
push!(probs, prob)
end
# Convert cell distance to meters
dist_m = separations .* cellsize .* 0.3048
plot(dist_m, probs,
xlabel = "Building Separation (m)",
ylabel = "Spread Probability",
title = "Hamada Building-to-Building Spread",
linewidth = 2,
legend = false
)
vline!([params.critical_separation], linestyle = :dash, label = "Critical separation")Simulate a fire approaching a residential area:
ncols, nrows = 120, 120
cellsize = 30.0
# Create fire state
state = FireState{Float64}(ncols, nrows, cellsize)
fuel_table = create_standard_fuel_table(Float64)
# Fuel: grass with houses in upper portion
fuel_ids = fill(1, ncols, nrows)
# Terrain
slope = zeros(Float64, ncols, nrows)
aspect = zeros(Float64, ncols, nrows)
# Weather - wind blowing north
weather = ConstantWeather{Float64}(
wind_speed_mph = 12.0,
wind_direction = 180.0, # From south
M1 = 0.05, M10 = 0.07, M100 = 0.09,
MLH = 0.50, MLW = 0.80
)
# Create WUI with buildings in northern portion
buildings = WUIBuilding{Float64}[]
id = 0
for ix in 30:15:90
for iy in 70:15:100
id += 1
ctype = rand([:wood, :wood, :masonry]) # 2/3 wood, 1/3 masonry
push!(buildings, WUIBuilding{Float64}(id, ix, iy; construction_type = ctype))
end
end
wui_grid = WUIGrid{Float64}(buildings, ncols, nrows)
println("Created $(length(buildings)) buildings")
# Ignite in southern portion
ignite!(state, 60, 20, 0.0)
# Run simulation and track WUI state
weather_interp = create_constant_interpolator(weather, ncols, nrows, cellsize)
rng = MersenneTwister(42)
# Simple simulation loop with WUI updates
t = 0.0
dt = 1.0
t_end = 60.0
while t < t_end
# Update WUI state
ignitions = update_wui_state!(wui_grid, state, weather, t, dt, rng)
if !isempty(ignitions)
for ig in ignitions
println("Building $(ig.building_id) ignited at t=$(round(t, digits=1)) via $(ig.ignition_source)")
end
end
t += dt
end
# Also run fire simulation
state2 = FireState{Float64}(ncols, nrows, cellsize)
ignite!(state2, 60, 20, 0.0)
simulate_uniform!(state2, 1, fuel_table, weather, 0.0, 0.0, 0.0, t_end)
# Visualize
p1 = heatmap(state2.burned',
title = "Fire Spread",
color = :YlOrRd,
aspect_ratio = 1
)
# Add building markers
building_x = [b.ix for b in buildings]
building_y = [b.iy for b in buildings]
colors = [wui_grid.ignited[i] ? :red : :blue for i in 1:length(buildings)]
scatter!(p1, building_x, building_y,
color = colors,
markersize = 6,
markershape = :square,
label = false
)
p1Created 15 buildings
Get summary statistics from the simulation:
stats = get_wui_statistics(wui_grid)
println("WUI Simulation Results:")
println(" Total buildings: $(stats.total_buildings)")
println(" Ignited buildings: $(stats.ignited_buildings)")
println(" Ignition fraction: $(round(stats.ignition_fraction * 100, digits=1))%")
println(" Mean ignition time: $(round(stats.mean_ignition_time, digits=1)) min")
println(" First ignition: $(round(stats.first_ignition_time, digits=1)) min")
println(" Last ignition: $(round(stats.last_ignition_time, digits=1)) min")
println(" Wood ignited: $(stats.wood_ignited)")
println(" Masonry ignited: $(stats.masonry_ignited)")WUI Simulation Results:
Total buildings: 15
Ignited buildings: 0
Ignition fraction: 0.0%
Mean ignition time: 0.0 min
First ignition: -1.0 min
Last ignition: -1.0 min
Wood ignited: 0
Masonry ignited: 0
The view factor determines how much radiative heat a building receives:
flame_heights = [2, 5, 10, 20] # meters
distances = 1:1:30 # meters
p = plot(xlabel = "Distance (m)", ylabel = "View Factor",
title = "View Factor vs Distance", legend = :topright)
for H in flame_heights
vfs = [compute_view_factor(Float64(H), Float64(d)) for d in distances]
plot!(p, distances, vfs, label = "Flame = $H m", linewidth = 2)
end
pSimulate different defensible space configurations:
# Test different buffer distances
buffer_distances = [0, 30, 60, 100] # feet
results = []
for buffer in buffer_distances
# Create scenario where fire approaches building
state = FireState{Float64}(80, 80, 30.0)
# Create fuel map with buffer zone
fuel_ids = fill(4, 80, 80) # Chaparral
if buffer > 0
buffer_cells = div(buffer, 30)
# Clear area around building at (40, 60)
for ix in max(1, 40-buffer_cells):min(80, 40+buffer_cells)
for iy in max(1, 60-buffer_cells):min(80, 60+buffer_cells)
fuel_ids[ix, iy] = 1 # Grass (lower intensity)
end
end
end
ignite!(state, 40, 20, 0.0)
simulate!(state, fuel_ids, fuel_table, weather, slope, aspect, 0.0, 45.0)
# Check if fire reached building location
reached = state.burned[40, 60]
intensity = state.fireline_intensity[40, 60]
push!(results, (buffer, reached, intensity))
end
for (buf, reached, intensity) in results
status = reached ? "REACHED (I=$(round(intensity, digits=0)) kW/m)" : "Did not reach"
println("Buffer $(buf)ft: $status")
endBuffer 0ft: Did not reach
Buffer 30ft: Did not reach
Buffer 60ft: Did not reach
Buffer 100ft: Did not reach
For detailed structure loss modeling, consider coupling with dedicated WUI loss models.