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.
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())