This post explains how to draw **connection lines** between several localizations on a map, using **R**. The method proposed here relies on the use of the **gcIntermediate** function from the geosphere package. Instead of making straight lines, it offers to draw the shortest routes, using great circles. A special care is given for situations where cities are very far from each other and where the shortest connection thus passes behind the map.

First we need to load 3 libraries. Maps allows to draw the background map, and geosphere provides the gcintermediate function.

1 2 3 4 5 |
library(tidyverse) library(maps) library(geosphere) |

**1- Draw an empty map**

This is easily done using the** ‘maps’** package. You can see plenty of other maps made with R in the map section of the R graph gallery.

1 2 3 4 |
par(mar=c(0,0,0,0)) map('world',col="#f2f2f2", fill=TRUE, bg="white", lwd=0.05,mar=rep(0,4),border=0, ylim=c(-80,80) ) |

**2- Add 3 cities**

First we create a data frame with the coordinates of Buenos Aires, Melbourne and Paris

1 2 3 4 5 6 7 |
Buenos_aires=c(-58,-34) Paris=c(2,49) Melbourne=c(145,-38) data=rbind(Buenos_aires, Paris, Melbourne) %>% as.data.frame() colnames(data)=c("long","lat") |

Then add it to the map using the ‘points’ function:

1 2 3 |
points(x=data$long, y=data$lat, col="slateblue", cex=3, pch=20) |

**4- Show connection between them**

Now we can connect cities drawing the shortest route between them. This is done using great circles, what gives a better visualization than using straight lines. This technique has been proposed by Nathan Yau on FlowingData

1 2 3 4 5 6 7 8 9 |
# Connection between Buenos Aires and Paris inter <- gcIntermediate(Paris, Buenos_aires, n=50, addStartEnd=TRUE, breakAtDateLine=F) lines(inter, col="slateblue", lwd=2) # Between Paris and Melbourne inter <- gcIntermediate(Melbourne, Paris, n=50, addStartEnd=TRUE, breakAtDateLine=F) lines(inter, col="slateblue", lwd=2) |

**5 – Correcting gcIntermediate**

If we use the same method between Melbourne and Buenos Aires, we get this disappointing result:

What happens is that gcintermediate follows the shortest path, which means it will go East from Australia until the date line, break the line and come back heading East from the pacific to South America. Because we do not want to see the horizontal line, we need to plot this connection in 2 times.

To do so we can use the following function, which breaks the line in 2 sections when the distance between 2 points is longer than 180 degrees.

1 2 3 4 5 6 7 8 9 10 11 12 13 |
plot_my_connection=function( dep_lon, dep_lat, arr_lon, arr_lat, ...){ inter <- gcIntermediate(c(dep_lon, dep_lat), c(arr_lon, arr_lat), n=50, addStartEnd=TRUE, breakAtDateLine=F) inter=data.frame(inter) diff_of_lon=abs(dep_lon) + abs(arr_lon) if(diff_of_lon > 180){ lines(subset(inter, lon>=0), ...) lines(subset(inter, lon<0), ...) }else{ lines(inter, ...) } } |

Let’s try it!

1 2 3 4 5 6 7 8 |
map('world',col="#f2f2f2", fill=TRUE, bg="white", lwd=0.05,mar=rep(0,4),border=0, ylim=c(-80,80) ) points(x=data$long, y=data$lat, col="slateblue", cex=3, pch=20) plot_my_connection(Paris[1], Paris[2], Melbourne[1], Melbourne[2], col="slateblue", lwd=2) plot_my_connection(Buenos_aires[1], Buenos_aires[2], Melbourne[1], Melbourne[2], col="slateblue", lwd=2) plot_my_connection(Buenos_aires[1], Buenos_aires[2], Paris[1], Paris[2], col="slateblue", lwd=2) |

**6 – Apply it to several pairs of cities**

Let’s consider 8 cities:

1 2 3 4 5 6 7 8 9 10 11 12 13 |
data=rbind( Buenos_aires=c(-58,-34), Paris=c(2,49), Melbourne=c(145,-38), Saint.Petersburg=c(30.32, 59.93), Abidjan=c(-4.03, 5.33), Montreal=c(-73.57, 45.52), Nairobi=c(36.82, -1.29), Salvador=c(-38.5, -12.97) ) %>% as.data.frame() colnames(data)=c("long","lat") |

