Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: slopes
Title: Calculate Slopes of Roads, Rivers and Trajectories
Version: 1.0.1.9000
Version: 1.0.2
Authors@R: c(
person("Robin", "Lovelace", email = "rob00x@gmail.com", role = c("aut", "cre", "cph"),
comment = c(ORCID = "0000-0001-5679-6536")),
Expand All @@ -25,18 +25,16 @@ BugReports: https://github.com/ropensci/slopes/issues
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
Imports:
sf,
raster,
terra,
methods,
pbapply,
geodist,
colorspace
Depends:
R (>= 2.10)
Suggests:
terra,
knitr,
rmarkdown,
ceramic,
Expand All @@ -53,3 +51,4 @@ Suggests:
Config/Needs/website: generics
VignetteBuilder: knitr
Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

export(dem_lisbon)
export(elevation_add)
export(elevation_extract)
export(elevation_get)
Expand Down
17 changes: 16 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# slopes (development version)
# slopes 1.0.2

* Migrated from the deprecated `raster` package to `terra` (closes #60).
`terra` is now a hard dependency (`Imports`); `raster` is no longer required.
* `elevation_extract()`, `slope_raster()`, and `elevation_add()` now always use
`terra`. The `terra =` argument is deprecated (with a message) and ignored.
* `elevation_get()` now always returns a `SpatRaster`. The `output_format =`
argument is deprecated and ignored.
* `slope_matrix_to_raster()` and `slope_xyz_simple()` now work with `SpatRaster`
objects. Legacy `Raster*` objects are accepted with an automatic conversion
message for backwards compatibility.
* The bundled dataset has been renamed from `dem_lisbon_raster` to `dem_lisbon`
and is now a `SpatRaster` object (was a `RasterLayer`).
* Fixed CRAN `--run-donttest` failure: the `ceramic`/Mapbox-dependent example in
`elevation_add()` is now wrapped in `\dontrun{}` instead of `\donttest{}` (closes #72).

# slopes 1.0.1


* Package source code now hosted at https://github.com/ropensci/slopes
* New documentation section showing how directed routes work: https://docs.ropensci.org/slopes/articles/slopes.html#splitting-the-network

Expand Down
19 changes: 15 additions & 4 deletions R/data.R
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@

#' Read the bundled Lisbon DEM as a SpatRaster
#'
#' Returns a `SpatRaster` (terra package) of the Digital Elevation Model
#' for central Lisbon, Portugal, bundled with the slopes package.
#'
#' @return A `SpatRaster` object with 133 rows, 200 columns, and 1 elevation layer.
#' @export
#' @examples
#' dem_lisbon()
dem_lisbon <- function() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

tif <- system.file("dem_lisbon.tif", package = "slopes")
if (!nzchar(tif)) stop("dem_lisbon.tif not found in slopes package installation.")
terra::rast(tif)
}

#' Cycle route data
#' @format An sf object
"cyclestreets_route"

#' Lisbon elevation raster
#' @format A raster object
"dem_lisbon_raster"

#' Lisbon road network
#' @format An sf object
"lisbon_road_network"
Expand Down
69 changes: 36 additions & 33 deletions R/slope_get.R
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
#' Get elevation data for routes
#'
#' Downloads elevation data using the ceramic package for given routes.
#' Returns a `SpatRaster` object (terra package).
#'
#' @param routes An sf object containing linestring geometries
#' @param ... Additional arguments passed to ceramic::cc_elevation
#' @param output_format Character string specifying output format ("raster" or "terra")
#' @return Elevation raster data covering the routes
#' @return A SpatRaster covering the routes
#' @export
elevation_get = function(routes, ..., output_format = "raster") {
if(requireNamespace("ceramic")) {
mid_ext = sf_mid_ext_lonlat(routes)
bw = max(c(mid_ext$width, mid_ext$height)) / 1 # buffer width
suppressWarnings({
e = ceramic::cc_elevation(loc = mid_ext$midpoint, buffer = bw, ...)
})
} else {
message("Install the package ceramic")
elevation_get = function(routes, ...) {
if (!requireNamespace("ceramic", quietly = TRUE)) {
stop("Install the package ceramic to use elevation_get().")
}
mid_ext = sf_mid_ext_lonlat(routes)
bw = max(c(mid_ext$width, mid_ext$height)) / 1 # buffer width
suppressWarnings({
e = ceramic::cc_elevation(loc = mid_ext$midpoint, buffer = bw, ...)
})
crs_routes = sf::st_crs(routes)
if(!requireNamespace("terra", quietly = TRUE)) {
message('install.packages("terra") # for this to work')
}
res = terra::project(e, y = crs_routes$wkt)
if(output_format == "raster") {
res = raster::raster(res)
}
res
terra::project(e, y = crs_routes$wkt)
}

#' Extract midpoint and extent from routes in lonlat
Expand All @@ -49,39 +41,50 @@ sf_mid_ext_lonlat = function(routes) {
res
}

#' Convert slope matrix to raster
#' Convert slope matrix to SpatRaster
#'
#' Converts a slope matrix or checks if input is already a raster.
#' Converts a slope matrix or a legacy RasterLayer to a SpatRaster (terra).
#' Accepts `SpatRaster`, legacy `Raster*`, or a plain matrix.
#'
#' @param x A matrix or RasterLayer object
#' @return A RasterLayer object
#' @param x A matrix, SpatRaster, or legacy RasterLayer object
#' @return A SpatRaster object
#' @export
slope_matrix_to_raster <- function(x) { # <-- Changed name
if (inherits(x, "Raster")) {
slope_matrix_to_raster <- function(x) {
if (methods::is(x, "SpatRaster")) {
return(x)
}
if (methods::is(x, "Raster")) {
message("Converting legacy Raster* object to SpatRaster. Consider using terra::rast() directly.")
return(terra::rast(x))
}
if (is.matrix(x)) {
return(raster::raster(x))
return(terra::rast(x))
}
stop("Input must be a matrix or RasterLayer")
stop("Input must be a matrix, SpatRaster, or legacy RasterLayer")
}

#' Extract XYZ coordinates from raster or matrix
#' Extract XYZ coordinates from SpatRaster or matrix
#'
#' Simplifies raster or matrix data to XYZ coordinate format.
#' Accepts `SpatRaster` (terra), legacy `Raster*` objects, or a plain matrix.
#'
#' @param x A RasterLayer or matrix object
#' @param x A SpatRaster, legacy RasterLayer, or matrix object
#' @return A data frame with x, y, z coordinates
#' @export
slope_xyz_simple <- function(x) {
if (inherits(x, "Raster")) {
xy <- raster::xyFromCell(x, 1:raster::ncell(x))
return(data.frame(x = xy[, 1], y = xy[, 2], z = raster::getValues(x)))
if (methods::is(x, "Raster")) {
message("Converting legacy Raster* object to SpatRaster. Consider using terra::rast() directly.")
x <- terra::rast(x)
}
if (methods::is(x, "SpatRaster")) {
df <- terra::as.data.frame(x, xy = TRUE)
names(df) <- c("x", "y", "z")
return(df)
}
if (is.matrix(x)) {
df <- as.data.frame(as.table(x))
names(df) <- c("y", "x", "z")
return(df)
}
stop("Input must be a RasterLayer or matrix")
stop("Input must be a SpatRaster, legacy RasterLayer, or matrix")
}
53 changes: 34 additions & 19 deletions R/slopes.R
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,26 @@ slope_matrices <- function(m_xyz_split, fun = slope_matrix_weighted, ...) {
#' Calculates slope gradients for routes using digital elevation model (DEM) raster data.
#'
#' @param routes An sf object containing linestring geometries
#' @param dem A raster object containing elevation data
#' @param dem A SpatRaster object (terra package) containing elevation data
#' @param lonlat Logical, whether coordinates are longitude/latitude (default: auto-detected)
#' @param method Method for raster extraction (default: "bilinear")
#' @param fun Function for slope calculation (default: slope_matrix_weighted)
#' @param terra Logical, whether to use terra package (default: auto-detected)
#' @param terra Deprecated. Ignored; terra is always used.
#' @param directed Logical, whether to calculate directed slopes (default: FALSE)
#' @return Numeric vector of slope values
#' @export
slope_raster <- function(routes, dem, lonlat = sf::st_is_longlat(routes),
method = "bilinear", fun = slope_matrix_weighted,
terra = has_terra() && methods::is(dem, "SpatRaster"),
terra = NULL,
directed = FALSE) {
if (!is.null(terra)) {
.Deprecated(msg = "The 'terra' argument is deprecated and ignored. terra is always used.")
}
if (is.na(lonlat)) stop("CRS of routes not known. Set CRS with sf::st_crs(routes) = 4326")
stop_is_not_linestring(routes)
routes <- sf::st_geometry(routes)
m <- sf::st_coordinates(routes)
z <- elevation_extract(m, dem, method = method, terra = terra)
z <- elevation_extract(m, dem, method = method)
m_xyz_df <- data.frame(x = m[, "X"], y = m[, "Y"], z = z, L1 = m[, "L1"])
slope_xyz(m_xyz_df, fun = fun, lonlat = lonlat, directed = directed)
}
Expand Down Expand Up @@ -202,36 +205,45 @@ slope_xyz <- function(route_xyz, fun = slope_matrix_weighted, lonlat = TRUE, dir
#' Extract elevation values from coordinates
#'
#' Extracts elevation values from a DEM raster at specified coordinate locations.
#' Accepts both `SpatRaster` (terra) and legacy `Raster*` (raster) objects;
#' legacy objects are automatically converted to `SpatRaster`.
#'
#' @param m Matrix or sf object with coordinates
#' @param dem A raster object containing elevation data
#' @param dem A SpatRaster (or legacy RasterLayer) containing elevation data
#' @param method Method for raster extraction (default: "bilinear")
#' @param terra Logical, whether to use terra package (default: auto-detected)
#' @param terra Deprecated. Ignored; terra is always used.
#' @return Numeric vector of elevation values
#' @export
elevation_extract <- function(m, dem, method = "bilinear", terra = has_terra() && methods::is(dem, "SpatRaster")) {
elevation_extract <- function(m, dem, method = "bilinear", terra = NULL) {
if (!is.null(terra)) {
.Deprecated(msg = "The 'terra' argument is deprecated and ignored. terra is always used.")
}
if (any(grepl(pattern = "sf", class(m)))) m <- sf::st_coordinates(m)
if (terra) {
terra::extract(dem, m[, 1:2], method = method)[[1]]
} else {
raster::extract(dem, m[, 1:2], method = method)
if (!methods::is(dem, "SpatRaster")) {
if (requireNamespace("terra", quietly = TRUE)) {
message("Converting legacy Raster* object to SpatRaster. Consider using terra::rast() directly.")
dem <- terra::rast(dem)
} else {
stop("terra package is required. Install it with: install.packages('terra')")
}
}
terra::extract(dem, m[, 1:2], method = method)[[1]]
}

#' Add elevation data to route linestrings
#'
#' Adds elevation (Z) coordinates to linestring geometries using DEM data.
#'
#' @param routes An sf object containing linestring geometries
#' @param dem A raster object containing elevation data (default: NULL for automatic download)
#' @param dem A SpatRaster object containing elevation data (default: NULL for automatic download)
#' @param method Method for raster extraction (default: "bilinear")
#' @param terra Logical, whether to use terra package (default: auto-detected)
#' @param terra Deprecated. Ignored; terra is always used.
#' @return An sf object with XYZ linestring geometries
#' @export
#' @examples
#' library(sf)
#' routes = lisbon_road_network[204, ]
#' dem = dem_lisbon_raster
#' dem = dem_lisbon()
#' (r3d = elevation_add(routes, dem))
#' st_z_range(routes)
#' st_z_range(r3d)
Expand All @@ -244,20 +256,23 @@ elevation_extract <- function(m, dem, method = "bilinear", terra = has_terra() &
#' plot_slope(r3d_get)
#' }
#' }
elevation_add <- function(routes, dem = NULL, method = "bilinear", terra = has_terra() && methods::is(dem, "SpatRaster")) {
elevation_add <- function(routes, dem = NULL, method = "bilinear", terra = NULL) {
if (!is.null(terra)) {
.Deprecated(msg = "The 'terra' argument is deprecated and ignored. terra is always used.")
}
stopifnotsf(routes)
if (is.null(dem)) {
dem <- elevation_get(routes) # Example will be \donttest{} in Rd
dem <- elevation_get(routes) # returns SpatRaster
r_original <- routes
routes <- sf::st_transform(routes, raster::crs(dem))
routes <- sf::st_transform(routes, terra::crs(dem))
suppressWarnings({sf::st_crs(routes) <- sf::st_crs(r_original)})
m <- sf::st_coordinates(routes)
mo <- sf::st_coordinates(r_original)
z <- as.numeric(elevation_extract(m, dem, method = method, terra = terra))
z <- as.numeric(elevation_extract(m, dem, method = method))
m_xyz <- cbind(mo[, 1:2], z)
} else {
m <- sf::st_coordinates(routes)
z <- as.numeric(elevation_extract(m, dem, method = method, terra = terra))
z <- as.numeric(elevation_extract(m, dem, method = method))
m_xyz <- cbind(m[, 1:2], z)
}
n <- nrow(routes)
Expand Down
4 changes: 2 additions & 2 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ knitr::opts_chunk$set(
The **slopes** R package calculates the slope (longitudinal steepness, also known as gradient) of roads, rivers and other linear (simple) features, based on two main inputs:

- [vector](https://r.geocompx.org/spatial-class.html) linestring geometries defined by classes in the [`sf`](https://r-spatial.github.io/sf/) package
- [raster](https://r.geocompx.org/spatial-class.html) objects with pixel values reporting average height, commonly known as digital elevation model (**DEM**) datasets, defined by classes in the [`raster`](https://cran.r-project.org/package=raster) or more recent [`terra`](https://rspatial.org/terra) packages
- [raster](https://r.geocompx.org/spatial-class.html) objects with pixel values reporting average height, commonly known as digital elevation model (**DEM**) datasets, defined by classes in the [`terra`](https://rspatial.org/terra) package (`SpatRaster`)

Data on slopes are useful in many fields of research, including [hydrology](https://en.wikipedia.org/wiki/Stream_gradient), natural hazards (including [flooding](https://response.reliefweb.int/afghanistan) and [landslide risk management](https://assets.publishing.service.gov.uk/media/57a08d0740f0b652dd0016f4/R7815-ADD017_col.pdf)), recreational and competitive sports such as [cycling](https://theclimbingcyclist.com/gradients-and-cycling-an-introduction/), [hiking](https://trailism.com/trail-grades/), and [skiing](https://www.snowplaza.co.uk/blog/16682-skiing-steeps-what-does-gradient-mean-ski-piste/).
Slopes are also also important in some branches of [transport and emissions modelling](https://doi.org/10.1016/j.trpro.2016.05.258) and [ecology](https://doi.org/10.1016/j.ncon.2016.10.001).
Expand Down Expand Up @@ -119,7 +119,7 @@ With the default argument `dem = NULL`, the function downloads the necessary ele
You can also this use a local DEM (`dem = ...`), as shown in the example below:

```{r}
sf_linestring_xyz_local = elevation_add(sf_linestring, dem = dem_lisbon_raster)
sf_linestring_xyz_local = elevation_add(sf_linestring, dem = dem_lisbon())
```

In both cases you can obtain the average gradient of the linestring with `slope_xyz()` and plot the elevation profile with `plot_slope()` as follows:
Expand Down
29 changes: 20 additions & 9 deletions cran-comments.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
Updated with DOI not journal's URL.
# slopes 1.0.2 — CRAN resubmission after archival

Removed the CRAN-SUBMISSIONS file.
## Summary of changes

Updated references based on feedback from manual CRAN checks.
The package was archived on 2026-05-24 due to a CRAN check failure (#72).
This submission fixes that issue and also resolves the long-standing issue #60
(migrate from the deprecated `raster` package to `terra`).

We have updated the URLs to comply with CRAN feedback
### Key changes:
- **Migrated from `raster` to `terra`** (closes #60): `raster` is no longer
developed and was removed from `Imports`. `terra` is now the sole raster
dependency.
- **Fixed CRAN `--run-donttest` failure** (closes #72): The
`elevation_add()` example that triggered `ceramic`/Mapbox downloads is now
wrapped in `\dontrun{}` instead of `\donttest{}`.
- The bundled DEM dataset has been renamed from `dem_lisbon_raster` (a
`RasterLayer`) to `dem_lisbon()` (a function returning a `SpatRaster`).
terra rasters cannot be serialised to `.rda`; shipping as a `.tif` in
`inst/` and exposing via an accessor function is the recommended pattern.

Exported `plot_dz()` to avoid using ::: in examples
## R CMD check results (local, --as-cran)

## R CMD check results
0 errors | 0 warnings | 0 notes

0 errors | 0 warnings | 1 note

* This is a new release.
Tested on:
- Linux (Ubuntu 24.04), R 4.6.x
Loading
Loading