I would like to plot a figure with small multiple maps using ggplot2::geom_sf
. The challenge here is how to do this keeping all maps centered in the image and at the same spatial scale. Here is the problem (data for reproducible example below):
A simple map using facet_wrap
put all polygons at the same spatial scale, but they are not centered.
ggplot(states6) +
geom_sf() +
facet_wrap(~name_state)
Here is a solution from this SO question
that uses cowplot
. In this case, polygons are centered but they come at different spatial scales
g <- purrr::map(unique(states6$name_state),
function(x) {
# subset data
temp_sf <- subset(states6, name_state == x)
ggplot() +
geom_sf(data = temp_sf, fill='black') +
guides(fill = FALSE) +
ggtitle(x) +
ggsn::scalebar(temp_sf, dist = 100, st.size=2,
height=0.01, model = 'WGS84',
transform = T, dist_unit='km')
})
g2 <- cowplot::plot_grid(plotlist = g)
g2
I've found the same problem using the tmap
library.
tm_shape(states6) +
tm_borders(col='black') +
tm_fill(col='black') +
tm_facets(by = "name_state ", ncol=3) +
tm_scale_bar(breaks = c(0, 50, 100), text.size = 3)
The output I would like to get is something similar to this:
library(sf)
library(geobr)
library(mapview)
library(ggplot2)
library(ggsn)
library(cowplot)
library(purrr)
library(tmap)
# Read all Brazilian states
states <- geobr::read_state(code_state = 'all', year=2015)
# Select six states
states6 <- subset(states, code_state %in% c(35,33,53,29,31,23))
To fix this, you may crop the geometries in your spatial dataset as if you took a knife a sliced through the shapes on the map to take out a rectangular region of interest. The alternative would be to restrict the display window of your plot, which is like taking the map and folding it so that you only see the region of interest.
These two types of maps--multiple layers and choropleths--form the basis for most mapping applications. With the appropriate combination of the tools demonstrated here and sufficient patience, you can create a wide range of maps with colors, points of interest, labels, highlights, and annotations.
Most likely that data frame is not an "sf" object. You can check the structure of the data frame by using str (dataframe), and seeing if it says that the dataframe is a 'sf" object or multiploygon object. A geometry refers to the x and y for each point/area, which geom_sf () needs.
We see that we have a spatial dataset consisting of several attributes (only name and continent are shown above) for each spatial feature which is stored in the geometry column. The header above the table displays further information such as the coordinate reference system (CRS) which will be important later.
It´s not ideal but you can make several plots programmatically with the same box size and then put them together using ::gridExtra. To get the center of each box, use the centroid of each geometry.
library(sf)
library(geobr)
library(mapview)
library(ggplot2)
library(gridExtra)
Read all Brazilian states:
states <- geobr::read_state(code_state = 'all', year=2015)
Select six states:
states6 <- subset(states, code_state %in% c(35,33,53,29,31,23))
centroids, for reference in the ggplot bellow (I had to set the projection, make changes here if needed):
states6$centroid <-
sf::st_transform(states6, 29101) %>%
sf::st_centroid() %>%
sf::st_transform(., '+proj=longlat +ellps=GRS80 +no_defs') %>%
sf::st_geometry()
set padding:
padding <-7
function to make plots:
graph <- function(x){
ggplot2::ggplot(states6[x,]) +
geom_sf() +
coord_sf(xlim = c(states6$centroid[[x]][1]-padding ,
states6$centroid[[x]][1]+padding),
ylim = c(states6$centroid[[x]][2]-padding ,
states6$centroid[[x]][2]+padding),
expand = FALSE)
}
create a bunch of plots:
plot_list <- lapply(X = 1:nrow(states6), FUN = graph)
grid them together:
g <- cowplot::plot_grid(plotlist = plot_list, ncol = 3)
g
A bit of a hack, but here is a possible tmap
solution based on computing the max width of the different states and then create a "dummy" layer of points spaced max_width/2 from the centroids of each state to "force" a constant width of the facets and thus a constant scale:
library(sf)
library(geobr)
library(tmap)
library(dplyr)
# Read all Brazilian states
states <- geobr::read_state(code_state = 'all', year=2015)
# Select six states
states6 <- subset(states, code_state %in% c(35,33,53,29,31,23)) %>%
sf::st_set_crs(4326)
# compute bboxes and find width of the widest one
bboxes <- lapply(sf::st_geometry(states6),
FUN = function(x) as.numeric(st_bbox((x))))
which_max_wid <- which.max(lapply(bbs, FUN = function(x) abs(x[1] - x[3])))
max_wid <- bbs[[which_max_wid]][1] - bbs[[which_max_wid]][3]
# create some fake points, at a distance of max_wid/2 from
# centroids of each state, then a multipoint by state_name
fake_points_min <- st_sf(name_state = states6$name_state,
geometry = st_geometry(sf::st_centroid(states6)) - c(max_wid/2, 0))
fake_points_max <- st_sf(name_state = states6$name_state,
geometry = st_geometry(sf::st_centroid(states6)) + c(max_wid/2, 0))
fake_points <- rbind(fake_points_min,fake_points_max) %>%
dplyr::group_by(name_state) %>%
dplyr::summarize() %>%
dplyr::ungroup() %>%
sf::st_set_crs(4326)
# plot
plot <- tm_shape(states6) +
tm_graticules() +
tm_borders(col='black') +
tm_fill(col='black') +
tm_facets(by = "name_state", ncol=3) +
tm_scale_bar(breaks = c(0, 150, 300), text.size = 3) +
tm_shape(fake_points) + #here we add the point layer to force constant width!
tm_dots(alpha = 0)+
tm_facets(by = "name_state", ncol=3)
plot
, giving:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With