diff --git a/R/interpolate_variable.R b/R/interpolate_variable.R index fa373bd..b7d1ad5 100644 --- a/R/interpolate_variable.R +++ b/R/interpolate_variable.R @@ -19,6 +19,7 @@ #' specific bounds, or a 4-element named vector with values xmin, xmax, ymin, #' and ymax. #' @param return.raster Should a raster be returned? +#' @param outputfilefun Function of the form f(mask, year, method, var) that returns a string holding the full path name where the output raster should be stored. If not included, default is [coldpool_dir]/output/raster/[mask]/[var]/[mask]_[method]_[year]_[var].tif #' @return Returns a data frame containing cold pool areas estimated by interpolation methods, for cutoffs at zero degrees and two degrees C. If argument write.to.file = TRUE, also writes a GeoTIFF raster to output directory. #' @export @@ -36,7 +37,12 @@ interpolate_variable <- function(dat, select.region = "sebs", methods = c("NN", "IDW", "IDW4", "Exp", "Sph", "Bes", "Gau", "Cir", "Mat", "Ste", "Tps"), bbox = NULL, - return_raster = FALSE) { + return_raster = FALSE, + outputfilefun = NULL) { + + #--------------------- + # Input checks + #--------------------- if(!all(tolower(methods) %in% c("nn", "idw", "idw4", "exp", "sph", "bes", "gau", "cir", "mat", "ste", "tps" ))) { @@ -64,11 +70,12 @@ interpolate_variable <- function(dat, } + #--------------------- + # Raster setup + #--------------------- + # When select.region is a character vector if(is.character(select.region)) { - - fpath_subdir <- select.region - # Load survey extent for masking ---- region_mask <- akgfmaps::get_base_layers(select.region = select.region, set.crs = interpolation.crs)$survey.area @@ -76,59 +83,56 @@ interpolate_variable <- function(dat, # When select.region is an sf object if(is.data.frame(select.region)) { - - fpath_subdir <- ifelse(is.na(pre), "var", pre) - region_mask <- select.region - - } - - if(!dir.exists(here::here("output", "raster", fpath_subdir, var.col))) { - dir.create(here::here("output", "raster", fpath_subdir, var.col), recursive = TRUE) } # Set dimensions for raster cells ---- if(is.null(bbox)) { + if (is.character(select.region)) { # Default interpolation bounds for named regions - if(select.region %in% c("sebs", "bs.south")) { - # Make raster for interpolation ---- - n_dim <- floor(abs(-1625000 - -35000))/cell.resolution - - interp_raster <- terra::rast(xmin = -1625000, - xmax = -35000, - ymin = 379500, - ymax = 1969500, - nrow = n_dim, - ncol = n_dim, - crs = interpolation.crs) - - } else { - - if(select.region %in% c("bs.north", "nbs")) {extrap.box = c(xmn = -179.5, xmx = -157, ymn = 50, ymx = 68)} - - if(select.region %in% c("bs.all", "ebs")) {extrap.box = c(xmn = -179.5, xmx = -157, ymn = 50, ymx = 68)} + if(select.region %in% c("sebs", "bs.south")) { + # Make raster for interpolation ---- + n_dim <- floor(abs(-1625000 - -35000))/cell.resolution - plot.boundary <- akgfmaps::transform_data_frame_crs(data.frame(x = c(extrap.box['xmn'], extrap.box['xmx']), - y = c(extrap.box['ymn'], extrap.box['ymx'])), - out.crs = interpolation.crs) + interp_raster <- terra::rast(xmin = -1625000, + xmax = -35000, + ymin = 379500, + ymax = 1969500, + nrow = n_dim, + ncol = n_dim, + crs = interpolation.crs) + } else { + if(select.region %in% c("bs.north", "nbs")) {extrap.box = c(xmn = -179.5, xmx = -157, ymn = 50, ymx = 68)} - n_dim <- floor(abs(plot.boundary$x[1] - plot.boundary$x[2]))/cell.resolution + if(select.region %in% c("bs.all", "ebs")) {extrap.box = c(xmn = -179.5, xmx = -157, ymn = 50, ymx = 68)} - interp_raster <- terra::rast(xmin = plot.boundary$x[1], - xmax = plot.boundary$x[2], - ymin = plot.boundary$y[1], - ymax = plot.boundary$y[2], - nrow = n_dim[1], - ncol = n_dim[2], - crs = interpolation.crs) + plot.boundary <- akgfmaps::transform_data_frame_crs(data.frame(x = c(extrap.box['xmn'], extrap.box['xmx']), + y = c(extrap.box['ymn'], extrap.box['ymx'])), + out.crs = interpolation.crs) + + n_dim <- floor(abs(plot.boundary$x[1] - plot.boundary$x[2]))/cell.resolution + interp_raster <- terra::rast(xmin = plot.boundary$x[1], + xmax = plot.boundary$x[2], + ymin = plot.boundary$y[1], + ymax = plot.boundary$y[2], + nrow = n_dim[1], + ncol = n_dim[2], + crs = interpolation.crs) + + } + } else { # Default interpolation bounds for sf object regions + bbox <- sf::st_bbox(st_buffer(region_mask, cell.resolution*5)) + bbox <- round(bbox/cell_resolution)*cell_resolution # round to align rows/cols + xshift <- ((-1625000/cell_resolution) - floor(-1625000/cell_resolution))*cell_resolution + yshift <- ((379500/cell_resolution) - floor(379500/cell_resolution))*cell_resolution + bbox <- bbox + c(xshift,yshift,xshift,yshift) } - } else { + } else { # User-customized interpolation bounds - # Section to maintain backwards compatibility for data2raster interp_raster <- terra::rast(xmin = bbox["xmin"], xmax = bbox["xmax"], ymin = bbox["ymin"], @@ -136,7 +140,6 @@ interpolate_variable <- function(dat, nrows = floor((bbox["ymax"]-bbox["ymin"])/cell.resolution), ncols = floor((bbox["xmax"]-bbox["xmin"])/cell.resolution), crs = interpolation.crs) - } @@ -151,6 +154,10 @@ interpolate_variable <- function(dat, sf::st_transform(crs = interpolation.crs) + #--------------------- + # Interpolate + #--------------------- + for(ii in 1:length(methods)) { if(tolower(methods[ii]) == "nn") { @@ -226,14 +233,38 @@ interpolate_variable <- function(dat, return(fit) } - # Write masked raster + #--------------------- + # Write raster to file + #--------------------- + + # Output folder and filename setup + + if (is.character(select.region)) { + maskname <- select.region + } else if (is.data.frame(select.region)) { + maskname <- ifelse(is.na(pre), "var", pre) + } + + if (is.null(outputfilefun)) { + # Default location: [coldpool]/output/raster/[mask]/[var]/[mask]_[method]_[year]_[var].tif + outfolder <- here::here("output", "raster", maskname, var.col) + outfile <- paste0(maskname, "_", tolower(methods[ii]), "_", dat.year, "_", var.col, ".tif" ) + } else { + # User-customized location + outfile <- outputfilefun(maskname, dat.year, methods[ii], var.col) + outfolder <- dirname(outfile) + outfile <- basename(outfile) + } + + if (!dir.exists(outfolder)) { + dir.create(outfolder, recursive = TRUE) + } + + # Rasterize and save to geotiff + akgfmaps::rasterize_and_mask(sgrid = fit, amask = region_mask) |> - coldpool::make_raster_file(filename = here::here("output", - "raster", - fpath_subdir, - var.col, - paste0(fpath_subdir, "_", tolower(methods[ii]), "_", dat.year, "_", var.col, ".tif" )), + coldpool::make_raster_file(filename = file.path(outfolder, outfile), format = "GTiff", overwrite = TRUE, layer_name = dat.year) diff --git a/analysis/cold_pool_for_regional_models.Rmd b/analysis/cold_pool_for_regional_models.Rmd new file mode 100644 index 0000000..f9a71a3 --- /dev/null +++ b/analysis/cold_pool_for_regional_models.Rmd @@ -0,0 +1,307 @@ +--- +title: "Bottom temperature and Cold Pool Index for regional models" +author: +- affiliation: NOAA Alaska Fisheries Science Center + description: + email: Kelly.Kearney@noaa.gov + name: Kelly Kearney +output: html_document +fontsize: 12pt +addr: + l1: 7600 Sand Point Way NE + l2: NMFS RACE Division, Groundfish Assessment Program + l3: Seattle, WA 98115 +--- + +# 1.0 Introduction + +This document provides code and documentation related to regional model-based versions of the cold pool indices and interpolated bottom temperature rasters. These model-based indices are used in a variety of research and management contexts to complement the survey-based indices. This example demonstrates how to use the utilities in this package to develop these products. + +This script was developed with the ROMS Bering10K hindcast in mind; see Kearney et al. (2020) for more information regarding this specific model. However, the code presented here is model agnostic, and can in theory be applied to any output that covers the eastern Bering Sea shelf region. + +The calculation of these survey-based cold pool metrics is part of the annual hindcast update procedure that is run every summer. +This script is dependent on two files that are generated as part of that update process prior to running: + +- survey-replicated data file, .csv format. See "Survey-replicated data", below. +- cold pool indices file, .nc format. See "Annual indices", below. + +For details regarding how those files are built, see the [beringnpz/Bering10KPostprocessing](https://github.com/beringnpz/Bering10KPostprocessing) repo (specifically, the surveyreplicatedbtemp.m, buildcoldpoolncfile.m, and addjul1btemp.m functions). The current coldpool-based code should be run after that process is complete in order to add survey and survey-replicated indices to the cold pool indices file. + +While this script is written similar to a vignette, we note that the current paths to these files will not be accessible to most users. Therefore, the script is meant to be used by the regional modeling team to run periodic updates, and otherwise is provided here as documentation only. + +## 1.1 Setup + +The following packages are used in this script, and should be loaded prior to running: + +- coldpool +- akfgmaps +- lubridate +- ncdf4 + +We start with some general setup that loads these packages and sets output figure resolution and the map projection to use for our calculations. + +```{r setup_nodevel, eval=FALSE} +library(coldpool) +``` +```{r setup_devel, include=FALSE} +develflag = TRUE +if (develflag) { + devtools::load_all() +} else { + library(coldpool) +} +``` +```{r setup, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE} +library(akgfmaps) +library(lubridate) +library(ncdf4) + +# Global options, mirroring primary 0_update_cold_pool_index.Rmd +fig_res <- 600 +proj_crs <- coldpool:::ebs_proj_crs +``` + + +All model-related files (both input and output) will be placed in a folder outside this repo, alongside other model output. Here, we choose the Level 3 folder associated with the operational hindcast simulation of the Bering10K ROMS model (see the [Bering10K Dataset Docs](https://beringnpz.github.io/roms-bering-sea/B10K-dataset-docs/) for more information). These details should be modified as needed for other users, computing locations, and/or models. + +```{r sim_paths, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE} + +# Path to local mount of UW HPC folder mox.hyak.uw.edu:/gscratch/bumblereem/ +moxdir = "~/Documents/mox_bumblereem" # Note: computer-specific, change as needed + +# Specific simulation +simname = "B10K-K20_CORECFS" # weekly-archived "operational" hindcast +#simname = "B10K-K20nobio_CORECFS_daily" # daily-archived variant + +# Model's Level 3 folder path +lev3path <- file.path(moxdir, "roms_for_public", simname, "Level3") +``` + +## 1.2 Survey-replicated data + +We can perform a rough comparison of model output and the survey data by simply extracting the model bottom temperature field on a specific date of each year, for example, a mid-summer date like July 1. However, the groundfish survey temperature data are collected over a 2-3 month period rather than on a single day, so the rasters built by this package likewise reflect that non-synoptic nature, with regions in the southeast section typically more representative of early summer and the northern regions reflecting late summer conditions. To provide a true one:one comparison between model and observations, we need to build model-derived rasters that reflect a similar pattern. + +The first step of this process is to build a survey-replicated dataset. We do this by sampling the model at the closest grid cell and archived output time step to each point in the survey dataset. The data is saved in a file that is identical to the [colpool]/data/index_hauls_temperature_data.csv file but with an additional column added for model bottom temperature. The file is generated external to this script. + +```{r csv_path, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE} +srepcsv_path <- file.path(lev3path, sprintf("survey_replicates_%s.csv", simname)) +``` +## 1.3 Masks + +The primary cold pool index calculations from this package rely on predefined masks from the akgfmaps R package. For our model-based indices, we use the southeastern Bering Sea (SEBS) mask from that package as well as a few variants: +- The SEBS region without the northwest strata (i.e. strata 82 and 90). These strata were added to the sampling a few years later than most of the EBS, and for some projects we ignore that region to maintain data continuity. +- The SEBS region (with and without the NW parts) with the shelf break set based on model bathymetry rather than real-world bathymetry. The shelf slope in the Bering10K ROMS model is smoothed in order to avoid numerical errors in the horizontal pressure gradient that are characteristic of sigma-coordinate models like ROMS in areas of steep topography. This results in the model having a slightly narrower shelf than the real world. This variant allows for better comparison between model and observations because it eliminates the area where we expect the two to disagree due to mismatched bottom depth. + +```{r masks, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE} +# The default SEBS mask + +akSEBS <- akgfmaps::get_base_layers(select.region = "bs.south", set.crs = proj_crs) + +# Read polygon defining the Bering10K shelf (i.e. <200m depth) + +modelshelfpolyfile = here::here("inst", "extdata", "B10K_lte200m_polygon.shp") + +b10k_lte_200 <- sf::st_read(modelshelfpolyfile, quiet = TRUE) |> + st_set_crs("+proj=longlat") |> + sf::st_transform(crs = sf::st_crs(proj_crs)) + +# SEBS without northwest strata (i.e. strata ID <= 62) + +akSEBSnonw <- akSEBS$survey.strata |> + dplyr::filter(Stratum <= 62) |> + dplyr::group_by(SURVEY) |> + dplyr::summarise() + +# Build 4 masks: SEBS w/o northwest, +# SEBS +# SEBS w/o northwest and trimmed to model shelf +# SEBS trimmed to model shelf +# (Note: order reflects that in the cold pool index .nc file) + +mymask <- list(akSEBSnonw, + "sebs", + st_intersection(akSEBSnonw, b10k_lte_200), + st_intersection(akSEBS$survey.area, b10k_lte_200)) + +maskname <- c("SEBS-noNW", "SEBS", "SEBS-noNW-modelshelf", "SEBS-modelshelf") + +``` + +## 1.4 Cold pool index netCDF file + +Model-based indices will be saved to a prexisting netCDF file. + +```{r netcdfpath, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE} +cpoolnc_path <- file.path(lev3path, sprintf("%s_coldpool.nc", simname)) +``` + +# 2.0 Generate bottom temperature rasters + +With our datasets in place, we first generate raster images with bottom temperature (from both survey and survey-replicated model) interpolated to 5-km grid. These rasters will form the base for many of the following calculations. Note that the survey-derived rasters should be identical to those created as part of the annual coldpool updates. + +```{r raster_calculations, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE, results='hide'} + +# Years: Start with all years in file + +temperature_df <- read.csv(file = srepcsv_path, + stringsAsFactors = FALSE) + +names(temperature_df) <- tolower(names(temperature_df)) +year_vec <- sort(unique(temperature_df$year)) + +# Interpolation parameters + +cell_resolution = 5000 # m, i.e. 5km +methods = "Ste" # Stein's Matern kriging + +# Create one interpolated raster per year per mask per data source + +outpath = file.path(moxdir, "roms_for_public", simname, "Level3", "coldpool_tifs"); + +for (ii in 1:length(year_vec)) { + + for (im in 1:length(mymask)) { + + # File-naming convention + + outfilefun = function(mask, year, method, variable) + file.path(outpath, + sprintf("%s_%s_%s_%d.tif", maskname[im], method, variable, year)) + + print(sprintf("Creating rasters: mask #%d, %d", im, year_vec[ii])) + + # Create rasters + + if (!file.exists(outfilefun(NULL, year_vec[ii], methods, "gear_temperature"))) { + + # ... for survey data + + interpolate_variable(dat = dplyr::filter(temperature_df, year == year_vec[ii]), + dat.year = year_vec[ii], + in.crs = "EPSG:4326", + interpolation.crs = proj_crs, + cell.resolution = cell_resolution, + lon.col = "longitude", + lat.col = "latitude", + var.col = "gear_temperature", + nm = Inf, + outputfilefun = outfilefun, + select.region = mymask[[im]], + methods = methods) + } + + if (!file.exists(outfilefun(NULL, year_vec[ii], methods, "model_bottom_temp"))) { + + # ... for model data + + interpolate_variable(dat = dplyr::filter(temperature_df, year == year_vec[ii]), + dat.year = year_vec[ii], + in.crs = "EPSG:4326", + interpolation.crs = proj_crs, + cell.resolution = cell_resolution, + lon.col = "longitude", + lat.col = "latitude", + var.col = "model_bottom_temp", + nm = Inf, + outputfilefun = outfilefun, + select.region = mymask[[im]], + methods = methods) + } + } +} + +``` +# 3.0 Annual indices + +Metrics of mean bottom temperature and cold pool fractions are calculated for both the survey-based and model-based rasters. The resulting survey-based time series using the SEBS mask should be identical to the one included within the coldpool package. + +```{r annualindices, include=TRUE, message=FALSE, warning=FALSE, echo = TRUE} + +# Open netCDF file + +nc <- nc_open(cpoolnc_path, write=TRUE) + +# Read relevant coordinate data + +thresh <- ncvar_get(nc, "threshold") +tdaysince <- ncvar_get(nc, "time") +tyear <- year(tdaysince + as.Date("1900-01-01")) + +# Read existing data + +btfile <- ncvar_get(nc, "average_bottom_temp", start=c(1,2,1)) +cpfile <- ncvar_get(nc, "cold_pool_index", start=c(1,1,2,1)) + +# Loop over new rasters and add data to file as needed + +for (ii in 1:length(year_vec)) { + + yidx = which(year_vec[ii] == tyear) + + filemissingdata <- all(is.na(btfile[,,yidx])) | all(is.na(cpfile[,,,yidx])) + + if (!filemissingdata & (any(is.na(btfile[,,yidx])) | any(is.na(cpfile[,,,yidx])))) { + warning(sprintf("Data for year %d includes some missing; check this!", year_vec[ii])) + } + + if (filemissingdata) { + + for (im in 1:length(mymask)) { + + print(sprintf("Saving data to file: %d mask %d:", year_vec[ii], im)); + + # Raster files for this year/mask + + svyraster = terra::rast(file.path(outpath, + sprintf("%s_%s_%s_%d.tif", + maskname[im], + "Ste", + "gear_temperature", + year_vec[ii]))) + modraster = terra::rast(file.path(outpath, + sprintf("%s_%s_%s_%d.tif", + maskname[im], + "Ste", + "model_bottom_temp", + year_vec[ii]))) + + # Calculate average bottom temperature and save to file + + btsvy <- mean(terra::values(svyraster), na.rm = TRUE) + btmod <- mean(terra::values(modraster), na.rm = TRUE) + + ncvar_put(nc, "average_bottom_temp", btsvy, start=c(im,2,yidx), count=c(1,1,1)) + ncvar_put(nc, "average_bottom_temp", btmod, start=c(im,3,yidx), count=c(1,1,1)) + + # Calculate and save cold pool indices + + totarea <- svyraster |> + cpa_from_raster(raster_units = "m", temperature_threshold = 100) + + for (ith in 1:length(thresh)) { + + cpsvy <- (svyraster |> + cpa_from_raster(raster_units = "m", temperature_threshold = thresh[ith]))/totarea + cpmod <- (modraster |> + cpa_from_raster(raster_units = "m", temperature_threshold = thresh[ith]))/totarea + + ncvar_put(nc, "cold_pool_index", cpsvy, start=c(ith,im,2,yidx), count=c(1,1,1,1)) + ncvar_put(nc, "cold_pool_index", cpmod, start=c(ith,im,3,yidx), count=c(1,1,1,1)) + + } + } + + # Update history attribute + + hisold = ncatt_get(nc, 0, "history") + hisnew = paste(format(Sys.time(), "%a %b %d %H:%M:%S %Y:"), + "Survey and survey-replicated data added for year", + year_vec[ii]) + ncatt_put(nc, 0, "history", paste(hisnew, hisold$value, sep="\n")) + + } +} +``` + + + diff --git a/analysis/cold_pool_for_regional_models.html b/analysis/cold_pool_for_regional_models.html new file mode 100644 index 0000000..ca5ed49 --- /dev/null +++ b/analysis/cold_pool_for_regional_models.html @@ -0,0 +1,724 @@ + + + + + + + + + + + + + +Bottom temperature and Cold Pool Index for regional models + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+

