RObservations #17: Plotting Flight Paths on Leaflet Maps

[This article was first published on r – bensstats, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
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!

My custom maps on post cards!

Want to see more of my content?

Be sure to subscribe and never miss an update!

To leave a comment for the author, please follow the link and comment on their blog: r – bensstats.

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.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)