sppHarmonize() now accepts its own output when several names in sppNameVector are one species in sppEquivCol (e.g. PSEU_MEN and PSEU_MEN_GLA are both Pseu_men). It compared the number of colours with the number of names, not species, so the second module to call it (Biomass_borealDataPrep after Biomass_speciesData) stopped with "Length of 'sppColorVect' differs" whenever species merged, as in southern-interior BC study areas with no sppEquiv supplied.
The SCANFI v3 download message says when there is no internet connection (reproducible::isConnectionError()), and that the year's file must already be local (run once on a machine with internet, or fetch it from the FTP mirror there), instead of saying the server can be slow under load and to retry later, which on a compute node without internet never helps. Any other failure keeps the retry message.
overlayLCCs() with a remapTable now returns the remapped land cover. It computed each pixel's remapped class but wrote out the original one, so only the pixels re-imputed through classesToReplace changed: a pixel the table made NA (non-vegetated) kept its original class. In LandWeb, where a current-condition layer marks water and barren land non-vegetated, 15,161 forest pixels of one study area stayed forest on land that layer calls water or barren. The re-imputed pixels also take their new class from the remapped land cover now, so a pixel the table made non-vegetated no longer counts as a neighbour.
overlayLCCs() with a remapTable now re-imputes every pixel the table sends to a class in classesToReplace, including a pixel with no class in one of the input layers. Such pixels kept the placeholder class, and when every pixel to re-impute was one of them overlayLCCs() stopped with a data.table join error (#283).
prepInputs_CWIM() downloads the national CWIM3A raster once, with reproducible::preProcess(), into its new destinationPath argument (and getOption("reproducible.destinationPathShared") if set) and reads the local copy, instead of reading the study window over the network through GDAL's /vsicurl/ on every call. A machine without internet (an HPC compute node) can now build the wetland layer once the file is there; the result is unchanged, and a local url is still read directly.
The SCANFI download messages say when there is no internet connection, instead of reporting a failure to reach Google Drive as a missing permission, and what to do (run once with internet, or pass your own copy). The access hints now fit their source: ask for access to the Drive folder and then run googledrive::drive_auth(), or download the file from opendata.nfis.org and pass it yourself. Needs reproducible >= 3.2.1.9075 (isConnectionError()).
prepInputsFireYear() also reads the fires before 1972. Since its 2025 release the National Fire Database ships them in a separate archive, NFDB_poly_pre1972.zip, beside NFDB_poly.zip, so a fire raster built from CWFIS's current version silently started in 1972 even with earliestYear = 1950. The new argument urlPre1972 defaults to that archive when url is CWFIS's current-version NFDB_poly.zip and earliestYear is before 1972, and is read only when the records from url hold no fire before 1972 (a local copy of an older release under the same URL already does); urlPre1972 = NULL reads url only.
prepInputs_SCANFI_LCC_FAO() reads the land cover of the forest-land years under LandR's SCANFI remap as well (with forestLandFrom = "both", the default, or "lccYears"). When the remap became per-download instead of global, only year's own land cover was read inside it, so the forest-land years (1985, 1995, 2005, 2015 and 2025 for a 2020 year) asked Google Drive for the restricted SCANFI files: a user without access failed, and one with access downloaded each national raster in full instead of reading the study-area window from the mirror. Their download errors now get the same SCANFI guidance as the other SCANFI reads.
LandR now supplies the SCANFI v2 remap itself, as getOption("reproducible.urlRemap", <LandR's default>) applied only for the length of each SCANFI download, instead of setting the global option when it loads (.onLoad() no longer touches reproducible.urlRemap, so other packages are unaffected). A reproducible.urlRemap you set always wins, and options(LandR.scanfiMirror = FALSE) turns the default off. The shipped manifest inst/extdata/arbutus_manifest_SCANFI_v2.csv now points at object-arbutus.alliancecan.ca; the old object-arbutus.cloud.computecanada.ca host returns NoSuchBucket for every SCANFI path. prepInputsStandAgeMap() now uses a supplied ageURL for SCANFI (it was always overwritten), and the SCANFI download errors are shorter and say which server refused the file: the mirror (usually a network problem) or Google Drive (usually no access). For a V2 Drive failure they point to the setting that applies: your own reproducible.urlRemap, or LandR.scanfiMirror if it is off.
prepInputsFireYear() has a new argument latestYear (default Inf, no change for existing calls) that drops fires after that year before rasterizing. A pixel that burned twice holds only its most recent fire, so for an earlier data year replaceAgeInFires() skipped it (the later fire was after startTime) and the pixel kept its satellite age. prepInputsStandAgeMap() now passes dataYear when it builds the fire raster, so a pixel burned in 1980 and 2015 gets the age since 1980 for dataYear 1990. A firePerimeters raster supplied by the caller is used as given.
updateCohortData() is faster when it re-groups all pixels (after seed dispersal): it builds the community string of each pixelGroup once and gives each pixel its group's string, instead of expanding cohortData to one row per pixel and cohort (20M rows at 10M cohorts) and pasting a string for each. The pixelGroup numbers, the rows kept and their order, the columns and the RNG state are those of the expanded version (a dispersal event took 46 s instead of 37 s with 10.6M cohorts and 1.3M treed pixels, and 18.5 s instead of 8.7 s with 0.4M cohorts and 1.3M treed pixels, with about 2 GB less peak memory in both). calcSiteShade() computes its two sums per pixelGroup in one pass and the shade cut once per ecoregion and bAM instead of once per pixelGroup (the first cohort of a pixelGroup is still the one read, as before), and rmMissingCohorts() works on the map values as a vector instead of building a table with a row per cell. Results are identical.
New leadingByPixelGroup(): the leading species (or "Mixed") of each pixelGroup, one row per pixelGroup. vegTypeGenerator() and vegTypeMapGenerator() now use it, in place of the two copies of the same code they carried, and it sums the species within pixelGroups in one pass instead of a 10M-row join (about 3 times faster at 10M cohorts). Results are identical, including the order of the levels and the Mixed decision at the threshold; the original algorithm is still used for mixedType 0 and 1, cover, a character speciesCode and NA in B.
updateCohortData() is faster after fire: .initiateNewCohorts() no longer sums the B of older cohorts when initialB is given (the result was discarded) and no longer recalculates sumB (which updateCohortData() drops), calcSeverityB() sums pre-fire B only for the burned pixelGroups, and rmMissingCohorts() copies cohortData only when it removes something. Results are identical. The initialB = NA calculation is unchanged, including that a new cohort's older B is counted as 0 when the first row of its pixelGroup is younger than successionTimestep, which is a separate issue.
modifySpeciesAndSpeciesEcoregionTable() no longer adds an all-NA row to speciesEcoregion for a species that is in speciesTable but has no speciesEcoregion row (its join is now an inner join). The row had year = NA, which Biomass_core's Init happened to filter out until PredictiveEcology/Biomass_core#124; results are unchanged.
LANDISDisp() has a new argument, pgv: the pixelGroupMap values as a vector. A caller that already has them (e.g. with burned pixels set to NA) can pass them instead of masking a copy of the raster that LANDISDisp() then reads again (#255).
LANDISDisp() (R reference and C++ spiral loop) no longer skips a draw because it exceeds the
largest ward probability of the species that hit on the previous spiral step
(lastWardMaxProb). That screen used only the species that had a source on the previous step, so
after a step where only a short-distance species found a source, a long-distance species whose
draw fell between the two probabilities was wrongly discarded. Each receiver with a source at a
step now compares its own draw with its own species' ward probability. The number and order of
uniform draws is unchanged, and R and C++ stay bit-identical. Results change: the seed-locked
goldens were regenerated (success counts moved by about -0.6% to +5% on the test fixtures, about
+0.5% on the largest).
The C++/R parity tests looked for spiralLoopCpp in the global environment, so they silently
skipped under parallel testthat. They now look in the LandR namespace.
The R reference of LANDISDisp() (useCpp = FALSE) gave different results on macOS (arm64).
Receivers that had already succeeded have row NA, and terra::cellFromRowCol() turns an NA
row into row 1 there (an undefined C++ cast), so they kept finding sources and consumed extra draws.
Their cell is now set to NA directly. Linux and Windows results are unchanged.
LANDISDisp() gains pgv, an optional integer vector of pixelGroup IDs per cell of
pixelGroupMap. When supplied the raster values are not read (about 1 s on a 9M-cell map).
When NULL the values are read once as integer, rather than as doubles and converted again in
spiralSeedDispersalCpp(). Output is unchanged.
The R-side preparation is also cheaper (no re-sort of the receiver table, cell numbers to
row/column by integer arithmetic, spiralLoopCpp() no longer scans every cell for the largest
pixelGroup, receiver cells found without a raster-length logical vector). Output is unchanged.
plotLeadingSpecies() gains a figurePath argument, the directory its .png is saved to,
so a module can pass SpaDES.core::figurePath(sim). The default, figures/ under outputDir,
is where the figure was saved before.
plotLeadingSpecies() writes the leading-change map as one layer, named leadingChange. It held one
identical layer per species, named after the species, so the figure showed that many identical panels.
prepInputs_SCANFI_LCC_FAO() and prepInputs_NTEMS_LCC_FAO() now cache their forest-land
inputs -- the FAO forest layer and the forestLandYears land covers -- as
options("reproducible.useCache") says, like any other Cache() call. Since #241 they were
cached with useCache = "always", which overrode a pipeline that turns reproducible's Cache
off because targets is its only cache. A new option, LandR.forestLandUseCache, keeps the
old behaviour when set to "always", so a caller that prepares several years of one study
area under spades.useCache = "eventsOnly", as fireSense does, still prepares each input
once (#269).
makeEcoregionMap() now gives ecoregionMap's category table the same IDs as its cell
values. The cells hold each ecoregionGroup's factor level index (alphabetical order), but
the table numbered its rows in the order the groups first appear in
ecoregionFiles$ecoregion, which for polygon input prepEcoregions() takes from the
polygons. So any reader that matches cell values to the table's ID, as
pemisc::factorValues2() does, got another group's label. In Biomass_core, seeding used the
wrong group's establishprob, and seedlings that established in a pixel with no cohorts were
stored with the wrong ecoregionGroup. Post-fire regeneration keeps a burned pixel's
ecoregionGroup, so the label stayed with the pixel, and its cohorts grew with the wrong
group's maxB and maxANPP for the rest of the run. In two 1000-year replicates of one study
area, cohorts with another group's label held 71-74% of the biomass at year 700 and 76-78% at
year 1000. With seedingAlgorithm = "universalDispersal", the siteShade join also found no
match wherever the label was wrong, which set shade to 0; "wardDispersal" joins siteShade
on pixelGroup only. assertERGs() did not catch it because the set of labels was unchanged.
Cell values are unchanged. The bug came in with ff24bcf8 (January 2026; development versions
only). An ecoregionGroup with more than one landcover or ecoregionName is now an
explicit error instead of a data.table length error.
prepInputsFireYear() now returns rasterToMatch's grid exactly. Given a studyArea polygon, it cropped the fire raster to the polygon's bounding box, so prepInputsStandAgeMap()/replaceAgeInFires() failed with "[[<-] lengths of cells and values do not match" wherever the grid reached past that box. A studyArea (or maskTo, to) now only masks.
loadSCANFISpeciesLayers() (and so prepSpeciesLayers_SCANFI()) now includes to, cropTo, projectTo and maskTo in its cache key. It was keyed only on file names, so changing the target grid or study area while keeping studyAreaName returned the earlier, differently-gridded layers from the cache.
makeAndCleanInitialCohortData() has a new argument, minSpeciesEcoregionShare (default 0, off). A species found in fewer than that share of an ecoregion's pixels is removed from every pixel of the ecoregion before cover is rescaled, so its cover goes to the other species and it is also absent when establishprob, maxB and maxANPP are estimated there. In BC ELFs with BEC zones, western redcedar is in up to 6.7% of mountain hemlock (MH) zone pixels and up to 2.2% of ESSF pixels, where it is not expected to persist; Biomass_borealDataPrep uses 0.07.
prepInputs_NTEMS_LCC_FAO()'s error for a missing crop target no longer suggests terraOptions(memfrac = 0), which makes terra much slower; it suggests todisk = TRUE only.
prepInputs_SCANFI_LCC_FAO() gains dataVersion = "V3", SCANFI v3's annual (1985-2025) land
cover, read with prepInputs() from NRCan's Cloud-Optimized GeoTIFFs on ftp.maps.canada.ca, the
same way as V1/V2. The new exported table scanfiV3ToCanadaLCC recodes v3's 20 land-cover codes
to the Canada LCC codes used elsewhere; burn scars (v3 code 4) have no Canada LCC equivalent and
keep a new code, 60, which the existing forest-land rule still relabels to disturbedCode on
forest land. V3 covers non-forest land cover only -- species, biomass and age layers are
unaffected and still come from V2.
prepInputs_SCANFI_LCC_FAO() and prepInputs_NTEMS_LCC_FAO() no longer relabel water (20),
snow/ice (31) or 0 (no data; SCANFI v3 cropland, urban and road) as disturbed forest (240) where
the record says forest land. A treed year elsewhere in the record, or an FAO forest code, does
not make a lake, a glacier, a town or a road into forest without trees; such pixels had become
flammable forest in fire models. Rock and barren land are still relabelled, since a severe fire
can expose them.
makeAndCleanInitialCohortData() now reads the age model's variables from its formula
(all.vars()) and, before fitting, drops rows with a zero in any of them, response included;
it reports them with a message instead of a warning. It used pemisc::termsInData(), which
drops the first token of the formula text as the response, so which zeros were dropped
depended on how the response was written: with a log(age) response it dropped log and kept
age only by accident. LandR no longer imports termsInData().
ecoregionProducer() now reads a categorical ecoregion raster's labels by raw cell value, from
its ecoregionName column when present. It used raster::factorValues(), which on a SpatRaster
returns only the ACTIVE category; terra makes the first text column active after a file round
trip, so a category table with another text column ahead of ecoregionName returned the wrong
labels and the join on "ecoregionName" failed. Rasters built by prepEcoregions() have
ecoregionName as their only label column and give the same result as before. This also
removes ecoregionProducer()'s use of raster::factorValues().
prepEcoregions() now treats a supplied categorical ecoregionRst the same way whether it is a
terra SpatRaster or a raster RasterLayer: the raster's category table and ecoregionTable
both become (ID, ecoregionName), the label column being ecoregionName if present and
otherwise terra's active category or raster's first label column, with IDs zero-padded as for
polygon input. The SpatRaster branch renamed only the table (and stopped on a category table with
more than one label column), so a label column with any other name reached
ecoregionProducer()'s join without an ecoregionName column; its IDs were not padded, so with
10 or more ecoregions they no longer matched; and a non-categorical SpatRaster was treated as
categorical, because its levels() is never NULL. The RasterLayer branch kept the attribute
table's own column names.
New imputeBadAgeModelDefault() supplies makeAndCleanInitialCohortData()'s default
age-imputation model, and is now the default value of its imputeBadAgeModel argument. The
model's response is log(age), not age, so an imputed age can no longer come back negative
and get clamped to 0 for a stand with positive biomass -- the cause of the age-0-with-biomass
cohorts CBMutils::cumPoolsCreateAGB() rejects (PredictiveEcology/Biomass_borealDataPrep#131).
Biomass_borealDataPrep's imputeBadAgeModel parameter now defaults to this function instead
of duplicating the formula. The predictors change too, not only the scale: LandR's old default
was age ~ B * speciesCode + cover * speciesCode + (1 | initialEcoregionCode); the new one is
log(age) ~ log(totalBiomass) * cover * speciesCode + (log(totalBiomass) | initialEcoregionCode),
the predictors Biomass_borealDataPrep already used. Callers that relied on LandR's default
get both changes. Predictions are back-transformed with exp(), which gives the median
(geometric-mean) age for given predictors, not the mean.
LandR no longer depends on quickPlot or ggpubr (#140, #174). plotVTM() combines its bar chart
and map with patchwork instead of ggpubr::ggarrange(), under one shared title, with the bar
chart's species labels angled so they no longer overprint; it returns a patchwork object, which
print(), ggplot2::ggsave() and SpaDES.core::Plots() accept as before. The colour tables
that Colors(), defineFlammable() and vegTypeMapGenerator() set on RasterLayers are
computed exactly as quickPlot::setColors<- did, now in LandR. The internal plotFunction()
uses theme_classic() instead of ggpubr::theme_pubr(). Tests and the LANDISDisp() example
plot with terra::plot().
standAgeMapGenerator() passed mapCode = "pixelGroup" to rasterizeReduced(), whose argument is mapcode; the misspelt name was silently dropped into ... and it worked only because the raster had been renamed "pixelGroup" on the line before. It now passes mapcode. Output is unchanged.
.initiateNewCohorts() now gives new cohorts their pixelGroup's existing ecoregionGroup with one
update join instead of a [.data.table call per pixelGroup, which took ~357 s per call on ~500k
pixelGroups. On 2 M synthetic pixelGroups: 563 s before, 0.6 s now, identical output. It now stops
if the existing cohorts of a pixelGroup carry more than one ecoregionGroup.
assertCohortDataERG() no longer calls browser() before its stop(), so a failed assertion
stops instead of pausing an interactive session in the debugger.
standAgeMapGenerator() no longer modifies the caller's cohortData. It computed the stand
age with cohortData[, weightedAge := ...], which adds the column to the caller's table by
reference, so e.g. NRV_summary left a weightedAge column in the simulation's cohortData.
It now aggregates into a new table; the returned map is unchanged. vegTypeMapGenerator()
already worked on a copy; tests now pin both.
makeEcoregionMap() now reads ecoregionMap's raw cell values as mapcodes instead of
as.data.table(ecoregionMap, cells = TRUE), which returns the labels of the SpatRaster's
ACTIVE category. ecoregionMap's raw cell values are already the mapcodes, but terra changes
which category is active whenever the raster goes through a file round trip (writeRaster()
then rast(), as Cache() and terraOptions(todisk = TRUE) both do), so the join no longer
reliably picked "mapcode". Pixels ended up joined to the wrong ecoregion, or the function
errored outright.
adjustAgeToLongevity() no longer changes the class of speciesCode. It joins
pixelCohortData[maxAges, on = "speciesCode"], and data.table takes the join column from i,
so a longevity table built from a character species column -- the usual case, e.g.
species[, .(speciesCode = species, longevity)] -- silently replaced a factor
pixelCohortData$speciesCode with character. The input's class and levels are now restored.
This surfaced far downstream: Biomass_core asserts speciesCode = "factor" on cohortData,
and Biomass_borealDataPrep used to mask it by rebuilding pixelCohortData (whose melt()
re-created the factor) right afterwards. Once that rebuild was removed upstream, any caller
using adjustAgeAndLongevity = TRUE got a failed assertion instead. The existing tests only
ever passed character speciesCode, so neither class was pinned; both are now.
Forest-land inputs are prepared once per study area. prepInputs_SCANFI_LCC_FAO() and
prepInputs_NTEMS_LCC_FAO() prepare their forest-land inputs -- the FAO forest layer and the
land cover of each of forestLandYears -- once per target geometry, instead of on every call. fireSense calls them once per dataYear on one study area, so
the same inputs were prepared five times: in production the FAO layer alone cost ~12 min per call,
and the whole forest-land step ~2 h per study area. Each input is now Cache()d with
useCache = "always" (new in reproducible 3.2.1.9037), so it is reused even with Cache off, as
under spades.useCache = "eventsOnly", and inside a Cache() that is off. The key is the land
cover's geometry (crs, extent, dimensions), not its values, which differ by year; so the inputs
are aligned with it but no longer masked by it (its NAs were already excluded when the
forest-land codes are applied), and results are unchanged. There is no switch: to recompute,
delete the forestLand_FAO / forestLand_landCover cache entries. prepInputs_FAO_forest()
gains ..., passed to prepInputs(). Needs reproducible (>= 3.2.1.9037).
New prepInputs_SCANFI_structure(): fetches SCANFI's canopy height and canopy closure
layers (V2, 1985-2025 in 5-year steps), the two structural attributes published alongside the
biomass layer prepRawBiomassMap() already serves. They say how much structure a pixel carries
independent of which species carry it, so they can be used as controls when comparing stands:
at equal height and closure a biomass difference is composition, not site quality.
Two leading/mixedwood thresholds replace three options. LandR.mixedwoodProp (0.75) is the
GROUP threshold -- all conifers, or all broadleaves -- and is the definition the national
products use (NTEMS/EOSD, NFI photo plots: 75% of total basal area or volume).
LandR.leadingSpeciesProp is the SINGLE-SPECIES threshold, and takes the mixedwood value unless
set, so options(LandR.leadingSpeciesProp = 0.51) gives "just a majority" without moving the
mixedwood definition. Read them with the new mixedwoodProp() and leadingSpeciesProp().
NTEMS.mixedwoodProp, LandR.vegLeadingProportion and LandR.lccLeadingProportion are gone.
The number itself is written down only in LandROptions(); no function carries its own default.
This changes results: the single-species threshold was 0.8 and is now 0.75, everywhere.
mixedType = 2 now sums the broadleaf group, which is what it always claimed to do. It
tested each deciduous species separately, so three broadleaf species at 15% each -- 45%
broadleaf, mixedwood by the definition -- was called pure conifer. Deciduous conifers stay
conifers: Larix is Type == "Conifer" in sppEquivalencies_CA, so tamarack never makes a
stand mixedwood. Calling vegTypeMapGenerator() or vegTypeGenerator() with mixedType = 2
and a LandR.leadingSpeciesProp that differs from LandR.mixedwoodProp now warns, because
mixedType = 2 asks the mixedwood question and uses the mixedwood threshold.
subsetDT()'s default subsample is 500, was 50 (new subsetDataSize(), option
LandR.subsetDataSize). 50 was chosen when these fits were expensive; it was small enough that
repeated runs of the same simulation gave visibly different maxB -- a median coefficient of
variation of 10% across ecoregion x species, up to 62%, on a 60 km boreal test window. The
LandR modules take their subsetData*Model defaults from subsetDataSize(), so the number
lives in one place.
SCANFI files are now fetched from the PredictiveEcology arbutus mirror by default. LandR
addresses SCANFI v2 by Google Drive id, and some of those ids 404 for anonymous users -- the
2020 land cover and 2020 stand age among them, which stopped Biomass_borealDataPrep's default
SCANFI path. LandR now ships the mirror manifest and, when loaded, sets
options(reproducible.urlRemap = scanfiUrlRemap()) -- only if no remap is set and
LandR.scanfiMirror is TRUE (the default). Species folders are remapped too, so listing them
needs no Google login. A remap you set yourself is never replaced; scanfiUrlRemap() is
exported so it can be combined with one.
new prepInputs_CWIM() builds a wetland site layer from the Canadian Wetland Inventory Map
v3A (10 m, national, public cloud-optimised GeoTIFF), reading only the study window. SCANFI's
land cover has no wetland classes, so without it a SCANFI-based map cannot tell treed wetland
from upland forest. Bog, fen, marsh and swamp count as wet; shallow water and NoData do not.
A target cell is wet when at least wetThreshold (0.5) of it is.
new wetlandToLCC() adds the NTEMS wetland codes to a land-cover map from such a layer: wet
and treed (210, 220, 230, and 240) becomes 81, wet otherwise becomes 80; water and existing
wetland codes are left alone.
prepInputs_NTEMS_LCC_FAO() and prepInputs_SCANFI_LCC_FAO() now decide forest land --
ground that grows trees, whether or not it carries any in the year being prepared -- from
the new forestLandFrom argument, and share one implementation of the rule (#221).
Previously both used the 2019 FAO layer's code 2 alone, i.e. "an opening in 2019", so a
stand that was open in the year being prepared but had grown back by 2019 was code 1 and
was left as shrubland, dropping it from the simulated forest. Now:
"fao" uses FAO codes 1 and 2, from faoYear (2022 by default, was fixed at 2019);"lccYears" calls a pixel forest land if it is treed in any of forestLandYears,
which also sees openings whose disturbance predates the 1984 start of the fire and
harvest record;"both" (default) takes the union. Each scanned year is one more layer to read.
New forestLandMask() and prepInputs_FAO_forest() are exported. convertibleClasses
controls which classes may be relabelled; the default, every non-treed class, is
unchanged behaviour. The NTEMS year range is now 1984-2022: 2023 was accepted although
NFIS publishes no 2023 land cover. The SCANFI path also gains the fast terra::ifel
implementation, which the NTEMS path already had.new LandROptions(), which lists the LandR options and their defaults, following
reproducible::reproducibleOptions() and SpaDES.core::spadesOptions(). ?LandROptions
(or ?opts.LandR) documents each one, and .onLoad() now sets the options from it instead
of from its own inline list. NTEMS.mixedwoodProp is a full member with a NULL default,
so it is documented without being set and the
getOption("NTEMS.mixedwoodProp", getOption("LandR.<which>LeadingProportion", <default>))
fallthrough still reaches the inner default. The package-level help now points at
LandROptions() rather than repeating a two-option list that said LandR.assertions
defaults to FALSE, when .onLoad() has always set it to TRUE.
speciesInStudyArea() also returns sppEquiv: the rows of sppEquivalencies_CA for the
species in the study area, without _Spp genus entries, only species with LANDIS traits,
and with the hybrid white x Engelmann spruce (Pice_eng_gla) merged into Engelmann spruce
(Pice_eng). This is the table fireSense modules built for themselves. The new argument
mergeHybridSpruce (default getOption("LandR.mergeHybridSpruce", "engelmann")) merges it
into white spruce ("white", Pice_gla) instead, or leaves it as its own species (NA).
Only the hybrid being on the raster triggers the merge, and its rows take the target's
LandR, LANDIS_traits and sppEquivCol names. Rows are matched on LandR whatever
naming the raster uses (SCANFI/NFI PICE_ENG_GLA or KNN Pice_Eng_Gla), so the table has
the same rows for any sppEquivCol. This merged into development at 1.2.0.9020, the
version already there, so 1.2.0.9021 is the first version a caller can require for it:
a reqdPkgs floor of >= 1.2.0.9020 is also met by a 1.2.0.9020 from before the merge,
which returns no sppEquiv and fails at run time instead of at install time.
?sppEquiv (an alias of ?sppEquivalencies_CA) now describes the sppEquiv table in one
place: its naming conventions, how rows and sppEquivCol work, the helpers that use it,
and which columns LandR functions read. The column list now matches the data (30 columns,
not 27; *_forestry names; SK_forestry, ON_forestry and NB_forestry added), and the
sppEquiv/sppEquivCol argument docs link to it. The documented SCANFINamesCol default of
loadSCANFISpeciesLayers() is corrected to "SCANFI".
speciesInStudyArea() no longer stops with "object 'bb' not found" when speciesPresentRas
is supplied, and uses a supplied url instead of ignoring it.
speciesTableUpdate() no longer fails when sppEquiv is NULL. It built its default from
data.table(utils::data("sppEquivalencies_CA", ...)), which holds the name of the dataset
rather than the dataset, so the call died in data.table with "Column or expression 1 of
'by' ... is type 'list'". It now get()s the table, as prepSpeciesTable() does.
sppColors(): the test for whether sppEquiv has enough distinct colorHex values read
length(unique(sppEquiv[[sppEquivCol]] <= length(unique(sppEquiv$colorHex)))), which
compares species names to a number and takes the length of the result (1 or 2, both
truthy), so it always passed. Two species sharing one colorHex were both given that
colour instead of falling back to the palette. Also length(newVals == 1) is now
length(newVals) == 1.
sppEquivalencies_CA: the KNN column was shifted up by one row across the Ulmus block,
so U. pumila carried Ulmu_Rub, U. rubra carried Ulmu_Spp and Ulmus spp. carried
Ulmu_Tho. equivalentName("Ulmu_Tho", column = "LandR") returned the elm genus and
"Ulmu_Rub" returned Siberian elm. Each name now sits on its own species.
sppEquivalencies_CA: rock elm (ULMU_THO) and pagoda dogwood (CORN_ALT) now have the
LandR names Ulmu_tho and Corn_alt. Both were blank, and LandR is the column rows
are keyed on, so neither species could be matched.
the "leading" threshold is no longer hard-coded in each function. Every site now reads a
nested pair of options,
getOption("NTEMS.mixedwoodProp", getOption("LandR.<which>LeadingProportion", <default>)),
where LandR.vegLeadingProportion (0.8) serves vegTypeMapGenerator(), vegTypeGenerator()
and plotVTM(), and LandR.lccLeadingProportion (0.75) serves lccMapGenerator().
Setting NTEMS.mixedwoodProp moves all of them at once; leaving it unset (the default --
it is not set at load) leaves each on the value it has always had, so no existing result
changes. The two inner defaults differ by history rather than by concept: both are the same
purity threshold on a biomass-like share, and 0.75 is the NTEMS/EOSD value (Wulder & Nelson
2003: coniferous or broadleaf at 75% or more of total basal area, mixed wood below that).
loadSCANFISpeciesLayers() and prepSpeciesLayers_SCANFI() now take the *to family
(to, cropTo, projectTo, maskTo) as formals, as a first step in retiring
rasterToMatch/studyArea. Both still accept the legacy pair -- it arrives through ...
and is translated by a new internal .legacyToTo(), which implements the table documented
in ?reproducible::postProcess: a rasterToMatch on its own is to; a studyArea on its
own crops and masks but does not reproject (unless useSAcrs); and when both are supplied
the raster gives extent, resolution, projection and alignment while the polygon gives the
mask. An explicitly passed *to argument always wins.
This changes output for callers that supplied both. The previous shims mapped
to -> studyArea and projectTo -> rasterToMatch, then called
prepInputs(to = rasterToMatch), so the mask was taken from the raster rather than from
the study area -- the reverse of the documented behaviour. Callers that supplied only one
of the two are unaffected.
The other prepSpeciesLayers_*() functions are unchanged and still take the legacy formals.
prepSpeciesLayers_SCANFI() passed projectTo = rasterToMatch twice to
loadSCANFISpeciesLayers(). R accepts duplicate names in ..., so this was silent
rather than an error; the duplicate is removed. Its cache entry was also tagged
"KNN", which is now "SCANFI" -- the tag is what Cache() searches on, so SCANFI
species layers were indistinguishable from kNN ones in the cache.
sppEquivalencies_CA: coastal Douglas-fir (PSEU_MEN_MEN) now has FuelClass
"DgFrPoPine", like the other two Pseu_men rows. It had "CedrMplOther", so any study
area containing Douglas-fir got two fuel classes for Pseu_men and
fireSenseUtils::cohortsToFuelClasses() stopped.
makePickellStack() no longer leaves terra::terraOptions(memmax) and
raster::rasterOptions(maxmemory) changed after it returns. Both are global,
process-wide settings, so a caller that had set its own memory ceiling silently
kept LandR's for the rest of the session -- visible in a long-lived worker that
set memmax = 4 and later found it at 1.
rasterRead() to use version from reproducible;columnsForPixelGroups is now a function (i.e., use columnsForPixelGroups() for consistent pixelGroup definitions);BioSIM (>= 1.1.0) is now required where BioSIM is used: 1.1.0 removed
getModelOutput() in favour of generateWeather(), which takes modelNames rather than
modelName and returns a named list instead of a single data.frame.
now requires reproducible (>= 3.1.1.9063) following non-backwards-compatible
changes to the reproducible API: options("reproducible.gdalwarp") was removed,
and reproducible.inputPaths was renamed to reproducible.destinationPathShared
(the old name remains as a deprecated alias);
remove deprecated package crayon in favour of cli instead;
remove deprecated package qs in favour of qs2 instead;
add knitr and rmarkdown to Suggests for the new vignette, and declare both in
VignetteBuilder. Listing rmarkdown there is what lets the vignette be rebuilt
under _R_CHECK_DEPENDS_ONLY_=true (the --as-cran "no suggests" check), which
otherwise hides it from the build even when installed;
move ggpubr to Suggests;
add arrow to Suggests;
assertPostFireDist() ported from the LIM branch, where it was the only thing
Biomass_regenerationPM still needed from LIM. The module called it unguarded, so with
reqdPkgs repointed at development the module failed with "could not find function
assertPostFireDist". Its only dependency, addPixels2CohortData(), is already here.cohortDefinitionCols() to ensure consistent cohort definitions;lccMapGenerator() to calculate landcover classes from cohortData and pixelGroupMap;prepInputs_NTEMS_DominantSpecies function for importing dominant species layers from NTEMS website;speciesPresentFromNTEMS function to import dominant species layer from NTEMS and create factor raster to be hosted on Google drive;loadSCANFISpeciesLayers and prepSpeciesLayers_SCANFI functions for loading SCANFI species data from Google drive;adjustAgeToLongevity to adjust initial cohort ages based on longevity for each species;studyAreaEco function to extend studyArea to ecological boundaries;
studyAreaEco allows studyArea = NULL, uses type = "ecozone" by default;plot_raster_stats and calc_raster_counts for generating summaries of numeric rasters in Canada;scanfi-summary, summarizing a national stand age map by ecozone
(closes #177). The rasters these summaries are designed for cannot be processed
during a build -- SCANFI at 30 m is 2.1e10 pixels, a single pass reads tens of GB,
CI runners have ~14 GB of disk, and the ecostratification polygon host throttles CI
IP ranges -- so the vignette is split at the one expensive step. The value-frequency
table from calc_raster_counts() is precomputed by data-raw/precompute-vignettes.R
and committed under inst/extdata/ (~640 KB, including a coarsened raster for maps
and simplified polygons); every statistic, histogram and map is then recomputed from
it when the vignette is built -- offline, in ~15 s. The committed table is pinned to
a hash of the normalized source of the functions that produced it, so it cannot go
stale unnoticed: tests/testthat/test-vignette-artifacts.R fails when the two
diverge and names the script to re-run (that check is skipped under covr, which
rewrites function bodies to insert trace counters, so the source it would hash is
not the source that produced the artifacts). The summary statistics are deliberately
excluded from that hash -- they are an exact function of the counts table, so
changing the quantile definition shows up in the next build rather than forcing a
multi-hour regeneration of a table that is still valid;SCANFI download failures now explain themselves (closes #163). SCANFI is distributed
through a Google Drive folder shared with collaborators, so a user without access saw only
reproducible's generic Could not access the Google Drive resource ... (404) Not Found.
That reads like a dead link and invites a hunt for a public mirror that does not exist.
prepInputsStandAgeMap(), prepRawBiomassMap(), loadSCANFISpeciesLayers(),
convert_SCANFI_LCC_codes() and prepInputs_SCANFI_LCC_FAO() now report it as a
permissions problem and name the ways out -- first checking that the mirror is enabled
(LandR.scanfiMirror), which serves SCANFI v2 with no Google login and is the likeliest
fix; then requesting access at https://opendata.nfis.org/; supplying your own copy; or
dataSource = "KNN" / "NTEMS" where the function offers them. The underlying error is
still shown in full, and failures that are not access problems pass through untouched;
convertUnwantedLCC() no longer uses the iterative spread2() search, whose run time grew
with the square of the radius of the largest contiguous block of classesToReplace. On
study areas containing large lakes/burns masked to an irregular boundary that search could
take hours, or never finish (a 3.8 M-cell Western-Alberta study area: >2.8 h and
unfinished). Each unwanted pixel now takes an available class drawn with probability
proportional to that class's abundance within the smallest window reaching the pixel's
nearest available class — the window at which spread2() would have stopped. Distances
come from one vectorized terra::distance() transform per candidate class and the counts
from a summed-area table, so the cost is independent of blob geometry: the same study area
now completes in seconds. Per-ecoregion availability constraints are preserved exactly,
and the abundance weighting reproduces the former search's class mix to within its own
seed-to-seed variance. The exact output is not reproducible — the window radius now
comes from a distance transform rather than from counting spread iterations — so runs
needing pre-1.2.0.9004 output bit-for-bit must pin LandR (<= 1.2.0.9003).
convertUnwantedLCC() gains a method argument, which overlayLCCs() also accepts and
passes through. Both options allocate identically and differ only in where the draw comes
from. method = "nearestWeighted" (the default) keys it on the pixel's ground position, so
it needs no set.seed(), is stable under Cache() (which does not key on RNG state), and
— because the key is the cell centre rather than the cell index — a grid-aligned crop
reproduces its parent raster cell for cell, so a small development subset agrees with the
scaled-up run. method = "nearestRandom" draws from the RNG instead, for when replicates
should differ.
convertUnwantedLCC()'s deterministic nearest-class rule, briefly present in
1.2.0.9004, has been removed. It broke distance ties to the lowest land-cover class, and
ties turn out to be common — 35–41% of unwanted pixels on real landscapes — so it pulled
systematically toward low-numbered classes. Under the Canada LCC coding those are the
sparse, non-forest types: pooled over four real landscapes it assigned shrubs 1.69× as
often as the previous implementation, broadleaf 0.58× and mixedwood 0.28×, moving
roughly one in fourteen unwanted pixels out of forest altogether. "nearestWeighted"
gives the same determinism without that bias.
loadSCANFISpeciesLayers() now lists the SCANFI species-layer folder via
reproducible::listGoogleDriveFolder() instead of googledrive::drive_ls().
When a directory-remap manifest is set (options(reproducible.urlRemap = ...),
e.g. one built with buckethost::makeMirrorManifest(directories = TRUE)), the
SCANFI files are fetched from a public mirror with no Google
authentication — the immediate use case is training/workshops, where
participants can pull SCANFI layers without a Google account or drive_auth().
Behaviour is unchanged when no manifest is set: it falls back to drive_ls()
and authenticates as before. (Requires the companion reproducible change that
adds listGoogleDriveFolder() and directory remaps.)
LANDISDisp() spiral seed dispersal loop ported to C++ via Rcpp
(~3.5–5.7× faster end-to-end depending on input size; ~5× on landscape-scale
fixtures of 9 M cells). Memory use also drops dramatically: the
per-cell-by-species source matrix is replaced with a per-pixelGroup
species bitmask. Bit-identical to the previous R implementation under a
fixed seed — guarded by 196 seed-locked / parity expectations (209 with
LANDR_SLOW_TESTS=1). Default behaviour is unchanged for callers; the new
path is on by default and can be opted out with
LANDISDisp(..., useCpp = FALSE) or
options(LandR.LANDISDisp.useCpp = FALSE). Rcpp (>= 1.0.10) added to
Imports and LinkingTo; digest added to Suggests (used by the
golden-output hash manifest in tests);
calc_raster_stats() now derives min/mean/max/quantiles/proportion-zero from
the value-frequency table produced by calc_raster_counts() instead of
reading every pixel via exactextractr, keeping memory bounded on very large
rasters; it gains a counts_df argument to reuse a precomputed table.
plot_raster_stats() now honours supplied counts_df/stats_df instead of
recomputing them, and fetches the Canada inset once, omitting it gracefully
when the source is unavailable. zonal removed from Suggests;
makeAndCleanInitialCohortData() now stop()s with an informative message
naming the species that cannot be age-imputed, instead of the cryptic
predict.merMod() "non-conformable arguments": a species with cohorts needing
age imputation but no usable known-age rows to fit the age model (all dropped
for zero biomass and/or cover) is absent from the model fit yet present in the
prediction set, so its fixed-effect speciesCode has no coefficient. A TODO
in the code outlines fix options (see #195);
SCANFI and 2020 now default data source and year for stand age and biomass functions;
use writeTo instead of filename2 in prepInputs() and related calls, following changes in reproducible;
minRelativeB defaults updated based on discussion surrounding over-representation of shade tolerant species establishing and generating unreasonably high levels of understory cohorts;
update speciesInStudyArea function to create dataSource parameter to direct function to download KNN or NTEMS factor raster from google drive and create associated species list;
update prepRawBiomassMap function to allow for incorporation of NTEMS or SCANFI biomass;
update prepInputsStandAgeMap function to allow for incorporation of SCANFI age map;
update documentation and citations for prepSpeciesLayers_* functions;
standardized sppEquivalencies_CA naming convention for provincial forestry columns with <province>_forestry ;
remove undifferentiated tree species variants from provincial forestry columns in sppEquivalencies_CA;
plotVTM now does not use Plot internally (with #140);
several minor updates to loadSCANFISpeciesLayers, prepSpeciesLayers_SCANFI to address more edge cases;
prepSpeciesLayers_SCANFI updates to improve join sppEquiv so "multiple - to - one" can be used;
improved transition plots, use arrow datasets to minimize memory use (important for large study areas);
use .scanfi_v1_years and .scanfi_v2_years instead of harcoded years in multiple places;
convert_SCANFI_LCC_codes() now writes an INT1U, 256x256-tiled, LZW-compressed
GeoTIFF (NAflag = 255) and gains a writeTo argument to name the output. The recode is
a bijection whose every output code lies in 20-230, so one unsigned byte suffices --- the
same footprint as the Byte SCANFI source. terra's default of Float32 in full-width
strips instead quadrupled the per-pixel cost and forced every crop of these national
178400 x 119100 rasters to read entire 178400-pixel rows. All 12 precomputed layers on
Google Drive (V2 1985-2025, V1 2000/2010/2020) were rebuilt and verified against their
distributed copies: 12/12 identical, with zero value mismatches and zero NA-pattern
mismatches over all 21,247,440,000 pixels each, and per-class histograms equal (which for
V2 2020 also reproduce the counts published in NRCan's own .tif.aux.xml sidecar). Total
storage falls from 32.98 GB to 15.05 GB (~2.19x). Values are unchanged, so a rebuilt layer
is a drop-in replacement for the precomputed copy it replaces.
plot_raster_stats() gains inset_polygons, the outline drawn behind the inset map.
It previously always downloaded the Canada boundary through gadm_canada(), so the
inset could not be drawn offline -- passing e.g. sf::st_union() of the polygons
being summarized keeps the call local;
plot_raster_stats() gains fig_width, fig_height and fig_dpi. The saved figure
size was hard-coded at 12 x 7.5 in and 300 dpi, which is ~20x larger than a web page
or vignette needs;
plot_raster_stats()'s per-polygon figure assembly is factored into internal panel
builders (.hist_panel(), .map_panel(), .inset_panel(), .stats_panel(),
.write_polygon_figure()), taking a 120-line loop body down to one call;
the calc_raster_stats()/plot_raster_stats() tests no longer skip on CI. They
previously downloaded the ecodistrict polygons from sis.agr.gc.ca, which throttles CI
runners; they now read a committed 23 KB fixture (data-raw/make-test-fixtures.R
regenerates it), so the full plotting path is exercised on every commit. A separate
test, still skipped on CI, checks the upstream source is reachable;
?speciesTable, ?Disturbances, ?prepSpeciesLayers, ?raster_stats, ?uniqueDefinitions,
?vegetationTransitions, ?dummyInputs and help("LandR-deprecated") resolve again. With
@rdname, roxygen takes \name{} from the first function in the block, so the topic a reader
would type was not an alias of anything -- the same defect that made [leadingProportions] a
dead link. The two hyphenated group topics also gained camelCase aliases, since ?dummy-inputs
parses as a subtraction and needs backticks.
Help pages no longer show stray backslashes where an asterisk was intended. ?BioSIM_getMPBSLR
rendered the geometric product as (S\*L\*R), and the initialB formula in
?Disturbances / ?updateCohortData rendered as maxANPP \* exp(...). The escapes are
correct for markdown but roxygen passes \* through to Rd, where it is not a macro.
BioSIM_getMPBSLR() returns data again. Its per-year lambda discarded the Cache()d result
and then called setDT() on an as-yet-unassigned slr, so the call errored out before it
could build the raster stack. It also never unwrapped the named list that generateWeather()
returns (one data.frame per model) -- the wind functions already do this via
[[windModel]].
assertPostFireDist() has a help page again. It is exported, but its @rdname assertions
never reached man/assertions.Rd, so R CMD check reported it as an undocumented code
object.
?leadingProportions and ?assertions resolve again. Both are @rdname topics, whose
generated \name{} is the first function in the block (mixedwoodProp and assert1
respectively), so the seven help pages linking to them pointed at nothing. Both now carry an
explicit @aliases.
.legacyToTo()'s documentation had silently migrated onto .legacyDot(), which was inserted
between the roxygen block and the function that block describes. .legacyDot() now precedes
the block.
prepInputsStandAgeMap() passed datatype to prepInputs() twice in both of its SCANFI
branches. datatype is not a formal of prepInputs(), so both copies travelled in ...
and R raised nothing -- but any function further down that chain taking datatype as a
named argument would have failed with "formal argument "datatype" matched by multiple
actual arguments", and the duplicate entered the Cache() key. The non-SCANFI branch
already passed it once.
prepSpeciesLayers_SCANFI() and loadSCANFISpeciesLayers() read their legacy rasterToMatch /
studyArea arguments out of ... exactly, instead of with $. $ on a list partial-matches, so
a studyAreaName passed through ... — as Biomass_speciesData does — was returned for
dots$studyArea. .legacyToTo() then took its studyArea-only branch and set cropTo and
maskTo to that character string, so the call died in postProcessToAssertions() with
"cropTo must be a Raster*, Spat*, sf or Spatial object". The same hazard applied to
rasterToMatchLarge prefix-matching rasterToMatch.
prepSpeciesLayers_SCANFI(): the Google Drive fallback (taken when RCurl::url.exists()
fails, e.g. during a network blip) referenced year, which is not a formal, so it
resolved to data.table::year and failed with "cannot coerce type 'closure' to
vector of type 'character'". It now uses dataYear.
assertERGs() now gives an informative error when ecoregionMap carries no
ecoregionGroup values (e.g. a GeoTIFF read without its companion .aux.xml,
so the raster attribute table is missing), instead of a cryptic
"subscript out of bounds" (#190, @SAY-5);
convertUnwantedLCC() again returns the newPossLCC column it returned prior to
1.2.0.9004 (the assigned land-cover class itself, i.e. ecoregionGroup without its
ecoregion prefix). Callers use it to write the replacement classes back into the LCC
raster — Biomass_borealDataPrep does so behind an is.null() guard, which silently
stopped firing when the column disappeared, leaving rstLCCAdj (and hence
ecoregionMap) still showing the replaced classes;
dropTerm now can deal with random effects better (#105);
prepRawBiomassMap - needed overwrite = TRUE for cases where download was corrupt;
prepRawBiomassMap needs httr2 package as remote site is failing with download.file;
don't delete CA_forest_VLCE2 raster in prepInputs_NTEMS_LCC_FAO() (#110);
corrected some BC forestry tree species entries;
minor bug fixes to prepInputsFireYear pertaining to file structure of NFDB data;
stats_from_counts() (and so calc_raster_stats()) no longer overflows on
national-scale pixel counts. value and count are both integer, so at 30 m
resolution value * count exceeds the 2^31^ - 1 integer limit and the mean came back
NA -- one ecozone of SCANFI holds ~1.5e9 pixels, and the Boreal Shield mean was
silently lost this way. sum(count) and cumsum(count) in weighted_quantile()
were within a factor of two of overflowing as well. The arithmetic now promotes to
double. Only visible at national extent: the same code on kNN at 250 m is ~70x below
the threshold;
prep_polygons() no longer assumes the geometry column is named geometry. It is,
for a shapefile, but a GeoPackage names it geom, and the hard-coded
st_union(geometry) failed with object 'geometry' not found. The dissolve now goes
through summarise()'s own do_union, so the column name is irrelevant;
prep_polygons() now accepts the SpatVector its documentation promises; previously
only sf input worked, since sf::st_transform() was called on the object directly;
plot_raster_stats()'s statistics panel no longer runs labels together. Label and
value were pasted into one centred string, so a wide value collided with its
neighbour (Min : 0Mean : 60.68Max : 395); they are now drawn as right- and
left-aligned columns about a shared centre;
plot_raster_stats() no longer requires purrr, which it has not used for some time
but still gated on via stopifnot(requireNamespace(...));
.overlay() to avoid warning with larger values;vegTypeMapGenerator() when mixedType = 1;mixedType = 0 in vegTypeMapGenerator();prepInputs_NTEMS_LCC_FAO();speciesEquivalencies_CA;raster to terra throughout;gdalUtilities dependency;prepInputLCC: orig argument no longer accepted by terra::compareGeom;calcSeverityB: output table was missing the proportion of B killed;maxB) and species establishment probabilities (SEP);speciesEcoregion table (brought over from Biomass_speciesParameters module), using estimated inflationFactor and mANPPproportion to adjust maxB and maxANPP, respectively;FireDisturbance and FireDisturbancePM pulled from;
Biomass_regeneration and Biomass_regenerationPM modules, respectively;overlayLCCs() now works correctly with terra (#99);standAgeMapGenerator() to produce standAgeMap from cohortData;prepInputs_NTEMS_Nonforest() and prepInputs_NTEMS_LCC_FAO();assertSpeciesTable() and assertSpeciesTableRaw();LandWebUtils package (#86)Colors -- new function to help with terra migration.prepInputsFireYear can now handle rasterToMatch that is a SpatRasterMuMIn (Sept 2022)raster/terra utility and plotting functionsnonForestedPixels used to detect pixels without species cover or a non-forested land-cover class;prepRawBiomassMap used to create rawBiomassMap;prepRasterToMatch used to create RasterToMatch and RasterToMatchLarge;prepInputsStandAgeMapcan now accept firePerimeters layer, avoiding inner download if layer is present;assertSppVectors) to check that species match between vectors (e.g. tables, colours and species list vector);sppHarmonize that deals with the 3 potential ways for a user to input the sim$sppEquiv, P(sim)$sppEquivCol, and sim$sppNameVector;speciesInStudyArea and `species;Require;makeAndCleanInitialCohortData can now be turned off;LANDISDisp(): skip dispersal when src or rcv data.tables are empty;.initiateNewCohorts) can now be a fixed integer or, as before, calculated using the LANDIS-II Biomass Succession Extension v3.2.1 approach (Scheller & Miranda 2015);prepInputsFireYear();statsModel;Biomass_borealDataPrep;assertColumns gives better message for which columns are incorrect/missing;minRelativeBDefaults is now a function so they are more easily accessible ;statsModel was pulling along with it all the data, 5x. Now it does not. The Caching of this should be fast and small now.modelBiomass (see Biomass_boreaDataPrep) with scaled data or different optimizer;loadkNNSpeciesLayers and prepSpeciesLayers_KNN prevent issues when default URL is down, or working offline (but layers are present locally);sppEquivalenciesCA (PSP, BC_Forestry and FuelClass);rawBiomassMap;fireURL = NULL to prepStandAgeMap() bypasses age imputation;sppEquivCheck;loadKNNSpeciesLayers can accept a sppEquiv table with one columnspeciesEquivalencies_CA data;speciesTableUpdate;LANDISDisp now (back to) native R. It is about 15x faster than the Rcpp implementation, and much simpler, with about 30% of the number of lines of code. It was inspired by the "spiral" approach as was used in the Rcpp in the pre-1.0.0 version of LandR, but much more efficiently as it is now correctly identifies every pixel outward from a centre pixel using raster::focalWeight, with the maximum of the seeddispersal_max across all species. RAM use appears under control, even for large problems (tested on 50M pixel Raster with 8M potential Source pixels and 500,000 Receiving pixels, with a peak additional RAM of 3 GB during LANDISDisp);
LANDISDisp now accommodates sub-cellSize dispersal distances, using the original Ward Dispersal equation. Previously, the sub-pixel dispersal was treated as if it was starting from the centre of the pixel. So, if less than a full pixel, then very little horizontal transfer. This has the effect that there will be a large increase in horizontal transfer for the species that have small seeddistance_max (i.e., less than cell size);prepSpeciesLayers_ONFRI;LANDISDisp did not correctly handle speciesCode when it is a factor. This is a common possibility. It now handles these correctly.updateSpeciesTable (moved from LandWebUtils) to allow user to update species parameters by passing a named list.assignLightProb now allows interpolating germination probability between species shade tolerance levels for any given stand shade value. This allows for for decimal values in species shade tolerance traits and greater fine tuning of shade-related germination probabilities.makeCohortDataFiles, as it is the last thing to do before making cohortData.createCohortData, makeAndCleanInitialCohortData , and a hard coded bit in Biomass_borealDataPrepstatsModel function has a new argument to improve caching with reproducible::Cache (i.e. not used internally);time, are now called currentTime - these changes are matched in LandR Biomass modulesoverlayLCCs which will help with overlaying more than one land cover classification raster.convertUnwantedLCC to accommodate more cases and eliminate redundant arguments.prepInputsLCC() now works with "LCC10"