Reputation: 329
So I need to make a polygon with R using the sf
package, and all I have for that is a relatively simple dataframe where I can see points that make up lines, arcs (some parts of the polygons edge are arcs and not straitgh lines), and then circles (these are inside the polygon and are holes in the surface).
The polygon edges are lines or arcs, and they should connect to one another so that I don't have to use st_convex_hull
or something like that.
Full circles are inside the polygon and will be holes in the polygon at the end.
I'm new to using sf
but I've figured out how to proceed for lines and circles, though there might be a better way.
I've created dummy data with 3 elements, one of each, and how I'm building the different geometries. I'm actually stuck with the arc part.
I'm quite confident this should be easy enough, maybe using circularstring
type but not sure if this is the best way.
Obviously there won't be a final polygon with these dummy data but hopefully it gets the idea across.
Here's the dummy data and my code.
library(tidyverse)
#> Warning: package 'tidyverse' was built under R version 4.0.3
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
(sample <- data.frame(number = 1,
name = c("Arc", "Circle", "Line"),
area = c(0.46, 330, NA),
start_angle = c(134, NA, NA),
angle_total = c(17, NA, NA),
center_x = c(974, 377, NA),
center_y = c(7299, 7250, NA),
center_z = c(0, 0, NA),
length = c(4.27, NA, 15),
radius = c(14, 10.2, NA),
angle = c(NA, NA, 270),
delta_x = c(NA, NA, 0),
delta_y = c(NA, NA, -15),
delta_z = c(NA, NA, 0),
start_x = c(NA, NA, 18.2),
start_y = c(NA, NA, 7000),
start_z = c(NA, NA, 0),
end_x = c(NA, NA, 18.2),
end_y = c(NA, NA, 146),
end_z = c(NA, NA, 0)) %>%
as_tibble())
#> # A tibble: 3 x 20
#> number name area start_angle angle_total center_x center_y center_z length
#> <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 Arc 0.46 134 17 974 7299 0 4.27
#> 2 1 Circ~ 330 NA NA 377 7250 0 NA
#> 3 1 Line NA NA NA NA NA NA 15
#> # ... with 11 more variables: radius <dbl>, angle <dbl>, delta_x <dbl>,
#> # delta_y <dbl>, delta_z <dbl>, start_x <dbl>, start_y <dbl>, start_z <dbl>,
#> # end_x <dbl>, end_y <dbl>, end_z <dbl>
line_start <- sample %>%
filter(name == "Line") %>%
select(start_x, start_y) %>%
rename(X = start_x, Y = start_y) %>%
mutate(ID = 1:nrow(.))
line_end <- sample %>%
filter(name == "Line") %>%
select(end_x, end_y) %>%
rename(X = end_x, Y = end_y) %>%
mutate(ID = 1:nrow(.))
(lines <- line_start %>%
bind_rows(line_end) %>%
st_as_sf(coords = c("X", "Y")) %>%
group_by(ID) %>%
summarise(do_union = FALSE) %>%
st_cast("LINESTRING"))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> Simple feature collection with 1 feature and 1 field
#> geometry type: LINESTRING
#> dimension: XY
#> bbox: xmin: 18.2 ymin: 146 xmax: 18.2 ymax: 7000
#> CRS: NA
#> # A tibble: 1 x 2
#> ID geometry
#> <int> <LINESTRING>
#> 1 1 (18.2 7000, 18.2 146)
# ggplot() +
# geom_sf(data = lines) +
# coord_sf(xlim = c(10, 20))
circle_centers <- sample %>%
filter(name == "Circle") %>%
select(center_x, center_y) %>%
rename(X = center_x, Y = center_y) %>%
mutate(ID = 1:nrow(.))
circle_radii <- sample %>%
filter(name == "Circle") %>%
select(radius) %>%
rename(R = radius) %>%
mutate(ID = 1:nrow(.))
(circle <- circle_centers %>%
st_as_sf(coords = c("X", "Y")) %>%
st_buffer(circle_radii$R))
#> Simple feature collection with 1 feature and 1 field
#> geometry type: POLYGON
#> dimension: XY
#> bbox: xmin: 366.8 ymin: 7239.8 xmax: 387.2 ymax: 7260.2
#> CRS: NA
#> # A tibble: 1 x 2
#> ID geometry
#> * <int> <POLYGON>
#> 1 1 ((387.2 7250, 387.186 7249.466, 387.1441 7248.934, 387.0744 7248.404, 3~
# ggplot() +
# geom_sf(data = circle)
# ggplot() +
# geom_sf(data = rbind(lines, circle)) +
# coord_sf(xlim = c(-500, 1000))
Created on 2020-12-03 by the reprex package (v0.3.0)
The only steps that are missing would be to combine all these geometries to actually have the final polygon with holes inside, but that should be easy once I have all the separated geometries.
Also there might be better ways for handling the lines and circles part. Again I'm just starting out with sf
so please feel free to teach me more efficient ways if you know one.
Thanks ahead of time for helping out !
Upvotes: 2
Views: 804
Reputation: 329
So, let's start from the beginning, and how to make circularstring
from the sample data I have (that's autoCAD export data btw).
arcs <- sample %>%
filter(name == "Arc") %>%
mutate(ID = 1:nrow(.)) %>%
mutate(X_1 = center_x + radius * cos(start_angle * pi / 180),
Y_1 = center_y + radius * sin(start_angle * pi / 180),
X_2 = center_x + radius * cos((angle_total + start_angle) * pi / 180),
Y_2 = center_y + radius * sin((angle_total + start_angle) * pi / 180),
X_3 = center_x + radius * cos((angle_total/2 + start_angle) * pi / 180),
Y_3 = center_y + radius * sin((angle_total/2 + start_angle) * pi / 180))
This is doing most of the heavy lifting by having the center of the arc (in yellow), the extremities (green and red) and the midpoint on that arc (in blue) :
ggplot() +
geom_point(aes(x = arcs$center_x, y = arcs$center_y), col = "yellow", alpha = 0.5) +
geom_point(aes(x = arcs$X_1, y = arcs$Y_1), col = "red", alpha = 0.5) +
geom_point(aes(x = arcs$X_2, y = arcs$Y_2), col = "green", alpha = 0.5) +
geom_point(aes(x = arcs$X_3, y = arcs$Y_3), col = "blue", alpha = 0.5)
From there it is pretty strategy forward to construct your circularstring
:
arcs_sf <- arcs %>%
mutate(CIRCULARSTRING = paste0("CIRCULARSTRING(", X_1, " ", Y_1, ", ", X_3, " ", Y_3, ", ", X_2, " ", Y_2, ")")) %>%
pull(CIRCULARSTRING) %>%
as.list() %>%
st_as_sfc() %>%
st_cast("LINESTRING") %>%
st_as_sf()
Plot to check everything is in good shape :
ggplot() +
geom_point(aes(x = arcs$center_x, y = arcs$center_y), col = "yellow", alpha = 0.5) +
geom_point(aes(x = arcs$X_1, y = arcs$Y_1), col = "red", alpha = 0.5) +
geom_point(aes(x = arcs$X_2, y = arcs$Y_2), col = "green", alpha = 0.5) +
geom_point(aes(x = arcs$X_3, y = arcs$Y_3), col = "blue", alpha = 0.5) +
geom_sf(data = arcs_sf)
Now the tricky part is to end up with a polygon because often times, an arc extremity for instance that should be joining with a line, these 2 points (extremities) that should be at the exact same coordinate are actually not. So the shape is not close and st_polygon
can't do anything.
Since the polygon is made if arcs and lines (circles are holes inside this shape), you can play around with st_segmentize()
to add points along lines and then with concaveman::concaveman(concavity = 0.1)
where concavity
is a guesstimation that you'd have to find out for your own use.
I can't remember all the details but concaveman()
is only considering points to construct a polygon, and not lines or arcs, hence the need of adding points prior to calling it.
Hopefully this is sheding some light and can be useful to someone.
Upvotes: 1