How to calculate distance from POINT to LINESTRING in R using `sf` library and get all POINT features from LINESTRING where distances were calculated?

While sf package don't have a built-in function or geosphere is not compatible with sf objects I would use a wrapper around geosphere::dist2Line function: just getting the matrix of coordinates instead using the entire sf object.

I also tried @jsta answer based on sampling the line and I compared the differences between both approaches. Since I'm working with a big dataset of points, I prefer not to add more points.

Wrapping geosphere::dist2Line function approach

# Transform sf objects to EPSG:4326 ---------------------------------------

pointsWgs84.sf <- st_transform(points.sf, crs = 4326)
lineWgs84.sf <- st_transform(line.sf, crs = 4326)


# Calculate distances -----------------------------------------------------

dist <- geosphere::dist2Line(p = st_coordinates(pointsWgs84.sf), line = st_coordinates(lineWgs84.sf)[,1:2])


# Create 'sf' object ------------------------------------------------------

dist.sf <- st_as_sf(as.data.frame(dist), coords = c("lon", "lat"))
dist.sf <- st_set_crs(x = dist.sf, value = 4326)

# Plot --------------------------------------------------------------------

xmin2 <- min(st_bbox(pointsWgs84.sf)[1], st_bbox(lineWgs84.sf)[1])
ymin2 <- min(st_bbox(pointsWgs84.sf)[2], st_bbox(lineWgs84.sf)[2])
xmax2 <- max(st_bbox(pointsWgs84.sf)[3], st_bbox(lineWgs84.sf)[3])
ymax2 <- max(st_bbox(pointsWgs84.sf)[4], st_bbox(lineWgs84.sf)[4])

plot(pointsWgs84.sf, pch = 19, col = "#53A8BD", xlab = "Longitude", ylab = "Latitude",
     xlim = c(xmin2, xmax2), ylim = c(ymin2, ymax2), graticule = st_crs(4326), axes = TRUE)
plot(lineWgs84.sf, col = "#C72259", add = TRUE)
plot(dist.sf, col = "#6C5593", add = TRUE, pch = 19, cex = 0.75)
text(st_coordinates(pointsWgs84.sf), as.character(1:7), pos = 3, col = "#53A8BD")
text(st_coordinates(lineWgs84.sf), LETTERS[1:5], pos = 1, col = "#C72259")
text(st_coordinates(dist.sf), as.character(1:7), pos = 2, cex = 0.75, col = "#6C5593")

plotDist2Line

Sampling approach based on jsta answer

# i = which point, n = number of points to add to segment

samplingApproach <- function(i, n) {
     line.sf_sample <- st_line_sample(x = line.sf, n = n) 
     line.sf_sample <- st_cast(line.sf_sample, "POINT")
     closest <- line.sf_sample[which.min(st_distance(line.sf_sample, points.sf[i,]))]  
     closest <- st_transform(closest, crs = 4326)
     return(closest) 
}

# Differences between approaches with different 'n' 
n2 <- lapply(X = seq(100, 6000, 50), FUN = function(x) 
          st_distance(samplingApproach(i = 2, n = x), dist.sf[2,]))

# Plot differences --------------------------------------------------------

plot(y = n2, 
     x = seq(100, 6000, 50),
     ylim = c(0, 1000),
     xlab = "Density (n)", 
     ylab = "Difference (meters)", type = "p", cex = 0.5, pch = 19,
     col = "#6C5593") lines(x = c(0,6000), y = c(0, 0), col = "#EB266D", lwd = 2) lines(smooth.spline(y = n2, x = seq(100, 6000, 50), spar = 0.75), lwd = 2, col = "#272822")

differences


Try this:

line.sf_sample <- st_line_sample(line.sf, 60)
line.sf_sample <- st_cast(line.sf_sample, "POINT")
points.sf <- st_cast(points.sf, "POINT")

closest <- list()
for(i in seq_len(nrow(points.sf))){
    closest[[i]] <- line.sf_sample[which.min(
        st_distance(line.sf_sample, points.sf[i,]))]
}

plot(points.sf, pch = 19, col = "#53A8BD", 
     xlab = "Longitude", ylab = "Latitude", 
     xlim = c(xmin,xmax), ylim = c(ymin,ymax), 
     graticule = st_crs(4326), axes = TRUE)
plot(line.sf, col = "#C72259", add = TRUE)
text(st_coordinates(points.sf), as.character(1:7), pos = 3)
text(st_coordinates(line.sf), LETTERS[1:5], pos = 1)
plot(closest[[2]], add = TRUE, col = "green", pch = 19)

enter image description here

You can adjust the density argument on st_line_sample based on your tolerance for error.