1

I have a small shapefile here: https://login.filesanywhere.com/fs/v.aspx?v=8c6d62865c626fb4a2ab called bay.RDS

 library(tmap)
 library(leaflet)
 library(mapview)
 bay <- readRDS('bay.RDS')
 mapview(bay)

I am trying to draw 50 meter wide polygons from shore to shore right at the below point and extract coordinates from it. I need multiple polygons next to each other but independently drawn as I need to color code them based on a column of values

pt <- st_sfc(st_point(c(-122.91, 38.17)), crs=4326)
mapview(bay) + mapview(pt, color ='red')

I have included a snapshot below of what I am trying to accomplish. I would appreciate it if someone can show me how I can make just a couple of polygons and extract the coordinates from it so I can merge them to my dataset. Let me know if is not clear

enter image description here

4
  • The coordinates you need to extract, are those the intersection of lake transsects with the coastline (4 points per polygon) or the coastline sections per polygon (two polylines per polygon)?
    – I_O
    Commented Nov 16, 2023 at 9:04
  • The coordinates I need to extract are the 4 corners of each polygon @I_O
    – Salvador
    Commented Nov 16, 2023 at 16:06
  • How do I keep the strings from shore to shore and avoid being extended to land?
    – Salvador
    Commented Nov 16, 2023 at 20:52
  • Those are the intersection points, already addressed in update edit below.
    – Chris
    Commented Nov 16, 2023 at 21:35

2 Answers 2

2

One approach, using {sf} and {dplyr}:

edit this solution was substantially rewritten following the helpful comments by @Chris and @Salvador.

TLDR: below solution creates a horizontal baseline, buffers this baseline to result in a horizontal strip of chosen width, creates adjacent clones of this strip, combines them into a multipolygon, and rotates and shifts the mulitpolygon to expand southwards from a chosen anchor.

  1. helper function get_strips(...) to return a multipolygon of adjacent strips:
get_strips <- function(n = 10, ## number of adjacent strips
                       strip_width = 1e3, # unit: m
                       strip_length = 1e5,
                       crs = 3857 ## default: 'google' mercator
                       ){
  ## make horizontal line of length 'strip_length':
  baseline <- matrix(c(-strip_length/2, strip_length/2, 0, 0), ncol = 2) |>
    st_linestring()
  ## make line a polygon of width 'strip_width'
  basestrip  <- baseline |> 
    st_buffer(strip_width/2, endCapStyle = 'FLAT')
  ## return a spatial dataframe of n adjacent strips from north to south:
  st_sf(strip_id = sprintf('strip_%02.f', 1:n),
        geometry = Map(1:n,
                       f = \(index) basestrip - c(0, index * strip_width - strip_width/2)
                       )|>
          st_sfc() |> st_cast('MULTIPOLYGON') |> st_set_crs(crs)
        )
}
  1. helper function to rotate the geometry of a spatial dataframe
    rotate_feature <- function(feature,
                               angle = 0, ## rotation in degree
                               anchor = c(0, 0) ## top center point of grid
                               ){
      crs <- st_crs(feature)
      a <- -pi * angle/180 ## convert to radiant ccw
      ## create rotation matrix:
      rotmat <- matrix(c(cos(a), sin(a), -sin(a), cos(a)), 2, 2)
      ## rotate, recenter, restore CRS:
      st_geometry(feature) <- st_geometry(feature) * rotmat + anchor
      feature |> st_set_crs(crs)
    }
  1. load libraries, create play data
    library(sf)
    library(dplyr)
    
    the_lake <- readRDS(file.choose()) ## spatial feature supplied by OP
    the_anchor <- c(5.08e5, 4.224e6) ## anchor strip grid here
  1. generate desired spatial features
    the_strips <- 
      get_strips(crs = st_crs(the_lake)) |>
      rotate_feature(angle = 30, anchor = the_anchor)
    
    the_cropped_strips <- the_strips |>
      st_intersection(the_lake)
    
    ## casting polygons to multilinestrings to only
    ## obtain the corner points (not areas) on intersection:
    the_corners <- 
      st_intersection(
        st_cast(the_strips, 'MULTILINESTRING'),
        st_cast(the_lake, 'MULTILINESTRING')
      )
  1. inspect
    library(ggplot2)
    
    anchor_point <- the_anchor |> st_point() |>
                                  st_sfc(crs = st_crs(the_lake))
    
    ggplot() +
      geom_sf(data = the_lake) +
      geom_sf(data = the_strips, fill = NA) +
      geom_sf(data = the_cropped_strips, aes(fill = strip_id)) +
      geom_sf(data = the_corners) +
      geom_sf(data = anchor_point, pch = '+', size = 5) +
      geom_sf_label(data = anchor_point, nudge_y = 1e3, label = 'Anchor') +
      coord_sf(xlim = 1e5 * c(5, 5.2), ylim = 1e6 * c(4.21, 4.235))

