Skip to content

Commit

Permalink
Merge pull request #53 from ropensci/fix/rm-sp-build-lines
Browse files Browse the repository at this point in the history
Fix/rm sp build lines
  • Loading branch information
robitalec committed Aug 17, 2023
2 parents 53b3a0d + d125c10 commit dfec811
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 134 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Suggests:
asnipe,
markdown
SystemRequirements: GEOS (>= 3.2.0)
RoxygenNote: 7.2.2
RoxygenNote: 7.2.3
VignetteBuilder: knitr
Roxygen: list(markdown = TRUE)
BugReports: https://github.com/ropensci/spatsoc/issues
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

export(build_lines)
export(build_lines_sp)
export(build_polys)
export(dyad_id)
export(edge_dist)
Expand Down
94 changes: 51 additions & 43 deletions R/build_lines.R
Original file line number Diff line number Diff line change
@@ -1,37 +1,54 @@
#' Build Lines
#'
#'
#' \code{build_lines} creates a \code{SpatialLines} object from a \code{data.table}.
#' The function accepts a \code{data.table} with relocation data, individual
#' identifiers a sorting column and a \code{projection}. The relocation data
#' is transformed into \code{SpatialLines} for each individual and optionally,
#' each \code{splitBy}. Relocation data should be in two columns representing
#' the X and Y coordinates.
#'
#' The \code{projection} argument expects a character string defining the EPSG
#' code. For example, for UTM zone 36N (EPSG 32736), the projection argument is
#' 'EPSG:32736'. See \url{https://spatialreference.org} for a list of
#' EPSG codes. Please note, R spatial has followed updates to GDAL and PROJ
#' `build_lines` generates a simple feature collection with LINESTRINGs from a
#' `data.table`. The function accepts a `data.table` with relocation data,
#' individual identifiers, a sorting column and a `projection`. The relocation
#' data is transformed into LINESTRINGs for each individual and, optionally,
#' combination of columns listed in `splitBy`. Relocation data should be in two
#' columns representing the X and Y coordinates.
#'
#' ## R-spatial evolution
#'
#' Please note, spatsoc has followed updates from R spatial, GDAL and PROJ
#' for handling projections, see more at
#' \url{https://r-spatial.org/r/2020/03/17/wkt.html}.
#'
#' The \code{sortBy} is used to order the input \code{data.table} when creating
#' \code{SpatialLines}. It must a \code{POSIXct} to ensure the rows are sorted
#' by date time.
#'
#' The \code{splitBy} argument offers further control building \code{SpatialLines}.
#' If in your \code{DT}, you have multiple temporal groups (e.g.: years) for
#' In addition, `build_lines` previously used [sp::SpatialLines] but has been
#' updated to use [sf::st_as_sf] and [sf::st_linestring] according to the
#' R-spatial evolution, see more at
#' \url{https://r-spatial.org/r/2022/04/12/evolution.html}. A deprecated
#' version of this function using [sp::SpatialLines] is retained as
#' [build_lines_sp] temporarily but users are urged to transition as soon as
#' possible.
#'
#' ## Notes on arguments
#'
#' The `projection` argument expects a numeric or character defining the
#' coordinate reference system.
#' For example, for UTM zone 36N (EPSG 32736), the projection argument is either
#' `projection = 'EPSG:32736'` or `projection = 32736`.
#' See details in [`sf::st_crs()`] and \url{https://spatialreference.org}
#' for a list of EPSG codes.
#'
#' The `sortBy` argument is used to order the input `DT` when creating
#' sf LINESTRINGs. It must a column in the input `DT` of type
#' POSIXct to ensure the rows are sorted by date time.
#'
#' The `splitBy` argument offers further control building LINESTRINGs.
#' If in your input `DT`, you have multiple temporal groups (e.g.: years) for
#' example, you can provide the name of the column which identifies them and
#' build \code{SpatialLines} for each individual in each year.
#' build LINESTRINGs for each individual in each year.
#'
#' \code{build_lines} is used by \code{group_lines} for grouping overlapping
#' lines created from relocations.
#' `build_lines` is used by `group_lines` for grouping overlapping
#' lines generated from relocations.
#'
#' @return \code{build_lines} returns a \code{SpatialLines} object with a line
#' for each individual (and optionally \code{splitBy} combination).
#' @return `build_lines` returns an sf LINESTRING object with a line
#' for each individual (and optionally `splitBy` combination).
#'
#' An error is returned when an individual has less than 2 relocations, making
#' it impossible to build a line.
#' Individuals (or combinations of individuals and `splitBy`) with less than two
#' relocations are dropped since it requires at least two relocations to
#' build a line.
#'
#' @inheritParams group_lines
#' @inheritParams build_polys
Expand All @@ -54,7 +71,7 @@
#' DT[, datetime := as.POSIXct(datetime, tz = 'UTC')]
#'
#' # EPSG code for example data
#' utm <- 'EPSG:32736'
#' utm <- 32736
#'
#' # Build lines for each individual
#' lines <- build_lines(DT, projection = utm, id = 'ID', coords = c('X', 'Y'),
Expand All @@ -64,7 +81,6 @@
#' DT[, yr := year(datetime)]
#' lines <- build_lines(DT, projection = utm, id = 'ID', coords = c('X', 'Y'),
#' sortBy = 'datetime', splitBy = 'yr')
#'
build_lines <-
function(DT = NULL,
projection = NULL,
Expand Down Expand Up @@ -132,7 +148,7 @@ build_lines <-
}