We can generate all pairs of coordinates

1 2 3 4 |
all_pairs=cbind(t(combn(data$long, 2)), t(combn(data$lat, 2))) %>% as.data.frame() colnames(all_pairs)=c("long1","long2","lat1","lat2") |

And plot every connections:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# background map par(mar=c(0,0,0,0)) map('world',col="#f2f2f2", fill=TRUE, bg="white", lwd=0.05,mar=rep(0,4),border=0, ylim=c(-80,80) ) # add every connections: for(i in 1:nrow(all_pairs)){ plot_my_connection(all_pairs$long1[i], all_pairs$lat1[i], all_pairs$long2[i], all_pairs$lat2[i], col="skyblue", lwd=1) } # add points and names of cities points(x=data$long, y=data$lat, col="slateblue", cex=2, pch=20) text(rownames(data), x=data$long, y=data$lat, col="slateblue", cex=1, pos=4) |

**7 – An alternative using the greatCircle function**

This is the method proposed by the Simply Statistics Blog to draw a twitter connection map. The idea is to calculate the whole great circle, and keep only the part that stays in front of the map, never going behind it.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# A function that keeps the good part of the great circle, by Jeff Leek: getGreatCircle = function(userLL,relationLL){ tmpCircle = greatCircle(userLL,relationLL, n=200) start = which.min(abs(tmpCircle[,1] - data.frame(userLL)[1,1])) end = which.min(abs(tmpCircle[,1] - relationLL[1])) greatC = tmpCircle[start:end,] return(greatC) } # map 3 connections: map('world',col="#f2f2f2", fill=TRUE, bg="white", lwd=0.05,mar=rep(0,4),border=0, ylim=c(-80,80) ) great=getGreatCircle(Paris, Melbourne) lines(great, col="skyblue", lwd=2) great=getGreatCircle(Buenos_aires, Melbourne) lines(great, col="skyblue", lwd=2) great=getGreatCircle(Paris, Buenos_aires) lines(great, col="skyblue", lwd=2) points(x=data$long, y=data$lat, col="slateblue", cex=3, pch=20) text(rownames(data), x=data$long, y=data$lat, col="slateblue", cex=1, pos=4) |

Note that the R graph gallery proposes lot of other examples of maps made with R. You can follow the gallery on Twitter or on Facebook to be aware or recent updates.

The sharing is very useful! Thanks a lot! But I still have one question. How to add arrows to these lines? From the origin to destination?

Hi Yanan,

In base R, you can add arrows using the arrow function. (https://stat.ethz.ch/R-manual/R-devel/library/graphics/html/arrows.html). Each line you see on the map is composed by several segments. You can try to redraw the last segment only using the arrow function to have an arrow at the end.

Note that this can be done using ggplot2 as well. The geom_line function has an arrow argument.

Hope it helps,

Yan

tinter <- tail(inter,2)

arrows(tinter[1,1], tinter[1,2], tinter[2,1], tinter[2,2], length=0.1, col = "slateblue" )

this works for me

This R tutorial is very helpful for beginners. I like that you took use through all the steps of creating a map to connecting the routes. I am trying to use a similar program for connecting locations of leads on our prospecting tools. Also, thanks for the tip on the R Graph Gallery. I will follow it too.

This is so clear and informative tutorial!.

I was wondering if there is any way to assign thickness/thiness to the lines ?

Say there are 10 flights from Paris to Buenos Aires , I would like to plot ten lines originating from Paris into Buenos Aires with one color. Etc.

Say there are 8 flights from bangkok to singapore, I would like to plot 8 lines originating from bangkok into Singapore with another color.

Can we do this in R? if so how? Thank you in advance

Thanks,

Vivek

Hi Vivek.

Thanks for your comment, I’m glad this little tutorial was useful to you.

You can definitely achieve what you need with R. For example, you could play with the lwd argument of the line function to control the thickness of each line. If you have hips of lines, I suggest to plot thin lines first, thick ones last.

Actually I am preparing a tutorial for that that I plan to publish in the next few weeks, so keep in touch!

Best,

Yan

Very useful! Thank you.

thank you

You’re welcome !

🙂