Nothing
#' Extract a routable graph from a geographic source
#'
#' Single entry point to build a \link{makegraph}-ready graph from a geographic linestring source. The
#' source is dispatched on automatically from \code{x}'s type (file extension, or object class). Exactly
#' five forms of \code{x} are recognized -- see the \code{x} parameter below for the precise contract of
#' each, since passing the wrong shape of object for a given source (e.g. only part of an \code{osmdata}
#' result) fails loudly rather than silently producing a wrong graph.
#'
#' Arguments are grouped by which family of source they apply to: arguments that work the same way
#' regardless of source stay as their own top-level parameter; source-specific ones are grouped into the
#' \code{osm_params} or \code{geom_params} list (see those two parameters below for the full list of what
#' each one accepts) -- so a shapefile/\code{sf} call never needs to mention \code{profile}/
#' \code{use_maxspeed}, and a \code{.pbf}/\code{osmdata}/\code{osmextract} call never needs to mention
#' \code{cost_field}/\code{direction_field}.
#'
#' @param x One of:
#' \itemize{
#' \item a path (character) to a \strong{\code{.shp}} shapefile -- the companion \code{.dbf}/\code{.prj}
#' must sit next to it, same base name;
#' \item an in-memory \strong{\code{sf}} object (\code{LINESTRING}/\code{MULTILINESTRING}), e.g. from
#' \code{sf::st_read()} on a GeoPackage or any other OGR-readable format;
#' \item a path (character) to an \strong{\code{.osm.pbf}} file;
#' \item the \strong{full result of \code{osmdata::osmdata_sf()}} (class \code{"osmdata"}) -- not just its
#' \code{$osm_lines} element. \code{$osm_points} must also be present: it holds the \code{osm_id} of every
#' node osmdata downloaded (including untagged vertices in the middle of a way), which is what lets
#' \code{cpp_extract} recover real OSM topology and detect junctions;
#' \item for \strong{\code{osmextract}}: not a distinct input type -- get the path to the \strong{raw
#' cached \code{.pbf}} with \code{osmextract::oe_get(..., download_only = TRUE, skip_vectortranslate =
#' TRUE)} (\strong{both} flags are required: \code{download_only} alone still runs the GDAL conversion and
#' returns a \code{.gpkg} path instead) and pass that \code{.pbf} path like any other \code{.pbf} file.
#' Do not let osmextract convert it to \code{sf}/GeoPackage via GDAL first: that conversion only keeps
#' tagged nodes by default, which is not enough to reconstruct topology.
#' }
#' GeoPackage is not supported and has no plans to be: convert it to a shapefile first (e.g. via
#' \code{sf::st_write(sf::st_read("file.gpkg"), "file.shp")}, or with any GIS tool) and use that instead.
#'
#' @param output One of \code{"graph"} (default, returns a ready-to-use \code{cppRouting_graph}),
#' \code{"data.frame"}, or \code{"csv"}. Common to every source.
#' @param out_prefix Path prefix used when \code{output = "csv"}. Common to every source.
#' @param coords logical. If \code{TRUE} (default), extract each node's coordinates: written to
#' \code{<out_prefix>_coords.csv} (\code{output = "csv"}, including the \code{.pbf} \code{max_mem}
#' disk-backed pipeline), returned as a \code{data.frame(node_id, X, Y)} (\code{output = "data.frame"}), or
#' passed to \code{makegraph(coords = ...)} (\code{output = "graph"}). Set to \code{FALSE} to skip this --
#' e.g. if you already have node coordinates elsewhere and only need the graph topology. For the
#' \code{max_mem} pipeline this also skips an entire external sort/merge pass, a real saving in disk I/O
#' and time at continent scale, not just a smaller output. If the source coordinates are geographic
#' (lat/lon -- always true for \code{.pbf}/\code{osmdata}) and \code{coords = TRUE}, \code{epsg} must be
#' supplied (see below), otherwise this errors rather than silently returning coordinates that break the
#' A*/NBA algorithms (see \link{get_distance_pair}). Common to every source.
#' @param epsg Optional. A projected EPSG code (or anything \code{sf::st_crs()} accepts) to reproject node
#' coordinates into, via \code{sf::st_transform()} (requires the \code{sf} package). Only takes effect if
#' \code{coords = TRUE} and the source CRS is geographic; already-projected coordinates (possible for
#' shapefile/sf sources) are left as-is regardless. Together with \code{osm_params$max_mem} on a
#' \code{.pbf} source, the reprojection runs in row chunks sized from the \code{max_mem} budget, so peak
#' RAM stays within that budget even on the disk-backed pipeline. Common to every source.
#' @param keep_length logical. If \code{TRUE}, keep each edge's length (in km) as \code{aux}, regardless of
#' how the main \code{cost} was actually resolved -- e.g. to get a time-based graph while still reporting
#' physical distance. Shapefile/sf sources: the geometric length (unreliable for a geographic CRS, same
#' caveat as \code{geom_params$length_field} below -- reproject or use \code{geom_params$keep_fields} with
#' your own length column instead in that case). OSM sources: the haversine length. \code{output = "csv"}
#' carries the same information as \code{output = "data.frame"}: \code{aux} is written to
#' \verb{<out_prefix>_aux.csv}. Common to every source.
#' @param snap_digits integer. Shapefile/sf sources: decimal places used to round coordinates before
#' deduplicating line endpoints into nodes. \code{osmdata} sources: decimal places used to match line
#' vertex coordinates against \code{$osm_points} coordinates. Unused for \code{.pbf} (topology comes from
#' OSM node ids directly, no coordinate matching needed). Defaults (\code{NULL}) to \code{7} for geographic
#' (lat/lon) coordinates or \code{4} for projected (meter-scale) ones -- geographic coordinates need many
#' decimal places to distinguish nearby points, but that precision is far too tight for projected
#' coordinates, where it starts picking up floating-point noise between independently-digitized but
#' logically-identical points rather than real geometric differences. The one argument that doesn't sort
#' cleanly into \code{osm_params}/\code{geom_params}: it applies to shapefile/sf sources and to
#' \code{osmdata}, but not to \code{.pbf}.
#'
#' @param osm_params A list of options for OSM sources (\code{.pbf}/\code{osmdata}/\code{osmextract}) only,
#' ignored for shapefile/\code{sf}. Any subset may be supplied -- omitted elements keep their default below;
#' an unrecognized element name raises an error immediately (rather than silently keeping its default, which
#' is what a typo'd list element would otherwise do). Recognized elements:
#' \itemize{
#' \item \code{mode}: one of \code{"car"} (default), \code{"foot"}, \code{"bicycle"} or \code{"hgv"} --
#' which access-tag/oneway resolution rules to apply (see \link{graph_from_osmpbf}'s \code{@details} for
#' the exact tags checked per mode). If \code{profile} is left \code{NULL}, also selects the matching
#' default profile (\link{osm_car_profile}/\link{osm_foot_profile}/\link{osm_bike_profile}/
#' \link{osm_hgv_profile}).
#' \item \code{profile}: a data.frame with columns \code{highway}, \code{clazz}, \code{speed} (see
#' \code{osm_car_profile} and friends): which \code{highway} tag values to keep, and their class id /
#' default speed (km/h) when not overridden by \code{maxspeed}. Defaults (\code{NULL}) to the profile
#' matching \code{mode}; pass your own to override independently of \code{mode}.
#' \item \code{use_maxspeed}: logical, default \code{TRUE}. If \code{TRUE}, a way's \code{maxspeed} tag
#' overrides the profile's default speed when present and parseable (plain km/h, or \code{"<n> mph"}). No
#' effect for \code{mode = "foot"}/\code{"bicycle"}.
#' \item \code{assume_oneway_motorway}: logical, default \code{TRUE}. If \code{TRUE}, \code{motorway}/
#' \code{motorway_link} ways are treated as one-way unless explicitly tagged \code{oneway=no}. Irrelevant
#' for \code{mode = "foot"} (oneway is always ignored for that mode).
#' \item \code{keep_clazz}: logical, default \code{FALSE}. If \code{TRUE}, keep each edge's resolved
#' highway class id as \code{aux}.
#' \item \code{max_mem}: \code{.pbf} only (ignored for \code{osmdata}). A budget in megabytes for a
#' disk-backed pipeline that spills every intermediate table (not just the final result) to disk, keeping
#' peak RAM close to this budget regardless of the input file's size -- for files too large to fit in RAM
#' even with the default pipeline (e.g. a full-planet extract). Requires \code{output = "csv"}. See
#' \code{graph_from_osmpbf} for details and the (rare but possible) requirement that nodes appear in
#' increasing id order in the file.
#' \item \code{tmp_path}: \code{.pbf} only. Directory for temporary files when \code{max_mem} is set.
#' Defaults to \code{tempdir()}.
#' \item \code{progress}: logical, default \code{TRUE}. Print progress messages while reading/matching.
#' }
#' @param geom_params A list of options for shapefile/\code{sf} sources only, ignored for OSM sources. Any
#' subset may be supplied -- omitted elements keep their default below; an unrecognized element name raises
#' an error immediately (same reasoning as \code{osm_params} above). Recognized elements:
#' \itemize{
#' \item \code{cost_field}: optional, name of a numeric attribute column to use directly as edge cost.
#' \item \code{speed_field}: optional, name of a numeric attribute column holding a speed in km/h. Used
#' with \code{length_field} (in km) to derive a cost in minutes as \code{60 * length / speed}. Ignored if
#' \code{cost_field} is provided.
#' \item \code{length_field}: optional, name of a numeric attribute column holding each line's length, in
#' km (to match the \code{60 * length / speed} formula above). If \code{NULL} and \code{speed_field} is
#' used, length is instead computed from the line geometry itself and converted from the CRS's native unit
#' (assumed to be meters, true for any typical projected CRS) to km.
#' \item \code{direction_field}: optional, name of an attribute column describing the direction of travel
#' allowed on each line (e.g. a "sens" column). Combined with \code{direction_values}.
#' \item \code{direction_values}: optional, a list with up to three character vectors: \code{forward}
#' (only from the line's first point to its last), \code{backward} (only from last to first) and
#' \code{twoway} (both directions). Values of \code{direction_field} not listed anywhere default to
#' \code{twoway}, with a warning.
#' \item \code{directed}: logical, default \code{FALSE}. Used only when \code{direction_field} is
#' \code{NULL}. If \code{TRUE}, every line is kept as digitized (first point to last point); if
#' \code{FALSE}, every line is made bidirectional.
#' \item \code{keep_fields}: optional character vector of extra attribute columns to keep as \code{aux}
#' (see \link{makegraph}).
#' }
#'
#' @return Depending on \code{output}: a \code{cppRouting_graph}, a named list of two data.frames
#' (\code{data}, \code{coords}), or (invisibly) the paths of the written CSV files. For
#' \code{output = "data.frame"}, \code{from}/\code{to}/\code{node_id} are \code{character} regardless of
#' source: a node id is a label, not a quantity, matching what \code{makegraph()} itself already guarantees
#' for \code{dict$ref}. For \code{output = "graph"}, that same real id is carried through \code{dict$ref};
#' \code{data$from}/\code{data$to} stay \code{makegraph()}'s own compact integer index (its adjacency list is
#' always built on that, not on the original id).
#' @details Shapefile/sf sources are assumed to already be topologically clean, i.e. lines only meet at
#' their endpoints. OSM sources
#' (\code{.pbf} or \code{osmdata}) instead detect junctions in the middle of a way from OSM's own node ids
#' (a node referenced by 2 or more kept ways, or a way's own endpoint); for \code{.pbf}, a way referencing a
#' node outside the file (a regional extract cut through it) is dropped entirely, and for \code{osmdata}, a
#' way with a vertex that can't be matched against \code{$osm_points} is dropped, with a warning. A plain
#' \code{sf} object that looks OSM-derived (it has an \code{osm_id} column) but isn't the full
#' \code{osmdata} result is refused with an explanatory error, rather than silently treated as
#' topologically clean like a plain \code{sf} source and producing a wrong graph.
#' @export
cpp_extract <- function(x,
output = c("graph", "data.frame", "csv"),
out_prefix = NULL,
coords = TRUE,
epsg = NULL,
keep_length = FALSE,
snap_digits = NULL,
osm_params = list(),
geom_params = list()){
output <- match.arg(output)
osm_params_defaults <- list(mode = "car", profile = NULL, use_maxspeed = TRUE,
assume_oneway_motorway = TRUE, keep_clazz = FALSE,
max_mem = NULL, tmp_path = tempdir(), progress = TRUE)
geom_params_defaults <- list(cost_field = NULL, speed_field = NULL, length_field = NULL,
direction_field = NULL, direction_values = NULL,
directed = FALSE, keep_fields = NULL)
osm_params <- merge_arg_list(osm_params, osm_params_defaults, "osm_params")
geom_params <- merge_arg_list(geom_params, geom_params_defaults, "geom_params")
args <- c(geom_params, list(snap_digits = snap_digits, keep_length = keep_length,
output = output, out_prefix = out_prefix, coords = coords, epsg = epsg))
osm_args <- list(mode = osm_params$mode, profile = osm_params$profile, use_maxspeed = osm_params$use_maxspeed,
assume_oneway_motorway = osm_params$assume_oneway_motorway,
keep_clazz = osm_params$keep_clazz, keep_length = keep_length, output = output,
out_prefix = out_prefix, coords = coords, epsg = epsg, progress = osm_params$progress)
# Class is the reliable signal for a full osmdata result (class "osmdata",
# a list -- not itself an "sf" object): route it to the osmdata adapter,
# which needs BOTH $osm_lines and $osm_points (checked inside
# graph_from_osmdata) to reconstruct real OSM topology.
if (inherits(x, "osmdata")) {
return(do.call(graph_from_osmdata, c(list(x = x, snap_digits = snap_digits), osm_args)))
}
if (inherits(x, "sf")) {
if ("osm_id" %in% names(x)) {
stop("This sf object has an 'osm_id' column but isn't the full osmdata result (class 'osmdata'). ",
"Topology reconstruction needs $osm_points alongside $osm_lines -- pass the whole result of ",
"osmdata::osmdata_sf() instead of just its $osm_lines element. If this came from osmextract ",
"instead, don't let it convert to sf/gpkg via GDAL: get the raw cached .pbf path with ",
"osmextract::oe_get(..., download_only = TRUE, skip_vectortranslate = TRUE) (both flags are ",
"required) and pass that .pbf path to cpp_extract() directly -- treating this sf object as a ",
"plain topologically-clean network (like ",
"graph_from_sf() does) would silently produce a wrong graph, since OSM ways can have ",
"junctions in the middle of a line.")
}
return(do.call(graph_from_sf, c(list(x = x), args)))
}
if (is.character(x) && length(x) == 1) {
ext <- tolower(tools::file_ext(x))
if (ext == "shp") return(do.call(graph_from_shapefile, c(list(path = x), args)))
if (ext == "gpkg") {
stop("GeoPackage is not supported. Convert it to a shapefile first, e.g. ",
"sf::st_write(sf::st_read('file.gpkg'), 'file.shp'), and pass that .shp path instead.")
}
if (ext == "pbf") {
return(do.call(graph_from_osmpbf, c(list(path = x, max_memory = osm_params$max_mem, temp_dir = osm_params$tmp_path), osm_args)))
}
if (ext == "osm") stop("Plain OSM XML (.osm) is not supported, only the binary .osm.pbf format")
stop("Unrecognized file extension '.", ext, "'. Supported: .shp, .pbf")
}
stop("cpp_extract() does not know how to handle an object of class: ", paste(class(x), collapse = "/"))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.