Calculate widest distance between two lines - r

I would like to calculate the mean distance as well as the widest distance between two lines. I know how to find the minimum distance using st_distance() function, but I'm not sure how to find the other two metrics. The red lines are what I believe I would need to measure to find the mean and widest distances between the two lines.
Attached is some example data.
pts1<- data.frame(
x= c(-103.485342, -103.482808),
y = c(31.348758, 31.376947))
) %>%
sf::st_as_sf(coords = c("x","y"))
st_crs(pts1)<- "+init=epsg:2257"
pts2<- data.frame(
x= c(-103.492822, -103.484231),
y = c(31.348181, 31.377191))
) %>%
sf::st_as_sf(coords = c("x","y"))
st_crs(pts2)<- "+init=epsg:2257"
a <- pts1 %>% st_coordinates() %>% st_linestring()
b<- pts2 %>% st_coordinates() %>% st_linestring()
min_dist<-st_distance(a,b,by_element = T)
See second example below. From the image i calculate ~300 meters from the perpendicular line which cross both lines at its maximum.
pts1 <- data.frame(x = c(-103.485342, -103.482808),
y = c(31.348758, 31.376947)) %>%
sf::st_as_sf(coords = c("x","y"))
st_crs(pts1) <- "+proj=longlat +datum=WGS84"
pts1<- st_transform(pts1,"+init=epsg:2257")
pts2 <- data.frame(x = c(-103.492812, -103.484231),
y = c(31.318181, 31.377991)) %>%
sf::st_as_sf(coords = c("x","y"))
st_crs(pts2) <- "+proj=longlat +datum=WGS84"
pts2<- st_transform(pts2,"+init=epsg:2257")
a <- pts1 %>% st_coordinates() %>% st_linestring()
b <- pts2 %>% st_coordinates() %>% st_linestring()
st_distance(pts1, pts2, by_element = T)

I'll give you an insight, maybe this is not a complete answer you would imagine.
As lines are made of points, if you just make two slightly changes, you can have not only min distance, but also max distance by doing the same thing you did with the lines, but with the points from which the lines are comprised.
library(sf)
#> Linking to GEOS 3.8.0, GDAL 2.4.2, PROJ 6.2.1
pts1 <- data.frame(x = c(-103.485342, -103.482808),
y = c(31.348758, 31.376947)) %>%
sf::st_as_sf(coords = c("x","y"))
st_crs(pts1) <- "+init=epsg:2257"
pts2 <- data.frame(x = c(-103.492822, -103.484231),
y = c(31.348181, 31.377191)) %>%
sf::st_as_sf(coords = c("x","y"))
st_crs(pts2)<- "+init=epsg:2257"
a <- pts1 %>% st_coordinates() %>% st_linestring()
b <- pts2 %>% st_coordinates() %>% st_linestring()
st_distance(pts1, pts2, by_element = T)
#> Units: [US_survey_foot]
#> [1] 0.007502222 0.001443768
For having the mean distance, I am not sure if this is what you want, but I think you can obtain the centroid of both lines and then process st_distance.
ca <- st_centroid(a)
cb <- st_centroid(b)
st_distance(ca, cb, by_element = T)
#> [1] 0.004454613
EDIT: my last try based on comment
I think you can maybe have what you're trying to have if you find the longest line (b in your example), than process and find the longest distance between the points of the shorter line and the longest line itself:
(I also made some changes to your original code to make it works)
library(sf)
pts1 <- data.frame(x = c(-103.485342, -103.482808),
y = c(31.348758, 31.376947)) %>%
st_as_sf(coords = c("x","y")) %>%
st_set_crs(4326) %>%
st_transform(2257)
pts2 <- data.frame(x = c(-103.492812, -103.484231),
y = c(31.318181, 31.377991)) %>%
st_as_sf(coords = c("x","y")) %>%
st_set_crs(4326) %>%
st_transform(2257)
a <- pts1 %>%
st_union(.) %>%
st_cast(to = "LINESTRING")
b <- pts2 %>%
st_union(.) %>%
st_cast(to = "LINESTRING")
longest <- ifelse(test = st_length(a) > st_length(b),
yes = quote(a),
no = quote(b))
max(st_distance(pts1, eval(longest)))
#> 955.7374 [US_survey_foot]

Related

Color sf points and linestring by numeric variable

