Search code examples
rr-sfrgdal

increase polyline length r sf


I'd like to find a way to increase a polyline 1000 feet from the beginning and 1000 feet from the end points. Any help is greatly appreciated! Below is a sample.

structure(
    list(
        TECHNICAL_ = NA_character_,
        geometry = structure(
            list(
                structure(
                    c(812697.360851467,
                      813792.18162311,
                      430678.939150205,
                      425750.102767913),
                    .Dim = c(2L, 2L),
                    class = c("XY", "LINESTRING", "sfg")
                )
            ),
            n_empty = 0L,
            crs = structure(
                list(
                    input = "+init=epsg:2257",
                    wkt = "PROJCRS[\"NAD83 / New Mexico East (ftUS)\",\n    BASEGEOGCRS[\"NAD83\",\n        DATUM[\"North American Datum 1983\",\n            ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n                LENGTHUNIT[\"metre\",1]]],\n        PRIMEM[\"Greenwich\",0,\n            ANGLEUNIT[\"degree\",0.0174532925199433]],\n        ID[\"EPSG\",4269]],\n    CONVERSION[\"SPCS83 New Mexico East zone (US Survey feet)\",\n        METHOD[\"Transverse Mercator\",\n            ID[\"EPSG\",9807]],\n        PARAMETER[\"Latitude of natural origin\",31,\n            ANGLEUNIT[\"degree\",0.0174532925199433],\n            ID[\"EPSG\",8801]],\n        PARAMETER[\"Longitude of natural origin\",-104.333333333333,\n            ANGLEUNIT[\"degree\",0.0174532925199433],\n            ID[\"EPSG\",8802]],\n        PARAMETER[\"Scale factor at natural origin\",0.999909091,\n            SCALEUNIT[\"unity\",1],\n            ID[\"EPSG\",8805]],\n        PARAMETER[\"False easting\",541337.5,\n            LENGTHUNIT[\"US survey foot\",0.304800609601219],\n            ID[\"EPSG\",8806]],\n        PARAMETER[\"False northing\",0,\n            LENGTHUNIT[\"US survey foot\",0.304800609601219],\n            ID[\"EPSG\",8807]],\n        ID[\"EPSG\",15339]],\n    CS[Cartesian,2],\n        AXIS[\"(E)\",east,\n            ORDER[1],\n            LENGTHUNIT[\"US survey foot\",0.304800609601219,\n                ID[\"EPSG\",9003]]],\n        AXIS[\"(N)\",north,\n            ORDER[2],\n            LENGTHUNIT[\"US survey foot\",0.304800609601219,\n                ID[\"EPSG\",9003]]],\n    USAGE[\n        SCOPE[\"unknown\"],\n        AREA[\"USA - New Mexico - SPCS - E\"],\n        BBOX[32,-105.72,37,-102.99]]]"
                ),
                class = "crs"
            ),
            class = c("sfc_LINESTRING", "sfc"),
            precision = 0,
            bbox = structure(
                c(xmin = 812697.360851467,
                  ymin = 425750.102767913,
                  xmax = 813792.18162311,
                  ymax = 430678.939150205),
                class = "bbox"
            )
        )
    ),
    row.names = 1L,
    sf_column = "geometry",
    agr = structure(c(TECHNICAL_ = NA_integer_),
                    .Label = c("constant", "aggregate", "identity"),
                    class = "factor"),
    class = c("sf", "tbl_df", "tbl", "data.frame")
)

Solution

  • Below is a more generalized way of doing it:

    First you need a function to find in which direction each end is pointing:

    st_ends_heading <- function(line)
    {
      M <- sf::st_coordinates(line)
      i <- c(2, nrow(M) - 1)
      j <- c(1, -1)
      
      headings <- mapply(i, j, FUN = function(i, j) {
        Ax <- M[i-j,1]
        Ay <- M[i-j,2]
        Bx <- M[i,1]
        By <- M[i,2]
        unname(atan2(Ay-By, Ax-Bx))
      })
      
      return(headings)
    }
    

    Then the main function which extends the line (one or both ends) by a given distance (in unit distance):

    st_extend_line <- function(line, distance, end = "BOTH")
    {
      if (!(end %in% c("BOTH", "HEAD", "TAIL")) | length(end) != 1) stop("'end' must be 'BOTH', 'HEAD' or 'TAIL'")
      
      M <- sf::st_coordinates(line)[,1:2]
      keep <- !(end == c("TAIL", "HEAD"))
      
      ends <- c(1, nrow(M))[keep]
      headings <- st_ends_heading(line)[keep]
      distances <- if (length(distance) == 1) rep(distance, 2) else rev(distance[1:2])
      
      M[ends,] <- M[ends,] + distances[keep] * c(cos(headings), sin(headings))
      newline <- sf::st_linestring(M)
      
      # If input is sfc_LINESTRING and not sfg_LINESTRING
      if (is.list(line)) newline <- sf::st_sfc(newline, crs = sf::st_crs(line))
      
      return(newline)
    }
    

    So in this case, using the sf object in the OP's question as line_feature:

    sf::st_geometry(line_feature) <- st_extend_line(sf::st_geometry(line_feature), 1000)