% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/spatial.R
\name{spatial_join}
\alias{spatial_join}
\title{Spatial join a streamed query against a resident sf object}
\usage{
spatial_join(
  x,
  y,
  join = NULL,
  geom = "geometry",
  coords = NULL,
  crs = NA,
  left = TRUE,
  suffix = c(".x", ".y"),
  partition = NULL,
  y_geom = NULL,
  y_coords = NULL,
  out_geom = NULL,
  flush_rows = NULL,
  ...
)
}
\arguments{
\item{x}{A \code{vectra_node} (from \code{\link[=tbl]{tbl()}}, \code{\link[=tbl_tiff]{tbl_tiff()}}, any verb chain, ...).
It is consumed by the stream.}

\item{y}{The right side of the join: an \code{sf} object held resident (the
default), or -- when \code{partition} is given -- a streamed \code{vectra_node}.}

\item{join}{An \pkg{sf} binary predicate function, e.g. \link[sf:st_intersects]{sf::st_intersects}
(default), \link[sf:st_within]{sf::st_within}, \link[sf:st_contains]{sf::st_contains}, \link[sf:st_nearest_feature]{sf::st_nearest_feature}.}

\item{geom}{Name of the input geometry column holding hex-WKB or WKT strings.
Default \code{"geometry"}. Ignored when \code{coords} is given.}

\item{coords}{Optional length-2 character vector naming the x and y
coordinate columns to assemble point geometry from (e.g. \code{c("x", "y")}),
for inputs such as \code{\link[=tiff_extract_points]{tiff_extract_points()}} output. The coordinate columns
are retained.}

\item{crs}{Coordinate reference system of the input geometry, in any form
\code{\link[sf:st_crs]{sf::st_crs()}} accepts (EPSG integer, WKT, proj string). Defaults to the
CRS the upstream node carries, or unknown.}

\item{left}{If \code{TRUE} (default) keep every left row (left join); if \code{FALSE}
keep only matches (inner join).}

\item{suffix}{Length-2 character vector disambiguating columns present on
both sides. Default \code{c(".x", ".y")}.}

\item{partition}{Optional \code{\link[=grid]{grid()}} specification enabling the two-sided
streamed path, in which \code{y} is itself a \code{vectra_node}. Default \code{NULL} keeps
the resident-\code{y} path.}

\item{y_geom, y_coords}{Geometry transport for a streamed \code{y} under
\code{partition}: the name of \code{y}'s hex-WKB geometry column (\code{y_geom}, default
the left \code{geom}), or a length-2 character vector of \code{y}'s coordinate columns
(\code{y_coords}). Ignored without \code{partition}.}

\item{out_geom}{Name of the output geometry column. Defaults to \code{geom}
(or \code{"geometry"} when \code{coords} is used).}

\item{flush_rows}{Transformed rows buffered before a spill flush. Larger
values mean fewer, bigger temporary files. Defaults to
\code{getOption("vectra.spatial_flush", 5e5)}.}

\item{...}{Further arguments passed to \code{\link[sf:st_join]{sf::st_join()}}.}
}
\value{
A \code{vectra_node} of the joined stream, backed by temporary \code{.vtr}
spills and carrying the left CRS.
}
\description{
Streams a large left side \code{x} through the engine and joins each batch against
a small right side \code{y} held resident in memory, using an \pkg{sf} binary
predicate (\code{st_intersects} by default). This is the spatial analogue of a
hash join with the small side on the build side: the billion-row left stream
never materializes, while \code{y} (admin polygons, habitat patches, ...) stays in
RAM. The dominant real workload it serves is tagging huge point sets with the
polygon they fall in.
}
\details{
For the recognised predicates -- the topological ones (intersects, within,
contains, overlaps, covers, covered by, touches, crosses), equals,
within-distance (\link[sf:st_is_within_distance]{sf::st_is_within_distance}, radius passed as \verb{dist =}), and
nearest feature (\link[sf:st_nearest_feature]{sf::st_nearest_feature}) -- on projected or unprojected
planar data, the match runs natively on the GEOS C API straight off the
hex-WKB column: \code{y} is parsed once into a spatial index, each batch's matches
come back from C, and \code{y}'s attributes are attached in R without decoding the
left side to \pkg{sf}. Coordinate-assembled (\code{coords}) point input runs
natively too, building each point in C (the emitted point geometry is built in
C as well). Geographic coordinates with spherical geometry on
(\code{sf::sf_use_s2()}), a disjoint join (whose matches are the bounding-box
complement an index cannot prune), and other extra \code{\link[sf:st_join]{sf::st_join()}} arguments
use \pkg{sf} instead, preserving its semantics.

When both sides are larger than RAM, pass \code{partition = grid(cellsize)} and a
streamed \code{vectra_node} as \code{y}: both inputs are binned to a uniform spatial
grid, then joined one shard at a time. Each left feature is assigned to the
single grid cell of its reference point while each right feature is
replicated to every cell its bounding box overlaps, so a left row is emitted
exactly once and the result equals the resident join. This is exact for point
left geometries (the dominant case -- tagging a huge point set with the
polygon it falls in) and finds, for an extended left feature, the matches
whose right bounding box overlaps the left reference cell; choose a \code{cellsize}
larger than the left features for an extended-on-extended join. The partition
path serves topological predicates (intersects, within, contains, overlaps,
covers, covered by). It also serves \link[sf:st_nearest_feature]{sf::st_nearest_feature}: because nearest
is not local to one cell, each left feature then searches its own cell and the
eight around it, so the true nearest is found when it lies within one cell of
the left reference cell (pick a \code{cellsize} at least the largest expected
nearest distance). Topology and CRS handling are \pkg{sf}'s; vectra supplies
the stream and the grid partition.
}
\examples{
\dontshow{if (requireNamespace("sf", quietly = TRUE)) withAutoprint(\{ # examplesIf}
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)

# A stream of points, stored with x/y coordinate columns.
set.seed(1)
pts <- sf::st_coordinates(sf::st_sample(nc, 200))
f <- tempfile(fileext = ".vtr")
write_vtr(data.frame(id = seq_len(nrow(pts)), x = pts[, 1], y = pts[, 2]), f)

# Tag each point with the county it falls in, streaming.
tagged <- tbl(f) |>
  spatial_join(nc["NAME"], join = sf::st_intersects,
               coords = c("x", "y"), crs = sf::st_crs(nc))
head(collect(tagged))

# Both sides streamed: bin to a grid and join per shard. Here y is a
# vectra_node rather than a resident sf object.
g <- tempfile(fileext = ".vtr")
write_vtr(data.frame(
  NAME = nc$NAME,
  geometry = sf::st_as_binary(sf::st_geometry(nc), hex = TRUE)
), g)
tagged2 <- tbl(f) |>
  spatial_join(tbl(g), coords = c("x", "y"), crs = sf::st_crs(nc),
               partition = grid(0.5))
head(collect(tagged2))
unlink(c(f, g))
\dontshow{\}) # examplesIf}
}
\seealso{
\code{\link[=spatial_map]{spatial_map()}} for per-feature transforms, \code{\link[=collect_sf]{collect_sf()}} to
materialize as \code{sf}, \code{\link[=offload]{offload()}} to partition both-sides-huge joins.
}
