I'm looking to create a masking polygon for multiple polygons.
It's easy enough for a single polygon:
How to apply a polygon mask layer in ggplot
but much trickier for multiple:
https://www.stat.auckland.ac.nz/~paul/Reports/GraphicsEngine/definitions/definitions.html
I feel like I'm fairly close but I need a method of drawing a line between a point for the closest polygon, i.e. I'd like to try and connect nearest islands with a line.
While also connecting those polygons to an outer polygon box but only with a single line:
library(tidyverse)
library(sf)
library(albersusa)
usa <- usa_sf()
HI <- st_coordinates(usa %>%
filter(name %in% c("Hawaii"))) %>%
as.data.frame() %>%
select(X, Y)
rec_box <-
data.frame(
X = c(-108,-108,-101,-101,-108),
Y = c(24, 28, 28, 24, 24)
)
mask <- rbind(HI, rec_box)
eg <- st_as_sf(data.frame(mask), coords = c("X", "Y"))
poly <- st_convex_hull(eg)
ggplot() +
geom_sf(data = poly) +
geom_density2d_filled(data = HI, aes(x = X, y = Y)) +
geom_polygon(data = mask,
aes(x = X, y = Y),
color = "black",
fill = "white")
Edit:
For only a mask around the islands:
library(tidyverse)
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
library(albersusa)
#library(concaveman)
#library(nngeo)
usa <- usa_sf()
# keep only the geometry from Hawaii (not the data)
HI <- usa %>%
filter(name %in% c("Hawaii")) %>%
st_cast('POLYGON') %>%
st_geometry() %>%
st_as_sf()
# HI as dataframe as your example
HI2 <- st_coordinates(usa %>%
filter(name %in% c("Hawaii"))) %>%
as.data.frame() %>%
select(X, Y)
# you may need a larger box. This uses sf:st_bbox to get exact
# rectangle of the polygons. adjust accordingly
hi_box <- st_bbox(HI) %>% st_as_sfc() %>% st_as_sf()
# find the difference betwee HI & hi_box
hi_mask <- st_difference(hi_box, st_union(HI))
ggplot() +
geom_density_2d_filled(data = HI2, aes(x = X, y = Y)) +
geom_sf(data = hi_mask, fill = 'black')
Created on 2021-04-04 by the reprex package (v0.3.0)
If you're happy with the HI mask created above, you can connect it to your defined bounding box using a single line with nngeo::st_connect().
This should find the shortest connecting line between the two. If you prefer the connecting line to be at a specific point (southwest corner in your example above), supply it as an sf object to the st_connect() function instead of the entire box.
library(tidyverse)
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
library(albersusa)
library(concaveman)
library(nngeo)
usa <- usa_sf()
# Hawaii, cast to POLYGON rather than the original MULTIPOLYGON
HI <- (usa %>%
filter(name %in% c("Hawaii"))) %>%
st_cast('POLYGON')
connected_HI <- concaveman(HI)
# rec box as an sf object with same crs as HI
rec_box <-
data.frame(X = c(-108, -108, -101, -101, -108),
Y = c(24, 28, 28, 24, 24)) %>%
st_as_sf(coords = c('X', 'Y'
)) %>%
st_set_crs(st_crs(HI))
# connecting HI mask you made to your rec_box
connected_to_rec_box <- nngeo::st_connect(st_zm(connected_HI), rec_box)
ggplot() +
geom_sf(data = connected_HI, fill = 'turquoise', alpha = .4) +
geom_sf(data = connected_to_rec_box, color = 'red')
Created on 2021-04-04 by the reprex package (v0.3.0)
Related
This question already has an answer here:
Sf package: Close a polygon fom complex shape
(1 answer)
Closed 5 months ago.
The reprex below shows how I would like to create a map via {osmdata} and {ggplot2} that has sea/ocean in it. I want to color-fill the land and/or sea area. However, it seems unexpectedly difficult to do so. This blog post even claims that it cannot be done.
This vignette of {osmplotr} seems to have to the solution: "Because OpenStreetMap represents coastline as line objects, all coastline data is contained within the $osm_lines object. The osm_line2poly() function can then convert these lines to polygons which can be used to plot filled areas.". Yet, just as in this similar StackOverflow question, the function throws an error as can be seen at the bottom of the reprex. I also found here that the {tigris} package can provide the necessary polygon data - but only for the US.
So how can I get this to work?
library(osmdata)
library(osmplotr)
library(sf)
library(tidyverse)
# define example bbox
bb <- tribble(
~xy, ~min, ~max,
"x", 12.00, 12.18,
"y", 54.08, 54.20
) %>% column_to_rownames("xy") %>% as.matrix()
# get "water"
water <- opq(bb) %>%
add_osm_feature(key = "natural", value = "water") %>%
osmdata_sf()
# get "coastline"
coast <- opq(bb) %>%
add_osm_feature(key = "natural", value = "coastline") %>%
osmdata_sf()
# ggplot
ggplot() +
geom_sf(
data = water$osm_multipolygons,
fill = "navy",
color = NA
) +
geom_sf(
data = coast$osm_lines,
fill = "navy",
color = "blue"
)
# trying osm_line2poly()
osmplotr::osm_line2poly(coast$osm_lines, bb)
#> Error in FUN(X[[i]], ...): unbenutztes Argument (V = c(3, 1, 6, 7, 2, NA, 5))
Created on 2022-09-23 with reprex v2.0.2
Thanks to #JindraLacko, I was able to make my reprex work. Basically, we create a rectangle/polygon which is the size of our bbox and then split it via the coastline.
library(lwgeom)
library(osmdata)
library(osmplotr)
library(sf)
library(tidyverse)
### define example bbox
lon_min <- 12.00 # xmin
lon_max <- 12.18 # xmax
lat_min <- 54.08 # ymin
lat_max <- 54.20 # ymax
bb <- get_bbox(c(lon_min, lat_min, lon_max, lat_max))
### get "water" that is not sea as polygons
water <- opq(bb) %>%
add_osm_feature(key = "natural", value = "water") %>%
osmdata_sf()
### get sea & land as polygons
# 1. get coastline (as line)
coast <- opq(bb) %>%
add_osm_feature(key = "natural", value = "coastline") %>%
osmdata_sf()
# 2. get overall rectangle for bbox
bb_rect <- data.frame(
lat = c(lat_min, lat_max),
lon = c(lon_min, lon_max)
) %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
st_bbox() %>%
st_as_sfc()
# 3. split overall rectangle for bbox via coastline
bb_rect_split <- bb_rect %>%
st_split(coast$osm_lines) %>%
st_collection_extract("POLYGON")
# 4. extract splitted rectangle parts
land <- bb_rect_split[1]
sea <- bb_rect_split[2]
### ggplot
ggplot() +
geom_sf(
data = land,
fill = "bisque",
color = NA
) +
geom_sf(
data = sea,
fill = "navy",
color = NA
) +
geom_sf(
data = water$osm_multipolygons,
fill = "navy",
color = NA
)
Created on 2022-09-26 with reprex v2.0.2
I'm looking for a quick way to move around my polygons in R... I'd like to move the my polygon based on it's center to a new center.
library(sf)
library(ggplot2)
theta <- (0:6) * pi / 3
hexagon <- data.frame(
x = sin(theta),
y = cos(theta))
poly <- hexagon %>%
st_as_sf(coords = c("x", "y")) %>%
mutate(geometry = st_combine(geometry))%>%
st_cast("POLYGON")
How can I move the polygon to the new center (here: red point)?
poly %>%
ggplot()+
geom_sf()+
geom_point(data=data.frame(x=10, y=10), aes(x, y), color="red")
Thanks!
What you describe is an affine transformation of type shift.
You can perform it by adding a point geometry to your polygon geometry (this works only for geometries, i.e. sfc objects, not full sf data frames with data).
It is safe to do for projected (planar) coordinate reference systems, might be messy for unprojected (lat-long) CRS. See this answer on our sister site, and the comment to it: https://gis.stackexchange.com/questions/437695/move-points-to-different-location/437699#437699
And because an example is more than 100 words:
library(sf)
library(dplyr)
library(ggplot2)
theta <- (0:6) * pi / 3
hexagon <- data.frame(
x = sin(theta),
y = cos(theta))
poly <- hexagon %>%
st_as_sf(coords = c("x", "y")) %>%
mutate(geometry = st_combine(geometry))%>%
st_cast("POLYGON")
poly %>%
ggplot()+
geom_sf()+
geom_point(data=data.frame(x=10, y=10), aes(x, y), color="red")
# the interesting part starts here:
# reframe your red point as an sf object
red_point <- data.frame(x=10, y=10) %>%
st_as_sf(coords = c("x", "y"))
# add the two geometries together (just the geometry columns!)
shifted_poly <- poly$geometry + red_point$geometry
# a visual check
ggplot() +
geom_sf(data = poly) +
geom_sf(data = shifted_poly) +
geom_sf(data = red_point, col = "red")
I'm looking to crop the density plot to only land while keeping to sf.
Here's a simple example problem:
library(tidyverse)
library(sf)
library(albersusa)
library(ggthemes)
library(jsonlite)
dat <-
fromJSON(
"https://services1.arcgis.com/Hp6G80Pky0om7QvQ/arcgis/rest/services/Fortune_500_Corporate_Headquarters/FeatureServer/0/query?where=1%3D1&outFields=LATITUDE,LONGITUDE,NAME,PROFIT&outSR=4326&f=json"
)
dat <- as.data.frame(dat$features$attributes)
top_50 <- dat %>%
arrange(desc(PROFIT)) %>%
head(50)
ggplot() +
geom_sf(data = usa_sf()) +
geom_density_2d_filled(aes(x = LONGITUDE, y = LATITUDE),
data = top_50,
alpha = .5) +
xlim(-125,-66.5) +
ylim(20, 50) +
theme_map() +
theme(legend.position = "none")
Not sure if I'm getting close to a solution but here's some of the code I've been trying:
test <- (MASS::kde2d(
top_50$LONGITUDE, top_50$LATITUDE,
lims = c(-125,-66.5, 20, 50)
))
ggpoly2sf <- function(poly, coords = c("long", "lat"),
id = "group", region = "region", crs = 4326) {
sf::st_as_sf(poly, coords = coords, crs = crs) %>%
group_by(!! as.name(id), !! as.name(region)) %>%
summarize(do_union=FALSE) %>%
sf::st_as_sf("POLYGON") %>%
ungroup() %>%
group_by(!! as.name(region)) %>%
summarize(do_union = TRUE) %>%
ungroup()
}
v <- contourLines(test)
vv <- v
for (i in seq_along(v)) vv[[i]]$group <- i
vv <- do.call(rbind, lapply(vv, as.data.frame))
dsi_sf <- ggpoly2sf(vv, coords = c("x", "y"), region = "level") %>% st_as_sf()
usa <- usa_sf()
dsi_i_sf <- st_intersection(usa$geometry, dsi_sf)
ggplot() +
geom_sf(data=usa) +
geom_sf(data=dsi_i_sf,color="red") +
geom_density2d_filled(aes(x = LONGITUDE, y = LATITUDE),
data = top_50,alpha=.4) +
xlim(-125,-66.5) +
ylim(20, 50) +
theme(legend.position = "none")
Create a rectangle of the same plot dimensions:
rec_box <- data.frame(x=c(-125,-125,-66.5,-66.5,-125), y=c(20,50,50,20,20))
Create an outline of the US and extract only the lat/lon points into a dataframe:
outline <- map("usa", plot=FALSE)
outline <- data.frame(x=outline$x,y=outline$y)
Bind the two together to create a polygon with a hole in the middle:
mask <- rbind(rec_box,outline)
Add a geom_polygon() to plot the mask data and color appropriately:
geom_polygon(data=mask,
aes(x=x,y=y),color="white",fill="white")
Everything combined:
library(tidyverse)
library(sf)
library(albersusa)
library(ggthemes)
library(jsonlite)
dat <-
fromJSON(
"https://services1.arcgis.com/Hp6G80Pky0om7QvQ/arcgis/rest/services/Fortune_500_Corporate_Headquarters/FeatureServer/0/query?where=1%3D1&outFields=LATITUDE,LONGITUDE,NAME,PROFIT&outSR=4326&f=json"
)
dat <- as.data.frame(dat$features$attributes)
top_50 <- dat %>%
arrange(desc(PROFIT)) %>%
head(50)
usa <- usa_sf()
outline <- map("usa", plot=FALSE)
outline <- data.frame(x=outline$x,y=outline$y)
rec_box <- data.frame(x=c(-125,-125,-66.5,-66.5,-125), y=c(20,50,50,20,20))
mask <- rbind(rec_box,outline)
ggplot() +
geom_sf(data = usa_sf()) +
geom_density_2d_filled(aes(x = LONGITUDE, y = LATITUDE),
data = top_50,
alpha = .5) +
xlim(-125,-66.5) +
ylim(20, 50) +
geom_polygon(data=mask,
aes(x=x,y=y),color="white",fill="white") +
theme_map() +
theme(legend.position = "none")
Really a thing of beauty.
For a mask layer over the US with AK & HI inset:
library(tidyverse)
library(sf)
library(albersusa)
library(ggthemes)
library(jsonlite)
library(spatstat)
dat <-
fromJSON(
"https://services1.arcgis.com/Hp6G80Pky0om7QvQ/arcgis/rest/services/Fortune_500_Corporate_Headquarters/FeatureServer/0/query?where=1%3D1&outFields=LATITUDE,LONGITUDE,NAME,PROFIT&outSR=4326&f=json"
)
dat <- as.data.frame(dat$features$attributes)
top_50 <- dat %>%
arrange(desc(PROFIT)) %>%
head(50)
usa <- usa_sf()
top50sf <- st_as_sf(top_50, coords = c("LONGITUDE", "LATITUDE")) %>%
st_set_crs(4326) %>%
st_transform(st_crs(usa))
# usa polygons combined
usa_for_mask <- usa_sf() %>%
st_geometry() %>%
st_cast('POLYGON') %>%
st_union()
# bounding box of us & inset AK + HI,
# expand as needed
us_bbox <- st_bbox(usa_for_mask) %>%
st_as_sfc() %>%
st_as_sf()
us_mask <- st_difference(us_bbox, usa_for_mask)
ggplot() +
geom_sf(data = usa) +
geom_density_2d_filled(aes(x = LONGITUDE, y = LATITUDE),
data = top_50,
alpha = .5) +
geom_sf(data = us_mask, fill = 'white') +
xlim(-125,-66.5) +
ylim(20, 50) +
theme_map() +
theme(legend.position = "none")
Created on 2021-04-05 by the reprex package (v1.0.0)
You can expand the bounding box to get rid of the purple border around the plot.
This does what you're asking for, but almost certainly isn't spatially accurate. It can get a point across to a general audience, but don't make any big decisions based on it.
More accurate spatial interpolation methods can be found here:
https://rspatial.org/raster/analysis/4-interpolation.html
https://mgimond.github.io/Spatial/interpolation-in-r.html
I would need to draw, in R the map of the European Union and a shaded circle that overlaps it. The circle should have a center at a point with known longitude and latitude and a predetermined radius.
First, I can't even draw the map, even though the code doesn't throw out any errors. The code is:
library(ggplot2)
library(grid)
library(rworldmap)
# Get the world map
worldMap <- getMap()
# Member States of the European Union
europeanUnion <- c("Austria","Belgium","Bulgaria","Croatia","Cyprus",
"Czech Rep.","Denmark","Estonia","Finland","France",
"Germany","Greece","Hungary","Ireland","Italy","Latvia",
"Lithuania","Luxembourg","Malta","Netherlands","Poland",
"Portugal","Romania","Slovakia","Slovenia","Spain",
"Sweden")
# Select only the index of states member of the E.U.
indEU <- which(worldMap$NAME%in%europeanUnion)
# Extract longitude and latitude border's coordinates of members states of E.U.
europeCoords <- lapply(indEU, function(i){
df <- data.frame(worldMap#polygons[[i]]#Polygons[[1]]#coords)
df$region =as.character(worldMap$NAME[i])
colnames(df) <- list("long", "lat", "region")
return(df)
})
europeCoords <- do.call("rbind", europeCoords)
value <- sample(x = seq(0,3,by = 0.1), size = length(europeanUnion),
replace = TRUE)
europeanUnionTable <- data.frame(country = europeanUnion, value = value)
europeCoords$value <- europeanUnionTable$value[match(europeCoords$region,europeanUnionTable$country)]
P <- ggplot() + geom_polygon(data = europeCoords, aes(x = long, y = lat, group = region, fill =
value),
colour = "black", size = 0.1) +
coord_map(xlim = c(-13, 35), ylim = c(32, 71))
How can I fix the problem and add the circle?
Thank you!
You might want to consider using the sf (simple features) package to work with geographic data.
Below is code to map the countries you've specified, and to plot a circle centered in Germany. You may need to crop or filter the data, as there are a few EU landmasses far outside what most consider 'Europe'.
The circle turns out a bit wonky due to the projection over a large area.
library(ggplot2)
library(grid)
#library(rworldmap)
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
# Member States of the European Union
europeanUnion <- c("Austria","Belgium","Bulgaria","Croatia","Cyprus",
"Czech Rep.","Denmark","Estonia","Finland","France",
"Germany","Greece","Hungary","Ireland","Italy","Latvia",
"Lithuania","Luxembourg","Malta","Netherlands","Poland",
"Portugal","Romania","Slovakia","Slovenia","Spain",
"Sweden")
library(rnaturalearth)
world_map <- ne_countries(scale = 50, returnclass = 'sf')
europe_map <- world_map %>% filter(name %in% europeanUnion)
circle <- st_as_sfc(st_bbox(europe_map %>% filter(name == 'Germany'))) %>%
st_transform(3035)%>% ## <- change projection to one in meters
st_centroid() %>% ## centroid point of the bounding box of germany
st_buffer(dist = 1e6) ## <- 1 million meters
p <- ggplot() +
geom_sf(data = europe_map, fill = 'orange') +
geom_sf(data = circle, fill = 'black', alpha = .2) +
theme_void()
p
Created on 2020-10-27 by the reprex package (v0.3.0)
Edit addressing questions in comment:
# make a point for frankfurt:
frankfurt <- st_point(x = c(8.6821, 50.1109)) %>% ## coords from google
st_geometry()
# 1896km buffer (circle) around frankfurt
frankfurt_circle_1896 <- frankfurt %>%
st_set_crs(4326) %>%
st_transform(3035) %>%
st_buffer(dist = 1896000) ## 1896km in m
## Crop the europe map, removing French Guana, etc.
## change x&y coords as needed, these are approximations
europe_bbox <- st_bbox(c(xmin = -12, xmax = 34, ymax = 71, ymin = 34), crs = st_crs(4326))
europe_map_cropped <- europe_map %>%
st_crop(europe_bbox)
# plotting
p2 <- ggplot() +
geom_sf(data = europe_map_cropped, fill = 'orange') +
geom_sf(data = frankfurt_circle_1896, alpha = .2, fill = 'black')
p2
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')