1.0 Introduction

+

This document provides code and documentation related to regional +model-based versions of the cold pool indices and interpolated bottom +temperature rasters. These model-based indices are used in a variety of +research and management contexts to complement the survey-based indices. +This example demonstrates how to use the utilities in this package to +develop these products.

+

This script was developed with the ROMS Bering10K hindcast in mind; +see Kearney et al. (2020) for more information regarding this specific +model. However, the code presented here is model agnostic, and can in +theory be applied to any output that covers the eastern Bering Sea shelf +region.

+

The calculation of these survey-based cold pool metrics is part of +the annual hindcast update procedure that is run every summer. This +script is dependent on two files that are generated as part of that +update process prior to running:

+ +

For details regarding how those files are built, see the beringnpz/Bering10KPostprocessing +repo (specifically, the surveyreplicatedbtemp.m, buildcoldpoolncfile.m, +and addjul1btemp.m functions). The current coldpool-based code should be +run after that process is complete in order to add survey and +survey-replicated indices to the cold pool indices file.

+

While this script is written similar to a vignette, we note that the +current paths to these files will not be accessible to most users. +Therefore, the script is meant to be used by the regional modeling team +to run periodic updates, and otherwise is provided here as documentation +only.

+
+

1.1 Setup

+

The following packages are used in this script, and should be loaded +prior to running:

