
Authors: Carlos Alberto Silva and Nadeem Fareed
rFDS provides an interface between LiDAR-based vegetation structure and the Fire Dynamics Simulator (FDS), enabling:
# CRAN release
install.packages("rFDS")
# Dev (r-universe; recommended for the latest)
install.packages(
"rFDS",
repos = c("https://carlos-alberto-silva.r-universe.dev/rFDS", "https://cloud.r-project.org")
)
library(rFDS)
You can install FDS manually from the official FireModels project or automatically via rFDS.
i) Download
ii) Windows
FDS6-…-SMV6-…-win64.exe.fds_local -help
smokeview -help
iii) Linux
/opt/fds/, then add its bin folder to your PATH.fds -help # or fds_local -help, depending on your build
smokeview -help
iv) macOS
fds -help # or fds_local -help
smokeview -help
If the commands aren’t found after installation, ensure the folder containing the executables (e.g.,
fds,fds_local,smokeview) is on your system PATH, or pass an explicit path torFDS::run_fds()(see below).
rFDS
rFDS includes a helper that fetches the official FDS distribution appropriate for your OS (when available) and registers it so you can call it from R:
# Installs/updates FDS from the official FireModels distribution
# Windows (no sudo; installer .bat is used)
install_fds(version = "6.10.1",
dest_dir = "C:/FDS", # choose a persistent folder
sudo = FALSE,
quiet_download = FALSE)
# Linux/macOS, user-local install (no sudo, writable dest_dir):
install_fds(version = "6.10.1",
dest_dir = "~/opt/fds",
sudo = FALSE,
quiet_download = TRUE)
# Linux/macOS, system-wide (requires sudo):
install_fds(version = "6.10.1",
dest_dir = "/opt/fds",
sudo = TRUE,
quiet_download = TRUE)
if (!requireNamespace(c("lidR","rgl"), quietly = TRUE)) install.packages(c("lidR","rgl"))
library(lidR)
library(rgl)
# 2.1.1) Load LAS from the package extdata
las_path <- system.file("extdata", "acf_longleaf_pine.laz", package = "rFDS")
stopifnot(nzchar(las_path), file.exists(las_path))
las <- readLAS(las_path)
# Quick bounds
summary(las@data)
# 2.1.2) Voxelization
vox <- laz_to_voxels(
las,
grid_res = 0.5,
class_col = "Classification",
local = FALSE
)
# 2.1.3) Quick visualization
plot_voxels(vox = vox, style = "points", color_by = "class", to_local = FALSE)
# 2.1.4) Domain + meshes + open boundaries
XB <- domain_from_lidar(vox, pad_xyz = c(0.5,0.5,0.5), centers = TRUE)
tiles <- tile_meshes_from_extent(XB, cell_size = c(0.5,0.5,0.5), max_ijk_per_mesh = 120)
mesh_lines <- meshes_to_fds(tiles)
vent_lines <- open_boundary_vents(sides = c("XMIN","XMAX","YMIN","YMAX","ZMAX"), surf_id = "OPEN")
# 2.1.5) Chemistry & materials
specs_reac <- c(
spec_fds(ID = "CELLULOSE", FORMULA = "C6H10O5"),
spec_fds(ID = "WATER VAPOR"),
reac_fds(FUEL = "CELLULOSE", C = 2.10, H = 6.20, O = 2.16,
SOOT_YIELD = 0.01, HEAT_OF_COMBUSTION = 17425., IDEAL = TRUE),
matl_fds("GENERIC VEGETATION", DENSITY = 500., CONDUCTIVITY = 0.2,
SPECIFIC_HEAT_RAMP = "c_v", A = 1040., E = 61041.,
NU_SPEC = 0.75, SPEC_ID = "CELLULOSE", NU_MATL = 0.25,
MATL_ID = "CHAR", HEAT_OF_REACTION = 418.),
matl_fds("CHAR", DENSITY = 300., CONDUCTIVITY = 0.052,
SPECIFIC_HEAT_RAMP = "c_v", SURFACE_OXIDATION_MODEL = TRUE,
A = 465., E = 68000., SPEC_ID = c("PRODUCTS","AIR"),
NU_SPEC = c(8.13, -7.17), MATL_ID = "ASH", NU_MATL = 0.04,
HEAT_OF_REACTION = -25000.),
matl_fds("ASH", DENSITY = 67., CONDUCTIVITY = 0.1, SPECIFIC_HEAT_RAMP = "c_v"),
ramp_fds("c_v", T = 0., F = 1.1),
ramp_fds("c_v", T = 200., F = 2.0),
ramp_fds("c_v", T = 800., F = 2.0)
)
# 2.1.6) Vegetation SURF/PART/INIT helpers
class_to_bdf <- function(i) file.path("bdf", sprintf("bulk_density_class_%s.bdf", i))
surf_grass <- surf_fds("grass surface",
`MATL_ID(1,1)` = "GENERIC VEGETATION",
`MATL_MASS_FRACTION(1,1)` = 1.0,
MOISTURE_FRACTION = 0.15, SURFACE_VOLUME_RATIO = 9770.,
LENGTH = 0.10, GEOMETRY = "CYLINDRICAL"
)
part_grass <- part_fds("grass", DRAG_COEFFICIENT = 2.8, SAMPLING_FACTOR = 1,
SURF_ID = "grass surface",
QUANTITIES = c("PARTICLE TEMPERATURE","PARTICLE BULK DENSITY"),
STATIC = TRUE, COLOR = "CHARTREUSE",
EMBER_PARTICLE = TRUE, EMBER_DENSITY_THRESHOLD = 70,
EMBER_VELOCITY_THRESHOLD = 0., TRACK_EMBERS = FALSE
)
init_grass <- init_fds(PART_ID = "grass", BULK_DENSITY_FACTOR = 2,
BULK_DENSITY_FILE = class_to_bdf(0)
)
surf_leaf <- surf_fds("leaf surface",
`MATL_ID(1,1)` = "GENERIC VEGETATION",
`MATL_MASS_FRACTION(1,1)` = 1.0,
MOISTURE_FRACTION = 1.0, SURFACE_VOLUME_RATIO = 3588.,
LENGTH = 0.10, GEOMETRY = "CYLINDRICAL"
)
part_leaf <- part_fds("leaf", DRAG_COEFFICIENT = 2.8, SAMPLING_FACTOR = 1,
SURF_ID = "leaf surface",
QUANTITIES = c("PARTICLE TEMPERATURE","PARTICLE BULK DENSITY"),
STATIC = TRUE, COLOR = "FOREST GREEN"
)
init_leaf <- init_fds(PART_ID = "leaf", CELL_CENTERED = FALSE,
BULK_DENSITY_FILE = class_to_bdf(1)
)
surf_wood <- surf_fds("wood surface",
`MATL_ID(1,1)` = "GENERIC VEGETATION",
`MATL_MASS_FRACTION(1,1)` = 1.0,
MOISTURE_FRACTION = 1.0, SURFACE_VOLUME_RATIO = 13.,
LENGTH = 0.10, GEOMETRY = "CYLINDRICAL"
)
part_wood <- part_fds("wood", DRAG_COEFFICIENT = 2.8, SAMPLING_FACTOR = 1,
SURF_ID = "wood surface",
QUANTITIES = c("PARTICLE TEMPERATURE","PARTICLE BULK DENSITY"),
STATIC = TRUE, COLOR = "BURLY WOOD 4"
)
init_wood <- init_fds(PART_ID = "wood", CELL_CENTERED = FALSE,
BULK_DENSITY_FILE = class_to_bdf(2)
)
# 2.1.7) Ignition (two thin OBST strips near ZMIN) + devices at strip centers
surf_fire <- surf_fds("IGN FIRE", HRRPUA = 800, COLOR = "RED", RAMP_Q = "ign_ramp")
r_ign <- list(
ramp_fds("ign_ramp", T = 0.0, F = 0.0),
ramp_fds("ign_ramp", T = 1.0, F = 1.0),
ramp_fds("ign_ramp", T = 120., F = 0.0)
)
yc <- min(XB[3:4]); dy <- 0.5; zmin <- XB[5]
dz <- min(0.01, (XB[6]-XB[5])*0.01)
obst_ign_1 <- obst_fds(c(XB[1], XB[2], yc - dy/2, yc + dy/2, zmin, zmin + dz), SURF_ID = "IGN FIRE")
obst_ign_2 <- obst_fds(c(XB[1], XB[2], yc + 30 - dy/2, yc + 30 + dy/2, zmin, zmin + dz), SURF_ID = "IGN FIRE")
obst_ign <- c(obst_ign_1, obst_ign_2)
# Parse &OBST to centers for devices
get_center_from_obst <- function(obst_line) {
m <- regexpr("XB\\s*=\\s*([0-9eE+\\-\\.,\\s]+)", obst_line, perl = TRUE)
xb <- regmatches(obst_line, m)
xb <- sub(".*XB\\s*=\\s*", "", xb)
xb <- sub(",?\\s*(SURF_ID|/).*", "", xb, ignore.case = TRUE)
v <- as.numeric(strsplit(gsub("\\s+", "", xb), ",", fixed = TRUE)[[1]])
c(mean(v[1:2]), mean(v[3:4]), mean(v[5:6]))
}
centers <- rbind(get_center_from_obst(obst_ign_1), get_center_from_obst(obst_ign_2))
dev_temp <- devc_fds(centers[1, , drop = FALSE], ID = "TC-01", QUANTITY = "TEMPERATURE")
dev_flux <- devc_fds(centers[2, , drop = FALSE], ID = "HF-01",
QUANTITY = "RADIATIVE HEAT FLUX GAS", IOR = 1)
dev_lines <- c(dev_temp, dev_flux)
# Visual check with ignition lines + devices
plot_voxels(
vox_df = vox,
style = "points",
color_by = "class",
to_local = FALSE,
ignition = obst_ign, # character vector of &OBST lines
devices = dev_lines, # character vector of &DEVC lines
device_color = "black",
device_radius = 0.3,
label_devices = TRUE
)
# 2.1.8) Outputs
outputs <- list(
dump_fds(DT_HRR = 1., DT_DEVC = 1.),
slcf_fds(PBZ = (XB[5] + XB[6]) / 2, QUANTITY = "TEMPERATURE"),
slcf_fds(PBZ = (XB[5] + XB[6]) / 2, QUANTITY = "HRRPUV"),
bndf_fds("RADIATIVE HEAT FLUX"),
bndf_fds("WALL TEMPERATURE")
)
# 2.1.9) Assemble .fds lines
fds_lines <- build_fds(
head_fds(CHID = "simulation_lidar", TITLE = "LiDAR-driven domain"),
misc_fds_literal(TMPA = 300., HUMIDITY = 10.50, VERBOSE = "TRUE"),
radi_fds(RADIATION_ITERATIONS = 2),
wind_fds(SPEED = 1.5, DIRECTION = 180.0),
time_fds(T_END = 2),
specs_reac,
mesh_lines,
vent_lines,
surf_fire, r_ign, obst_ign,
surf_grass, part_grass, init_grass,
surf_leaf, part_leaf, init_leaf,
surf_wood, part_wood, init_wood,
dev_lines,
outputs
)
# 2.1.10) Stage run folder + write BDFs/.fds
run_dir <- file.path(tempdir(), "fds_demo")
dir.create(file.path(run_dir, "bdf"), recursive = TRUE, showWarnings = FALSE)
voxel_to_bdf(
vox_df = vox,
bdf_base_path = file.path(run_dir, "bdf", "bulk_density.bdf"),
class_col = "class_id",
value_col = "bulk_density",
include_na = FALSE,
normalize_xyz = FALSE
)
fds_path <- file.path(run_dir, "simulation_lidar.fds")
# Now resolve to long path with backslashes (or use winslash = "/")
fds_path <- normalizePath(fds_path, winslash = "\\", mustWork = TRUE)
run_dir <- normalizePath(run_dir, winslash = "\\", mustWork = TRUE)
# 2.1.11) Run FDS
run_fds(fds_path)
# 2.1.12) Run smokeview
open_smv(file.path(run_dir, "simulation_lidar.smv"))