result map

  1. extract corners per polygon:
    the_corners |>
      rowwise() |>
      reframe(strip_id,
              coords = st_coordinates(geometry)
              )
    ## # A tibble: 50 x 2
    ##    strip_id coords[,1]     [,2]  [,3]
    ##    <chr>         <dbl>    <dbl> <dbl>
    ##  1 strip_01    508864. 4223922.     1
    ##  2 strip_01    507927. 4223381.     1
    ##  3 strip_01    508230. 4222400.     1
    ##  4 strip_01    509328. 4223035.     1
    ##  5 strip_02    508230. 4222400.     1
    ##  6 strip_02    508791. 4221570.     1
16
  • I think this is terrific, and OP's challenge seems like establishing a search grid south of first point, to perhaps as much as a km in 50m strips, which I suppose might suggest Map(1:n, though I likely misunderstand. Were it, 50m strips on km, I'd crop out my km and work from there. Map(1:-n actually.
    – Chris
    Commented Nov 16, 2023 at 16:07
  • This looks really promising @I_O. One thing that stands out is the strings go onto the land. How do I keep the strips right on the edge of the shore on each side of the lake?
    – Salvador
    Commented Nov 16, 2023 at 20:39
  • 1
    To keep the coast-to-coast trajectories only, you could swap the intersection arguments (keeping the lake as a polygon rather than a linestring) like so: st_intersection(the_lake, the_parallels)
    – I_O
    Commented Nov 16, 2023 at 21:26
  • 1
    That's the strip_width in get_parallels( for 5km, 10km etc. As Map(-n:n, parallels are from southmost to north, 1:7. Best I can say.
    – Chris
    Commented Nov 16, 2023 at 21:49
  • 1
    This is amazing, thank you so much you guys. Will wait for the final answer so I can put the check mark and end this long chain of comments.
    – Salvador
    Commented Nov 16, 2023 at 22:25
1

Another approach with space filling polygons, here squares.

n100 = c(100,100)
bay_square_100 = st_make_grid(
bay,
cellsize = c(diff(st_bbox(bay)[c(1,3)]), diff(st_bbox(bay)[c(2,4)]))/n100,
offset = st_bbox(bay)[c('xmin', 'ymin')],
what = 'polygon',
square = TRUE
)
# get index of just the ones inside
bay_sq_100_cont = st_contains_properly(bay, bay_square_100)
contained_bay_sq = bay_square_100[unlist(bay_sq_100_cont)]

Index contained_bay_sq with the marvelously useful cgwtools::seqle, an rle for sequences, 1,2,3...i

end = cumsum(cgwtools::seqle(unlist(bay_sq_100_cont))$lengths)
start = end - cgwtools::seqle(unlist(bay_sq_100_cont))$lengths +1

This approach to st_union creates an object that is seemingly unworkable until you run across knapply comment, that reminds, or informs that unlist has a recursive argument.

unioned_rows = list()
for (i in 1:length(start)) {
unioned_rows[[i]] = st_union(c(bay_cont[start[i]:end[i]]))
}
unioned_row4 = st_sfc(unlist(unioned_rows, recursive = FALSE)) #many difficult objects created

plot(unioned_row4, col = c('red', 'blue', 'green', 'orange', 'pink') )

This can be done with hexagons and indexing along diagonals to create a faux angle, but I think @I_O 's approach is more elegantly flexible.

Tomales Bay 100m colored strips

Actually, the grid can be rotated as demonstrated Stackoverflow - rotate grid

library(sf)
Linking to GEOS 3.12.0, GDAL 3.8.0, PROJ 9.3.0; sf_use_s2() is TRUE
 bay = st_geometry(readRDS('~/Downloads/bay.RDS'))
 rot = function(a) matrix(c(cos(a), sin(a), -sin(a), cos(a)), 2, 2)
 tran = function(geo, ang, center) (geo - center) * rot(ang * pi / 180) + center
 center = st_centroid(st_union(bay))
 rotang = -30 # sign, -CounterClockWise +CW
 grd = st_make_grid(tran(bay, -rotang, center), cellsize = 100, crs = st_crs(bay))
 grd_rot = tran(grd, rotang, center)
 bay_rot_100_cont_idx = st_contains_properly(bay, grd_rot)
Error in st_geos_binop("contains_properly", x, y, sparse = sparse, prepared = TRUE,  : 
  st_crs(x) == st_crs(y) is not TRUE
 st_crs(grd_rot) = 26910
 bay_rot_100_cont_idx = st_contains_properly(bay, grd_rot)
 cont_bay_rot = grd_rot[unlist(bay_rot_100_cont_idx)]
 end_rot = cumsum(cgwtools::seqle(unlist(bay_rot_100_cont_idx))$lengths)
 start_rot = end_rot - cgwtools::seqle(unlist(bay_rot_100_cont_idx))$lengths + 1
 unioned_rows_rot = list()
 for (i in 1:length(start_rot)) {
 unioned_rows_rot[[i]] = st_union(c(cont_bay_rot[start_rot[i]:end_rot[i]]))
 }
 unioned_rot_sfc = st_sfc(unlist(unioned_rows_rot, recursive = FALSE)
plot(unioned_rot_sfc, col = c('green', 'white', 'red'))

Tomales Bay rotated grid

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