Search code examples
rggplot2mapslinespatial

Drawing lines between countries on pre-defined Robinson map


I have a specific map to which I'd like to add lines between countries, and later on, also line weights. The code below runs, but nothing shows up on the map. Note that df is created with ne_countries(), but I st_transform the map to Robinson projection, so I'm not sure the coordinates of df are correct.

I know there are many ways of drawing lines on a map, and I'm not sure geom_segment is the best so I'm open to other solutions. I'm fine with adding points and then lines, which I've seen but also couldn't make work.

library(ggplot2)
library(sf)
library(rnaturalearth)
library(dplyr)
library(rgdal)

#creating the map:

# Download countries polygons
shp <- ne_countries(type = "countries", 
                    scale = "medium",
                    returnclass = "sf") |>
  # transform to desired projection
  st_transform("ESRI:54030")

shp <- subset(shp, select = -c(1:17, 19:63))

# Creating df with long and lat

countries <- ne_countries()

countries$longitude <- coordinates(countries)[,1]

countries$latitude <- coordinates(countries)[,2]

countries_xy <- countries@data %>%
  select(admin, longitude, latitude)


# Joining long and lat to origin & destination

df1 <- tibble(origins = sample(c('Australia'), 
                                  size = 1, replace = TRUE), 
                 destinations = sample(c('Fiji', 'Papua New Guinea', 'Sweden'), 
                                       size = 3, replace = FALSE))

df2 <- df1 %>%
  left_join(countries_xy, by = c('origins' = 'admin')) %>%
  left_join(countries_xy, by = c('destinations' = 'admin'))

df2$longitude.y <- as.numeric(as.character(df3$longitude.y))

df2$latitude.y <- as.numeric(as.character(df3$latitude.y))

# Merging shp and df

shp <- merge(shp,df2, by = "name", all.x = TRUE)

# Drawing the lines on the map (note that I also color the map with a discrete variable, but I don't think that is important here)

shp |>
  ggplot() +
  geom_sf(aes(fill = discrete_var)) +
  geom_segment(aes(x = latitude.x, 
                   y = latitude.y,
                   xend = longitude.x,
                   yend = longitude.y), 
               color = "white") +
  scale_fill_brewer(palette = 'Purples', direction=-1, na.value="grey", drop = FALSE) +
  coord_sf(expand = FALSE)
  

I know there are many other ways of drawing lines on a map, and I'm not sure geom_segment is the best so I'm open to other solutions.


Solution

  • It is way easier to plot and analyse spatial data if they are all spatial objects (sf, sfc, SpatRaster etc.). So here's a method to create your lines as an sf object.

    It was not clear from your code where you wanted the lines to terminate so I have arbitrarily used country centroids for this example. It is possible to use st_nearest_points() if you want, but I believe centroids are a better choice visually for what you are mapping. Comment below if you want the lines to terminate in a different location.

    Also, you do not need to explicitly load the rgdal library as sf binds to it GDAL by default. Further, rgdal was deprecated last year and it's functions are being passed to sf. For example, functions like coordinates() can be handled by sf as st_coordinates().

    EDIT: Based on OP's comments, if you have previously installed rgdal, you may need to remove all traces of it from your library directory. That includes the versions of GDAL, GEOS, and PROJ that rgdal binds to. The answer was also updated as the OP requested a curved geom, hence the inclusion of the x1, x2, y1, and y2 columns.

    library(sf)
    library(dplyr)
    library(rnaturalearth)
    library(ggplot2)
    
    # Create world map in Robinson projection
    countries <- ne_countries() %>%
      st_transform("ESRI:54030")
    
    # Subset countries of interest and add lines between Australia and the rest
    sf_line <- countries %>%
      select(admin) %>%
      filter(admin %in% c("Australia", "Fiji", "Papua New Guinea", "Sweden")) %>%
      mutate(to = st_centroid(geometry)) %>%
      st_set_geometry("to") %>%
      select(-geometry) %>%
      mutate(from = to[admin == "Australia"],
             line_geom = st_union(to, from, by_feature = TRUE)) %>%
      filter(!admin == "Australia") %>%
      st_set_geometry("line_geom") %>%
      st_cast("LINESTRING") %>%
      mutate(x1 = st_coordinates(from)[,1],
             y1 = st_coordinates(from)[,2],
             x2 = st_coordinates(to)[,1],
             y2 = st_coordinates(to)[,2])
    
    ggplot() +
      geom_sf(data = countries, colour = "grey50") +
      geom_curve(data = sf_line, 
                 aes(x = x1, y = y1, 
                     xend = x2, yend = y2), 
                 colour = "#CD0BBC",
                 arrow = arrow(length = unit(0.03, "npc"), type="closed")) +
      theme(axis.title = element_blank())
    

    result