RObservations #17: Plotting Flight Paths on Leaflet Maps
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Introduction
While I thought I would to put exploring making maps in leaflet to rest, I was tempted to touch making flight maps similar to the ones on Atlas.co and have successfully managed to do it by incorporating the geosphere
package. In this blog I am going to share some more code on how to plot individual and multiple flight paths in R with the leaflet
, osrm
, tidygeocoder
and geosphere
packages . For how to make your own road trip maps and incorporate the Mapbox API to make things pretty, Check out my previous two blogs here and here.
The code
While the code does most of the talking, the main idea for getting flight path points is to use the geosphere
package’s gcIntermediate()
function and tune the arguments accordingly. While it may not be the the only way, it sure is convienient.
Here’s a sample of the output gcIntermediate()
produces when given geocoded locations.
library(tidyverse) library(leaflet) library(tidygeocoder) library(geosphere) library(osrm) # Getting Geocoded Locations- Flying from Toronto to Tel Aviv addresses <- tibble(singlelineaddress = c("YYZ","TLV")) %>% geocode(address=singlelineaddress,method = 'arcgis') %>% transmute(id = singlelineaddress, lon=long, lat=lat) # Geocoded Locations addresses ## # A tibble: 2 x 3 ## id lon lat ## <chr> <dbl> <dbl> ## 1 YYZ -79.6 43.7 ## 2 TLV 34.8 32.1 trip <-gcIntermediate(addresses[1,2:3], addresses[2,2:3], # Choose the number of points you want. Determines Smoothness (less is more) n=30, addStartEnd = T) # Curve points trip ## lon lat ## lon -79.6128600 43.68066 ## -76.6377314 45.33974 ## -73.4875834 46.91700 ## -70.1521267 48.40271 ## -66.6231274 49.78636 ## -62.8954387 51.05672 ## -58.9681873 52.20204 ## -54.8460270 53.21037 ## -50.5403081 54.06995 ## -46.0699503 54.76967 ## -41.4617740 55.29970 ## -36.7500653 55.65208 ## -31.9752511 55.82122 ## -27.1817327 55.80437 ## -22.4151231 55.60180 ## -17.7192969 55.21681 ## -13.1337086 54.65544 ## -8.6913660 53.92604 ## -4.4176681 53.03871 ## -0.3301223 52.00472 ## 3.5612087 50.83591 ## 7.2527620 49.54421 ## 10.7462762 48.14129 ## 14.0475406 46.63826 ## 17.1652318 45.04551 ## 20.1099154 43.37261 ## 22.8932409 41.62827 ## 25.5273265 39.82038 ## 28.0243112 37.95601 ## 30.3960470 36.04150 ## 32.6538991 34.08252 ## lon 34.8086300 32.08413
I find that using a smaller n
makes for a nicer map and is pretty subjective. So for the plotting function I let the user be in control of the n
with the nCurves
argument. It is set default at 30.
Individual Flights
I first worked on plotting flights with just two points. The plot_flight
function takes two primary arguments: to
and from
. The other arguments are relevant for tuning the aesthetics and choosing a MapBox Template you want to use.
plot_flight<-function(to, from, colour="black", opacity=1, weight=1, radius=2, label_text=c(to,from), label_position="bottom", font = "Lucida Console", font_weight="bold", font_size= "14px", text_indent="15px", mapBoxTemplate= "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", nCurves=30){ address_single <- tibble(singlelineaddress = c(to,from)) %>% geocode(address=singlelineaddress,method = 'arcgis') %>% transmute(id = singlelineaddress, lon=long, lat=lat) trip <-gcIntermediate(address_single[1,2:3], address_single[2,2:3], n=nCurves, addStartEnd = T) m<-leaflet(trip, options = leafletOptions(zoomControl = FALSE, attributionControl=FALSE)) %>% fitBounds(lng1 = max(address_single$lon)+0.1, lat1 = max(address_single$lat)+0.1, lng2 = min(address_single$lon)-0.1, lat2 = min(address_single$lat)-0.1) %>% addTiles(urlTemplate = mapBoxTemplate) %>% addCircleMarkers(lat = address_single$lat, lng = address_single$lon, color = colour, stroke = FALSE, radius = radius, fillOpacity = opacity) %>% addPolylines(color = colour, opacity=opacity, weight=weight, smoothFactor = 0) %>% addLabelOnlyMarkers(address_single$lon, address_single$lat, label = label_text, labelOptions = labelOptions(noHide = T, direction = label_position, textOnly = T, style=list("font-family" = font, "font-weight"= font_weight, "font-size"=font_size, "text-indent"=text_indent))) m }
A visual of a flight from Toronto, Canada to Tel-Aviv, Israel looks like this:
(Be sure to put in the Airport Codes for a more exact location.)
plot_flight("YYZ", "TLV", weight = 1, opacity = 1, nCurves = 30, mapBoxTemplate = "https://api.mapbox.com/styles/v1/benyamindsmith/ckvufe72g2qvu15pewrh6m20f/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiYmVueWFtaW5kc21pdGgiLCJhIjoiY2tiMDR3NXpzMDU3bjJ1cXVqNmx3ODhudyJ9.KffbwvHgcIn1GL1DV4uUBQ")
Multiple Flights
The code for 2 or more flights is very similar to the code for an individual flight except instead of having to
and from
arguments, there is an addresses
argument which will take a vector of locations which should be ordered in the flight path you want.
plot_flights<-function(addresses, colour="black", opacity=1, weight=1, radius=2, label_text=addresses, label_position="bottom", font = "Lucida Console", font_weight="bold", font_size= "14px", text_indent="15px", mapBoxTemplate= "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", nCurves=100){ address_single <- tibble(singlelineaddress = addresses) %>% geocode(address=singlelineaddress,method = 'arcgis') %>% transmute(id = singlelineaddress, lon=long, lat=lat) trip <- matrix(nrow=1,ncol=2) for(i in 2:nrow(address_single)){ trip<-rbind(trip, gcIntermediate(address_single[i-1,2:3], address_single[i,2:3], n=nCurves, addStartEnd = T) ) } m<-leaflet(trip, options = leafletOptions(zoomControl = FALSE, attributionControl=FALSE)) %>% fitBounds(lng1 = max(address_single$lon)+0.1, lat1 = max(address_single$lat)+0.1, lng2 = min(address_single$lon)-0.1, lat2 = min(address_single$lat)-0.1) %>% addTiles(urlTemplate = mapBoxTemplate) %>% addCircleMarkers(lat = address_single$lat, lng = address_single$lon, color = colour, stroke = FALSE, radius = radius, fillOpacity = opacity) %>% addPolylines(color = colour, opacity=opacity, weight=weight, smoothFactor = 0) %>% addLabelOnlyMarkers(address_single$lon, address_single$lat, label = label_text, labelOptions = labelOptions(noHide = T, direction = label_position, textOnly = T, style=list("font-family" = font, "font-weight"= font_weight, "font-size"=font_size, "text-indent"=text_indent))) m } plot_flights(c("YYZ", "GIG", "CPT/FACT", "BOM", "TLV"), weight = 1, opacity = 1, nCurves = 10, label_position="right", mapBoxTemplate = "https://api.mapbox.com/styles/v1/benyamindsmith/ckvufe72g2qvu15pewrh6m20f/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoiYmVueWFtaW5kc21pdGgiLCJhIjoiY2tiMDR3NXpzMDU3bjJ1cXVqNmx3ODhudyJ9.KffbwvHgcIn1GL1DV4uUBQ")
Conclusion
Playing around with leaflet
has been an incredible experience, and it is pretty cool to have been able to reverse engineer a custom map business with it as well. When I get the time, maybe I’ll try to put this together in a package or try building my own platform for making custom maps with Shiny. So far, its been cool playing with this within a developer environment, but it would be cool to try building (and deploying) a end user product with Shiny to show that its possible as well.
Don’t steal my idea! But if you do – I’ll be flattered, but ask for some credit!
Want to see more of my content?
Be sure to subscribe and never miss an update!
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.