0

I have the following dataframe with an ID column and XY point coordinates. It's a simple case with only two points per line (dataframe only has ID pairs):

data <- data.frame(
    id = c("1C1", "1C1", "1C2", "1C2"),
    X = c(666427.3830, 666440.9899, 666375.8780, 666365.8079),
    Y = c(4681239.659, 4681239.3219,4680517.0930, 4680526.1789))

Each ID is duplicated because it points to the start and end of a line. I have created an R script to perform the following steps with the sf package:

  1. Filter the dataframe by ID
  2. Create a LINESTRING geometry object
  3. Find each centroid
  4. Store the ID and its centroid point in a new dataframe
library(sf)
points <- st_as_sf(data, coords = c('X', 'Y'), crs = 25830)

# Get unique IDs
ids <- levels(as.factor(points$id))
# Apply a function to get points from the above IDs and
# create a LINESTRING object
makeLine <- function(id){
    # Select two points (start and end) by ID
    ps <- points[points$id == id,]
    # Create a Linestring geometry object
    geo <- st_cast(st_union(ps[1, 'geometry'], ps[2, 'geometry']), "LINESTRING")
    return(geo)
}
# Get lines by points
lines <- lapply(ids, makeLine)
# Unlist values to extract the sf geometry objects
lines_ <- do.call(c, unlist(lines, recursive=FALSE))
# Save centroids
centroids <- st_centroid(lines_)
# Create a new sf object with centroids and their IDs
points_c <- st_as_sf(data.frame(ids = ids, centroids))
plot(points_c)

Is there a better way to achieve the above result in R?

I know that in this GIS StackExchange post and in this one there are approaches to do these operations, but I want to perform the analysis in R. This post to create a line by 2 geometry columns in the same dataframe gave me some inspiration.

R version 4.3.3 (2024-02-29 ucrt)
sf version 1.0-16
2
  • Do you only ever have two points in each line? Or can lines have more than two points (ie duplicated ID values)? What about one point? Can that happen?
    – Spacedman
    Commented May 6 at 11:25
  • I have edited the question to clarify your point. Thanks for the comment!
    – ciranzo
    Commented May 6 at 11:37

1 Answer 1

2

I don't know if this a "better" way, but thanks to the integration of sf and dplyr you can also do a group_by(id) + summarise() (that is similar to perform a st_union by level). The code would be simplified as:

data <- data.frame(
  id = c("1C1", "1C1", "1C2", "1C2"),
  X = c(666427.3830, 666440.9899, 666375.8780, 666365.8079),
  Y = c(4681239.659, 4681239.3219,4680517.0930, 4680526.1789))


library(sf)
library(dplyr)

points <- st_as_sf(data, coords = c('X', 'Y'), crs = 25830)

# Make lines
lines <- points %>%
  group_by(id) %>%
  summarise() %>%
  st_cast("LINESTRING")

lines
#> Simple feature collection with 2 features and 1 field
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 666365.8 ymin: 4680517 xmax: 666441 ymax: 4681240
#> Projected CRS: ETRS89 / UTM zone 30N
#> # A tibble: 2 x 2
#>   id                                geometry
#>   <chr>                     <LINESTRING [m]>
#> 1 1C1     (666427.4 4681240, 666441 4681239)
#> 2 1C2   (666365.8 4680526, 666375.9 4680517)

# And just the centroids
centroids <- st_centroid(lines)
#> Warning in st_centroid.sf(lines): st_centroid assumes attributes are constant
#> over geometries of x

centroids
#> Simple feature collection with 2 features and 1 field
#> Geometry type: POINT
#> Dimension:     XY
#> Bounding box:  xmin: 666370.8 ymin: 4680522 xmax: 666434.2 ymax: 4681239
#> Projected CRS: ETRS89 / UTM zone 30N
#> # A tibble: 2 x 2
#>   id              geometry
#> * <chr>        <POINT [m]>
#> 1 1C1   (666434.2 4681239)
#> 2 1C2   (666370.8 4680522)

Created on 2024-05-06 with reprex v2.1.0

2
  • Thank you for your comment. I compared your approach with mine by applying the codes to a CSV with 180 points. My version takes 1.22 seconds, while yours takes 0.8 seconds, so I assume it's better in terms of performance. I have one more question: why does a warning message appear when your st_centroid is applied? I don't get that warning.
    – ciranzo
    Commented May 6 at 12:05
  • 2
    I think that's because your approach creates sfc objects (i.e., just the geometry without extra features) while mine creates a tibble/sf object preserving the "feature" id. If you do st_geometry(lines) |> st_centroid() you won't see the warning, that can be in any case ignored safely.
    – dieghernan
    Commented May 6 at 16:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.