diff --git a/.Rbuildignore b/.Rbuildignore index 4ea4a75..b427041 100755 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,3 +6,6 @@ ^data-raw$ ^\.github$ ^codecov\.yml$ +^_pkgdown\.yml$ +^docs$ +^pkgdown$ diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..bfc9f4d --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,49 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + release: + types: [published] + workflow_dispatch: + +name: pkgdown.yaml + +permissions: read-all + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index fefc52e..e050312 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -4,9 +4,8 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] -name: test-coverage +name: test-coverage.yaml permissions: read-all @@ -40,7 +39,8 @@ jobs: - uses: codecov/codecov-action@v4 with: - fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} file: ./cobertura.xml plugin: noop disable_search: true diff --git a/.gitignore b/.gitignore index 811830b..081f1a6 100755 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.DS_Store /data-raw/geodata/ *.tif +docs diff --git a/DESCRIPTION b/DESCRIPTION index 00f9b91..98836c9 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,7 +9,7 @@ Authors@R: c( ) Description: What the package does (one paragraph). License: MIT + file LICENSE -URL: https://github.com/idem-lab/sdmtools +URL: https://github.com/idem-lab/sdmtools, https://idem-lab.github.io/sdmtools/ BugReports: https://github.com/idem-lab/sdmtools/issues Depends: R (>= 4.1.0) @@ -30,7 +30,7 @@ Imports: Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Suggests: testthat (>= 3.0.0) Remotes: diff --git a/NAMESPACE b/NAMESPACE index 821c4d9..dd9b981 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -12,6 +12,7 @@ export(inside_mask) export(make_africa_mask) export(make_mpp_list) export(mask_all) +export(mask_from_all) export(maskpointsdf) export(match_ref) export(nearest_land) diff --git a/R/assign_nearest_land.R b/R/assign_nearest_land.R index 991a1f4..43db9eb 100644 --- a/R/assign_nearest_land.R +++ b/R/assign_nearest_land.R @@ -2,7 +2,7 @@ #' @title Assign to nearest raster cell on mask #' @description #' Adapted from seegSDM. -#' Reposition observations to a location within mask if within a specified distance. +#' Reposition point observations to a location within mask if within a specified distance. #' Useful for when coastal observations drop off jagged mask and similar #' #' @param dat_object data.frame @@ -19,15 +19,21 @@ assign_nearest_land <- function(dat_object, max_distance, verbose=TRUE) { - stopifnot(sum(colnames(dat_object) %in% c("longitude", "latitude"))==2) - message("dat_object object must contain columns 'longitude' and 'latitude'.") + if(!sum(colnames(dat_object) %in% c("longitude", "latitude"))==2) { - # ensure that data object is spatial data frame - spat_dat <- sf::st_as_sf(dat_object) + stop("Error: dat_object object must contain columns 'longitude' and 'latitude'.") + + } + + # ensure that data object is a simple features spatial data frame + spat_dat <- sf::st_as_sf(dat_object, coords = c("longitude", "latitude")) # assert that mask is SpatRaster - stopifnot(inherits(mask_object, 'SpatRaster')) - message("mask must be a SpatRaster") + if(!inherits(mask_object, 'SpatRaster')) { + + stop("Error: mask must be a SpatRaster") + + } # isolate coord info from spatial data frame data_coords <- sf::st_coordinates(spat_dat) diff --git a/R/inside_mask.R b/R/inside_mask.R index 6fa5c37..75ee5c4 100644 --- a/R/inside_mask.R +++ b/R/inside_mask.R @@ -2,9 +2,10 @@ #' @title title #' @description Checks whether longitude and latitude coincide with #' non-missing pixels of a raster. The function takes two arguments: -#' points, a dataframe containing columns named -#' longitude' and 'latitude', and mask is a raster. Returns a dataframe -#' of longitude and latitude only those rows with points falling on +#' points, a dataframe containing at a minimum columns named +#' longitude' and 'latitude' (but could include other attributes), +#' and mask is a raster. Returns a dataframe of the same dimensions as the +#' input object, containing only those rows with points falling on #' non-missing pixels. If all points fall on missing pixels, #' the function throws an error. #' diff --git a/R/mask_from_all.R b/R/mask_from_all.R new file mode 100644 index 0000000..4aa3a58 --- /dev/null +++ b/R/mask_from_all.R @@ -0,0 +1,58 @@ +#' @title Create mask from raster layers +#' @description +#' Creates a mask where a cell in any layer of `r` that is `NA` will be returned +#' as `NA`. +#' +#' @details +#' Similar in intention to `mask_all`, but (a) will work on larger rasters +#' because it only holds the values of a single layer in memory at a time, and +#' (b) returns a mask layer, rather than masking each layer in `r`. +#' **Can be very slow** +#' +#' +#' @param r `SpatRaster` with >1 layer. +#' +#' @return `SpatRaster` with values `NA` or 1. +#' @export +#' +#' @examples +#' r <- example_raster(seed = 10) +#' s <- example_raster(seed = 11) +#' +#' r[10:20] <- NA +#' +#' s[5:15] <- NA +#' +#' q <- mask_from_all(c(r,s)) +#' +#' library("terra") +#' plot(c(r,s,q)) +#' +mask_from_all <- function(r){ + + j <- terra::nlyr(r) + + if(j == 1){stop("Must have >1 layer")} + + k <- which(is.na(terra::values(r[[1]]))) + + for(i in 2:j){ + + a <- which(is.na(terra::values(r[[i]]))) + + b <- c(k, a) + + d <- duplicated(b) + + k <- b[!d] + + } + + z <- r[[1]] + + z[] <- 1 + z[k] <- NA + + z + +} diff --git a/R/split_rast.R b/R/split_rast.R index 0b27227..d7ad221 100644 --- a/R/split_rast.R +++ b/R/split_rast.R @@ -82,7 +82,7 @@ split_rast <- function( ) |> dplyr::mutate( r = purrr::pmap( - .l = list(xmin, xmax, ymin, ymax, x, write_temp), + .l = list(xmin, xmax, ymin, ymax), .f = function(xmin, xmax, ymin, ymax, x, write_temp){ xt <- terra::ext(c(xmin, xmax, ymin, ymax)) @@ -97,7 +97,9 @@ split_rast <- function( z - } + }, + x, + write_temp ) ) |> dplyr::pull(r) diff --git a/R/standardise_rast.R b/R/standardise_rast.R index 2e429ba..3f27571 100644 --- a/R/standardise_rast.R +++ b/R/standardise_rast.R @@ -3,9 +3,7 @@ #' #' **Defunct**. #' -#' DO NOT USE THIS FUNCTION -#' -#' Instead use `terra::scale` +#' DO NOT USE THIS FUNCTION; use `terra::scale` instead #' #' @param x A `SpatRaster` object. #' diff --git a/R/std_rast.R b/R/std_rast.R index ee82696..a9ded54 100644 --- a/R/std_rast.R +++ b/R/std_rast.R @@ -3,6 +3,7 @@ #' by dividing by the maximum value in each layer. *Only operates by layer* #' #' @param x `SpatRaster` to standardise +#' @param reverse `logical` if `TRUE` will subtract standardised values from 1 #' @param filename Optional `character` path and filename to write output #' @param overwrite `logical` if `TRUE` will overwrite `filename` #' @@ -14,9 +15,9 @@ #' #' #' @examples -#' example_raster() |> -#' std_rast() -std_rast <- function(x, filename = NULL, overwrite = TRUE){ +#' example_raster(seed = 3010) |> +#' std_rast(reverse = TRUE) +std_rast <- function(x, reverse = FALSE, filename = NULL, overwrite = TRUE){ vals <- terra::values(x) nvs <- apply( @@ -29,6 +30,10 @@ std_rast <- function(x, filename = NULL, overwrite = TRUE){ r <- x + if(reverse){ + nvs <- 1 - nvs + } + r[] <- nvs diff --git a/R/writereadrast.R b/R/writereadrast.R index 500a9d3..dced127 100644 --- a/R/writereadrast.R +++ b/R/writereadrast.R @@ -6,7 +6,9 @@ #' #' @param x A `terra::SpatRaster` #' @param filename A `character` file path and name to save `x` to disc. -#' @param overwrite `logical`; overwrite existing raster. +#' @param overwrite `logical`; overwrite existing raster. **NB:** by default, +#' `overwrite = TRUE`, this is the opposite of the default behaviour of +#' `terra::writeRaster` #' @param layernames `character` of length `nlyr(x)` #' #' @return A `terra::SpatRaster` object reading from disc at `filename`. @@ -16,6 +18,12 @@ #' \dontrun{ #' # create raster then assign #' r <- sdmtools::example_raster() +#' +#' # usual workflow in two slow tedious boring steps +#' terra::writeRaster(r, "LowerSpringvale.tif") +#' s <- terra::rast("LowerSpringvale.tif") +#' +#' Better workflow in one fast enjoyable lithesome step with `sdmtools` #' r <- writereadrast(r, "tootgarook.tif") #' #' # or roll into single step with pipe diff --git a/README.Rmd b/README.Rmd index b7c87d1..0db0726 100755 --- a/README.Rmd +++ b/README.Rmd @@ -25,6 +25,7 @@ knitr::opts_chunk$set( ![GitHub last commit](https://img.shields.io/github/last-commit/idem-lab/sdmtools) ![GitHub R package version](https://img.shields.io/github/r-package/v/idem-lab/sdmtools) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/idem-lab/sdmtools/latest) +[![Codecov test coverage](https://codecov.io/gh/idem-lab/sdmtools/graph/badge.svg)](https://app.codecov.io/gh/idem-lab/sdmtools) A set of helper functions to facilitate species distribution modelling. diff --git a/README.md b/README.md index 72086a2..d0199a7 100755 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ commit](https://img.shields.io/github/last-commit/idem-lab/sdmtools) version](https://img.shields.io/github/r-package/v/idem-lab/sdmtools) ![GitHub commits since latest release](https://img.shields.io/github/commits-since/idem-lab/sdmtools/latest) +[![Codecov test +coverage](https://codecov.io/gh/idem-lab/sdmtools/graph/badge.svg)](https://app.codecov.io/gh/idem-lab/sdmtools) A set of helper functions to facilitate species distribution modelling. @@ -258,10 +260,7 @@ functions, rather than storing it. ``` r library(terra) -#> terra 1.7.79 -``` - -``` r +#> terra 1.8.5 r <- example_raster() r #> class : SpatRaster @@ -273,9 +272,6 @@ r #> name : example #> min value : 0.0627102 #> max value : 7.3352526 -``` - -``` r plot(r) ``` @@ -292,9 +288,6 @@ v #> dimensions : 10, 0 (geometries, attributes) #> extent : 0.2293562, 8.00672, 1.375653, 8.951683 (xmin, xmax, ymin, ymax) #> coord. ref. : -``` - -``` r plot(v) ``` @@ -310,13 +303,12 @@ africa_mask <- make_africa_mask(type = "vector") #> Loading ISO 19139 XML schemas... #> Loading ISO 19115 codelists... #> Please Note: Because you did not provide a version, by default the version being used is 202403 (This is the most recent version of admin unit shape data. To see other version options use function listShpVersions) +#> Start tag expected, '<' not found +#> Start tag expected, '<' not found #> although coordinates are longitude/latitude, st_union assumes that they are #> planar #> Warning: [crs<-] not all geometries were transferred, use svc for a geometry #> collection -``` - -``` r plot(africa_mask) ``` diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..ff3e7bd --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,4 @@ +url: https://idem-lab.github.io/sdmtools/ +template: + bootstrap: 5 + diff --git a/man/assign_nearest_land.Rd b/man/assign_nearest_land.Rd index 12c2c23..0ed4ccf 100644 --- a/man/assign_nearest_land.Rd +++ b/man/assign_nearest_land.Rd @@ -20,6 +20,6 @@ modified data object minus observations not within specified maximum distance of } \description{ Adapted from seegSDM. -Reposition observations to a location within mask if within a specified distance. +Reposition point observations to a location within mask if within a specified distance. Useful for when coastal observations drop off jagged mask and similar } diff --git a/man/inside_mask.Rd b/man/inside_mask.Rd index 5f3c4e4..6e60192 100644 --- a/man/inside_mask.Rd +++ b/man/inside_mask.Rd @@ -19,9 +19,10 @@ pixels, the function throws an error. \description{ Checks whether longitude and latitude coincide with non-missing pixels of a raster. The function takes two arguments: -points, a dataframe containing columns named -longitude' and 'latitude', and mask is a raster. Returns a dataframe -of longitude and latitude only those rows with points falling on +points, a dataframe containing at a minimum columns named +longitude' and 'latitude' (but could include other attributes), +and mask is a raster. Returns a dataframe of the same dimensions as the +input object, containing only those rows with points falling on non-missing pixels. If all points fall on missing pixels, the function throws an error. } diff --git a/man/mask_from_all.Rd b/man/mask_from_all.Rd new file mode 100644 index 0000000..d1761ea --- /dev/null +++ b/man/mask_from_all.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mask_from_all.R +\name{mask_from_all} +\alias{mask_from_all} +\title{Create mask from raster layers} +\usage{ +mask_from_all(r) +} +\arguments{ +\item{r}{\code{SpatRaster} with >1 layer.} +} +\value{ +\code{SpatRaster} with values \code{NA} or 1. +} +\description{ +Creates a mask where a cell in any layer of \code{r} that is \code{NA} will be returned +as \code{NA}. +} +\details{ +Similar in intention to \code{mask_all}, but (a) will work on larger rasters +because it only holds the values of a single layer in memory at a time, and +(b) returns a mask layer, rather than masking each layer in \code{r}. +\strong{Can be very slow} +} +\examples{ +r <- example_raster(seed = 10) +s <- example_raster(seed = 11) + +r[10:20] <- NA + +s[5:15] <- NA + +q <- mask_from_all(c(r,s)) + +library("terra") +plot(c(r,s,q)) + +} diff --git a/man/sdmtools-package.Rd b/man/sdmtools-package.Rd index 1ad492f..b61ee40 100644 --- a/man/sdmtools-package.Rd +++ b/man/sdmtools-package.Rd @@ -12,6 +12,7 @@ What the package does (one paragraph). Useful links: \itemize{ \item \url{https://github.com/idem-lab/sdmtools} + \item \url{https://idem-lab.github.io/sdmtools/} \item Report bugs at \url{https://github.com/idem-lab/sdmtools/issues} } diff --git a/man/standardise_rast.Rd b/man/standardise_rast.Rd index b4a5467..83fdc8a 100644 --- a/man/standardise_rast.Rd +++ b/man/standardise_rast.Rd @@ -15,9 +15,7 @@ A \code{SpatRaster} objects with mean of zero and standard deviation of one. \description{ \strong{Defunct}. -DO NOT USE THIS FUNCTION - -Instead use \code{terra::scale} +DO NOT USE THIS FUNCTION; use \code{terra::scale} instead } \examples{ \dontrun{ diff --git a/man/std_rast.Rd b/man/std_rast.Rd index 3a97bf0..21bc0b5 100644 --- a/man/std_rast.Rd +++ b/man/std_rast.Rd @@ -4,11 +4,13 @@ \alias{std_rast} \title{Standardise raster} \usage{ -std_rast(x, filename = NULL, overwrite = TRUE) +std_rast(x, reverse, filename = NULL, overwrite = TRUE) } \arguments{ \item{x}{\code{SpatRaster} to standardise} +\item{reverse}{\code{logical} if \code{TRUE} will subtract standardised values from 1} + \item{filename}{Optional \code{character} path and filename to write output} \item{overwrite}{\code{logical} if \code{TRUE} will overwrite \code{filename}} @@ -24,6 +26,6 @@ by dividing by the maximum value in each layer. \emph{Only operates by layer} Will break for non-numeric rasters } \examples{ -example_raster() |> - std_rast() +example_raster(seed = 3010) |> + std_rast(reverse = TRUE) } diff --git a/man/writereadrast.Rd b/man/writereadrast.Rd index 83c9465..8f2421f 100644 --- a/man/writereadrast.Rd +++ b/man/writereadrast.Rd @@ -11,7 +11,9 @@ writereadrast(x, filename, overwrite = TRUE, layernames = NULL) \item{filename}{A \code{character} file path and name to save \code{x} to disc.} -\item{overwrite}{\code{logical}; overwrite existing raster.} +\item{overwrite}{\code{logical}; overwrite existing raster. \strong{NB:} by default, +\code{overwrite = TRUE}, this is the opposite of the default behaviour of +\code{terra::writeRaster}} \item{layernames}{\code{character} of length \code{nlyr(x)}} } @@ -28,6 +30,12 @@ steps: \code{terra::writeRaster} then reading and re-assigning via \code{terra:: \dontrun{ # create raster then assign r <- sdmtools::example_raster() + +# usual workflow in two slow tedious boring steps +terra::writeRaster(r, "LowerSpringvale.tif") +s <- terra::rast("LowerSpringvale.tif") + +Better workflow in one fast enjoyable lithesome step with `sdmtools` r <- writereadrast(r, "tootgarook.tif") # or roll into single step with pipe diff --git a/sdmtools.Rproj b/sdmtools.Rproj index 69fafd4..b79b207 100755 --- a/sdmtools.Rproj +++ b/sdmtools.Rproj @@ -1,4 +1,5 @@ Version: 1.0 +ProjectId: bacad5f8-ad9d-4dfa-98ad-948bba9e05fb RestoreWorkspace: No SaveWorkspace: No