+
    +
  • coldpool
  • +
  • akfgmaps
  • +
  • lubridate
  • +
  • ncdf4
  • +
+

We start with some general setup that loads these packages and sets +output figure resolution and the map projection to use for our +calculations.

+
library(coldpool)
+
library(akgfmaps)
+library(lubridate)
+library(ncdf4)
+
+# Global options, mirroring primary 0_update_cold_pool_index.Rmd
+fig_res <- 600
+proj_crs <- coldpool:::ebs_proj_crs
+

All model-related files (both input and output) will be placed in a +folder outside this repo, alongside other model output. Here, we choose +the Level 3 folder associated with the operational hindcast simulation +of the Bering10K ROMS model (see the Bering10K +Dataset Docs for more information). These details should be modified +as needed for other users, computing locations, and/or models.

+
# Path to local mount of UW HPC folder mox.hyak.uw.edu:/gscratch/bumblereem/
+moxdir = "~/Documents/mox_bumblereem" # Note: computer-specific, change as needed
+
+# Specific simulation
+simname = "B10K-K20_CORECFS" # weekly-archived "operational" hindcast
+#simname = "B10K-K20nobio_CORECFS_daily" # daily-archived variant
+
+# Model's Level 3 folder path
+lev3path <- file.path(moxdir, "roms_for_public", simname, "Level3")
+
+
+

