I'm trying to calculate distances from a set of POINT features (1, 2, 3, 4, 5, 6 & 7) to a LINESTRING feature using sf library in R. I could calculate all the distances using the function st_distance but I can't get which POINT feature from LINESTRING feature were used by the function to calculate those distances:
- First example: if the minimum distance between
POINT"2" and theLINESTRINGis the distance between POINT "2" and the POINT "A" in theLINESTRING, how can I get POINT "A"? - Second example: if the minimum distance between
POINT"4" and theLINESTRINGis the distance between POINT "4" and the POINT "X" that is located between POINT "B" and POINT "C" in theLINESTRING, how can I get POINT "X"?
Note: I know it's possible to achieve this using geosphere::dist2Line function but at the moment of this question geosphere package don't have compatibility with simple features objects. So I'm trying to solve this issue using sf without using Spatial objects from sp.
Below is a reproducible code example and a figure of the problem:
# Load Libraries ----------------------------------------------------------
library('sf')
# Test data ---------------------------------------------------------------
points.df <- data.frame(
'x' = c(-53.50000, -54.15489, -54.48560, -52.00000, -52.57810, -49.22097, -48.00000),
'y' = c(-38.54859, -39.00000, -38.80000, -38.49485, -38.00000, -38.50000, -37.74859)
)
line.df <- data.frame(
'x' = c(-52.53557, -52.00000, -50.00000, -48.00000, -46.40190),
'y' = c(-41.00000, -40.60742, -40.08149, -40.82503, -39.00000)
)
# Create 'sf' objects -----------------------------------------------------
points.sf <- st_as_sf(points.df, coords = c("x", "y"))
st_crs(points.sf) <- st_crs(4326) # assign crs
points.sf <- st_transform(points.sf, crs = 32721) # transform
line.sf <- st_sf(id = 'L1', st_sfc(st_linestring(as.matrix(line.df), dim = "XY")))
st_crs(line.sf) <- st_crs(4326) # assign crs
line.sf <- st_transform(line.sf, crs = 32721) # transform
# Plots -------------------------------------------------------------------
xmin <- min(st_bbox(points.sf)[1], st_bbox(line.sf)[1])
ymin <- min(st_bbox(points.sf)[2], st_bbox(line.sf)[2])
xmax <- max(st_bbox(points.sf)[3], st_bbox(line.sf)[3])
ymax <- max(st_bbox(points.sf)[4], st_bbox(line.sf)[4])
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)
# Distances ---------------------------------------------------------------
distances <- st_distance(x = points.sf, y = line.sf)
print(distances)
Units: m
[,1]
[1,] 262727.8
[2,] 256710.2
[3,] 292476.9
[4,] 223153.4
[5,] 291143.6
[6,] 188868.4
[7,] 198670.4
Answer
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.
# 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")
# 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")



No comments:
Post a Comment