Code
# load in necessary libraries
library(tidyverse)
library(sf)
library(terra)
library(tmap)
library(patchwork)
library(kableExtra)December 12, 2025
GitHub repository: https://github.com/sofiiir/aquaculture-exclusive-economic-zones
When established and regulated in a sustainable manner, aquaculture is a viable solution to help address over-fishing while providing ocean foods to communities that rely on them (Smith et al., 2010). It additionally has the capacity to create a job sector, increasing local employment opportunities (Engle et al., 2025). Though there are many factors in choosing suitable locations for aquaculture, sea surface temperature (SST) and bathymetry are two key considerations.
This analysis focuses on creating a function that identifies potential aquaculture locations based on SST and bathymetry species requirements. Further, the findings of potential aquaculture locations for each species are broken into Exclusive Economic Zones (EEZ). EEZs are areas off of countries’ coasts extending approximately 200 nautical miles into open water. These areas are controlled by the coastal countries, which maintain control over the resources and happenings in these zones (NOAA Ocean Exploration). For this analysis, these EEZs are divided into state regions.
Fun fact: According to the National Oceanic and Atmospheric Administration, the EEZs of the United States are larger than the land area of the 50 states combined!
EEZ data is available through marineregions.org. Since this is vector data, we use the sf package to read in the data.
Bathymetry data is available through the General Bathymetric Chart of the Oceans (GEBCO). Since this is raster data, we use the terra package to read in the data.
Note: Bethymetry and depth are used interchangeably throughout this analysis. They refer to the same data set and concept.
SST data was acquired from the NOAA’s 5km Daily Global Satellite Sea Surface Temperature Anomaly v3.1. SST data from 2008 to 2012 are used and averaged for this analysis. This averaging is necessary to account for year-to-year variations in SST. Since this is raster data, we use the terra package to read in the data.
We stack all of the SST data for each year to make a single raster.
# read in raster sea surface temperature data
sst_2008 <- rast(here::here("blog", "potential-aquaculture-locations", "data", "average_annual_sst_2008.tif"))
sst_2009 <- rast(here::here("blog", "potential-aquaculture-locations", "data", "average_annual_sst_2009.tif"))
sst_2010 <- rast(here::here("blog", "potential-aquaculture-locations", "data", "average_annual_sst_2010.tif"))
sst_2011 <- rast(here::here("blog", "potential-aquaculture-locations", "data", "average_annual_sst_2011.tif"))
sst_2012 <- rast(here::here("blog", "potential-aquaculture-locations", "data", "average_annual_sst_2012.tif"))
# stack data into a raster stack
sst <- c(sst_2008, sst_2009, sst_2010, sst_2011, sst_2012)CRS is one of the three components that are essential to check before plotting. CRSs must match for all of the data to accurately perform functions on different data.
To check the CRS of a raster, use crs and to check the CRS of a simple feature object, use st_crs.
Warning messages are especially useful as they’ll alert when the specification is not met. I add warning messages to assert that essential components match. Here, I add a warning message to check the CRS for SST and depth match.
Warning: still not a match
Since not all of the CRSs match, we need to change some of the CRSs. I chose to make all of the CRS match the SST CRS. From personal experience, matching the CRS of raster data is less prone to causing errors down the road due to incorrect CRS.
I use warning messages again to make sure the CRS alterations were successful. They were!
This analysis works based on the mean of Sea Surface Temperature (SST) measured between 2008 and 2012, so I use map algebra to calculate the mean. This analysis is also based on Celsius, so I subtract 273 to convert all of the values from the Kelvin scale to the Celsius scale with a global operation.
To reiterate, the three important aspects when working with spatial data are that the extent, resolution, and positions match. For this analysis, SST and depth need to have the same extent, resolution, and positions. I crop to make sure the extents of the rasters are the same.
Then I resample to make sure that the resolutions of the rasters are the same.
I also add another warning message to make sure the extents and resolutions match for the SST and depth data frames.
[1] "extents match!"
[1] "resolutions match!"
The steps to finding locations that satisfy conditions for a specific species based on SST and depth are:
Sea surface temperature is adequate for oysters in the range of 11-30°C. I classify sea surface temperature to find those areas that are potential zones for oyster aquaculture.
I assign anything less than 11 degrees Celsius and anything more than 30 degrees Celsius to 0, since these areas are not suitable for oyster aquaculture. Anything between the range of 11 and 30 degrees Celsius, I assign a 1 to signify that these areas are suitable for oyster aquaculture.
Oysters can survive between the depths of 70 and 0 meters below the sea surface. I classify depth to find those areas that are potential zones for oyster aquaculture.
I assign anything below 70 meters and anything above sea level to 0, since these areas are not suitable for oyster aquaculture. Anything between the range of 70 meters deep and sea level, I assign a 1 to signify that these areas are suitable for oyster aquaculture.
I then create a function to determine what locations are suitable for oyster aquaculture based on both sea surface temperature and depth.
Anywhere a zero is present for either depth or SST will result in a 0 since anything times zero is zero. Only areas where there is a 1 for both depth and SST will be left.
I apply the function to determine what areas are suitable based on both SST and depth for oyster aquaculture. Then, I set anything that is a zero to NA to avoid any analysis issues moving forward.
Here are some preliminary maps to assess any differences between suitable locations based on depth, SST, and both depth and SST.
I like to use intermediate mapping to see if what I think I’m doing is actually what I’m doing.
# suitable depth for oyster aquaculture
tm_basemap("Esri.OceanBasemap") +
tm_shape(sst_classified) +
tm_raster(style = "cat",
palette = c("skyblue", "red"),
labels = c("unsuitable", "suitable"),
title = "Sea Surface Temperature (SST)") +
tm_title(text = "Suitable SST for Oyster Aquaculture",
size = 2) +
tm_compass() +
tm_scalebar() +
tm_layout(fontsize = 2,
scale = 0.5,
compass.position = tm_pos_in("left", "bottom"),
scalebar.position = tm_pos_in("left", "bottom")
) +
tm_layout(component.autoscale = FALSE)# suitable depth for oyster aquaculture
tm_shape(depth_classified) +
tm_raster(style = "cat",
palette = c("skyblue", "red"),
labels = c("unsuitable", "suitable"),
title = "Depth suitability") +
tm_title(text = "Suitable Depths for Oyster Aquaculture",
size = 2) +
tm_compass() +
tm_scalebar() +
tm_layout(fontsize = 2,
scale = 0.5,
compass.position = tm_pos_in("left", "bottom"),
scalebar.position = tm_pos_in("left", "bottom")
) +
tm_layout(component.autoscale = FALSE)# suitable locations for oyster aquaculture
tm_basemap("Esri.OceanBasemap") +
tm_shape(suit_oyster_aqu) +
tm_raster(style = "cat",
palette = c( "red"),
labels = c("suitable area"),
title = "Aquaculture suitability") +
tm_title(text = "Suitable Oyster Aquaculture Locations",
position = tm_pos_out("center", "top", pos.h = "center"),
size = 2) +
tm_title("based on depth and SST",
position = tm_pos_out("center", "top", pos.h = "center"),
size = 2) +
tm_compass() +
tm_scalebar() +
tm_layout(fontsize = 2,
scale = 0.5,
compass.position = tm_pos_in("left", "bottom"),
scalebar.position = tm_pos_in("left", "bottom")
) +
tm_layout(component.autoscale = FALSE)The final step is to find the suitable oyster aquaculture locations in each EEZ. States only have jurisdiction over their respective EEZs. To reiterate, this is within 200 nautical miles off the coast. I want to see the amount of area in each EEZ region that can be used for oyster aquaculture. This can help with creating policy to make these potential aquaculture ventures a reality or inform possible aquaculturists of where they might find the most success when pursuing oyster aquaculture.
I rasterize the vector data so that the operations can be applied smoothly between the two objects. Since I’m only interested in the suitable habitat within the Exclusive Economic Zones, I crop the suitable oyster aquaculture zones by the EEZ object.
I’m then interested in the size of the cells that are suitable in each EEZ area, so I use the zonal function to calculate the suitable areas.
# rasterize the eez sf object
eez_rast <- rasterize(eez_sf, suit_oyster_aqu, field = "rgn_id")
# select only suitable oyster aquaculture locations that are within the eez
eez_oysters <- mask(suit_oyster_aqu, eez_sf)
# calculate average size of cells
avg_area <- cellSize(eez_oysters, unit = "km")
# sum the suitable aquaculture locations based on eez zones
oyst_area <- zonal(x = (eez_oysters * avg_area),
z = eez_rast,
fun = "sum",
na.rm = TRUE) |>
as.data.frame()
# join to make one data frame with the total area
eez_sf <- left_join(eez_sf, oyst_area, by = "rgn_id")
# rename new column
eez_sf <- eez_sf |>
rename("area" = "lyr1")I extract the amount of area that is suitable throughout the West Coast. This is an interesting metric for any stakeholders who might be interested in seeing whether oyster aquaculture is something to pursue.
The total amount of space in the Exclusive Economic Zones (EEZ) on the West Coast that is suitable for oyster aquaculture based on suitable sea surface temperature and suitable bathymetry is 1.1799613^{4} km^2.
Here’s a final map with the coloration of the EEZ zones signifying the total suitable aquaculture area that each zone contains.
tm_basemap("Esri.OceanBasemap") +
tm_shape(eez_sf) +
tm_polygons(fill = 'area',
breaks = c(400, 1000, 1500, 3000, 3400, 4000),
palette = c("#EEF5FC",
"#96C3ED",
"#237DD1",
"#1B5E9D",
"#0C3259"),
title = "Suitable Aquaculture Area (km^2)") +
tm_text("rgn", size = 0.7) +
tm_shape(eez_oysters) +
tm_raster(col.scale = tm_scale_categorical(labels = (c("Suitable Areas", "Unsuitable")),
values= (c("red", "white"))),
col.legend = tm_legend("Aquaculture Areas")) +
tm_compass() +
tm_scalebar() +
tm_title("Suitable Oyster Aquaculture Area in Exclusive Economic Zones (EEZ)",
size = 1.5) +
tm_layout(fontsize = 2,
scale = 0.5,
compass.position = tm_pos_in("left", "bottom"),
scalebar.position = tm_pos_in("left", "bottom")) +
tm_layout(component.autoscale = FALSE)I also make polished tables to visualize the differences in suitable aquaculture areas and verify that the map above is accurate.
eez_kable <- eez_sf |>
st_drop_geometry() |>
rename("State" = "rgn",
"Suitable area (km^2)" = "area")
# create a table with the counts of adequate aquaculture zones
eez_kable |>
select("State", "Suitable area (km^2)") |>
kbl(caption = "Suitable Oyster Aquaculture areas on the West Coast based on Exclusive Economic Zones (EEZ).") |>
kable_styling()| State | Suitable area (km^2) |
|---|---|
| Oregon | 1349.4808 |
| Northern California | 452.6195 |
| Central California | 3724.5984 |
| Southern California | 3062.2031 |
| Washington | 3210.7111 |
Central California has the largest amount of suitable area for oyster aquaculture based on depth and SST. Washington and Southern California are close behind in the amount of suitable oyster aquaculture area based on depth and SST. Oregon and Northern California have the least amount of suitable oyster aquaculture area based on depth and SST. The tables’ suitable area values match our map.
I combine all the steps conducted above to make a function that can work on calculating potential aquaculture areas for any species. I replace variables that would vary for different species by making those variables the arguments in a function. Function arguments should also include all of the necessary data frames for clarity.
In the potent_aqu function, I add three data frames as arguments. The SST, EEZ, and depth data frames are all required. Additionally, I add a variable to change the minimum and maximum values for the suitable SST and depth of each species. The species name is also an argument for clarity that allows me to add the name of the species to the title of the plot.
potent_aqu <- function (sst,
eez_sf,
depth,
min_sst,
max_sst,
min_depth,
max_depth,
species_name){
# make all of the CRS match
depth <- project(depth, crs(sst))
st_crs(eez_sf) <- crs(sst)
# calculate the mean of sst
mean_sst <- mean(sst)
# convert the units from kelvin to celsius
mean_sst <- mean_sst - 273
# crop the depth raster to match the sst extent
depth <- crop(depth, mean_sst)
# resample depth raster to match the sst resolution
depth <- resample(depth, mean_sst)
# create a classification matrix for sst
sst_matrix <- matrix(c(-Inf, min_sst, 0,
min_sst, max_sst, 1,
max_sst, Inf, 0),
ncol = 3, byrow = TRUE)
# classify where suitable sst habitat for oysters are
sst_classified <- classify(mean_sst, rcl = sst_matrix)
# create a classification matrix for depth
depth_matrix = matrix(c(-Inf, min_depth, 0,
min_depth, max_depth, 1,
max_depth, Inf, 0),
ncol = 3, byrow = TRUE)
# classify where suitable depth habitat for oysters are
depth_classified <- classify(depth, rcl = depth_matrix)
# function to determine what habitats are suitable
suitable_habitat <- function(rast1, rast2){
rast1 * rast2
}
# find suitable locations for aquaculture
suit_aqu <- lapp(c(sst_classified, depth_classified), fun = suitable_habitat)
# rasterize the eez sf object
eez_rast <- rasterize(eez_sf, suit_aqu, field = "rgn_id")
# set any locations with a zero to NA
suit_aqu[suit_aqu == 0] <- NA
# select only suitable oyster aquaculture locations that are within the eez
eez_spec <- mask(suit_aqu, eez_sf)
# calculate average size of cells
avg_area <- cellSize(eez_spec, unit = "km")
# sum the suitable aquaculture locations based on eez zones
spec_area <- zonal(x = (eez_spec * avg_area),
z = eez_rast,
fun = "sum",
na.rm = TRUE) |>
as.data.frame()
# join to make one data frame with the total area
spec_area_eez <- left_join(eez_sf, spec_area, by = "rgn_id")
# rename new column
spec_area_eez <- spec_area_eez |>
rename("spec_area" = "lyr1")
# map the eezs based on the amount of suitable area per region
tm_basemap("Esri.OceanBasemap") +
tm_shape(spec_area_eez) +
tm_polygons(fill = "spec_area",
fill.legend = tm_legend("Suitable Aquaculture Area (km^2)")) +
tm_text("rgn", size = 0.7) +
tm_shape(eez_oysters) +
tm_raster(col.scale = tm_scale_categorical(labels = (c("Suitable Areas", "Unsuitable")),
values= (c("red", "white"))),
col.legend = tm_legend("Aquaculture Areas")) +
tm_compass() +
tm_scalebar() +
tm_title( paste("Suitable Aquaculture Area in Exclusive Economic Zones (EEZ): ", species_name),
size = 1.5) +
tm_layout(fontsize = 2,
scale = 0.5,
compass.position = tm_pos_in("left", "bottom"),
scalebar.position = tm_pos_in("left", "bottom")
) +
tm_layout(component.autoscale = FALSE)
}Red sea urchin are prized for their gonads, which are eaten under the name uni. Uni is popular in sushi establishments and fine dining worldwide. Because of their relatively higher price, these may be an economically advantageous choice for aspiring aquaculturists. The natural red sea urchin stocks are well managed on the west coast (Sea Grant California). However, due to the moratorium on limits for harvesting red sea urchin, harvests may not keep up with demand.
Let’s try out our function!
Sea surface temperature is adequate for red sea urchins in the range of 6.75-13.02°C. Red sea urchins can survive between the depths of 125 and 0 meters below the sea surface.
Washington and Oregon have the largest potential suitable aquaculture areas for red sea urchins based on SST and depth. Central California has the third most potential suitable aquaculture areas for red sea urchins based on SST and depth. Northern California and Southern California have the least amount of potentially suitable aquaculture areas for red sea urchins.
Analyzing potential aquaculture locations based on SST and bathymetry is only the first step in finding suitable locations for new aquaculture endeavors. One limitation of the above analysis is that SST is an average taken from 2008 to 2012. With climate change shifting temperatures, projection data would be important to include in analyzing the best future aquaculture sites. Referencing species’ native ranges is also essential to reducing any potential negative outcomes of introducing non-native species.
There is a vast amount of potential suitable aquaculture areas for both oyster and red sea urchin aquaculture. Aquaculture ventures would increase the job market in these areas. If aquaculture is sustainably actualized, it could also reduce the pressures on wild stocks.
Red sea urchins are sustainably managed presently. However, according to Sea Grant California, in certain areas without rocky surfaces, juvenile red sea urchins use the spines of mature sea urchins for protection. These areas likely have fewer red sea urchins due to harvesting, as the more mature red sea urchins are not present to aid in populating these areas with young sea urchins. This is one case study. Species-dependent analyses are necessary to avoid drawbacks from creating environmentally detrimental aquaculture systems.
The repository containing all of the code for this analysis can be found here.
Engle, C. R., Senten, J. V., Fong Q., Good, M. (July 2025) The economic contribution of aquaculture to the U.S. Western Region. North American Journal of Aquaculture. Volume 87, Issue 3, Pages 155–167. https://doi.org/10.1093/naaqua/vraf008
Flanders Marine Institute (2025): MarineRegions.org. Available online at www.marineregions.org. Consulted on 2025-11-14.
Gentry, R. R., Froehlich, H. E., Grimm, D., Kareiva, P., Parke, M., Rust, M., Gaines, S. D., & Halpern, B. S. Mapping the global potential for marine aquaculture. Nature Ecology & Evolution, 1, 1317-1324 (2017).
GEBCO Compilation Group (2022) GEBCO_2022 Grid doi:10.5285/e0f0bb80-ab44-2739-e053-6c86abc0289c.
Hall, S. J., Delaporte, A., Phillips, M. J., Beveridge, M. & O’Keefe, M. Blue Frontiers: Managing the Environmental Costs of Aquaculture (The WorldFish Center, Penang, Malaysia, 2011).
Smith, M. D., Roheim, C. A., Crowder, L. B., Halpern, B. S., Turnipseed, M., Anderson, J. L., Asche, F., Bourillón, L., Guttormsen, A. G., Khan, A., Liguori, L. A., McNevin, A., O’Connor, M. I., Squires, D., Tyedmers, P., Brownstein, C., Carden, K., Klinger, D. H., Sagarin, R., Selkoe, K. A. (2010). Sustainability and Global Seafood. Science 327, 784–786. DOI: 10.1126/science.1185345
NOAA Coral Reef Watch. 2019, updated daily. NOAA’s 5km Daily Global Satellite Sea Surface Temperature Anomaly v3.1 for the West Coast, 2008 - 2012. College Park, Maryland, USA: NOAA Coral Reef Watch. Data set accessed 2025-11-14 at https://coralreefwatch.noaa.gov/product/5km/index_5km_ssta.php.
NOAA Ocean Exploration. What is the EEZ?. Accessed 2025-12-11 at https://oceanexplorer.noaa.gov/ocean-fact/useez/.
Palomares, M.L.D. and D. Pauly. Editors. 2025. SeaLifeBase. World Wide Web electronic publication. www.sealifebase.org, version (04/2025).
Sea Grant California. Red Sea Urchin. Accessed 2025-12-12 at https://caseagrant.ucsd.edu/seafood-profiles/red-sea-urchin.
Longo, S.B., York, R., Why aquaculture may not conserve wild fish. (2024) Sci. Adv.10,eado3269.DOI:10.1126/sciadv.ado3269
@online{rodas2025,
author = {Rodas, Sofia},
title = {Potential {Aquaculture} {Locations} Within {Exclusive}
{Economic} {Zones}},
date = {2025-12-12},
url = {https://sofiiir.github.io/blog/potential-aquaculture-locations/},
langid = {en}
}