Snaps point locations onto their nearest network edge and splits that edge
there, turning each snapped location into a routable node — the osmnxr
counterpart of sfnetworks::st_network_blend(), with the snapping done in
the Rust core (grid-indexed planar distance) so city-sized networks blend
in seconds.
Arguments
- g
An osm_graph to blend into.
- y
What to blend: an
sf/sfcofPOINTgeometries, or anotherosm_graph(same CRS asg). Attribute columns of ansfyare carried onto the new nodes they create.- tolerance
Maximum snapping distance, in CRS units. Points (or
ynodes) farther than this from every edge ofgare not blended. DefaultInf.- connector_attrs
Named list of constant attribute values for the connector edges created in graph mode (e.g.
list(highway = "connector", travel_time = 60)). Columns the connectors do not set areNA-filled — in particular, settravel_timehere (or fix it afterwards) before routing the blended graph on travel time. Ignored whenyis a set of points.- snap_nodes
Which points (or
ynodes) may be snapped:TRUE(the default, all of them) or a logical vector with one flag per point /ynode. Flagged-off points get anNArow inmeta$blend; in graph mode, flagged-offynodes still enter the union withy's own edges — they just get no connector (e.g. a station's street-level gates snap, its platforms don't and are reached through the gates).- snap_edges
Which edges of
gmay receive snapped points:TRUE(the default, all of them) or a logical vector with one flag per row ofg$edges(e.g. rule out limited-access flyovers that pass close by). Note the flag governs where points may land, not split propagation: when a snapped edge is split, edges sharing its geometry (the reversed twin of a two-way street) are split with it regardless of their own flag, to keep both directions routable.- connector_node_attrs
Character vector of
ynode attribute columns to copy onto the two connector edges of each snappedynode in graph mode (e.g. a per-station access time that should weight the connector). Errors when a column is absent fromy's nodes or clashes withconnector_attrsor a connector-reserved column (u,v,length). Ignored whenyis a set of points.
Value
An osm_graph. meta$blend records the mapping as
a tibble: one row per input point (or y node not
shared with g), with the node osmid it became or attached to (NA
when beyond tolerance or flagged off by snap_nodes), the snapping
distance, and u / v — the endpoints of the g edge it snapped onto,
as that edge was before the blend (splitting replaces it with pieces
chained through the new nodes).
Details
Two-way streets are handled properly: every edge sharing the split edge's
geometry (its reversed twin, or a same-direction duplicate) is split at the
same location into the same new node, so routability is preserved in both
directions. Points that project onto an existing node are attached to that
node instead of splitting. Points farther than tolerance from every edge
are left out with a warning. New nodes get consecutive osmids starting
just above the largest id in sight (g's — and y's too in graph mode),
so existing ids keep their meaning.
When y is another osm_graph, the result is a combined
multimodal network, built in three steps (as proposed in the issue that
motivated this feature): (1) every node of y (within tolerance, and
flagged by snap_nodes) is blended into g as above; (2) the two graphs
are stacked with ox_union(), so y's own nodes and edges are kept
intact; (3) a pair of directed connector edges is added between each y
node and the node it snapped to, which is the connectivity a plain blend +
union would miss. Node osmids shared by both graphs are merged by the
union directly (no connector needed) — but only when they sit at the same
coordinates: reusing an id of g for a genuinely different location in
y is an error (renumber y's ids first). The union here does not
deduplicate edges (the two layers are assumed to be distinct networks);
combine overlapping downloads with ox_union() first when that matters.
Distances are planar, in the units of the graph's CRS (reproject
geographic networks first — sf::st_transform() on nodes and edges —
when a metric tolerance matters). Split edges keep their attributes;
length is
recomputed from the new geometries, and travel_time / penalty (when
present) are rescaled proportionally to each piece's share of the parent
edge, so routing weights stay consistent.
Examples
g <- example_osm_graph()
# blend two POIs into the grid: each becomes a routable node
pts <- sf::st_sfc(sf::st_point(c(150, 40)), sf::st_point(c(250, 260)),
crs = 3857)
b <- ox_blend(g, pts)
b$meta$blend
#> # A tibble: 2 × 5
#> point node dist u v
#> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 1 17 40 5 9
#> 2 2 18 40 12 16
ox_shortest_path(b, b$meta$blend$node[1], b$meta$blend$node[2])
#> [1] 17 9 10 11 12 18
# blend a tiny second network (e.g. a metro line) into the grid
metro <- new_osm_graph(
sf::st_sf(osmid = c(1001, 1002), access_time = c(30, 45),
geometry = sf::st_sfc(sf::st_point(c(40, 150)),
sf::st_point(c(260, 150)), crs = 3857)),
sf::st_sf(u = 1001, v = 1002, length = 220,
geometry = sf::st_sfc(sf::st_linestring(
rbind(c(40, 150), c(260, 150))), crs = 3857)),
meta = list(network_type = "metro")
)
mm <- ox_blend(g, metro, connector_attrs = list(highway = "connector"),
connector_node_attrs = "access_time")
mm
#>
#> ── osm_graph ───────────────────────────────────────────────────────────────────
#> 20 nodes, 57 edges
#> Network type: "drive+metro"
#> Simplified: FALSE
#> CRS: "EPSG:3857"