# 2.2.1) Read LAS files from package extdata
grass_path <- system.file("extdata", "grass_crop.laz", package = "rFDS")
leaf_path <- system.file("extdata", "leaf_crop.laz", package = "rFDS")
shrub_path <- system.file("extdata", "shrub_crop.laz", package = "rFDS")
wood_path <- system.file("extdata", "wood_crop.laz", package = "rFDS")
stopifnot(all(file.exists(c(grass_path, leaf_path, shrub_path, wood_path))))
# 2.2.2) Load LAS and assign classes
grass_crop <- readLAS(grass_path); grass_crop@data$Classification <- 1L
leaf_crop <- readLAS(leaf_path); leaf_crop@data$Classification <- 2L
shrub_crop <- readLAS(shrub_path); shrub_crop@data$Classification <- 3L
wood_crop <- readLAS(wood_path); wood_crop@data$Classification <- 4L
# 2.2.3) Combine into one LAS and sanity-check bounds
las2 <- rbind(grass_crop, leaf_crop, shrub_crop, wood_crop)
range(las2@data$Z); range(las2@data$X); range(las2@data$Y)
# 2.2.4) Voxelization
vox2 <- laz_to_voxels(las2, grid_res = 0.5, class_col = "Classification", local = FALSE)
# 2.2.5) Quick visualization
plot_voxels(vox = vox2, style = "points", color_by = "class", to_local = FALSE)
# 2.2.6) Domain, meshes, and open boundaries
XB2 <- domain_from_lidar(vox2, pad_xyz = c(0.5,0.5,0.5), centers = TRUE)
tiles2 <- tile_meshes_from_extent(XB2, cell_size = c(0.5,0.5,0.5), max_ijk_per_mesh = 120)
mesh2 <- meshes_to_fds(tiles2)
vents2 <- open_boundary_vents(sides = c("XMIN","XMAX","YMIN","YMAX","ZMAX"), surf_id = "OPEN")
# 2.2.7) (Chemistry/Materials) Reuse `specs_reac` from Example 1
# 2.2.8) SURF/PART/INIT for 4 classes (1=grass, 2=leaf, 3=shrub, 4=wood)
init_grass2 <- init_fds(PART_ID = "grass", BULK_DENSITY_FACTOR = 2, BULK_DENSITY_FILE = class_to_bdf(1))
init_leaf2 <- init_fds(PART_ID = "leaf", CELL_CENTERED = FALSE, BULK_DENSITY_FILE = class_to_bdf(2))
surf_shrub <- surf_fds("shrub surface",
`MATL_ID(1,1)` = "GENERIC VEGETATION",
`MATL_MASS_FRACTION(1,1)` = 1.0,
MOISTURE_FRACTION = 0.15,
SURFACE_VOLUME_RATIO = 8000.,
LENGTH = 0.10,
GEOMETRY = "CYLINDRICAL")
part_shrub <- part_fds("shrub",
DRAG_COEFFICIENT = 2.8,
SAMPLING_FACTOR = 1,
SURF_ID = "shrub surface",
QUANTITIES = c("PARTICLE TEMPERATURE","PARTICLE BULK DENSITY"),
STATIC = TRUE,
COLOR = "DARK GREEN",
EMBER_PARTICLE = TRUE,
EMBER_DENSITY_THRESHOLD = 70,
EMBER_VELOCITY_THRESHOLD = 0.,
TRACK_EMBERS = FALSE)
init_shrub2 <- init_fds(PART_ID = "shrub", BULK_DENSITY_FACTOR = 2, BULK_DENSITY_FILE = class_to_bdf(3))
init_wood2 <- init_fds(PART_ID = "wood", CELL_CENTERED = FALSE, BULK_DENSITY_FILE = class_to_bdf(4))
# 2.2.9) Ignition
surf_fire2 <- surf_fds("IGN FIRE", HRRPUA = 800, COLOR = "RED", RAMP_Q = "ign_ramp")
r_ign2 <- list(
ramp_fds("ign_ramp", T = 0.0, F = 0.0),
ramp_fds("ign_ramp", T = 1.0, F = 1.0),
ramp_fds("ign_ramp", T = 120., F = 0.0)
)
yc2 <- mean(XB2[3:4]); dy2 <- 0.5; zmin2 <- XB2[5]
dz2 <- min(0.01, (XB2[6]-XB2[5]) * 0.01)
obst_ign2 <- obst_fds(c(XB2[1], XB2[2], yc2 - dy2/2, yc2 + dy2/2, zmin2, zmin2 + dz2),
SURF_ID = "IGN FIRE")
# 2.2.10) Outputs
outputs2 <- list(
dump_fds(DT_HRR = 1., DT_DEVC = 1.),
slcf_fds(PBZ = (XB2[5] + XB2[6]) / 2, QUANTITY = "TEMPERATURE"),
slcf_fds(PBZ = (XB2[5] + XB2[6]) / 2, QUANTITY = "HRRPUV"),
bndf_fds("RADIATIVE HEAT FLUX"),
bndf_fds("WALL TEMPERATURE")
)
# 2.2.11) Assemble the .fds for example 2
fds2 <- build_fds(
head_fds(CHID = "simulation_lidar", TITLE = "LiDAR-driven domain"),
misc_fds_literal(TMPA = 300., HUMIDITY = 10.50, VERBOSE = "TRUE"),
radi_fds(RADIATION_ITERATIONS = 2),
wind_fds(SPEED = 1.5, DIRECTION = 180.0),
time_fds(T_END = 20),
specs_reac, # from Example 1
mesh2,
vents2,
surf_fire2, r_ign2, obst_ign2,
# reuse grass/leaf/wood SURF/PART from example 1 objects:
surf_grass, part_grass, init_grass2,
surf_leaf, part_leaf, init_leaf2,
surf_shrub, part_shrub, init_shrub2,
surf_wood, part_wood, init_wood2,
outputs2
)
# 2.2.12) Stage run folder and write BDFs
run_dir2 <- file.path(tempdir(), "fds_demo2")
dir.create(file.path(run_dir2, "bdf"), recursive = TRUE, showWarnings = FALSE)
voxel_to_bdf(
vox_df = vox2,
bdf_base_path = file.path(run_dir2, "bdf", "bulk_density.bdf"),
class_col = "class_id",
value_col = "bulk_density",
include_na = FALSE,
normalize_xyz = FALSE
)
# 2.2.13) Write .fds and run
fds_path2 <- file.path(run_dir2, "simulation_lidar.fds")
write_fds(fds_path2, fds2)
# 2.2.14) Run and visualize (uncomment to execute)
run_fds(fds_path2)
open_smv(file.path(run_dir2, "simulation_lidar.smv"))

We gratefully acknowledge funding from ESTCP for the project FuelsCraft: An innovative wildland fuel mapping tool for prescribed fire decision support on Department of Defense military installations. We also thank undergraduate student Lucas Bielak Rezende for his initial contribution to the project.
Please report any issue regarding the rGEDI package to c.silva@ufl.edu
Silva,C.A; Fareed,N; rFDS: An R Package for Fire Dynamics Simulation from LiDAR Data. version 0.0.1, accessed on October. 23 2025, available at: https://CRAN.R-project.org/package=rFDS
rFDS package is developed by the Forest Biometrics, Remote Sensing and Artificial Intelligence Laboratory (Silvalab) at the University of Florida. It comes with no guarantee, expressed or implied, and the authors hold no responsibility for its use or reliability of its outputs.