rtmap

Inset formatting in tmap


I would like to use the tmap package to create an extent indicator inset map to show a zoomed-out view of the area. However, I'm having trouble controlling the frame and size of the inset map. In the example below, I have tried changing a range of options that seem like they should impact the issue. However, I still cannot get the inset area to be tight to the inset map and change things like the size without margins/padding affecting placement, even though I can make those components invisible, they are still affecting the overall layout.

library(sf)
library(tmap)
library(tidyverse)
library(maptiles)
library(tmaptools)



nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) 

nc_sub <- nc %>%
  filter(NAME %in% c("Clay")) 


nc_sub_bound <- nc_sub %>%
  st_bbox() %>%
  st_as_sfc() %>% st_as_sf()



nc_basemap <- get_tiles(nc,
                        provider = "Esri.WorldTopoMap" ,
                        crop = T,
                        zoom = 8)
nc_sub_basemap <- get_tiles(nc_sub,
                            provider = "Esri.WorldTopoMap" ,
                            crop = T,
                            zoom = 12)


# no customisation --------------------------------------------------------

nc_sub_map_raw <- tm_shape(nc_sub_basemap) + tm_rgb(options = tmap:::opt_tm_rgb(interpolate = T)) +
  tm_shape(nc_sub) + tm_polygons(fill_alpha = 0, lwd = 2)


nc_map_raw <- tm_shape(nc_basemap) + 
  tm_rgb(options = tmap:::opt_tm_rgb(interpolate = T)) +
  tm_shape(nc) + tm_polygons(fill_alpha = 0) + 
  tm_shape(nc_sub_bound) + 
  tm_borders(col =
               "red", lwd = 2) 


#does not work need to convert to grob
nc_sub_map_raw+
  tm_inset(nc_map_raw)


inset_grob_raw <- tmap_grob(nc_map_raw)

#draws maps but mismatch on frame and inset
nc_sub_map_raw+
  tm_inset(inset_grob_raw, position = c("right", "top"))




# change options -----------------------------------------------------------


#remove all margins and set aspect ratio explicitly on the maps

nc_sub_map <- tm_shape(nc_sub_basemap) + tm_rgb(options = tmap:::opt_tm_rgb(interpolate = T)) +
  tm_shape(nc_sub) + tm_polygons(fill_alpha = 0, lwd = 2)+ 
  tm_layout(margins = c(0, 0, 0, 0),
            outer.margins = c(0, 0, 0, 0),
            inner.margins = c(0, 0, 0, 0),
            asp=get_asp_ratio(nc_sub))



nc_map <- tm_shape(nc_basemap) + 
  tm_rgb(options = tmap:::opt_tm_rgb(interpolate = T)) +
  tm_shape(nc) + tm_polygons(fill_alpha = 0) + 
  tm_shape(nc_sub_bound) + 
  tm_borders(col =
               "red", lwd = 2) + 
  tm_layout(margins = c(0, 0, 0, 0),
            outer.margins = c(0, 0, 0, 0),
            inner.margins = c(0, 0, 0, 0),
            asp=get_asp_ratio(nc))


inset_asp <- get_asp_ratio(nc_map)


inset_grob <- tmap_grob(nc_map)


#still has larger frame
nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"))


#gets rid of frame but causes issues if resizing due to margins? only makes frame and backgroupd invisible but still impacting layout
nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = NA, bg.alpha = 0)


nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = NA, bg.alpha = 0, height=10, width = 10*inset_asp)

nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = 1, bg.alpha = 0, height=10, width = 10*inset_asp)# same but turning frame back on to show being moved around




# trying different options ------------------------------------------------


#setting margins to zero does not affect in inset call
nc_sub_map+
  nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = 1, bg.alpha = 0, height=10, width = 10*inset_asp, margins = c(0, 0, 0, 0), between_margin = c(0, 0, 0, 0))




#layout options also not fixing


nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = 1, bg.alpha = 0, height=10, width = 10*inset_asp, margins = c(0, 0, 0, 0), between_margin = c(0, 0, 0, 0))+
  tm_layout(inset_grob.height = 10,  inset_grob.width = 10*5, inset_map.height = 10, inset_map.width = 10*inset_asp,inset.margins=c(0,0,0,0), inset.in = 0)

nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = 1, bg.alpha = 0, margins = c(0, 0, 0, 0), between_margin = c(0, 0, 0, 0))+
  tm_layout(inset_grob.height = 10,  inset_grob.width = 10*inset_asp)

nc_sub_map+
  tm_inset(inset_grob, position = c("right", "top"), frame.lwd = 1, bg.alpha = 0, margins = c(0, 0, 0, 0), between_margin = c(0, 0, 0, 0))+
  tm_layout(inset_grob.height = 10,  inset_grob.width = 10*inset_asp, inset_map.margins = c(0, 0, 0, 0), inset_map.frame = F, inset.frame = F, inset.margins = c(0, 0, 0, 0), inset.between_margin = c(0, 0, 0, 0), inset_map.between_margin = c(0, 0, 0, 0))


# Save final map with inset
final_map <- nc_sub_map +
  tm_inset(inset_grob, position = c("right", "top"), 
           frame.lwd = 1, bg.alpha = 0, 
           height = 10, width = 10 * inset_asp)

# Save to file
tmap_save(final_map, filename = "nc_map_with_inset.png", width = 10, height = 10, units = "in", dpi = 300)

Output: enter image description here


Solution

  • You can use magick package to remove empty margins from the final file and add a customized border. Also you can make use of viewport and tmap_save to save the map with the inset in a single file (and control inset position).

    library(grid)
    library(magick)
    
    # Save map with inset
    tmap_save(nc_sub_map_raw,
              filename = "map1.png",
              dpi = 300, 
              insets_tm = nc_map_raw, 
              insets_vp = viewport(x = 0.97,
                                       y = 0.78, 
                                       width = 0.2,
                                       height = 0.2, 
                                       just = c("right", "top")),
              height = 15, 
              width = 20, 
              units = "cm")
    
    # Trim empty spaces
    map1 <- image_read("map1.png")
    map1 <- image_trim(map1)
    # Add 20 px border in white
    map1 <- image_border(map1, "#FFFFFF", "20x20")
    
    # Save final map
    image_write(map1, 
                path = "map1.png", 
                format = "png")
    
    

    enter image description here