I've got some GPS data (latlong) and I want to plot the GPS points and their connecting lines and color both by the time difference between the two GPS points. I've figured out how to color the points and convert the points to a LINESTRING but I can't figure out how to recolor the scale of the line.
I saw this post:
Color portions of sf LINESTRING by variable that shows how to break the linestrings into segments and color the segments by a categorical variable but as I have close to 100,000 observations I'd like to avoid just splitting my plot up into 99,999 pieces and also, my data is continuous.
Here's some toy data:
# Create some data points
fake_data = data.frame(Time = 1:6,
Long = c(-90.46200, -90.46160, -90.46170, -90.46150, -90.46100, -90.46240),
Lat = c(33.88540, 33.88750, 33.88520, 33.88340, 33.88540, 33.88150))
# Define as points
points = st_as_sf(fake_data, coords = c("Long", "Lat"), crs = 4326, remove = FALSE)
# Connect the dots
lines = points %>% summarize(do_union = FALSE) %>% st_cast("LINESTRING")
library(ggplot2)
# Plot
ggplot(data = points)+
geom_sf(aes(color = as.numeric(points$Time)))+
geom_sf(data = lines)+#, aes(color = numeric(points$Time[1:(length(points$Time)-1)])))+ #did not work
ylim(c(33.87, 33.89))+
xlim(c(-90.47, -90.45))+
scale_color_gradient(name = "Time", position="bottom" , low = "blue", high = "red")
Thank you!
I'm confident there are prettier ways to do this, but this works!
I needed to add in a group variable to use to generate linegroups. This was inspired by: https://stackoverflow.com/a/48979401/3642716 and their answer with how to solve for troops in the tidyverse dataset.
library(sf)
library(dplyr)
library(ggplot2)
# Create some data points
fake_data = data.frame(Time = 1:6,
Long = c(-90.46200, -90.46160, -90.46170, -90.46150, -90.46100, -90.46240),
Lat = c(33.88540, 33.88750, 33.88520, 33.88340, 33.88540, 33.88150),
group = 1)
# Define as points
points = st_as_sf(fake_data, coords = c("Long", "Lat"), crs = 4326, remove = FALSE)
# Connect the dots
lines <- fake_data
lines %<>% group_by(group) %>%
slice(rep(1:n(), each = 2)) %>%
slice(-c(1, n())) %>%
mutate(linegroup = lapply(1:(n()/2), function(x) rep(x, 2)) %>% unlist) %>%
ungroup %>%
group_by(linegroup) %>%
st_as_sf(coords = c("Long","Lat"), crs = 4326, remove = F) %>%
summarize( do_union = F) %>%
st_cast("LINESTRING")
# Plot
ggplot(data = points)+
geom_sf(aes(color = `Time`))+
geom_sf(data = lines, aes(color = `linegroup`))+#, aes(color = numeric(points$Time[1:(length(points$Time)-1)])))+ #did not work
ylim(c(33.881, 33.888))+
xlim(c(-90.463, -90.460))+
scale_color_gradient(name = "Time", position="bottom" , low = "blue", high = "red")
Looks like this:

How to set maximum radius of polygon

I draw a map in which points are represented as polygons. The points are found close to a southern state border. The code is:
library(leaflet)
library(sf)
long <- c( 4.676119175, 4.53172103 , 4.939782877, 5.074127987, 5.072757119)
lat <- c(51.477299959, 51.589766239, 51.624436295, 51.520707997, 51.631483055)
labs <- c("A", "B", "C", "D", "E")
colors <- rainbow(length(labs))
df <- data.frame(ID = labs, X = long, Y = lat)
points <- st_geometry(st_as_sf(df, coords = c("X", "Y")))
points0 <- st_set_crs(points, 4326)
area <- rnaturalearth::ne_countries(country = c('netherlands'), scale = 'large', returnclass = 'sf')
polys <- points %>% st_union() %>% st_voronoi() %>% st_cast() %>% st_set_crs(., 4326)
polys <- polys[unlist(st_intersects(points0, polys))] %>% st_intersection(y = area)
leaflet() %>%
addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
addPolygons (data = polys,
fillColor = colors,
fillOpacity = 1,
weight = 0.5,
color = "black") %>%
addCircleMarkers(lng = long,
lat = lat,
label = labs,
color = "black",
radius = 5,
weight = 1,
fill = TRUE,
fillColor = colors,
fillOpacity = 1)
The results looks like this:
The problem is that the polygons get stretched out to the north way to much. This may be solved by setting a bounding box, but I would prefer to solve this by setting a maximum size (or radius) for the polygons. How exactly can I set the maximum radius?
I suggest adding another call of sf::st_intersection() with an object of buffered points.
You have two alternatives:
intersect with a buffer of a single lab - number C seems a good candidate, as it is placed in the middle
intersect with pairwise buffers of each lab; purrr:map2() will be your friend here for pairwise intersection
I find the results of a single intersection more visually pleasing, but this may be not the most important factor so make your own choice...
As a comment: my natural earth is a bit buggy at the moment, so I am using GISCO by Eurostat as a source of map of the Netherlands instead; in a high resolution because I really dig the tiny exclaves of Belgium...
library(leaflet)
library(rnaturalearth)
library(sf)
long <- c( 4.676119175, 4.53172103 , 4.939782877, 5.074127987, 5.072757119)
lat <- c(51.477299959, 51.589766239, 51.624436295, 51.520707997, 51.631483055)
labs <- c("A", "B", "C", "D", "E")
colors <- rainbow(length(labs))
points <- data.frame(ID = labs, X = long, Y = lat) %>%
st_as_sf(coords = c("X", "Y"), crs = 4326) %>%
mutate(ID = ordered(ID))
area <- giscoR::gisco_get_countries(country = "NL", resolution = "01") # my Natural Earth is buggy at the momemt
# intersection with a single buffer around C
polys <- points %>%
st_union() %>%
st_voronoi() %>%
st_cast() %>%
st_set_crs(., 4326) %>%
st_intersection(area) %>%
st_intersection(st_buffer(dplyr::filter(points, ID == "C"), units::as_units(30000, "m"))) %>%
st_as_sf() %>%
st_join(points) # add back labs id's
palette <- colorFactor(palette = colors,
domain = polys$ID)
leaflet(data = polys) %>%
addProviderTiles("Esri.WorldGrayCanvas") %>%
addPolygons(fillColor = ~palette(ID),
stroke = F) %>%
addCircleMarkers(data = points,
fillColor = ~palette(ID),
stroke = F,
fillOpacity = 1)
# intersection with pairwise buffers
polys <- points %>%
st_union() %>%
st_voronoi() %>%
st_cast() %>%
st_set_crs(., 4326) %>%
st_intersection(area) %>%
purrr::map2(st_geometry(st_buffer(points, units::as_units(30000, "m"))), st_intersection) %>%
st_as_sfc(crs = 4326) %>%
st_as_sf() %>%
st_join(points) # add back labs id's
mapview::mapview(polys)