if (!('POSIXct' %in%
unlist(lapply(DT[, .SD, .SDcols = sortBy], class)))) {
unlist(lapply(DT[, .SD, .SDcols = c(sortBy)], class)))) {
stop('sortBy provided must be 1 column of type POSIXct')
}

Expand All @@ -143,21 +159,13 @@ build_lines <-
warning('some rows dropped, cannot build lines with less than two points')
}

lst <- split(DT[dropRows, on = splitBy][!(dropped)][order(get(sortBy))],
by = c(splitBy), sorted = TRUE)
wo_drop <- DT[dropRows, on = splitBy][!(dropped)]

if (length(lst) == 0) {
return(NULL)
} else {
proj4string <- sp::CRS(projection)
l <- lapply(seq_along(lst), function(i) {
sp::SpatialLines(list(sp::Lines(sp::Line(
cbind(lst[[i]][[coords[1]]],
lst[[i]][[coords[2]]])
),
names(lst)[[i]])),
proj4string = proj4string)
})
return(do.call(sp::rbind.SpatialLines, l))
}
data.table::setorderv(wo_drop, sortBy)

lines <- sf::st_as_sf(
wo_drop[, .(geometry = sf::st_sfc(sf::st_linestring(as.matrix(.SD)))),
by = c(splitBy), .SDcols = coords],
crs = sf::st_crs(projection)
)
}
108 changes: 108 additions & 0 deletions R/build_lines_sp.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#' Build Lines (deprecated version of function with retired spatial packages)
#'
#'
#' @inheritParams build_lines
#'
#' @export
#'
#' @family Build functions
#' @seealso \code{\link{group_lines}}
#'
#' @import data.table
#'
build_lines_sp <-
function(DT = NULL,
projection = NULL,
id = NULL,
coords = NULL,
sortBy = NULL,
splitBy = NULL) {
.Deprecated(msg = 'build_lines has been updated to use modern spatial R packages, removing dependencies on rgdal, rgeos, maptools in favor of sf. This version will be preserved until September 2023 for testing and user transition.')
# due to NSE notes in R CMD check
dropped <- . <- NULL

if (is.null(DT)) {
stop('input DT required')
}

if (is.null(coords)) {
stop('coords must be provided')
}

if (is.null(id)) {
stop('id must be provided')
}

if (is.null(projection)) {
stop('projection must be provided')
}

if (is.null(sortBy)) {
stop('sortBy must be provided')
}

if (length(coords) != 2) {
stop('coords requires a vector of column names for coordinates X and Y')
}

if (any(!(c(id, coords, splitBy, sortBy) %in% colnames(DT)))) {
stop(paste0(
as.character(paste(setdiff(
c(id, coords, splitBy, sortBy), colnames(DT)
),
collapse = ', ')),
' field(s) provided are not present in input DT'
))
}

if (any(!(DT[, vapply(.SD, is.numeric, TRUE), .SDcols = coords]))) {
stop('coords must be numeric')
}

if (is.null(splitBy)) {
splitBy <- id
} else {
splitBy <- c(id, splitBy)
}
if (any(!(DT[, lapply(.SD, FUN = function(x) {
is.numeric(x) | is.character(x) | is.integer(x)
}
), .SDcols = splitBy]))) {
stop(
strwrap(prefix = " ", initial = "",
x = 'id (and splitBy when provided)
must be character, numeric or integer type'
)
)
}

if (!('POSIXct' %in%
unlist(lapply(DT[, .SD, .SDcols = sortBy], class)))) {
stop('sortBy provided must be 1 column of type POSIXct')
}


dropRows <- DT[, .(dropped = .N < 2), by = c(splitBy)]

if (dropRows[(dropped), .N] > 0) {
warning('some rows dropped, cannot build lines with less than two points')
}

lst <- split(DT[dropRows, on = splitBy][!(dropped)][order(get(sortBy))],
by = c(splitBy), sorted = TRUE)

if (length(lst) == 0) {
return(NULL)
} else {
proj4string <- sp::CRS(projection)
l <- lapply(seq_along(lst), function(i) {
sp::SpatialLines(list(sp::Lines(sp::Line(
cbind(lst[[i]][[coords[1]]],
lst[[i]][[coords[2]]])
),
names(lst)[[i]])),
proj4string = proj4string)
})
return(do.call(sp::rbind.SpatialLines, l))
}
}
6 changes: 3 additions & 3 deletions R/build_polys.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
#' @inheritParams group_polys
#' @param spPts alternatively, provide solely a SpatialPointsDataFrame with one
#' column representing the ID of each point.
#' @param projection character string defining the projection to be passed to
#' \code{sp::CRS}. For example, for UTM zone 36S (EPSG 32736),
#' the projection argument is 'EPSG:32736'. See details.
#' @param projection numeric or character defining the coordinate reference
#' system to be passed to [sf::st_crs()]. For example, either
#' `projection = "EPSG:32736"` or `projection = 32736`.
#' @export
#'
#' @family Build functions
Expand Down
65 changes: 39 additions & 26 deletions man/build_lines.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit dfec811

Please sign in to comment.