A left join done as an overlay: every part of x keeps its own attributes and gains y's wherever the two overlap, NA everywhere else. x's footprint is preserved exactly – no area invented, none lost, and none counted twice – with geometries split at the overlay boundaries so that each location carries exactly one set of values.

overlay_left_join(x, y)

Arguments

x

SpatVector polygons; the left-hand layer, whose footprint is kept.

y

SpatVector polygons whose attributes are carried onto x. May be empty or miss x entirely, in which case y's columns come back all NA.

Value

SpatVector polygons covering exactly x, carrying the columns of x followed by those of y that x did not already have.

Why not a spatial join

sf::st_join() is not an overlay. It keeps whole geometries from x and emits one copy per feature of y they touch, so a polygon spanning two of y's features comes back twice at full extent, each copy carrying a different set of y's values. Measured on one study area, joining a disturbance layer to a forest inventory that way inflated 4.02 M polygons / 4.09 Mha into 7.78 M polygons / 39.5 Mha.

Why not a union

terra::union() has the documented semantics of the ArcGIS Union tool, which is what makes it the obvious choice, and on real geometry it returned output polygons overlapping one another and a dissolved footprint smaller than its input – double-counting some land while dropping other land outright, in opposite directions, which is what makes the totals look plausibly rather than obviously wrong. Measured on a 2,933-polygon window it gave 74,959.504 ha against a base of 74,738.411 ha with a dissolved footprint of 74,656.115 ha: 303 ha double-counted, 82 ha lost, and 233 s against 8 s here.

Reported as https://github.com/rspatial/terra/issues/2175 and fixed upstream. Intersection and difference partition x exactly, so the result here is a partition by construction rather than by trust.