Expand a SpatEnvelope to the SpatVoxel, with an input of
depth levels. A depth level belongs to a cell when the slab of water it
stands for shares some thickness with that cell's [depth_min, depth_max]
interval; depths outside the interval, and cells that are NA in the
envelope, are NA in every layer.
Usage
envelope_to_voxel(
x,
depths,
values = NULL,
profile = NULL,
bounds = c("top", "midpoint"),
varname = "presence"
)Arguments
- x
SpatEnvelope, e.g. from
as_envelope()orvoxel_to_envelope().- depths
Array of values that can be coerced into numeric type, represents metres depth below sea level. Sorted shallow to deep, and deduplicated, before use.
- values
Optional numeric or SpatRaster of same CRS, resolution, extent as
x. Defines values to write for voxel cells that are within depth intervals.NULL(the default) writes1at every occupied level, giving simple presence/absence.- profile
Optional function distributing
valuesacross the depth dimension, taking(ind, depths, n_depths)and returning the weight to apply.NULL(the default) writes the same value at every depth, asprofile_flat()does;profile_equal()divides the value equally across the depths a cell occupies. Any function meeting the contract in voxel_profiles works, including your own.- bounds
How to place the edges of the slab each depth level stands for.
"top"(the default) runs the slab from the level down to the next, so the level is its shallow edge;"midpoint"puts the edges halfway between neighbouring levels. Under both, the outer edges are clamped torange(depths)rather than extrapolated.- varname
Name to use for depth layer, taking on form of
{varname}_depth={depths[i]}
Value
A SpatVoxel with one layer per depth in depths, on the
grid of x, layers ordered shallow to deep.
Details
valuesis the magnitude, which can be a single number for the whole grid or a SpatRaster carrying a value per cell. Default value is1, which makes the output a presence mask. Ex.valuescan be 2D fisheries effort rasters.profiledecides how that magnitude is spread down the water column. DefaultNULLmeans the full value is written at every occupied level. Providing aprofile_*function determines howvaluesis transformed for each depth level. Ex.profile_equal()divides it evenly over the levels the cell occupies, so sum of all depth levels in the voxel equalsvalues.
A depth level stands for a slab of water rather than a knife-edge, and a cell
occupies that level when its envelope overlaps the slab by more than a
shared edge. An interval of [10, 20] against levels c(0, 100) therefore
occupies the 0 m level, rather than falling between the levels and coming
back empty. Touching is not overlapping: an interval that ends exactly where
a slab begins, [0, 100] against the slab 100-200, does not occupy that
level, so adjacent depth ranges are placed on disjoint levels and do not
intersect as voxels. This is the same rule intersect_3d() applies between
two envelopes.
Where a slab's edges fall is a property of the dataset the levels came from,
not of the levels themselves, so bounds selects the convention:
"top"(the default) runs each slab from its level down to the next, so a level names the top of what it stands for. Levelsc(0, 100, 200)give slabs0-100,100-200."midpoint"puts the edges halfway between neighbouring levels, so a level sits in the middle of what it stands for. This is the World Ocean Atlas convention:0, 5, 10, ...the 0 m layer covers0-2.5m, the 5 m layer2.5-7.5m, the 10 m layer7.5-12.5m, and so on.
Under either convention the slabs cover the span from the shallowest level to
the deepest and no further: outer edges are clamped to range(depths) rather
than extrapolated, which is why WOA's 0 m layer starts at the surface rather
than half a gap above it. The expansion is therefore limited only at the two
ends when a cell whose envelope lies wholly above min(depths) or wholly below
max(depths). Under "top" the deepest level has no next level to run to,
so it stands for a zero-thickness slab: an envelope is recorded there only
when it reaches below max(depths), and one that ends exactly at
max(depths) stops at the level above. For the same reason a
voxel_to_envelope() round trip drops each cell's deepest occupied level:
the envelope's depth_max names that level, which is the top of its slab.
See also
voxel_to_envelope(), the reverse (and lossy) collapse.
Examples
fp <- terra::rast(nrows = 1, ncols = 2, xmin = 0, xmax = 2, ymin = 0, ymax = 1)
terra::values(fp) <- c(1, NA)
e <- as_envelope(fp, depth_min = 50, depth_max = 200)
# presence at every standard depth the envelope covers
terra::values(envelope_to_voxel(e, depths = c(0, 50, 100, 200, 300)))
#> presence_depth=0 presence_depth=50 presence_depth=100 presence_depth=200
#> [1,] NA 1 1 NA
#> [2,] NA NA NA NA
#> presence_depth=300
#> [1,] NA
#> [2,] NA
# the same pattern, carrying a constant of your choosing instead of 1
terra::values(
envelope_to_voxel(e, depths = c(0, 50, 100, 200, 300),
values = 5)
)
#> presence_depth=0 presence_depth=50 presence_depth=100 presence_depth=200
#> [1,] NA 5 5 NA
#> [2,] NA NA NA NA
#> presence_depth=300
#> [1,] NA
#> [2,] NA
# spread that value down the column instead: an even share at each of the
# depths the cell occupies, summing back to 1
terra::values(
envelope_to_voxel(e, depths = c(0, 50, 100, 200, 300),
profile = profile_equal,
varname = "time")
)
#> time_depth=0 time_depth=50 time_depth=100 time_depth=200 time_depth=300
#> [1,] NA 0.5 0.5 NA NA
#> [2,] NA NA NA NA NA
# the same envelope read against WOA-style layer bounds: [60, 70] sits in the
# 50-150 m slab of the 100 m level, where the default puts it at 0-100 m
shallow <- as_envelope(fp, depth_min = 60, depth_max = 70)
terra::values(envelope_to_voxel(shallow, depths = c(0, 100, 200)))
#> presence_depth=0 presence_depth=100 presence_depth=200
#> [1,] 1 NA NA
#> [2,] NA NA NA
terra::values(envelope_to_voxel(shallow, depths = c(0, 100, 200),
bounds = "midpoint"))
#> presence_depth=0 presence_depth=100 presence_depth=200
#> [1,] NA 1 NA
#> [2,] NA NA NA
# a per-cell magnitude comes in as a raster: split each cell's effort evenly
# over the depths it occupies, conserving the cell total
effort <- terra::setValues(terra::rast(fp), c(100, NA))
terra::values(
envelope_to_voxel(
e, depths = c(0, 50, 100, 200, 300), values = effort,
profile = profile_equal,
varname = "effort"
)
)
#> effort_depth=0 effort_depth=50 effort_depth=100 effort_depth=200
#> [1,] NA 50 50 NA
#> [2,] NA NA NA NA
#> effort_depth=300
#> [1,] NA
#> [2,] NA