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:
- extracting values from depth-stratified oceanographic rasters within user-defined geographic areas and depth windows;
- characterising the abiotic habitat (e.g., temperature, dissolved oxygen) of marine species using IUCN Red List ranges and World Ocean Atlas 2023 climatologies;
- calculating three-dimensional overlap between species distributions, fisheries effort, and other spatial layers;
- producing depth-aware maps and depth-profile plots of species ranges, environmental conditions, and cumulative fishing pressure.
How it works
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.
Installation
You can install the development version of ocean3d from GitHub with:
# install.packages("devtools")
devtools::install_github("Marine-Biodiversity-Conservation-Lab/ocean3d")Function overview
Create a raster covering extent of multiple spatial inputs
-
create_study_raster()— build an empty study-areaSpatRastercovering the combined extent of one or more spatial inputs.
3D representations
-
as_voxel()— wrap a multi-depthSpatRaster(or a list of single-depth ones) as a validatedSpatVoxel, ordered shallow to deep with{variable}_depth={value}layer names. -
as_envelope()— define a 3D envelope with two rasters, withdepth_minanddepth_maxcorresponding to the minimum and maximum depths present in given cell.
Conversion between 3D representations
-
vect_to_envelope()— rasterize a single species range or fishery footprint onto the study grid and attach per-celldepth_min/depth_max, returning aSpatEnvelope. -
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.
3D spatial query
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), orNA(neither present). -
mask(x, mask)—terra::mask()made depth-aware. Keeps the values ofxwhere the mask domain reaches that cell at that depth.
3D volume
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 onintersect_3d().
Environmental extraction to areas
-
extract_to_area()— crop aSpatVoxelto 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.
Environmental extraction to point observations
-
extract_to_point()— the general engine: match observation points (data frame, tibble,sfPOINT, 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 returnNA. -
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.
Common spatial dataset utilities
IUCN Red List API utilities
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.
GEBCO Bathymetry
-
load_gebco_bathymetry()— load a GEBCO bathymetry NetCDF as aSpatRaster.
World Ocean Atlas 2023 utilities
-
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 aSpatRasterwith the package’s standard{variable}_depth={value}layer names. -
woa_cache_dir()/woa_cache_clear()— manage the persistent WOA download cache.
Global Fishing Watch (fisheries effort)
-
gfw_effort_to_raster()— turn the long-format apparent-fishing-hours tibble fromgfwr::gfw_ais_fishing_hours()into a multi-layerSpatRaster, one layer per gear (or other grouping). Extending that into 3D uses the general converters —as_envelope()andenvelope_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 thegfw-fishing-effort-3darticle.
Contributing
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:
- CONTRIBUTING.md — development setup, data requirements, the branching/coordination workflow, and coding conventions.
- SPEC.md — the roadmap; every planned function with its priority and source. Unchecked items are up for grabs.
- Code of Conduct.
New contributors: the “Known rough edges / good first issues” section of CONTRIBUTING.md lists concrete starting points.