diff --git a/DESCRIPTION b/DESCRIPTION index c91b5a2..e0de1a3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,17 +8,25 @@ Authors@R: c( comment = c(ORCID = "0000-0003-3286-0139")), person("Netherlands eScience Center", , , "info@esciencecenter.nl", role = "fnd") ) -Description: CRiSp (City River Spaces) provides tools to automate the +Description: CRiSp (City River Spaces) provides tools to automate the morphological delineation of riverside urban areas. License: Apache License (>= 2) URL: https://cityriverspaces.github.io/CRiSp/ BugReports: https://github.com/CityRiverSpaces/crisp/issues +Imports: + dplyr, + lwgeom, + osmdata, + sf, + sfnetworks, + tidygraph Suggests: knitr, rmarkdown, testthat (>= 3.0.0) +VignetteBuilder: + knitr Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.1 -VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index 6ae9268..7eea947 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,2 +1,21 @@ # Generated by roxygen2: do not edit by hand +export(calc_weights) +export(cap_corridor) +export(clean_network) +export(create_network) +export(define_aoi) +export(delineate_corridor) +export(get_corridor_edge) +export(get_latlon) +export(get_osmdata) +export(get_osmdata_river_corridor) +export(get_target_points) +export(get_vertices) +export(merge_streets) +export(not_intersects) +export(osm_bb) +export(osmdata_as_sf) +export(simplify_network) +export(split_aoi) +export(trim_network) diff --git a/R/delineate-corridor.R b/R/delineate-corridor.R new file mode 100644 index 0000000..a2418ba --- /dev/null +++ b/R/delineate-corridor.R @@ -0,0 +1,192 @@ +#' Define an area of interest (AoI) from a bounding box and a coordinate reference system (CRS). +#' +#' @param bb A bounding box as a matrix with 4 elements: xmin, ymin, xmax, ymax +#' @param crs A coordinate reference system as an epsg code, e.g. 4326 for WGS84 +#' @param buffer_dist A numeric value to buffer the area of interest +#' +#' @return An area of interest as a simple feature geometry +#' @export +define_aoi <- function(bb, crs, buffer_dist = 0) { + bbox <- as.vector(bb) + names(bbox) <- c("xmin", "ymin", "xmax", "ymax") + aoi <- sf::st_bbox(bbox, crs = sf::st_crs(4326)) |> + sf::st_as_sfc() |> + sf::st_transform(crs) + + if (buffer_dist != 0) aoi <- sf::st_buffer(aoi, buffer_dist) else aoi +} + +#' Split the area of interest (AoI) by a river. +#' +#' @param aoi Area of interest as a simple feature +#' @param river River as a simple feature +#' +#' @return A simple feature geometry set of two areas of interest +#' @export +split_aoi <- function(aoi, river) { + areas <- aoi |> + lwgeom::st_split(river) |> + sf::st_collection_extract() +} + +#' Trim a network to an area of interest (AoI) and a river corridor. +#' +#' @param net A network object +#' @param area An area of interest as a simple feature +#' @param river_corridor A river corridor as a simple feature +#' +#' @return A network object +#' @export +trim_network <- function(net, area, river_corridor){ + net |> + sfnetworks::activate("nodes") |> + sf::st_filter(area, .predicate = sf::st_intersects) |> + sf::st_filter(river_corridor, .predicate = CRiSp::not_intersects) +} + +#' Simplify a street network by removing multiple edges and loops. +#' +#' @param net A network object +#' +#' @return A simplifed network object +#' @export +simplify_network <- function(net) { + net|> + sfnetworks::activate("edges") |> + # TODO incorporate this comment in the function description + # reorder the edges so that the shortest is kept + dplyr::arrange(sfnetworks::edge_length()) |> + dplyr::filter(!tidygraph::edge_is_multiple()) |> + dplyr::filter(!tidygraph::edge_is_loop()) +} + +#' Clean a street network by subdividing edges and removing pseudo-nodes. +#' +#' @param net A network object +#' +#' @return A cleaned network object +#' @export +clean_network <- function(net) { + net |> + CRiSp::simplify_network() |> + tidygraph::convert(sfnetworks::to_spatial_subdivision) |> + tidygraph::convert(sfnetworks::to_spatial_smooth) +} + +#' Determine the end vertices of the initial river corridor. +#' +#' Determine the "vertices" of the initial river corridor as the intersections +#' of the initial river corridor with the AoI boundary. +#' +#' @param aoi Area of interest as a simple feature +#' @param corridor_initial Initial river corridor as a simple feature +#' +#' @return A simple feature geometry set of two points +#' @export +get_vertices <- function(aoi, corridor_initial) { + aoi |> + sf::st_boundary() |> + sf::st_intersection(corridor_initial) |> + # this should consists of two linestring components, determine the endpoints + sf::st_cast("POINT") +} + +#' Get start and end points of corridor edge on the network. +#' +#' TODO add description +#' +#' @param vertices A simple feature geometry set of two points +#' @param area An area of interest as a simple feature +#' @param threshold A numeric value +#' +#' @return A simple feature geometry set of two points +#' @export +get_target_points <- function(vertices, area, threshold = 0.001){ + vertices |> + sf::st_as_sf() |> + # TODO incorporate this comment into the function description + # keep threshold to check which points intersect the polygons + sf::st_filter(area, .predicate = sf::st_is_within_distance, dist = threshold) |> + sf::st_geometry() +} + +#' Determine the corridor edge on the network. +#' +#' Find the corridor edge on one side of the river by using a shortest path algorithm. +#' +#' @param net A network object +#' @param area An area of interest as a simple feature +#' @param vertices A simple feature geometry set of two points +#' +#' @return A simple feature geometry +#' @export +get_corridor_edge <- function(net, area, vertices){ + target_points <- CRiSp::get_target_points(vertices, area) + + paths <- sfnetworks::st_network_paths( + net, + from = target_points[1], + to = target_points[2], + weights = "weight", + type = "shortest" + ) + + edges <- net |> sfnetworks::activate("edges") |> sf::st_geometry() + edge_path <- paths |> dplyr::pull("edge_paths") |> unlist() + edges[edge_path] +} + +#' Cap corridor edges with a city boundary. +#' +#' @param corridor_edges Edge of the corridor as a simple feature +#' @param river River centerline as a simple feature +#' @param crs A coordinate reference system as an epsg code, e.g. 4326 for WGS84 +#' @param bb A bounding box as a matrix with 4 elements: xmin, ymin, xmax, ymax +#' @param cap Character string with the type of cap to be used. Default is "city" +#' +#' @return A simple feature geometry +#' @export +cap_corridor <- function(corridor_edges, river, crs, bb, cap = "city") { + + if (cap == "city") { + cap <- CRiSp::osmdata_as_sf("place", "city", bb) + cap <- cap$osm_multipolygons |> + sf::st_geometry() + } else { + cap <- cap + } + cap <- cap |> sf::st_transform(crs) + + capped_corridor <- cap |> + lwgeom::st_split(corridor_edges) |> + sf::st_collection_extract("POLYGON") |> + sf::st_as_sf() |> + sf::st_filter(river, .predicate = sf::st_intersects) +} + +#' Delineate a corridor around a river. +#' +#' @param place A place name as a string +#' @param river A river name as a string +#' @param crs A coordinate reference system as an epsg code, e.g. 4326 for WGS84 +#' +#' @return A simple feature geometry +#' @export +delineate_corridor <- function(place, river, crs) { + bb <- CRiSp::osm_bb(place) + + highways_value <- c("motorway", "primary", "secondary", "tertiary") + net <- CRiSp::osmdata_as_sf("highway", highways_value, bb) |> + CRiSp::merge_streets() |> + CRiSp::create_network() + + areas <- CRiSp::define_aoi(bb, crs) |> CRiSp::split_aoi(river) + + vertices <- CRiSp::get_vertices(areas, river) + + corridor_edge_1 <- CRiSp::get_corridor_edge(net, areas[1], vertices) + corridor_edge_2 <- CRiSp::get_corridor_edge(net, areas[2], vertices) + corridor_edges <- sf::st_union(corridor_edge_1, corridor_edge_2) + + CRiSp::cap_corridor(corridor_edges, river, crs) +} diff --git a/R/hello.R b/R/hello.R deleted file mode 100644 index fa3b065..0000000 --- a/R/hello.R +++ /dev/null @@ -1,18 +0,0 @@ -# Hello, world! -# -# This is an example function named 'hello' -# which prints 'Hello, world!'. -# -# You can learn more about package authoring with RStudio at: -# -# https://r-pkgs.org -# -# Some useful keyboard shortcuts for package authoring: -# -# Install Package: 'Cmd + Shift + B' -# Check Package: 'Cmd + Shift + E' -# Test Package: 'Cmd + Shift + T' - -hello <- function() { - print("Hello, world!") -} diff --git a/R/preprocess.R b/R/preprocess.R new file mode 100644 index 0000000..b50deb1 --- /dev/null +++ b/R/preprocess.R @@ -0,0 +1,29 @@ +# TODO if we can avoid adding lines from polygons, we should remove this +#' Merge streets from polygons and lines +#' +#' @param highways A list object containing streets from OpenStreetMap +#' +#' @return A data frame with a column named 'highway' containing line strings +#' @export +merge_streets <- function(highways) { + poly_to_lines <- highways$osm_polygons |> + sf::st_cast("LINESTRING") + highways_lines <- highways$osm_lines |> + dplyr::bind_rows(poly_to_lines) +} + +#' Create a network from a line strings +#' +#' @param data A data frame with a column named 'highway' containing line strings +#' @param crs A coordinate reference system as an epsg code, e.g. 4326 for WGS84 +#' +#' @return A network object +#' @export +create_network <- function(data, crs = NULL) { + net <- data |> + sfnetworks::as_sfnetwork(directed = FALSE) + + if (!is.null(crs)) { + net <- net |> sf::st_transform(crs) + } +} diff --git a/R/retreive-osm.R b/R/retreive-osm.R new file mode 100644 index 0000000..3d22c00 --- /dev/null +++ b/R/retreive-osm.R @@ -0,0 +1,79 @@ +#' Retrieve bounding box from OpenStreetMap +#' +#' @param name A character string with the name of the place to retrieve the bounding box +#' +#' @return A list with the bounding box +#' @export +#' +#' @examples +#' osm_bb("Bucharest") +osm_bb <- function(name) { + bb <- osmdata::getbb(name) +} + +#' Retrieve OpenStreetMap data as sf object +#' +#' Query the Overpass API for a key:value pair within a given bounding box. +#' +#' @param key A character string with the key to filter the data +#' @param value A character string with the value to filter the data +#' @param bb A list with the bounding box +#' +#' @return An sf object with the retrieved OpenStreetMap data +#' @export +osmdata_as_sf <- function(key, value, bb) { + bb |> + osmdata::opq() |> + osmdata::add_osm_feature(key = key, value = value) |> + osmdata::osmdata_sf() +} + +#' Retrieve OpenStreetMap data for a given location +#' +#' @param name A character string with the name of the place to retrieve the bounding box +#' @param key A character string with the key to filter the data +#' @param value A character string with the value to filter the data +#' +#' @return An sf object with the retrieved OpenStreetMap data for the given location +#' @export +#' +#' @examples +#' get_osmdata("Bucharest", "waterway", "river") +get_osmdata <- function(name, key, value) { + bb <- CRiSp::osm_bb(name) + CRiSp::osmdata_as_sf(key, value, bb) +} + +name <- NULL +#' Get OpenStreetMap data for a river corridor +#' +#' @param city_name A character string with the name of the place to retrieve the bounding box +#' @param river_name A character string with the name of the river +#' @param epsg_code An integer with the EPSG code for the projection +#' @param buffer_dist A numeric with the buffer distance in meters from the water stream +#' +#' @return An sf object with the river corridor +#' @export +get_osmdata_river_corridor <- function(city_name, river_name, epsg_code, buffer_dist) { + key = "waterway" + value = "river" + waterways <- CRiSp::get_osmdata(city_name, key, value) + waterway <- waterways$osm_multilines |> + dplyr::filter(name == river_name) |> + sf::st_transform(epsg_code) |> + sf::st_geometry() + + key = "natural" + value = "water" + water <- CRiSp::get_osmdata(city_name, key, value) + + waterbody <- dplyr::bind_rows(water$osm_polygons, water$osm_multipolygons) |> + sf::st_transform(epsg_code) |> + sf::st_filter(waterway, .predicate = sf::st_intersects) |> + sf::st_geometry() |> + sf::st_union() + + corridor_initial <- c(waterway, waterbody) |> + sf::st_buffer(buffer_dist) |> + sf::st_union() +} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..ff882f9 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,35 @@ +#' Add latitude and longitude information to an sf object +#' +#' Add latitude and longitude information to an sf object (WGS84) +#' +#' @param x Object of class sf, sfc or sfg +#' +#' @return An sf object with latitude and longitude +#' @export +get_latlon <- function(x) { + sf::st_transform(x, 4326) |> + sf::st_geometry() +} + +#' Check if two sf objects do not intersect +#' +#' @param x sf object +#' @param y sf object +#' +#' @return A logical vector +#' @export +not_intersects <- function(x, y) { + !sf::st_intersects(x, y) +} + +#' Calculate the weights of the edges of a network based on their length +#' +#' @param net A network object +#' +#' @return A network object with a new column 'weight' containing the length of the edges +#' @export +calc_weights <- function(net){ + net |> + sfnetworks::activate("edges") |> + dplyr::mutate(weight = sfnetworks::edge_length()) +} diff --git a/man/calc_weights.Rd b/man/calc_weights.Rd new file mode 100644 index 0000000..4fa5930 --- /dev/null +++ b/man/calc_weights.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{calc_weights} +\alias{calc_weights} +\title{Calculate the weights of the edges of a network based on their length} +\usage{ +calc_weights(net) +} +\arguments{ +\item{net}{A network object} +} +\value{ +A network object with a new column 'weight' containing the length of the edges +} +\description{ +Calculate the weights of the edges of a network based on their length +} diff --git a/man/cap_corridor.Rd b/man/cap_corridor.Rd new file mode 100644 index 0000000..fa1428e --- /dev/null +++ b/man/cap_corridor.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{cap_corridor} +\alias{cap_corridor} +\title{Cap corridor edges with a city boundary.} +\usage{ +cap_corridor(corridor_edges, river, crs, bb, cap = "city") +} +\arguments{ +\item{corridor_edges}{Edge of the corridor as a simple feature} + +\item{river}{River centerline as a simple feature} + +\item{crs}{A coordinate reference system as an epsg code, e.g. 4326 for WGS84} + +\item{bb}{A bounding box as a matrix with 4 elements: xmin, ymin, xmax, ymax} + +\item{cap}{Character string with the type of cap to be used. Default is "city"} +} +\value{ +A simple feature geometry +} +\description{ +Cap corridor edges with a city boundary. +} diff --git a/man/clean_network.Rd b/man/clean_network.Rd new file mode 100644 index 0000000..cf1f6ca --- /dev/null +++ b/man/clean_network.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{clean_network} +\alias{clean_network} +\title{Clean a street network by subdividing edges and removing pseudo-nodes.} +\usage{ +clean_network(net) +} +\arguments{ +\item{net}{A network object} +} +\value{ +A cleaned network object +} +\description{ +Clean a street network by subdividing edges and removing pseudo-nodes. +} diff --git a/man/create_network.Rd b/man/create_network.Rd new file mode 100644 index 0000000..c14c749 --- /dev/null +++ b/man/create_network.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/preprocess.R +\name{create_network} +\alias{create_network} +\title{Create a network from a line strings} +\usage{ +create_network(data, crs = NULL) +} +\arguments{ +\item{data}{A data frame with a column named 'highway' containing line strings} + +\item{crs}{A coordinate reference system as an epsg code, e.g. 4326 for WGS84} +} +\value{ +A network object +} +\description{ +Create a network from a line strings +} diff --git a/man/define_aoi.Rd b/man/define_aoi.Rd new file mode 100644 index 0000000..4088fd8 --- /dev/null +++ b/man/define_aoi.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{define_aoi} +\alias{define_aoi} +\title{Define an area of interest (AoI) from a bounding box and a coordinate reference system (CRS).} +\usage{ +define_aoi(bb, crs, buffer_dist = 0) +} +\arguments{ +\item{bb}{A bounding box as a matrix with 4 elements: xmin, ymin, xmax, ymax} + +\item{crs}{A coordinate reference system as an epsg code, e.g. 4326 for WGS84} + +\item{buffer_dist}{A numeric value to buffer the area of interest} +} +\value{ +An area of interest as a simple feature geometry +} +\description{ +Define an area of interest (AoI) from a bounding box and a coordinate reference system (CRS). +} diff --git a/man/delineate_corridor.Rd b/man/delineate_corridor.Rd new file mode 100644 index 0000000..5f35fb3 --- /dev/null +++ b/man/delineate_corridor.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{delineate_corridor} +\alias{delineate_corridor} +\title{Delineate a corridor around a river.} +\usage{ +delineate_corridor(place, river, crs) +} +\arguments{ +\item{place}{A place name as a string} + +\item{river}{A river name as a string} + +\item{crs}{A coordinate reference system as an epsg code, e.g. 4326 for WGS84} +} +\value{ +A simple feature geometry +} +\description{ +Delineate a corridor around a river. +} diff --git a/man/get_corridor_edge.Rd b/man/get_corridor_edge.Rd new file mode 100644 index 0000000..ee34648 --- /dev/null +++ b/man/get_corridor_edge.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{get_corridor_edge} +\alias{get_corridor_edge} +\title{Determine the corridor edge on the network.} +\usage{ +get_corridor_edge(net, area, vertices) +} +\arguments{ +\item{net}{A network object} + +\item{area}{An area of interest as a simple feature} + +\item{vertices}{A simple feature geometry set of two points} +} +\value{ +A simple feature geometry +} +\description{ +Find the corridor edge on one side of the river by using a shortest path algorithm. +} diff --git a/man/get_latlon.Rd b/man/get_latlon.Rd new file mode 100644 index 0000000..b16b9d2 --- /dev/null +++ b/man/get_latlon.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{get_latlon} +\alias{get_latlon} +\title{Add latitude and longitude information to an sf object} +\usage{ +get_latlon(x) +} +\arguments{ +\item{x}{Object of class sf, sfc or sfg} +} +\value{ +An sf object with latitude and longitude +} +\description{ +Add latitude and longitude information to an sf object (WGS84) +} diff --git a/man/get_osmdata.Rd b/man/get_osmdata.Rd new file mode 100644 index 0000000..17fd662 --- /dev/null +++ b/man/get_osmdata.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/retreive-osm.R +\name{get_osmdata} +\alias{get_osmdata} +\title{Retrieve OpenStreetMap data for a given location} +\usage{ +get_osmdata(name, key, value) +} +\arguments{ +\item{name}{A character string with the name of the place to retrieve the bounding box} + +\item{key}{A character string with the key to filter the data} + +\item{value}{A character string with the value to filter the data} +} +\value{ +An sf object with the retrieved OpenStreetMap data for the given location +} +\description{ +Retrieve OpenStreetMap data for a given location +} +\examples{ +get_osmdata("Bucharest", "waterway", "river") +} diff --git a/man/get_osmdata_river_corridor.Rd b/man/get_osmdata_river_corridor.Rd new file mode 100644 index 0000000..89ddc5f --- /dev/null +++ b/man/get_osmdata_river_corridor.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/retreive-osm.R +\name{get_osmdata_river_corridor} +\alias{get_osmdata_river_corridor} +\title{Get OpenStreetMap data for a river corridor} +\usage{ +get_osmdata_river_corridor(city_name, river_name, epsg_code, buffer_dist) +} +\arguments{ +\item{city_name}{A character string with the name of the place to retrieve the bounding box} + +\item{river_name}{A character string with the name of the river} + +\item{epsg_code}{An integer with the EPSG code for the projection} + +\item{buffer_dist}{A numeric with the buffer distance in meters from the water stream} +} +\value{ +An sf object with the river corridor +} +\description{ +Get OpenStreetMap data for a river corridor +} diff --git a/man/get_target_points.Rd b/man/get_target_points.Rd new file mode 100644 index 0000000..71c3af7 --- /dev/null +++ b/man/get_target_points.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{get_target_points} +\alias{get_target_points} +\title{Get start and end points of corridor edge on the network.} +\usage{ +get_target_points(vertices, area, threshold = 0.001) +} +\arguments{ +\item{vertices}{A simple feature geometry set of two points} + +\item{area}{An area of interest as a simple feature} + +\item{threshold}{A numeric value} +} +\value{ +A simple feature geometry set of two points +} +\description{ +TODO add description +} diff --git a/man/get_vertices.Rd b/man/get_vertices.Rd new file mode 100644 index 0000000..aef0844 --- /dev/null +++ b/man/get_vertices.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{get_vertices} +\alias{get_vertices} +\title{Determine the end vertices of the initial river corridor.} +\usage{ +get_vertices(aoi, corridor_initial) +} +\arguments{ +\item{aoi}{Area of interest as a simple feature} + +\item{corridor_initial}{Initial river corridor as a simple feature} +} +\value{ +A simple feature geometry set of two points +} +\description{ +Determine the "vertices" of the initial river corridor as the intersections +of the initial river corridor with the AoI boundary. +} diff --git a/man/merge_streets.Rd b/man/merge_streets.Rd new file mode 100644 index 0000000..de8eb84 --- /dev/null +++ b/man/merge_streets.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/preprocess.R +\name{merge_streets} +\alias{merge_streets} +\title{Merge streets from polygons and lines} +\usage{ +merge_streets(highways) +} +\arguments{ +\item{highways}{A list object containing streets from OpenStreetMap} +} +\value{ +A data frame with a column named 'highway' containing line strings +} +\description{ +Merge streets from polygons and lines +} diff --git a/man/not_intersects.Rd b/man/not_intersects.Rd new file mode 100644 index 0000000..6ea8cd5 --- /dev/null +++ b/man/not_intersects.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{not_intersects} +\alias{not_intersects} +\title{Check if two sf objects do not intersect} +\usage{ +not_intersects(x, y) +} +\arguments{ +\item{x}{sf object} + +\item{y}{sf object} +} +\value{ +A logical vector +} +\description{ +Check if two sf objects do not intersect +} diff --git a/man/osm_bb.Rd b/man/osm_bb.Rd new file mode 100644 index 0000000..5282a43 --- /dev/null +++ b/man/osm_bb.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/retreive-osm.R +\name{osm_bb} +\alias{osm_bb} +\title{Retrieve bounding box from OpenStreetMap} +\usage{ +osm_bb(name) +} +\arguments{ +\item{name}{A character string with the name of the place to retrieve the bounding box} +} +\value{ +A list with the bounding box +} +\description{ +Retrieve bounding box from OpenStreetMap +} +\examples{ +osm_bb("Bucharest") +} diff --git a/man/osmdata_as_sf.Rd b/man/osmdata_as_sf.Rd new file mode 100644 index 0000000..6bc8d81 --- /dev/null +++ b/man/osmdata_as_sf.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/retreive-osm.R +\name{osmdata_as_sf} +\alias{osmdata_as_sf} +\title{Retrieve OpenStreetMap data as sf object} +\usage{ +osmdata_as_sf(key, value, bb) +} +\arguments{ +\item{key}{A character string with the key to filter the data} + +\item{value}{A character string with the value to filter the data} + +\item{bb}{A list with the bounding box} +} +\value{ +An sf object with the retrieved OpenStreetMap data +} +\description{ +Query the Overpass API for a key:value pair within a given bounding box. +} diff --git a/man/simplify_network.Rd b/man/simplify_network.Rd new file mode 100644 index 0000000..9a86f6e --- /dev/null +++ b/man/simplify_network.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{simplify_network} +\alias{simplify_network} +\title{Simplify a street network by removing multiple edges and loops.} +\usage{ +simplify_network(net) +} +\arguments{ +\item{net}{A network object} +} +\value{ +A simplifed network object +} +\description{ +Simplify a street network by removing multiple edges and loops. +} diff --git a/man/split_aoi.Rd b/man/split_aoi.Rd new file mode 100644 index 0000000..c5c093c --- /dev/null +++ b/man/split_aoi.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{split_aoi} +\alias{split_aoi} +\title{Split the area of interest (AoI) by a river.} +\usage{ +split_aoi(aoi, river) +} +\arguments{ +\item{aoi}{Area of interest as a simple feature} + +\item{river}{River as a simple feature} +} +\value{ +A simple feature geometry set of two areas of interest +} +\description{ +Split the area of interest (AoI) by a river. +} diff --git a/man/trim_network.Rd b/man/trim_network.Rd new file mode 100644 index 0000000..a907753 --- /dev/null +++ b/man/trim_network.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/delineate-corridor.R +\name{trim_network} +\alias{trim_network} +\title{Trim a network to an area of interest (AoI) and a river corridor.} +\usage{ +trim_network(net, area, river_corridor) +} +\arguments{ +\item{net}{A network object} + +\item{area}{An area of interest as a simple feature} + +\item{river_corridor}{A river corridor as a simple feature} +} +\value{ +A network object +} +\description{ +Trim a network to an area of interest (AoI) and a river corridor. +} diff --git a/tests/testthat/test-hello.R b/tests/testthat/test-hello.R deleted file mode 100644 index 8849056..0000000 --- a/tests/testthat/test-hello.R +++ /dev/null @@ -1,3 +0,0 @@ -test_that("multiplication works", { - expect_equal(2 * 2, 4) -}) diff --git a/tests/testthat/test-osm_bb.R b/tests/testthat/test-osm_bb.R new file mode 100644 index 0000000..5279960 --- /dev/null +++ b/tests/testthat/test-osm_bb.R @@ -0,0 +1,6 @@ +test_that("OSM bounding box is correctly retreived from Overpass API", { + bb <- CRiSp::osm_bb("Bucharest") + expected <- matrix(c(25.966674, 44.334247, 26.225577, 44.541396), + ncol = 2, dimnames = list(c("x", "y"), c("min", "max"))) + expect_equal(bb, expected) +}) diff --git a/vignettes/corridor-delineation.Rmd b/vignettes/corridor-delineation.Rmd new file mode 100644 index 0000000..fb1f8df --- /dev/null +++ b/vignettes/corridor-delineation.Rmd @@ -0,0 +1,152 @@ +--- +title: "Corridor delineation" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Corridor delineation} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(CRiSp) +library(sf) +library(dplyr) +``` + +In this notebook we explore how to delineate a river corridor using Bucharest as the study area. We focus on one of the rivers and use a specific projected CRS for the analysis. Also, we make sure that we include a given area around the city boundaries. + +```{r variables} +city_name <- "Bucharest, Romania" # Be specific and spell as it appears in OSM +river_name <- "Dâmbovița" # Spell as it appears in OpenStreetMap +epsg_code <- 32635 # UTM zone 35N +bbox_buffer <- 2000 # Buffer around the city boundary in meters +``` + +## Setting the area of interest + +```{r aoi} +# Get the bounding box from the Nominatim API provided by OSM. +bb <- osm_bb(city_name) + +aoi <- define_aoi(bb, epsg_code, bbox_buffer) +``` + +```{r city} +city_boundary <- osmdata_as_sf("place", "city", bb)$osm_multipolygons |> + st_transform(epsg_code) |> + st_geometry() +``` + +## Constructing the initial corridor + +```{r river} +river_centerline <- osmdata_as_sf("waterway", "river", bb)$osm_multilines |> + filter(name == river_name) |> + st_transform(epsg_code) |> + st_geometry() |> + st_intersection(st_buffer(aoi, bbox_buffer)) + +river_surface <- osmdata_as_sf("natural", "water", bb) +river_surface <- river_surface$osm_multipolygons |> + bind_rows(river_surface$osm_polygons) |> + st_transform(epsg_code) |> + st_filter(river_centerline, .predicate = st_intersects) |> + st_geometry() |> + st_union() +``` + +With these features we can now delineate a theoretical river corridor representing the maximum area from which the river could be reached within a walking distance. We construct the initial corridor from the merged geometries of `river_centerline` and `river_surface`, with a buffer of 500m around them: + +```{r corridor_initial} +corridor_buffer <- 500 +corridor_initial <- c(river_centerline, river_surface) |> + st_buffer(corridor_buffer) |> + st_union() +``` + +We can now plot the initial corridor: + +```{r} +plot(city_boundary) +plot(corridor_initial, border = "red", main = "Initial river corridor", add = TRUE) +``` + +## Getting the street network + +Querying the Overpass API for the `highway` key: + +```{r streets} +highway_values <- c("motorway", "primary", "secondary", "tertiary") +streets <- osmdata_as_sf("highway", highway_values, bb) +streets <- merge_streets(streets) |> + select("highway") +``` + +We split the area of interest in two parts using the river centreline as separator. The two areas can then be used to split the network in two as a preparation to the shortest path algorithm used for edge delineation in the next step: + +```{r} +aoi_split <- aoi |> + split_aoi(river_centerline) + +net <- create_network(streets, epsg_code) + +net_1 <- net |> + trim_network(aoi_split[1], corridor_initial) |> + clean_network() |> + calc_weights() |> + sfnetworks::activate("nodes") |> + filter(tidygraph::group_components() == 1) + +net_2 <- net |> + trim_network(aoi_split[2], corridor_initial) |> + clean_network() |> + calc_weights() |> + sfnetworks::activate("nodes") |> + filter(tidygraph::group_components() == 1) +``` + +## Corridor edge delineation + +Determine the "vertices" of the initial river corridor as the intersections of the initial river corridor with the AoI boundary. We will use these points as extremes for the corridor edges: + +```{r} +vertices_1 <- get_vertices(aoi_split[1], corridor_initial |> st_cast("LINESTRING")) +vertices_2 <- get_vertices(aoi_split[2], corridor_initial |> st_cast("LINESTRING")) +``` + +```{r} +corridor_edge_1 <- get_corridor_edge(net_1, aoi_split[1], vertices_1) +corridor_edge_2 <- get_corridor_edge(net_2, aoi_split[2], vertices_2) + +corridor_edges <- st_union(corridor_edge_1, corridor_edge_2) +``` + +## Capping the corridor + +```{r} +capped_corridor <- city_boundary |> + lwgeom::st_split(corridor_edges) |> + sf::st_collection_extract("POLYGON") |> + sf::st_as_sf() |> + sf::st_filter(river_centerline, .predicate = sf::st_intersects) + +# TODO replace with call to cap_corridor() +# corridor <- cap_corridor(corridor_edges, river_centerline, epsg_code, bb) +``` + +Note that this is not ideal, as the municipal boundaries can be arbitrary and might exclude important end features of the corridors, so the user should have the option to input their own feature to cap the corridor ends. In the case of Bucharest, this can be the ring road. + +## All in one step + +```{r} +# TODO this function should run the entire delineation process +# delineate_corridor(city_name, river_name, epsg_code) +``` + diff --git a/vignettes/urc-delineation.Rmd b/vignettes/urc-delineation.Rmd deleted file mode 100644 index 4413c16..0000000 --- a/vignettes/urc-delineation.Rmd +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "1. Urban river corridor delineation" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{urc-delineation} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>" -) -``` - -```{r setup} -library(CRiSp) -``` diff --git a/vignettes/urs-delineation.Rmd b/vignettes/urs-delineation.Rmd deleted file mode 100644 index 06ad8c9..0000000 --- a/vignettes/urs-delineation.Rmd +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: "2. Urban river space delineation" -output: rmarkdown::html_vignette -vignette: > - %\VignetteIndexEntry{urs-delineation} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>" -) -``` - -```{r setup} -library(CRiSp) -```