Projecting a quartered circle with a 50km radius in r/sf?

I'm hoping to create a series of quartered circles (i.e. circles split into 4 equal quadrants), each with a 50km radius, that I can map onto various longitudes and latitudes throughout the United States. I'd also like the option to rotate these quartered circles as desired.
Using the code below (and guidance from here), I've been able to make the following start:
New York State Map
I have two questions:
How can I meaningfully set the radius of these circles? Is there a way to draw shapes a certain distance (in km) from a coordinate in a projected CRS? So far I'm defining the radius in terms of degrees of longitude and latitude, but distance would be more useful.
My circles appear to be turning into ellipses after projecting them and mapping them in WGS84. Is there any way to prevent this from happening?
I would be happy to consider alternative approaches. Thanks!
library(sf)
library(ggplot2)
library(maps)
#Two functions to create coordinate quartered circle polygons
#x = long, y = lay, r = radius, theta_rotate = rotation
st_wedge <- function(x,y,r,start,width, theta_rotate){
n <- 20
theta = seq(start+theta_rotate, start+width+theta_rotate, length=n)
xarc = x + r*sin(theta)
yarc = y + r*cos(theta)
xc = c(x, xarc, x)
yc = c(y, yarc, y)
st_polygon(list(cbind(xc,yc)))
}
st_wedges <- function(x, y, r, nsegs, theta_rotatex){
width = (2*pi)/nsegs
starts = (1:nsegs)*width
polys = lapply(starts, function(s){st_wedge(x,y,r,s,width, theta_rotatex)})
#Cast to crs 4326, WGS84
mpoly = st_cast((st_sfc(polys, crs = 4326)), "MULTIPOLYGON")
mpoly
}
#Create quartered sf circle polygon
custom_circle_sf <- st_wedges(x = -76, y = 43, r = .3, nsegs = 4, theta_rotatex = 200) %>%
st_sf() %>%
mutate(group = row_number()) %>% dplyr::select(group, geometry)
#Create New York State sf polygon
ny_map_sf <- map_data("state", region="new york") %>%
st_as_sf(coords = c("long", "lat"), crs = 4326) %>%
group_by(group) %>%
summarise(geometry = st_combine(geometry)) %>%
st_cast("POLYGON")
#Plot results
ggplot() +
geom_sf(data=ny_map_sf,
size = 1,
colour = "blue",
fill = "white") +
geom_sf(data=custom_circle_sf,
size = .1,
aes(fill=group),
colour = "white")
For anyone who is curious about splitting polygons in sf using R, this was how I went about solving this:
#Function to create circle with quadrants. Save desired projection as projected_crs
create_circle <- function(lat_x, long_y, theta_x, buffer_m){
#Create circle with radius buffer_m centered at (lat_x, long_y)
circle_buffer <- st_point(c(lat_x, long_y)) %>% st_sfc(crs = 4326) %>%
st_cast("POINT") %>%
st_transform(projected_crs) %>%
st_buffer(buffer_m)
#Create two orthogonal lines at origin
p1 <- rbind(c(lat_x,long_y - 1), c(lat_x,long_y + 1))
p2 <- rbind(c(lat_x+1,long_y), c(lat_x-1,long_y))
mls <- st_multilinestring(list(p1,p2)) %>% st_sfc(crs = 4326) %>%
st_transform(projected_crs)
#Use orthogonal lines to split circle into 4 quadrants
x1 <- st_split(circle_buffer, mls)
#Convert origin into projected CRS
center_in_crs <- st_point(c(lat_x, long_y)) %>%
st_sfc(crs = 4326) %>%
st_transform(projected_crs)
sp_obj <- x1 %>% st_collection_extract(type="POLYGON") %>%
#Convert to spatial to use sp functions
as_Spatial() %>%
#rotate x degrees
elide(rotate = theta_x + 45, center = center_in_crs[[1]]) %>%
#return to sf
st_as_sf()
Regarding your question 2: "circles appear to be turning into ellipses". If you add to your ggplot the coord_equal() function then the grid will be square, and the ellipses will be shown as circles.

How to remove unwanted lines/connections in plotly::ggplotly() animation?

I've created an animation using the plotly library which calls a ggplot with geom_sf() layers. When I run the animation I get weird, random lines or connections between unconnected points in the map layer that I don't want.
I saw a post suggesting the order of the points matters (i.e. should be ordered by x ascending) but I tried ordering the points and still experience the issue.
I've created a MRE; when you load the 'scotland.html' file in your browser it should display similar behaviour:
library("dplyr")
library("tidyr")
library("sf")
library("ggplot2")
library("plotly")
library("htmlwidgets")
tmp <- tempdir()
tmpfile <- paste0(tmp, "/scotland.zip")
url <- "https://borders.ukdataservice.ac.uk/ukborders/easy_download/prebuilt/shape/Scotland_ol_1991.zip"
download.file(url, tmpfile)
unzip(tmpfile, exdir = tmp)
scotland <- read_sf(tmp, "scotland_ol_1991")
points <- data.frame(
id = 20:1,
year = 20:1,
east = sample(7459:469817, size = 20),
north = sample(530297:1219574, size = 20)
)
points <-
points %>%
uncount(year) %>%
group_by(id) %>%
mutate(year = row_number()) %>%
ungroup()
# Try rearranging by x coordinate as suggested by this post:
# https://community.plot.ly/t/scatterplot-lines-unwanted-connecting/8729/3
scotland_order =
scotland %>%
group_by(name) %>%
st_coordinates() %>%
as_tibble() %>%
group_by(L2) %>%
summarise(min_x = min(X))
scotland =
scotland %>%
mutate(order = scotland_order$min_x) %>%
arrange(order) %>%
select(-order)
points <- st_as_sf(points, coords = c("east", "north"))
points <- st_set_crs(points, 27700)
p <- ggplotly(
ggplot() +
geom_sf(data = scotland) +
geom_sf(data = points, aes(frame = year, ids = id))
)
saveWidget(p, "scotland.html")
I don't think it's a problem with the data (i.e. the scotland layer) because:
Not all animation frames are affected
If you run the animation, go back, and play it again it doesn't show the weird behaviour
Any suggestions about what's going on, how to solve it, or any workarounds greatly received!

Ploting a Buffer Around a Point on a Map - R SF

I've been trying to plot a buffer around a point on a map but when I do the buffer doesn't appear in the right place like this.
Faulty R Map
The correct location is in California.
Here's my code:
library(tigris)
library(sf)
library(tidyverse)
projection <- 102003
options(tigris_use_cache = TRUE)
county_polys <- counties(class = 'sf') %>%
filter(STATEFP %in% c('06','41','53','04','16','32','49')) %>%
st_transform(projection)
centroids <- county_polys %>%
as_tibble %>% select(INTPTLON,INTPTLAT) %>%
mutate(
INTPTLON = as.double(INTPTLON),
INTPTLAT = as.double(INTPTLAT)) %>%
st_as_sf(coords = c('INTPTLON','INTPTLAT'), crs = projection)
pt <- centroids[2,]
pt_buffer <- st_buffer(pt,150000)
ggplot() + geom_sf(data = county_polys) + geom_sf(data = pt_buffer,color = 'red')
We can use the st_centroid function to get the centroid to avoid errors. There is no need to convert the sf object to other classes.
# This is the only thing I changed from your original code
# Get the centroid by st_centroid
centroids <- county_polys %>% st_centroid()
pt <- centroids[2,]
pt_buffer <- st_buffer(pt,150000)
ggplot() + geom_sf(data = county_polys) + geom_sf(data = pt_buffer,color = 'red')

Resources