ocean3d is an R package for three-dimensional marine spatial analysis of species ranges, fisheries, and depth-stratified oceanographic data. It grew out of work on sharks, rays, and chimaeras, with the functions generalising to any marine taxon or spatial layer.
The three-dimensional marine environment poses a unique challenge for
spatial analyses. Most conventional GIS workflows represent space as a
two-dimensional plane. This abstraction fails to capture the range of
depths and vertical overlap of marine habitat. Fisheries also operate at
different depths depending on gear type and target species, creating
depth-specific patterns of threat exposure that require analysis in 3D
volume rather than 2D planes to quantify. ocean3d provides
documented workflows and reusable functions for:
In order to represent the 3D space in the ocean, we use a stacked-raster approach built on terra. We call this the Spatial Voxel Model, with the class SpatVoxel. Using this SpatVoxel, we can represent any number of spatial phenomena in the ocean, including species distributions, fishing effort, and oceanographic variables.

This package includes functions for converting common vector representations to the SpatVoxel class 3D representation. One example operation enabled by ocean3d is to take an existing vector and observed depth range, and have it match with a pre-existing SpatVoxel.

ocean3d uses two representations for 3D geometry: A SpatEnvelope stores one
continuous [depth_min, depth_max] interval per cell; a SpatVoxel (shown above) stores a
value at each of a set of depth levels, so it can hold a variable and interior gaps.
SpatEnvelope is useful for representing discrete 3D geometry (ex. species ranges
with defined boundaries for area and depth). In contrast, SpatVoxel is ideal for
representing continuous values (ex. species distribution models with probabilities
of occurrence, rather than presence and absences). SpatVoxel can also represent
overhangs, holes, etc. in 3D space since it contains a value for every depth, rather than a depth range.
This package includes utilities for working with commonly used datasets in marine habitat spatial analyses, including IUCN Red List, World Ocean Atlas, GEBCO, Copernicus Marine, and Global Fishing Watch.
You can install the development version of ocean3d from GitHub with:
# install.packages("devtools")
devtools::install_github("Marine-Biodiversity-Conservation-Lab/ocean3d")
New to ocean3d? Watch the recording of the EEA 2026 workshop, which walks through the package and its main workflows:
create_study_raster() — build an empty study-area SpatRaster
covering the combined extent of one or more spatial inputs.as_voxel() — wrap a multi-depth SpatRaster (or a list of
single-depth ones) as a validated SpatVoxel, ordered shallow to deep
with {variable}_depth={value} layer names.as_envelope() — define a 3D envelope with two rasters, with depth_min
and depth_max corresponding to the minimum and maximum depths present in
given cell.vect_to_envelope() — rasterize a single species range or fishery
footprint onto the study grid and attach per-cell depth_min /
depth_max, returning a SpatEnvelope.envelope_to_voxel() — expand an envelope onto a set of depth levels.voxel_to_envelope() — collapse a voxel to the envelope bounding it,
using a predicate on the cell values. Lossy: interior gaps are filled.Three functions ask how a SpatEnvelope or SpatVoxel relates to another
object in 3D. The other object can be a 3D object or a 2D one: a
SpatRaster footprint or polygons (SpatVector, sf). A 2D object
restricts the domain horizontally and without restricting the depths.
intersect_3d(x, y) — the shared 3D space between the two inputs. Returns an envelope with the shared depth interval or a presence voxel, depending on inputs.intersects_3d(x, y) — whether inputs share any 3D space, cell by cell:
TRUE, FALSE (both present but disjoint, or only one present), or
NA (neither present).mask(x, mask) — terra::mask() made depth-aware. Keeps the values of x where the mask domain reaches that cell at that depth.Both dispatch on the 3D representation, so they take either a
SpatEnvelope or a SpatVoxel. A voxel’s volume sums the slab each
occupied depth level stands for, so interior gaps cost volume instead of
being filled in. Given one of each, the envelope is discretized onto the
voxel’s depth levels.
volume() — total 3D volume (km³) of a rasterized domain.calc_volume_overlap() — per-cell depth intervals and volumes for two
rasterized domains and their intersection (returns a 9-layer stack).
Built on intersect_3d().extract_to_area() — crop a SpatVoxel to an area polygon and select
layers within a depth range.See vignette("ocean3d") for the full workflow, from a
range polygon through to a vertical profile of the values inside it.
extract_to_point() — the general engine: match observation points
(data frame, tibble, sf POINT, matrix, or named list) against one or
more NetCDF files, snapping to the nearest longitude, latitude, time
and depth cell. Returns the input with the extracted values appended,
structure preserved.extract2d() — for variables with no depth dimension.extract3d_nearest() — the valid, non-missing depth layer closest to
each observation’s depth, so a cell below the seabed does not silently
return NA.extract3d_surface() / extract3d_bottom() — the first available
depth layer, or the deepest non-missing one at that cell.extract3d_all() — nearest, surface and bottom in one pass, as three
columns.Add an IUCN Red List API key to access species-assessment functions. Sign up and acquire your API key at https://api.iucnredlist.org/:
usethis::edit_r_environ()
# Add the following line to the .Renviron file
# IUCN_REDLIST_KEY="your_iucn_api_key_here"
fetch_species_assessments() — query the IUCN Red List API for
taxonomy, Red List category, and depth limits, by SIS ID, scientific
name, or comprehensive group code.fill_missing_depths() — fix swapped upper/lower depth values and
fill missing values from genus-level means.load_gebco_bathymetry() — load a GEBCO bathymetry NetCDF as a
SpatRaster.woa_download() — download WOA 2023 NetCDF files (temperature,
salinity, dissolved oxygen, oxygen saturation, AOU, nitrate,
phosphate, silicate, density) at 0.25°, 1°, or 5° resolution, with
caching.woa_load_nc() — load a WOA NetCDF and select a statistical field
(e.g., objectively analyzed climatology), returning a SpatRaster
with the package’s standard {variable}_depth={value} layer names.woa_cache_dir() / woa_cache_clear() — manage the persistent WOA
download cache.gfw_effort_to_raster() — turn the long-format apparent-fishing-hours
tibble from gfwr::gfw_ais_fishing_hours() into a multi-layer
SpatRaster, one layer per gear (or other grouping).
Extending that into 3D uses the general converters — as_envelope() and
envelope_to_voxel() — driven by a gear-to-depth-band lookup. Those
operating-depth priors are analysis assumptions rather than package data,
so they live in the gfw-fishing-effort-3d article.Contributions are welcome. ocean3d is being actively built out from a set
of lab analyses into a general, tested package, and there is plenty of
well-scoped work available. Start with:
New contributors: the "Known rough edges / good first issues" section of
CONTRIBUTING.md lists concrete starting points.
Thank you to the people that have inspired and collaborated on this work!
David Ruiz-García, Rachel Aitchison, Wade VanderWright, Amanda Arnold, Dr. Samm Sherman, Dr. Alifa Haque, Prof Nicholas K. Dulvy.
Jay H. Matsushiba, David Ruiz-García, Rachel M. Aitchison, Wade J. VanderWright, C. Samantha Sherman, Alifa Bintha Haque, Nicholas K. Dulvy. ocean3d: An R Package for Three-Dimensional Spatial Analyses of Marine Habitat.