Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Small multiple maps with geom_sf at the same spatial scale

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)

enter image description here

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

enter image description here

I've found the same problem using the tmaplibrary.

 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)

Desired output

The output I would like to get is something similar to this:

enter image description here

Data for reproducible example

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))
like image 493
rafa.pereira Avatar asked Oct 24 '19 22:10

rafa.pereira


People also ask

How do I crop the geometries in my spatial dataset?

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.

What are the two types of maps in geography?

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.

Is it possible to use Geom_SF() on a Dataframe?

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.

Where are spatial features stored in the dataset?

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.


2 Answers

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

results

like image 122
Arthur Avatar answered Nov 15 '22 07:11

Arthur


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:

enter image description here

like image 41
lbusett Avatar answered Nov 15 '22 07:11

lbusett