A SpatVoxel samples
the water column at a set of depth levels, such as the World Ocean Atlas
standard depths. A SpatEnvelope stores a continuous
[depth_min, depth_max] interval per cell. Converting
between the two with envelope_to_voxel(), or combining them
with terra::mask(), intersect_3d(),
intersects_3d() or volume(), needs a rule for
which levels an interval occupies. This article sets out that rule.
The examples use a two-cell footprint, so every result can be printed in full.
library(ocean3d)
fp <- terra::rast(nrows = 1, ncols = 2, xmin = 0, xmax = 2, ymin = 0, ymax = 1)
terra::values(fp) <- c(1, 1)A cell occupies a level when its envelope overlaps the slab that
level stands for by more than a shared edge. This matters for thin
intervals. An envelope of [10, 20] contains none of
c(0, 100, 200), but it is clearly within the water the 0 m
level represents:
An envelope of [0, 100] ends exactly where the 100 m
slab begins, so it occupies the 0 m level alone:
upper <- as_envelope(fp, depth_min = 0, depth_max = 100)
lower <- as_envelope(fp, depth_min = 100, depth_max = 200)
terra::values(envelope_to_voxel(upper, depths = c(0, 100, 200)))
#> presence_depth=0 presence_depth=100 presence_depth=200
#> [1,] 1 NA NA
#> [2,] 1 NA NATwo envelopes that meet at 100 m therefore do not intersect.
intersects_3d() gives the same answer for the envelopes
themselves and for their voxels, which land on disjoint levels:
boundsWhere the slab edges fall is a property of the dataset the levels
came from, so bounds selects the convention:
"top" (the default) runs each slab from its level down
to the next."midpoint" puts the edges halfway between neighbours.
This is what World Ocean Atlas levels mean, so use it with WOA
data.An interval of [60, 70] lands in a different level under
each:
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,] 1 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 1 NAThe same bounds argument is accepted by
terra::mask(), intersect_3d(),
intersects_3d(), volume() and
calc_volume_overlap() wherever they place an envelope on a
voxel’s depth levels. See ?envelope_to_voxel for the full
rule, including how the shallowest and deepest levels are clamped.