Search code examples
rtmap

How to force text displayed by the tm_text function of R's tmap to remain inside polygon?


Congressional districts of Alabama during the 103rd US CongressI'm using the tm_text function in the tmap package (for R) to draw maps and I want to force the text that is displayed to remain inside the polygon that it refers to. How do I achieve this with this function? (Some polygons are weirdly shaped and they end up having their text falling outside the polygon. The picture above illustrates an example; the 6th district has a weird shape. I would like to know if it is possible to have its label automatically placed inside the polygon as I need to plot a large number of maps.)


Solution

  • First of all your text label for e.g. 6 is outside the polygon since tmap's tm_text uses the polygon centroids as position to place the label and the centroid of the corresponding polygon is inside. Here's a reproduction of the centroids (blue dots) that are used (see left plot below):

    library(tidyverse)
    library(sf)
    library(tmap)
    
    # shape file data from: 
    # https://earthworks.stanford.edu/catalog/tufts-uscongdist107th01
    df <- st_read("GISPORTAL_GISOWNER01_USCONGDIST107TH01.shp") %>% 
      filter(STATE == "AL") %>% 
      distinct(CONG_DIST, .keep_all = T)
    
    # get polygon centroids to show how tmap works
    df_points_centroid <- df %>% 
      mutate(centroid = st_centroid(geometry)) %>% 
      as.data.frame() %>% 
      select(-geometry) %>% 
      st_as_sf()
    
    tm_shape(df) +
      tm_borders() +
      tm_fill(col = "gray") +
      tm_text("CONG_DIST") +
      tm_shape(df_points_centroid) +
      tm_dots(col = "blue", size = .1) +
      tm_layout(frame = F)
    

    To use label positions which are always inside the polygon you can use st_point_on_surface() from the sf package (see red dots and labels in the right plot):

    # get points guaranteed to be within the polygon
    df_points_within <- df %>% 
      mutate(point_within = st_point_on_surface(geometry)) %>%
      as.data.frame() %>%
      select(-geometry) %>%
      st_as_sf()
    
    # show old and new text locations together
    tm_shape(df) +
      tm_borders() +
      tm_fill(col = "gray") +
      tm_shape(df_points_centroid) +
      tm_dots(col = "blue", size = .1) +
      tm_shape(df_points_within) +
      tm_dots(col = "red", size = .1) +
      tm_text("CONG_DIST") +
      tm_layout(frame = F)
    

    enter image description here