diff --git a/DESCRIPTION b/DESCRIPTION index 86e5f57d..51bfd32a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -43,6 +43,7 @@ Imports: curl (>= 3.2), data.table, dplyr (>= 0.7.6), + geosphere, httr (>= 1.3.1), jsonlite (>= 1.5), lwgeom (>= 0.1.4), diff --git a/NAMESPACE b/NAMESPACE index 96e821ac..7259b3e6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -65,6 +65,7 @@ export(line_segment) export(line_segment1) export(line_via) export(mats2line) +export(n_segments) export(n_vertices) export(od2line) export(od2odf) diff --git a/R/linefuns.R b/R/linefuns.R index 854fcab4..9e5787b2 100644 --- a/R/linefuns.R +++ b/R/linefuns.R @@ -8,7 +8,7 @@ #' @family lines #' @export #' @examples -#' l = routes_fast_sf +#' l <- routes_fast_sf #' n_vertices(l) #' n_vertices(zones_sf) n_vertices <- function(l) { @@ -59,21 +59,21 @@ is_linepoint <- function(l) { #' @family lines #' @export #' @examples -#' lib_versions <- sf::sf_extSoftVersion() -#' lib_versions -#' # fails on some systems (with early versions of PROJ) -#' if (lib_versions[3] >= "6.3.1") { -#' bearings_sf_1_9 <- line_bearing(flowlines_sf[1:5, ]) -#' bearings_sf_1_9 # lines of 0 length have NaN bearing -#' line_bearing(flowlines_sf[1:5, ], bidirectional = TRUE) -#' } +#' l <- flowlines_sf[1:5, ] +#' bearings_sf_1_9 <- line_bearing(l) +#' bearings_sf_1_9 # lines of 0 length have NaN bearing +#' line_bearing(l, bidirectional = TRUE) line_bearing <- function(l, bidirectional = FALSE) { - p <- sf::st_geometry(line2points(l)) - i_s <- seq_along(sf::st_geometry(l)) * 2 - 1 - - bearing_radians <- sapply(i_s, function(i) lwgeom::st_geod_azimuth(p[i:(i + 1)])) - - bearing <- bearing_radians * 180 / pi + # Convert to lon/lat data if not already + is_longlat <- sf::st_is_longlat(l) + if (!is_longlat) { + l <- sf::st_transform(l, "EPSG:4326") + } + odc <- od::od_coordinates(l) + bearing <- geosphere::bearing( + p1 = odc[, 1:2], + p2 = odc[, 3:4] + ) if (bidirectional) { bearing <- make_bidirectional(bearing) } @@ -133,22 +133,25 @@ angle_diff <- function(l, angle, bidirectional = FALSE, absolute = TRUE) { #' @family lines #' @export #' @examples -#' l = routes_fast_sf[2:5, ] +#' l <- routes_fast_sf[2:5, ] #' plot(l$geometry, col = 2:5) -#' midpoints = line_midpoint(l) +#' midpoints <- line_midpoint(l) #' plot(midpoints, add = TRUE) line_midpoint <- function(l, tolerance = NULL) { - if(is.null(tolerance)) { - sub = lwgeom::st_linesubstring(x = l, from = 0, to = 0.5) + if (is.null(tolerance)) { + sub <- lwgeom::st_linesubstring(x = l, from = 0, to = 0.5) } else { - sub = lwgeom::st_linesubstring(x = l, from = 0, to = 0.5, tolerance = tolerance) + sub <- lwgeom::st_linesubstring(x = l, from = 0, to = 0.5, tolerance = tolerance) } lwgeom::st_endpoint(sub) } #' Divide an sf object with LINESTRING geometry into regular segments #' -#' This function keeps the attributes +#' This function keeps the attributes. +#' Note: results differ when `use_rsgeo` is `TRUE`: +#' the `{rsgeo}` implementation is faster and more reliably +#' keeps returned linestrings below a the `segment_length` value. #' #' @inheritParams line2df #' @param segment_length The approximate length of segments in the output (overides n_segments if set) @@ -159,56 +162,65 @@ line_midpoint <- function(l, tolerance = NULL) { #' @family lines #' @export #' @examples +#' library(sf) #' l <- routes_fast_sf[2:4, ] -#' l_seg_multi = line_segment(l, segment_length = 1000) +#' l_seg_multi <- line_segment(l, segment_length = 1000, use_rsgeo = FALSE) #' plot(l_seg_multi, col = seq_along(l_seg_multi), lwd = 5) +#' round(st_length(l_seg_multi)) #' # Test rsgeo implementation: #' # rsmulti = line_segment(l, segment_length = 1000, use_rsgeo = TRUE) +#' # plot(rsmulti, col = seq_along(l_seg_multi), lwd = 5) +#' # round(st_length(rsmulti)) #' # waldo::compare(l_seg_multi, rsmulti) line_segment <- function( - l, - segment_length = NA, - use_rsgeo = NULL, - debug_mode = FALSE -) { + l, + segment_length = NA, + use_rsgeo = NULL, + debug_mode = FALSE) { UseMethod("line_segment") } #' @export line_segment.sf <- function( - l, - segment_length = NA, - use_rsgeo = NULL, - debug_mode = FALSE -) { + l, + segment_length = NA, + use_rsgeo = NULL, + debug_mode = FALSE) { if (is.na(segment_length)) { rlang::abort( "`segment_length` must be set.", call = rlang::caller_env() ) } - n_row_l = nrow(l) - # browser() + # Decide whether to use rsgeo or lwgeom, if not set: + if (is.null(use_rsgeo)) { + use_rsgeo <- use_rsgeo(l) + } + if (use_rsgeo) { + # If using rsgeo, we can do the whole thing in one go: + segment_lengths <- as.numeric(sf::st_length(l)) + n_segments <- n_segments(segment_lengths, segment_length) + res <- line_segment_rsgeo(l, n_segments = n_segments) + return(res) + } + n_row_l <- nrow(l) if (n_row_l > 1) { - res_list = pbapply::pblapply(seq(n_row_l), function(i) { + res_list <- pbapply::pblapply(seq(n_row_l), function(i) { if (debug_mode) { message(paste0("Processing row ", i, " of ", n_row_l)) } - # if( i == 108) { - # browser() - # } - l_segmented = line_segment1(l[i, ], n_segments = NA, segment_length = segment_length, use_rsgeo) + l_segmented <- line_segment1(l[i, ], n_segments = NA, segment_length = segment_length) res_names <- names(sf::st_drop_geometry(l_segmented)) # Work-around for https://github.com/ropensci/stplanr/issues/531 if (i == 1) { res_names <<- names(sf::st_drop_geometry(l_segmented)) } - l_segmented = l_segmented[res_names] + l_segmented <- l_segmented[res_names] l_segmented - }) - res = bind_sf(res_list) + }) + res <- bind_sf(res_list) } else { # If there's only one row: - res = line_segment1(l, n_segments = NA, segment_length = segment_length, use_rsgeo) + res <- line_segment1(l, n_segments = NA, segment_length = segment_length) } res } @@ -216,18 +228,17 @@ line_segment.sf <- function( #' @export line_segment.sfc_LINESTRING <- function( - l, - segment_length = NA, - use_rsgeo = NULL, - debug_mode = FALSE -) { + l, + segment_length = NA, + use_rsgeo = NULL, + debug_mode = FALSE) { l <- sf::st_as_sf(l) - res = line_segment(l, segment_length = segment_length, use_rsgeo, debug_mode) + res <- line_segment(l, segment_length = segment_length, use_rsgeo, debug_mode) sf::st_geometry(res) } #' Segment a single line, using lwgeom or rsgeo -#' +#' #' @inheritParams line_segment #' @param n_segments The number of segments to divide the line into #' @family lines @@ -236,7 +247,7 @@ line_segment.sfc_LINESTRING <- function( #' l <- routes_fast_sf[2, ] #' l_seg2 <- line_segment1(l = l, n_segments = 2) #' # Test with rsgeo (must be installed): -#' # l_seg2_rsgeo = line_segment1(l = l, n_segments = 2, use_rsgeo = TRUE) +#' # l_seg2_rsgeo = line_segment1(l = l, n_segments = 2) #' # waldo::compare(l_seg2, l_seg2_rsgeo) #' l_seg3 <- line_segment1(l = l, n_segments = 3) #' l_seg_100 <- line_segment1(l = l, segment_length = 100) @@ -246,21 +257,17 @@ line_segment.sfc_LINESTRING <- function( #' plot(sf::st_geometry(l_seg_100), col = seq(nrow(l_seg_100)), lwd = 5) #' plot(sf::st_geometry(l_seg_1000), col = seq(nrow(l_seg_1000)), lwd = 5) line_segment1 <- function( - l, - n_segments = NA, - segment_length = NA, - use_rsgeo = NULL -) { + l, + n_segments = NA, + segment_length = NA) { UseMethod("line_segment1") } #' @export line_segment1.sf <- function( - l, - n_segments = NA, - segment_length = NA, - use_rsgeo = NULL -) { + l, + n_segments = NA, + segment_length = NA) { if (is.na(n_segments) && is.na(segment_length)) { rlang::abort( "`n_segment` or `segment_length` must be set.", @@ -275,27 +282,17 @@ line_segment1.sf <- function( return(l) } - # Decide whether to use rsgeo or lwgeom, if not set: - if (is.null(use_rsgeo)) { - use_rsgeo <- use_rsgeo(l) - } + res <- line_segment_lwgeom(l, n_segments) - if (use_rsgeo) { - res <- line_segment_rsgeo(l, n_segments) - } else { - res <- line_segment_lwgeom(l, n_segments) - } res } #' @export line_segment1.sfc_LINESTRING <- function( - l, - n_segments = NA, - segment_length = NA, - use_rsgeo = NULL -) { + l, + n_segments = NA, + segment_length = NA) { l <- sf::st_as_sf(l) - res <- line_segment1(l, n_segments, segment_length = segment_length, use_rsgeo) + res <- line_segment1(l, n_segments, segment_length = segment_length) sf::st_geometry(res) } @@ -313,59 +310,52 @@ make_bidirectional <- function(bearing) { #' @param x List of sf objects to combine #' @return An sf data frame #' @family geo -bind_sf = function(x) { +bind_sf <- function(x) { if (length(x) == 0) stop("Empty list") - geom_name = attr(x[[1]], "sf_column") - # browser() - x = data.table::rbindlist(x, use.names = FALSE) + geom_name <- attr(x[[1]], "sf_column") + x <- data.table::rbindlist(x, use.names = FALSE) # x = collapse::unlist2d(x, idcols = FALSE, recursive = FALSE) - x[[geom_name]] = sf::st_sfc(x[[geom_name]], recompute_bbox = TRUE) - x = sf::st_as_sf(x) + x[[geom_name]] <- sf::st_sfc(x[[geom_name]], recompute_bbox = TRUE) + x <- sf::st_as_sf(x) x } use_rsgeo <- function(shp) { rsgeo_installed <- rlang::is_installed("rsgeo", version = "0.1.6") - crs <- sf::st_crs(shp) - is_projected <- !crs$IsGeographic - # if its NA set false - if (is.na(is_projected)) { - warning("CRS is NA, assuming projected") - is_projected <- TRUE + if (!rsgeo_installed) { + warning("rsgeo not installed, using lwgeom") + return(FALSE) } - should_use_rsgeo <- rsgeo_installed & is_projected - should_use_rsgeo + TRUE } -line_segment_rsgeo <- function(l, n_segments, segment_length) { +line_segment_rsgeo <- function(l, n_segments) { + crs <- sf::st_crs(l) + # extract geometry and convert to rsgeo geo <- rsgeo::as_rsgeo(sf::st_geometry(l)) - # if n_segments is missing it needs to be calculated - if (is.na(n_segments)) { - l_length <- rsgeo::length_euclidean(geo) - n_segments <- max(round(l_length / segment_length), 1) - } - # segmentize the line strings - res <- rsgeo::line_segmentize(geo, n_segments) + res_rsgeo <- rsgeo::line_segmentize(geo, n_segments) # make them into sfc_LINESTRING - res <- sf::st_cast(sf::st_as_sfc(res), "LINESTRING") + res <- sf::st_cast(sf::st_as_sfc(res_rsgeo), "LINESTRING") # give them them CRS res <- sf::st_set_crs(res, crs) # calculate the number of original geometries - n <- length(geo) + n_lines <- length(geo) # create index ids to grab rows from - ids <- rep.int(1:n, rep(n_segments, n)) + ids <- rep.int(seq_len(n_lines), n_segments) # index the original sf object - res_tbl <- sf::st_drop_geometry(l)[ids, ] + res_tbl <- sf::st_drop_geometry(l)[ids, , drop = FALSE] # assign the geometry column + nrow(res_tbl) + res_tbl[[attr(l, "sf_column")]] <- res # convert to sf and return @@ -374,9 +364,9 @@ line_segment_rsgeo <- function(l, n_segments, segment_length) { } line_segment_lwgeom <- function(l, n_segments) { - from_to_sequence = seq(from = 0, to = 1, length.out = n_segments + 1) + from_to_sequence <- seq(from = 0, to = 1, length.out = n_segments + 1) suppressWarnings({ - line_segment_list = lapply(seq(n_segments), function(i) { + line_segment_list <- lapply(seq(n_segments), function(i) { lwgeom::st_linesubstring( x = l, from = from_to_sequence[i], @@ -387,3 +377,17 @@ line_segment_lwgeom <- function(l, n_segments) { res <- bind_sf(line_segment_list) res } + +#' Vectorised function to calculate number of segments given a max segment length +#' @param line_length The length of the line +#' @param max_segment_length The maximum length of each segment +#' @family lines +#' @export +#' @examples +#' n_segments(50, 10) +#' n_segments(50.1, 10) +#' n_segments(1, 10) +#' n_segments(1:9, 2) +n_segments <- function(line_length, max_segment_length) { + pmax(ceiling(line_length / max_segment_length), 1) +} diff --git a/R/rnet_boundary_points.R b/R/rnet_boundary_points.R index 9cd06cb2..378c5bb7 100644 --- a/R/rnet_boundary_points.R +++ b/R/rnet_boundary_points.R @@ -72,25 +72,3 @@ rnet_duplicated_vertices <- function(rnet, n = 2) { p } -# bench::mark(check = FALSE, -# rnet_boundary_points(rnet), -# rnet_boundary_points_lwgeom(rnet), -# rnet_duplicated_vertices(rnet), -# line2points(rnet) -# ) -# A tibble: 4 x 13 -# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc -# -# 1 rnet_boundary_points(rnet) 372µs 384.58µs 2559. 331KB 15.0 1198 7 -# 2 rnet_boundary_points_lwgeom(rnet) 1.21ms 1.25ms 783. 249KB 15.1 363 7 -# 3 rnet_duplicated_vertices(rnet) 1.13ms 1.15ms 864. 273KB 10.6 408 5 -# 4 line2points(rnet) 1.93ms 1.97ms 501. 265KB 13.0 231 6 -# # … with 5 more variables: total_time , result , memory , time , -# # gc - -# coords_single <- stplanr::od_id_szudzik(coords$x, coords$y) -# coords_count <- table(coords_single) -# coords_in <- coords[coords_single %in% names(coords_count)[coords_count >= 3], ] -# coords_n <- unique(coords_in[-c(1, 2)]) - - diff --git a/R/rnet_join.R b/R/rnet_join.R index b55893b5..27557ec5 100644 --- a/R/rnet_join.R +++ b/R/rnet_join.R @@ -48,12 +48,10 @@ #' @examples #' library(sf) #' library(dplyr) -#' # Uncomment for interactive examples: -#' plot(st_geometry(route_network_small)) #' plot(osm_net_example$geometry, lwd = 5, col = "grey", add = TRUE) #' plot(route_network_small["flow"], add = TRUE) -#' rnetj = rnet_join(osm_net_example, route_network_small, dist = 9) -#' rnetj2 = rnet_join(osm_net_example, route_network_small, dist = 9, segment_length = 10) +#' rnetj <- rnet_join(osm_net_example, route_network_small, dist = 9) +#' rnetj2 <- rnet_join(osm_net_example, route_network_small, dist = 9, segment_length = 10) #' # library(mapview) #' # mapview(rnetj, zcol = "flow") + #' # mapview(rnetj2, zcol = "flow") + @@ -63,64 +61,64 @@ #' plot(rnetj2["flow"], add = TRUE) #' plot(route_network_small["flow"], add = TRUE) #' summary(rnetj2$length_y) -#' rnetj_summary = rnetj2 %>% +#' rnetj_summary <- rnetj2 %>% #' filter(!is.na(length_y)) %>% #' sf::st_drop_geometry() %>% #' group_by(osm_id) %>% -#' summarise( -#' flow = weighted.mean(flow, length_y, na.rm = TRUE), -#' ) -#' osm_joined_rnet = dplyr::left_join(osm_net_example, rnetj_summary) +#' summarise( +#' flow = weighted.mean(flow, length_y, na.rm = TRUE), +#' ) +#' osm_joined_rnet <- dplyr::left_join(osm_net_example, rnetj_summary) #' plot(sf::st_geometry(route_network_small)) #' plot(route_network_small["flow"], lwd = 3, add = TRUE) #' plot(sf::st_geometry(osm_joined_rnet), add = TRUE) #' # plot(osm_joined_rnet[c("flow")], lwd = 9, add = TRUE) #' # Improve fit between geometries and performance by subsetting rnet_x -#' osm_subset = rnet_subset(osm_net_example, route_network_small, dist = 5) -#' osm_joined_rnet = dplyr::left_join(osm_subset, rnetj_summary) +#' osm_subset <- rnet_subset(osm_net_example, route_network_small, dist = 5) +#' osm_joined_rnet <- dplyr::left_join(osm_subset, rnetj_summary) #' plot(route_network_small["flow"]) #' # plot(osm_joined_rnet[c("flow")]) #' # mapview(joined_network) + #' # mapview(route_network_small) #' @export -rnet_join = function(rnet_x, rnet_y, dist = 5, length_y = TRUE, key_column = 1, - subset_x = TRUE, dist_subset = NULL, segment_length = 0, - endCapStyle = "FLAT", contains = TRUE, max_angle_diff = NULL, - crs = geo_select_aeq(rnet_x), ...) { +rnet_join <- function(rnet_x, rnet_y, dist = 5, length_y = TRUE, key_column = 1, + subset_x = TRUE, dist_subset = NULL, segment_length = 0, + endCapStyle = "FLAT", contains = TRUE, max_angle_diff = NULL, + crs = geo_select_aeq(rnet_x), ...) { if (is.null(dist_subset)) { - dist_subset = dist + 1 + dist_subset <- dist + 1 } if (subset_x) { - rnet_x = rnet_subset(rnet_x, rnet_y, dist = dist_subset, ...) + rnet_x <- rnet_subset(rnet_x, rnet_y, dist = dist_subset, ...) } - if(!is.null(max_angle_diff)) { - rnet_x$angle_x = line_bearing(rnet_x, bidirectional = TRUE) - contains = FALSE + if (!is.null(max_angle_diff)) { + rnet_x$angle_x <- line_bearing(rnet_x, bidirectional = TRUE) + contains <- FALSE } - rnet_x_buffer = geo_buffer(rnet_x, dist = dist, nQuadSegs = 2, endCapStyle = endCapStyle, crs = crs) + rnet_x_buffer <- geo_buffer(rnet_x, dist = dist, nQuadSegs = 2, endCapStyle = endCapStyle, crs = crs) if (segment_length > 0) { - rnet_y = line_segment(rnet_y, segment_length = segment_length) + rnet_y <- line_segment(rnet_y, segment_length = segment_length) } - if(!is.null(max_angle_diff)) { - rnet_y$angle_y = line_bearing(rnet_y, bidirectional = TRUE) + if (!is.null(max_angle_diff)) { + rnet_y$angle_y <- line_bearing(rnet_y, bidirectional = TRUE) } if (length_y) { - rnet_y$length_y = as.numeric(sf::st_length(rnet_y)) + rnet_y$length_y <- as.numeric(sf::st_length(rnet_y)) } if (contains) { - rnetj = sf::st_join(rnet_x_buffer[key_column], rnet_y, join = sf::st_contains) + rnetj <- sf::st_join(rnet_x_buffer[key_column], rnet_y, join = sf::st_contains) # # For debugging: # library(tmap) # tmap_mode("view") # tm_shape(rnet_y) + tm_lines(lwd = 3) + qtm(rnetj) + qtm(rnet_x) + # qtm(osm_net_example) } else { - rnet_y_centroids = sf::st_centroid(rnet_y) - rnetj = sf::st_join(rnet_x_buffer[c(names(rnet_x)[1], "angle_x")], rnet_y_centroids) + rnet_y_centroids <- sf::st_centroid(rnet_y) + rnetj <- sf::st_join(rnet_x_buffer[c(names(rnet_x)[1], "angle_x")], rnet_y_centroids) } if (!is.null(max_angle_diff)) { - rnetj$angle_diff = rnetj$angle_y - rnetj$angle_x - rnetj = rnetj[abs(rnetj$angle_diff) < max_angle_diff, ] + rnetj$angle_diff <- rnetj$angle_y - rnetj$angle_x + rnetj <- rnetj[abs(rnetj$angle_diff) < max_angle_diff, ] } rnetj } @@ -137,47 +135,36 @@ rnet_join = function(rnet_x, rnet_y, dist = 5, length_y = TRUE, key_column = 1, #' @param rm_disconnected Remove ways that are #' @export #' @examples -#' rnet_x = osm_net_example[1] -#' rnet_y = route_network_small["flow"] +#' rnet_x <- osm_net_example[1] +#' rnet_y <- route_network_small["flow"] #' plot(rnet_x$geometry, lwd = 5) #' plot(rnet_y$geometry, add = TRUE, col = "red", lwd = 3) -#' rnet_x_subset = rnet_subset(rnet_x, rnet_y) +#' rnet_x_subset <- rnet_subset(rnet_x, rnet_y) #' plot(rnet_x_subset, add = TRUE, col = "blue") -rnet_subset = function(rnet_x, rnet_y, dist = 10, crop = TRUE, min_length = 20, rm_disconnected = TRUE) { - # browser() - rnet_x_original = data.frame( +rnet_subset <- function(rnet_x, rnet_y, dist = 10, crop = TRUE, min_length = 20, rm_disconnected = TRUE) { + rnet_x_original <- data.frame( id = rnet_x[[1]], length_original = as.numeric(sf::st_length(rnet_x)) - ) - names(rnet_x_original)[1] = names(rnet_x)[1] - rnet_y_union = sf::st_union(rnet_y) - rnet_y_buffer = stplanr::geo_buffer(rnet_y_union, dist = dist, nQuadSegs = 2) - if(crop) { - rnet_x = sf::st_intersection(rnet_x, rnet_y_buffer) - rnet_x = line_cast(rnet_x) + ) + names(rnet_x_original)[1] <- names(rnet_x)[1] + rnet_y_union <- sf::st_union(rnet_y) + rnet_y_buffer <- stplanr::geo_buffer(rnet_y_union, dist = dist, nQuadSegs = 2) + if (crop) { + rnet_x <- sf::st_intersection(rnet_x, rnet_y_buffer) + rnet_x <- line_cast(rnet_x) } else { - rnet_x = rnet_x[rnet_y_buffer, , op = sf::st_within] + rnet_x <- rnet_x[rnet_y_buffer, , op = sf::st_within] } - if(min_length > 0) { - rnet_x$length_new = as.numeric(sf::st_length(rnet_x)) - rnet_x_joined = dplyr::left_join(rnet_x, rnet_x_original) - sel_short_remove = rnet_x_joined$length_new < min_length - sel_changed_remove = rnet_x_joined$length_new < rnet_x_joined$length_original - sel_remove = sel_short_remove & sel_changed_remove - - # browser() - # # Testing: - # # ids_to_keep = rnet_x_joined[[1]][!sel_remove] - # rnet_x_joined[sel_remove, ] - # plot(rnet_x_joined$geometry[sel_remove]) - # plot(rnet_x_joined$geometry[!sel_remove]) - # rnet_x_original_full = rnet_x - # rnet_x = rnet_x[rnet_x[[1]] %in% ids_to_keep, ] - - rnet_x = rnet_x_joined[!sel_remove, ] + if (min_length > 0) { + rnet_x$length_new <- as.numeric(sf::st_length(rnet_x)) + rnet_x_joined <- dplyr::left_join(rnet_x, rnet_x_original) + sel_short_remove <- rnet_x_joined$length_new < min_length + sel_changed_remove <- rnet_x_joined$length_new < rnet_x_joined$length_original + sel_remove <- sel_short_remove & sel_changed_remove + rnet_x <- rnet_x_joined[!sel_remove, ] } - if(rm_disconnected) { - rnet_x = rnet_connected(rnet_x) + if (rm_disconnected) { + rnet_x <- rnet_connected(rnet_x) } rnet_x } @@ -188,7 +175,7 @@ rnet_subset = function(rnet_x, rnet_y, dist = 10, crop = TRUE, min_length = 20, #' #' @param x Linestring object #' @export -line_cast = function(x) { +line_cast <- function(x) { sf::st_cast(sf::st_cast(x, "MULTILINESTRING"), "LINESTRING") } @@ -203,15 +190,16 @@ line_cast = function(x) { #' @export #' @examples #' # The source object: -#' rnet_y = route_network_small["flow"] +#' rnet_y <- route_network_small["flow"] #' # The target object -#' rnet_x = rnet_subset(osm_net_example[1], rnet_y) +#' rnet_x <- rnet_subset(osm_net_example[1], rnet_y) #' plot(rnet_x$geometry, lwd = 5) #' plot(rnet_y$geometry, add = TRUE, col = "red", lwd = 2) -#' rnet_y$quietness = rnorm(nrow(rnet_y)) -#' funs = list(flow = sum, quietness = mean) -#' rnet_merged = rnet_merge(rnet_x[1], rnet_y[c("flow", "quietness")], -#' dist = 9, segment_length = 20, funs = funs) +#' rnet_y$quietness <- rnorm(nrow(rnet_y)) +#' funs <- list(flow = sum, quietness = mean) +#' rnet_merged <- rnet_merge(rnet_x[1], rnet_y[c("flow", "quietness")], +#' dist = 9, segment_length = 20, funs = funs +#' ) #' plot(rnet_y$geometry, lwd = 5, col = "lightgrey") #' plot(rnet_merged["flow"], add = TRUE, lwd = 2) #' @@ -232,53 +220,53 @@ line_cast = function(x) { #' @return An sf object with the same geometry as `rnet_x` rnet_merge <- function(rnet_x, rnet_y, dist = 5, funs = NULL, sum_flows = TRUE, crs = geo_select_aeq(rnet_x), ...) { if (is.null(funs)) { - print('funs is NULL') - funs = list() + print("funs is NULL") + funs <- list() for (col in names(rnet_y)) { if (is.numeric(rnet_y[[col]])) { - funs[[col]] = sum + funs[[col]] <- sum } } } - sum_cols = sapply(funs, function(f) identical(f, sum)) - sum_cols = names(funs)[which(sum_cols)] - rnetj = rnet_join(rnet_x, rnet_y, dist = dist, crs = crs, ...) + sum_cols <- sapply(funs, function(f) identical(f, sum)) + sum_cols <- names(funs)[which(sum_cols)] + rnetj <- rnet_join(rnet_x, rnet_y, dist = dist, crs = crs, ...) names(rnetj) - rnetj_df = sf::st_drop_geometry(rnetj) + rnetj_df <- sf::st_drop_geometry(rnetj) # Apply functions to columns with lapply: res_list <- lapply(seq_along(funs), function(i) { # i = 1 - nm = names(funs[i]) - fn = funs[[i]] + nm <- names(funs[i]) + fn <- funs[[i]] if (identical(fn, sum) && sum_flows) { - res = rnetj_df %>% + res <- rnetj_df %>% dplyr::group_by_at(1) %>% dplyr::summarise(dplyr::across(dplyr::all_of(nm), function(x) sum(x * length_y))) } else { - res = rnetj_df %>% + res <- rnetj_df %>% dplyr::group_by_at(1) %>% dplyr::summarise(dplyr::across(dplyr::all_of(nm), fn)) } - names(res)[2] = nm - if(i > 1) { - res = res[-1] + names(res)[2] <- nm + if (i > 1) { + res <- res[-1] } res }) - res_df = dplyr::bind_cols(res_list) + res_df <- dplyr::bind_cols(res_list) - res_sf = dplyr::left_join(rnet_x, res_df) + res_sf <- dplyr::left_join(rnet_x, res_df) if (sum_flows) { - res_sf$length_x = as.numeric(sf::st_length(res_sf)) - for(i in sum_cols) { + res_sf$length_x <- as.numeric(sf::st_length(res_sf)) + for (i in sum_cols) { # TODO: deduplicate - length_y = as.numeric(sf::st_length(rnet_y)) + length_y <- as.numeric(sf::st_length(rnet_y)) # i = sum_cols[1] - res_sf[[i]] = res_sf[[i]] / res_sf$length_x - over_estimate = sum(res_sf[[i]] * res_sf$length_x, na.rm = TRUE) / + res_sf[[i]] <- res_sf[[i]] / res_sf$length_x + over_estimate <- sum(res_sf[[i]] * res_sf$length_x, na.rm = TRUE) / sum(rnet_y[[i]] * length_y, na.rm = TRUE) - res_sf[[i]] = res_sf[[i]] / over_estimate + res_sf[[i]] <- res_sf[[i]] / over_estimate } } res_sf diff --git a/man/angle_diff.Rd b/man/angle_diff.Rd index 98fc6e9d..1d0e3091 100644 --- a/man/angle_diff.Rd +++ b/man/angle_diff.Rd @@ -52,6 +52,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/geo_toptail.Rd b/man/geo_toptail.Rd index 3f03eff3..df9a3d6c 100644 --- a/man/geo_toptail.Rd +++ b/man/geo_toptail.Rd @@ -51,6 +51,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/is_linepoint.Rd b/man/is_linepoint.Rd index 7e461837..0045e5dc 100644 --- a/man/is_linepoint.Rd +++ b/man/is_linepoint.Rd @@ -38,6 +38,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line2df.Rd b/man/line2df.Rd index 7265a1e9..f4aa2c9a 100644 --- a/man/line2df.Rd +++ b/man/line2df.Rd @@ -29,6 +29,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line2points.Rd b/man/line2points.Rd index 660a3057..516e85e7 100644 --- a/man/line2points.Rd +++ b/man/line2points.Rd @@ -54,6 +54,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line_bearing.Rd b/man/line_bearing.Rd index 2dba9eee..c7626adf 100644 --- a/man/line_bearing.Rd +++ b/man/line_bearing.Rd @@ -21,14 +21,10 @@ Returns a boolean vector. TRUE means that the associated line is in fact a point (has no distance). This can be useful for removing data that will not be plotted. } \examples{ -lib_versions <- sf::sf_extSoftVersion() -lib_versions -# fails on some systems (with early versions of PROJ) -if (lib_versions[3] >= "6.3.1") { - bearings_sf_1_9 <- line_bearing(flowlines_sf[1:5, ]) - bearings_sf_1_9 # lines of 0 length have NaN bearing - line_bearing(flowlines_sf[1:5, ], bidirectional = TRUE) -} +l <- flowlines_sf[1:5, ] +bearings_sf_1_9 <- line_bearing(l) +bearings_sf_1_9 # lines of 0 length have NaN bearing +line_bearing(l, bidirectional = TRUE) } \seealso{ Other lines: @@ -43,6 +39,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line_breakup.Rd b/man/line_breakup.Rd index 17c2efc8..9d3b805d 100644 --- a/man/line_breakup.Rd +++ b/man/line_breakup.Rd @@ -45,6 +45,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line_midpoint.Rd b/man/line_midpoint.Rd index 60651a4d..fe295ae6 100644 --- a/man/line_midpoint.Rd +++ b/man/line_midpoint.Rd @@ -16,9 +16,9 @@ See \code{\link[lwgeom:st_linesubstring]{lwgeom::st_linesubstring()}}.} Find the mid-point of lines } \examples{ -l = routes_fast_sf[2:5, ] +l <- routes_fast_sf[2:5, ] plot(l$geometry, col = 2:5) -midpoints = line_midpoint(l) +midpoints <- line_midpoint(l) plot(midpoints, add = TRUE) } \seealso{ @@ -34,6 +34,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line_segment.Rd b/man/line_segment.Rd index 1ef34894..7ed55816 100644 --- a/man/line_segment.Rd +++ b/man/line_segment.Rd @@ -18,14 +18,21 @@ If \code{rsgeo} is not available, the \code{lwgeom} package is used.} \item{debug_mode}{Should debug messages be printed? Default is FALSE.} } \description{ -This function keeps the attributes +This function keeps the attributes. +Note: results differ when \code{use_rsgeo} is \code{TRUE}: +the \code{{rsgeo}} implementation is faster and more reliably +keeps returned linestrings below a the \code{segment_length} value. } \examples{ +library(sf) l <- routes_fast_sf[2:4, ] -l_seg_multi = line_segment(l, segment_length = 1000) +l_seg_multi <- line_segment(l, segment_length = 1000, use_rsgeo = FALSE) plot(l_seg_multi, col = seq_along(l_seg_multi), lwd = 5) +round(st_length(l_seg_multi)) # Test rsgeo implementation: # rsmulti = line_segment(l, segment_length = 1000, use_rsgeo = TRUE) +# plot(rsmulti, col = seq_along(l_seg_multi), lwd = 5) +# round(st_length(rsmulti)) # waldo::compare(l_seg_multi, rsmulti) } \seealso{ @@ -41,6 +48,7 @@ Other lines: \code{\link{line_segment1}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line_segment1.Rd b/man/line_segment1.Rd index 80094d97..4974c337 100644 --- a/man/line_segment1.Rd +++ b/man/line_segment1.Rd @@ -4,7 +4,7 @@ \alias{line_segment1} \title{Segment a single line, using lwgeom or rsgeo} \usage{ -line_segment1(l, n_segments = NA, segment_length = NA, use_rsgeo = NULL) +line_segment1(l, n_segments = NA, segment_length = NA) } \arguments{ \item{l}{A spatial lines object} @@ -12,10 +12,6 @@ line_segment1(l, n_segments = NA, segment_length = NA, use_rsgeo = NULL) \item{n_segments}{The number of segments to divide the line into} \item{segment_length}{The approximate length of segments in the output (overides n_segments if set)} - -\item{use_rsgeo}{Should the \code{rsgeo} package be used? -If \code{rsgeo} is available, this faster implementation is used by default. -If \code{rsgeo} is not available, the \code{lwgeom} package is used.} } \description{ Segment a single line, using lwgeom or rsgeo @@ -24,7 +20,7 @@ Segment a single line, using lwgeom or rsgeo l <- routes_fast_sf[2, ] l_seg2 <- line_segment1(l = l, n_segments = 2) # Test with rsgeo (must be installed): -# l_seg2_rsgeo = line_segment1(l = l, n_segments = 2, use_rsgeo = TRUE) +# l_seg2_rsgeo = line_segment1(l = l, n_segments = 2) # waldo::compare(l_seg2, l_seg2_rsgeo) l_seg3 <- line_segment1(l = l, n_segments = 3) l_seg_100 <- line_segment1(l = l, segment_length = 100) @@ -47,6 +43,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/line_via.Rd b/man/line_via.Rd index a483eca0..210fbc62 100644 --- a/man/line_via.Rd +++ b/man/line_via.Rd @@ -49,6 +49,7 @@ Other lines: \code{\link{line_segment1}()}, \code{\link{line_segment}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/mats2line.Rd b/man/mats2line.Rd index 2fa174b7..7fa9a56e 100644 --- a/man/mats2line.Rd +++ b/man/mats2line.Rd @@ -40,6 +40,7 @@ Other lines: \code{\link{line_segment1}()}, \code{\link{line_segment}()}, \code{\link{line_via}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, diff --git a/man/n_segments.Rd b/man/n_segments.Rd new file mode 100644 index 00000000..f639725d --- /dev/null +++ b/man/n_segments.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/linefuns.R +\name{n_segments} +\alias{n_segments} +\title{Vectorised function to calculate number of segments given a max segment length} +\usage{ +n_segments(line_length, max_segment_length) +} +\arguments{ +\item{line_length}{The length of the line} + +\item{max_segment_length}{The maximum length of each segment} +} +\description{ +Vectorised function to calculate number of segments given a max segment length +} +\examples{ +n_segments(50, 10) +n_segments(50.1, 10) +n_segments(1, 10) +n_segments(1:9, 2) +} +\seealso{ +Other lines: +\code{\link{angle_diff}()}, +\code{\link{geo_toptail}()}, +\code{\link{is_linepoint}()}, +\code{\link{line2df}()}, +\code{\link{line2points}()}, +\code{\link{line_bearing}()}, +\code{\link{line_breakup}()}, +\code{\link{line_midpoint}()}, +\code{\link{line_segment1}()}, +\code{\link{line_segment}()}, +\code{\link{line_via}()}, +\code{\link{mats2line}()}, +\code{\link{n_vertices}()}, +\code{\link{onewaygeo}()}, +\code{\link{points2line}()}, +\code{\link{toptail_buff}()} +} +\concept{lines} diff --git a/man/n_vertices.Rd b/man/n_vertices.Rd index 570a8a58..4db0c195 100644 --- a/man/n_vertices.Rd +++ b/man/n_vertices.Rd @@ -13,7 +13,7 @@ n_vertices(l) Returns a vector of the same length as the number of sf objects. } \examples{ -l = routes_fast_sf +l <- routes_fast_sf n_vertices(l) n_vertices(zones_sf) } @@ -31,6 +31,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}, \code{\link{toptail_buff}()} diff --git a/man/onewaygeo.Rd b/man/onewaygeo.Rd index 8ddfd784..02c32f2e 100644 --- a/man/onewaygeo.Rd +++ b/man/onewaygeo.Rd @@ -44,6 +44,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{points2line}()}, \code{\link{toptail_buff}()} diff --git a/man/points2line.Rd b/man/points2line.Rd index 52c8f4db..d63ebd16 100644 --- a/man/points2line.Rd +++ b/man/points2line.Rd @@ -31,6 +31,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{toptail_buff}()} diff --git a/man/rnet_join.Rd b/man/rnet_join.Rd index 885a8c43..bf14a4ac 100644 --- a/man/rnet_join.Rd +++ b/man/rnet_join.Rd @@ -84,12 +84,10 @@ and a link to an interactive example of inputs and outputs shown below. \examples{ library(sf) library(dplyr) -# Uncomment for interactive examples: -plot(st_geometry(route_network_small)) plot(osm_net_example$geometry, lwd = 5, col = "grey", add = TRUE) plot(route_network_small["flow"], add = TRUE) -rnetj = rnet_join(osm_net_example, route_network_small, dist = 9) -rnetj2 = rnet_join(osm_net_example, route_network_small, dist = 9, segment_length = 10) +rnetj <- rnet_join(osm_net_example, route_network_small, dist = 9) +rnetj2 <- rnet_join(osm_net_example, route_network_small, dist = 9, segment_length = 10) # library(mapview) # mapview(rnetj, zcol = "flow") + # mapview(rnetj2, zcol = "flow") + @@ -99,21 +97,21 @@ plot(rnetj["flow"], add = TRUE) plot(rnetj2["flow"], add = TRUE) plot(route_network_small["flow"], add = TRUE) summary(rnetj2$length_y) -rnetj_summary = rnetj2 \%>\% +rnetj_summary <- rnetj2 \%>\% filter(!is.na(length_y)) \%>\% sf::st_drop_geometry() \%>\% group_by(osm_id) \%>\% - summarise( - flow = weighted.mean(flow, length_y, na.rm = TRUE), - ) -osm_joined_rnet = dplyr::left_join(osm_net_example, rnetj_summary) + summarise( + flow = weighted.mean(flow, length_y, na.rm = TRUE), + ) +osm_joined_rnet <- dplyr::left_join(osm_net_example, rnetj_summary) plot(sf::st_geometry(route_network_small)) plot(route_network_small["flow"], lwd = 3, add = TRUE) plot(sf::st_geometry(osm_joined_rnet), add = TRUE) # plot(osm_joined_rnet[c("flow")], lwd = 9, add = TRUE) # Improve fit between geometries and performance by subsetting rnet_x -osm_subset = rnet_subset(osm_net_example, route_network_small, dist = 5) -osm_joined_rnet = dplyr::left_join(osm_subset, rnetj_summary) +osm_subset <- rnet_subset(osm_net_example, route_network_small, dist = 5) +osm_joined_rnet <- dplyr::left_join(osm_subset, rnetj_summary) plot(route_network_small["flow"]) # plot(osm_joined_rnet[c("flow")]) # mapview(joined_network) + diff --git a/man/rnet_merge.Rd b/man/rnet_merge.Rd index 9a6db7d6..8e21a4dd 100644 --- a/man/rnet_merge.Rd +++ b/man/rnet_merge.Rd @@ -41,15 +41,16 @@ Merge route networks, keeping attributes with aggregating functions } \examples{ # The source object: -rnet_y = route_network_small["flow"] +rnet_y <- route_network_small["flow"] # The target object -rnet_x = rnet_subset(osm_net_example[1], rnet_y) +rnet_x <- rnet_subset(osm_net_example[1], rnet_y) plot(rnet_x$geometry, lwd = 5) plot(rnet_y$geometry, add = TRUE, col = "red", lwd = 2) -rnet_y$quietness = rnorm(nrow(rnet_y)) -funs = list(flow = sum, quietness = mean) -rnet_merged = rnet_merge(rnet_x[1], rnet_y[c("flow", "quietness")], - dist = 9, segment_length = 20, funs = funs) +rnet_y$quietness <- rnorm(nrow(rnet_y)) +funs <- list(flow = sum, quietness = mean) +rnet_merged <- rnet_merge(rnet_x[1], rnet_y[c("flow", "quietness")], + dist = 9, segment_length = 20, funs = funs +) plot(rnet_y$geometry, lwd = 5, col = "lightgrey") plot(rnet_merged["flow"], add = TRUE, lwd = 2) diff --git a/man/rnet_subset.Rd b/man/rnet_subset.Rd index 37a4aaa7..541d5cec 100644 --- a/man/rnet_subset.Rd +++ b/man/rnet_subset.Rd @@ -32,10 +32,10 @@ before the cropping process will be removed. 3 by default.} Subset one route network based on overlaps with another } \examples{ -rnet_x = osm_net_example[1] -rnet_y = route_network_small["flow"] +rnet_x <- osm_net_example[1] +rnet_y <- route_network_small["flow"] plot(rnet_x$geometry, lwd = 5) plot(rnet_y$geometry, add = TRUE, col = "red", lwd = 3) -rnet_x_subset = rnet_subset(rnet_x, rnet_y) +rnet_x_subset <- rnet_subset(rnet_x, rnet_y) plot(rnet_x_subset, add = TRUE, col = "blue") } diff --git a/man/toptail_buff.Rd b/man/toptail_buff.Rd index 2102c20e..4faf234e 100644 --- a/man/toptail_buff.Rd +++ b/man/toptail_buff.Rd @@ -41,6 +41,7 @@ Other lines: \code{\link{line_segment}()}, \code{\link{line_via}()}, \code{\link{mats2line}()}, +\code{\link{n_segments}()}, \code{\link{n_vertices}()}, \code{\link{onewaygeo}()}, \code{\link{points2line}()}