1.2 Survey-replicated data

+

We can perform a rough comparison of model output and the survey data +by simply extracting the model bottom temperature field on a specific +date of each year, for example, a mid-summer date like July 1. However, +the groundfish survey temperature data are collected over a 2-3 month +period rather than on a single day, so the rasters built by this package +likewise reflect that non-synoptic nature, with regions in the southeast +section typically more representative of early summer and the northern +regions reflecting late summer conditions. To provide a true one:one +comparison between model and observations, we need to build +model-derived rasters that reflect a similar pattern.

+

The first step of this process is to build a survey-replicated +dataset. We do this by sampling the model at the closest grid cell and +archived output time step to each point in the survey dataset. The data +is saved in a file that is identical to the +[colpool]/data/index_hauls_temperature_data.csv file but with an +additional column added for model bottom temperature. The file is +generated external to this script.

+
srepcsv_path <- file.path(lev3path, sprintf("survey_replicates_%s.csv", simname))
+
+
+

1.3 Masks

+

The primary cold pool index calculations from this package rely on +predefined masks from the akgfmaps R package. For our model-based +indices, we use the southeastern Bering Sea (SEBS) mask from that +package as well as a few variants: - The SEBS region without the +northwest strata (i.e. strata 82 and 90). These strata were added to the +sampling a few years later than most of the EBS, and for some projects +we ignore that region to maintain data continuity. - The SEBS region +(with and without the NW parts) with the shelf break set based on model +bathymetry rather than real-world bathymetry. The shelf slope in the +Bering10K ROMS model is smoothed in order to avoid numerical errors in +the horizontal pressure gradient that are characteristic of +sigma-coordinate models like ROMS in areas of steep topography. This +results in the model having a slightly narrower shelf than the real +world. This variant allows for better comparison between model and +observations because it eliminates the area where we expect the two to +disagree due to mismatched bottom depth.

+
# The default SEBS mask
+
+akSEBS <- akgfmaps::get_base_layers(select.region = "bs.south", set.crs = proj_crs)
+
+# Read polygon defining the Bering10K shelf (i.e. <200m depth)
+
+modelshelfpolyfile = here::here("inst", "extdata", "B10K_lte200m_polygon.shp")
+
+b10k_lte_200 <- sf::st_read(modelshelfpolyfile, quiet = TRUE) |>
+  st_set_crs("+proj=longlat") |>
+  sf::st_transform(crs = sf::st_crs(proj_crs)) 
+
+# SEBS without northwest strata (i.e. strata ID <= 62)
+
+akSEBSnonw <- akSEBS$survey.strata |>
+  dplyr::filter(Stratum <= 62) |>
+  dplyr::group_by(SURVEY) |>
+  dplyr::summarise()
+
+# Build 4 masks: SEBS w/o northwest, 
+#                SEBS
+#                SEBS w/o northwest and trimmed to model shelf
+#                SEBS trimmed to model shelf
+# (Note: order reflects that in the cold pool index .nc file)
+
+mymask <- list(akSEBSnonw, 
+               "sebs",
+               st_intersection(akSEBSnonw, b10k_lte_200), 
+               st_intersection(akSEBS$survey.area, b10k_lte_200))
+
+maskname <- c("SEBS-noNW", "SEBS", "SEBS-noNW-modelshelf", "SEBS-modelshelf")
+
+
+

1.4 Cold pool index netCDF file

+

Model-based indices will be saved to a prexisting netCDF file.

+
cpoolnc_path <- file.path(lev3path, sprintf("%s_coldpool.nc", simname))
+
+
+
+

2.0 Generate bottom temperature rasters

+

With our datasets in place, we first generate raster images with +bottom temperature (from both survey and survey-replicated model) +interpolated to 5-km grid. These rasters will form the base for many of +the following calculations. Note that the survey-derived rasters should +be identical to those created as part of the annual coldpool +updates.

+
# Years: Start with all years in file
+
+temperature_df <- read.csv(file = srepcsv_path,
+                           stringsAsFactors = FALSE)
+    
+names(temperature_df) <- tolower(names(temperature_df))
+year_vec <- sort(unique(temperature_df$year))
+
+# Interpolation parameters
+
+cell_resolution = 5000 # m, i.e. 5km
+methods = "Ste" # Stein's Matern kriging
+
+# Create one interpolated raster per year per mask per data source
+
+outpath = file.path(moxdir, "roms_for_public", simname, "Level3", "coldpool_tifs");
+
+for (ii in 1:length(year_vec)) {
+
+  for (im in 1:length(mymask)) {
+  
+    # File-naming convention
+    
+    outfilefun = function(mask, year, method, variable) 
+      file.path(outpath, 
+                sprintf("%s_%s_%s_%d.tif", maskname[im], method, variable, year))  
+
+    print(sprintf("Creating rasters: mask #%d, %d", im, year_vec[ii]))
+    
+    # Create rasters
+    
+    if (!file.exists(outfilefun(NULL, year_vec[ii], methods, "gear_temperature"))) {
+
+      # ... for survey data
+    
+      interpolate_variable(dat = dplyr::filter(temperature_df, year == year_vec[ii]),
+                                   dat.year = year_vec[ii],
+                                   in.crs = "EPSG:4326",
+                                   interpolation.crs = proj_crs,
+                                   cell.resolution = cell_resolution,
+                                   lon.col = "longitude",
+                                   lat.col = "latitude",
+                                   var.col = "gear_temperature",
+                                   nm = Inf,
+                                   outputfilefun = outfilefun,
+                                   select.region = mymask[[im]],
+                                   methods = methods)
+    }
+    
+    if (!file.exists(outfilefun(NULL, year_vec[ii], methods, "model_bottom_temp"))) {
+    
+      # ... for model data
+      
+      interpolate_variable(dat = dplyr::filter(temperature_df, year == year_vec[ii]),
+                                   dat.year = year_vec[ii],
+                                   in.crs = "EPSG:4326",
+                                   interpolation.crs = proj_crs,
+                                   cell.resolution = cell_resolution,
+                                   lon.col = "longitude",
+                                   lat.col = "latitude",
+                                   var.col = "model_bottom_temp",
+                                   nm = Inf,
+                                   outputfilefun = outfilefun,
+                                   select.region = mymask[[im]],
+                                   methods = methods)
+    }
+  }
+}
+
+
+

3.0 Annual indices

+

Metrics of mean bottom temperature and cold pool fractions are +calculated for both the survey-based and model-based rasters. The +resulting survey-based time series using the SEBS mask should be +identical to the one included within the coldpool package.

+
# Open netCDF file
+
+nc <- nc_open(cpoolnc_path)
+
+# Read relevant coordinate data
+
+thresh    <- ncvar_get(nc, "threshold")
+tdaysince <- ncvar_get(nc, "time")
+tyear <- year(tdaysince + as.Date("1900-01-01"))
+
+# Read existing data
+
+btfile <- ncvar_get(nc, "average_bottom_temp", start=c(1,2,1))
+cpfile <- ncvar_get(nc, "cold_pool_index", start=c(1,1,2,1))
+
+# Loop over new rasters and add data to file as needed
+
+for (ii in 1:length(year_vec)) {
+  
+  yidx = which(year_vec[ii] == tyear)
+  
+  filemissingdata <- all(is.na(btfile[,,yidx])) | all(is.na(cpfile[,,,yidx]))
+  
+  if (!filemissingdata & (any(is.na(btfile[,,yidx])) | any(is.na(cpfile[,,,yidx])))) {
+    warning(sprintf("Data for year %d includes some missing; check this!", year_vec[ii]))
+  }
+  
+  if (filemissingdata) {
+  
+    for (im in 1:length(mymask)) {
+  
+      print(sprintf("Saving data to file: %d mask %d:", year_vec[ii], im));
+    
+      # Raster files for this year/mask
+    
+      svyraster = terra::rast(file.path(outpath, 
+                                      sprintf("%s_%s_%s_%d.tif", 
+                                              maskname[im], 
+                                              "Ste", 
+                                              "gear_temperature",  
+                                              year_vec[ii])))  
+      modraster = terra::rast(file.path(outpath, 
+                                      sprintf("%s_%s_%s_%d.tif", 
+                                              maskname[im], 
+                                              "Ste", 
+                                              "model_bottom_temp", 
+                                              year_vec[ii]))) 
+    
+      # Calculate average bottom temperature and save to file
+    
+      btsvy <- mean(terra::values(svyraster), na.rm = TRUE)
+      btmod <- mean(terra::values(modraster), na.rm = TRUE)
+      
+      ncvar_put(nc, "average_bottom_temp", btsvy, start=c(im,2,yidx), count=c(1,1,1))
+      ncvar_put(nc, "average_bottom_temp", btmod, start=c(im,3,yidx), count=c(1,1,1))
+    
+      # Calculate and save cold pool indices
+      
+      totarea <- svyraster |>
+        cpa_from_raster(raster_units = "m", temperature_threshold = 100)
+      
+      for (ith in 1:length(thresh)) {
+        
+        cpsvy <- (svyraster |> 
+                    cpa_from_raster(raster_units = "m", temperature_threshold = thresh[ith]))/totarea
+        cpmod <- (modraster |> 
+                    cpa_from_raster(raster_units = "m", temperature_threshold = thresh[ith]))/totarea
+        
+        ncvar_put(nc, "cold_pool_index", cpsvy, start=c(ith,im,2,yidx), count=c(1,1,1,1))
+        ncvar_put(nc, "cold_pool_index", cpmod, start=c(ith,im,3,yidx), count=c(1,1,1,1))
+        
+      }
+    }
+    
+    # Update history attribute
+    
+    hisold = ncatt_get(nc, 0, "history")
+    hisnew = paste(format(Sys.time(), "%a %b %d %H:%M:%S %Y:"),
+                   "Survey and survey-replicated data added for year", 
+                   year_vec[ii])
+    ncattput(nc, 0, "history", paste(hisnew, hisold$value, sep="\n"))
+    
+  }
+}
+
+ + + + +
+ + + + + + + + + + + + + + + diff --git a/analysis/roms_level3_coldpool_part2.R b/analysis/roms_level3_coldpool_part2.R deleted file mode 100644 index 912e773..0000000 --- a/analysis/roms_level3_coldpool_part2.R +++ /dev/null @@ -1,136 +0,0 @@ -# roms_level3_coldpool_part2.R -# -# This script is part of the post-processing suite for the Bering10K ROMS -# hindcast simulation (see https://github.com/beringnpz/Bering10KPostprocessing). -# It calculates the survey-based and survey-replicated cold pool indices and -# adds those to the existing cold pool index file (created by -# roms_level3_coldpool_part1.m). -# -# Note: Because it interacts with some remotely-stored netCDF files (located on the -# UW mox-hyak HPC system and accessed via a very computer-specific path to a -# remote mount), this example will not run out of the box. - -library(coldpool) -library(ncdf4) -library(lubridate) - -#---------------- -# User setup -#---------------- - -# Cold pool index file: on UW mox-hyak at -# /gscratch/bumblereem/roms_for_public/B10K-K20_CORECFS/Level3/B10K-K20_CORECFS_coldpool.nc -# Path below is the remote mount path via K. Kearney's computer. -# I opt not to include a copy of this file in this repository to avoid problems -# associated with regularly-updated binary files in git repositories. - -moxdir = "~/Documents/mox_bumblereem/" # Path to mox-hyak /gscratch/bumblereem remote mount -hindcastcpoolfile <- file.path(moxdir, "roms_for_public", "B10K-K20_CORECFS", "Level3", "B10K-K20_CORECFS_coldpool.nc") - -# Survey-replicates data file. File format is identical to data/index_hauls_temperature_data.csv -# but with an extra column added for the model-based bottom temp values. This -# file is a copy of -#/gscratch/bumblereem/roms_for_public/B10K-K20_CORECFS/Level3/survey_replicates_B10K-K20_CORECFS.csv. - -srepdatafile <- here::here("data", "survey_replicates_B10K-K20_CORECFS.csv") - -# This shapefile indicates the portion of the Bering10K ROMS model shelf that is -# less than 200m (model shelf is narrower than real-world shelf due to -# numerically-necessary bathymetric smoothing). - -modelshelfpolyfile = here::here("data", "B10K_lte200m_polygon.shp") - -#---------------- -# Cold pool index -# calculation for -# model and obs -#---------------- - -ncin <- nc_open(hindcastcpoolfile) - -# Read existing data and parse years we need to calculate - -time <- ncvar_get(ncin,"time") -tdate <- lubridate::ymd("1900-01-01") + lubridate::days(time) - -meanbtmp <- ncvar_get(ncin,"average_bottom_temp") -cpindex <- ncvar_get(ncin, "cold_pool_index") -thresh <- ncvar_get(ncin, "threshold") - -nc_close(ncin) - -ismiss <- is.na(meanbtmp[1,2,]) | meanbtmp[1,2,] > 1e36 # Don't know how to query fill value in R... - -select_years <- year(tdate[ismiss]) -select_years <- select_years[select_years >= 1982 & select_years != 2020] -# select_years <- c(2005, 2017, 2018, 2019) - -# User-set variables - -proj_crs <- coldpool:::ebs_proj_crs -cell_resolution <- 5000 - -# Masks: w/ and w/o NW (82+90), w/ and w/o narrowed-to-model-200m-isobath - -akSEBS <- akgfmaps::get_base_layers(select.region = "bs.south", set.crs = proj_crs) - -b10_lte_200 <- sf::st_read(modelshelfpolyfile, quiet = TRUE) |> - st_set_crs("+proj=longlat") |> - sf::st_transform(crs = sf::st_crs(proj_crs)) - -akSEBSnonw <- akSEBS$survey.strata |> - dplyr::filter(Stratum <= 62) |> - dplyr::group_by(SURVEY) |> - dplyr::summarise() - -mymask <- list(akSEBSnonw, - akSEBS$survey.area, - st_intersection(akSEBSnonw, b10_lte_200), - st_intersection(akSEBS$survey.area, b10_lte_200)) - -# Calculate indices using survey and survey-replicated data - -cpname <- gsub("-", "m", sprintf("AREA_LTE_%.1f", thresh)) - -yrmask <- year(tdate) %in% select_years -for (ii in 1:length(mymask)) { - - totarea <- as.numeric(st_area(mymask[[ii]]))/1e6 # m^2 to km^2 - - svy <- calcindices_temp(srepdatafile, mymask[[ii]], - select_years = select_years, - bottomvar="gear_temperature", - surfacevar=NULL, - threshold=thresh) - - srep <- calcindices_temp(srepdatafile, mymask[[ii]], - select_years = select_years, - bottomvar="model_bottom_temp", - surfacevar=NULL, - threshold=thresh) - - meanbtmp[ii,2,yrmask] <- svy$MEAN_GEAR_TEMPERATURE - meanbtmp[ii,3,yrmask] <- srep$MEAN_GEAR_TEMPERATURE - - cpindex[,ii,2,yrmask] <- aperm(data.matrix(svy[,cpname]), c(2,1))/totarea - cpindex[,ii,3,yrmask] <- aperm(data.matrix(srep[,cpname]), c(2,1))/totarea -} - -# Write to file - -ncout <- nc_open(hindcastcpoolfile, write=TRUE) - -ncvar_put(ncout, "average_bottom_temp", meanbtmp) -ncvar_put(ncout, "cold_pool_index", cpindex) - -hisstr <- ncatt_get(ncout, 0, "history") -if (length(select_years) == 1) { - newhis <- paste0(date(), ": Survey and survey-replicated data added for year ", select_years) -} else { - newhis <- paste0(date(), ": Survey and survey-replicated data added for year ", min(select_years), "-", max(select_years)) -} -hisstr <- paste(newhis, hisstr$value, sep="\n") - -ncatt_put(ncout, 0, "history", hisstr) - -nc_close(ncout) diff --git a/data/B10K_lte200m_polygon.dbf b/inst/extdata/B10K_lte200m_polygon.dbf similarity index 100% rename from data/B10K_lte200m_polygon.dbf rename to inst/extdata/B10K_lte200m_polygon.dbf diff --git a/data/B10K_lte200m_polygon.shp b/inst/extdata/B10K_lte200m_polygon.shp similarity index 100% rename from data/B10K_lte200m_polygon.shp rename to inst/extdata/B10K_lte200m_polygon.shp diff --git a/data/B10K_lte200m_polygon.shx b/inst/extdata/B10K_lte200m_polygon.shx similarity index 100% rename from data/B10K_lte200m_polygon.shx rename to inst/extdata/B10K_lte200m_polygon.shx diff --git a/man/interpolate_variable.Rd b/man/interpolate_variable.Rd index 5338bdc..758a789 100644 --- a/man/interpolate_variable.Rd +++ b/man/interpolate_variable.Rd @@ -20,7 +20,8 @@ interpolate_variable( methods = c("NN", "IDW", "IDW4", "Exp", "Sph", "Bes", "Gau", "Cir", "Mat", "Ste", "Tps"), bbox = NULL, - return_raster = FALSE + return_raster = FALSE, + outputfilefun = NULL ) } \arguments{ @@ -53,6 +54,8 @@ interpolate_variable( specific bounds, or a 4-element named vector with values xmin, xmax, ymin, and ymax.} +\item{outputfilefun}{Function of the form f(mask, year, method, var) that returns a string holding the full path name where the output raster should be stored. If not included, default is [coldpool_dir]/output/raster/[mask]/[var]/[mask]_[method]_[year]_[var].tif} + \item{return.raster}{Should a raster be returned?} } \value{