Interactive maps in R (with leaflet)

I’ve been asked a few times about making interactive maps in R. I thought I’d follow up on an example I wrote up for static bathymetric maps with a (very) quick example of interactive mapping with leaflet in R. More details are available at the awesome R leaflet website.

Step 1: make some data

Here’s some test data to plot. These structures could, of course, be replaced by real data.

# make a simple track line
lin = data.frame(lon = c(-65.17536, -65.37423, -65.64541, -66.06122, -66.15161),
                 lat = c(43.30837, 42.94679, 42.87448, 42.92871, 42.72985))

# make a few points
pts = data.frame(lon = c(-65.3, -65.7, -64.1),
                 lat = c(43.4, 43, 42.9))

# build a polygon (in this case the 'Roseway Basin Area To Be Avoided')
ply = data.frame(lon = c(-64.916667, -64.983333, -65.516667, -66.083333),
                 lat = c(43.266667, 42.783333, 42.65, 42.866667))

Step 2: build a basemap

Now let’s build a basemap. This is achieved with the leaflet() function, and a series of pipes (%>%) to sequentially add more features. Hopefully the comments adequately describe each feature. The whole thing is stored as the variable map.

# required libraries
library(leaflet, quietly = T, warn.conflicts = F)

# start basemap
map <- leaflet() %>% 
  
  # add ocean basemap
  addProviderTiles(providers$Esri.OceanBasemap) %>%
  
  # add another layer with place names
  addProviderTiles(providers$Hydda.RoadsAndLabels, group = 'Place names') %>%
  
  # add graticules from a NOAA webserver
  addWMSTiles(
    "https://gis.ngdc.noaa.gov/arcgis/services/graticule/MapServer/WMSServer/",
    layers = c("1-degree grid", "5-degree grid"),
    options = WMSTileOptions(format = "image/png8", transparent = TRUE),
    attribution = NULL,group = 'Graticules') %>%
  
  # focus map in a certain area / zoom level
  setView(lng = -65, lat = 43, zoom = 7) %>%
  
  # add layers control
  addLayersControl(overlayGroups = c('Place names',
                      'Graticules',
                      'Points',
                      'Lines',
                      'Polygons'),
                      options = layersControlOptions(collapsed = FALSE),
                      position = 'topright') %>%
      
      # list groups to hide on startup
      hideGroup(c('Place names'))

# show map
map

Step 3: add data to basemap

Adding data to the map is relatively straightforward. It’s nearly the same as adding features to the basemap. The main difference is that you need to actually overwrite and update the variable map each time you add something new, hence the map <- map %>% with each data type.

# add points
map <- map %>%
  addCircleMarkers(data = pts, ~lon, ~lat,
                   weight = 0.5,
                   col = 'black', 
                   fillColor = 'darkslategrey',
                   radius = 4, 
                   fillOpacity = 0.9, 
                   stroke = T, 
                   label = ~paste0('Point at: ', 
                                   as.character(round(lat,3)), ', ', 
                                   as.character(round(lon,3))), 
                   group = 'Points')

# add lines
map <- map %>%
  addPolylines(data = lin, ~lon, ~lat,
               weight = 3,
               color = 'red',
               popup = 'This is a line!', 
               smoothFactor = 3,
               group = 'Lines') 

# add polygons
map <- map %>%
  addPolygons(data=ply, lng=~lon, lat=~lat,
              weight = 1, 
              color = 'grey', 
              fillColor = 'grey',
              fill = T, 
              fillOpacity = 0.25, 
              stroke = T, 
              dashArray = c(5,5), 
              smoothFactor = 3,
              options = pathOptions(clickable = F),
              group = 'Polygons')

# show map
map

Step 4: add extra features

There are a ton of cool features that can be added. Check out packages like leaflet_extras for more details. These are a few that I find very useful.

# add more features
map <- map %>% 
      
      # add a map scalebar
      addScaleBar(position = 'topright') %>%
      
      # add measurement tool
      addMeasure(
        primaryLengthUnit = "kilometers",
        secondaryLengthUnit = 'miles', 
        primaryAreaUnit = "hectares",
        secondaryAreaUnit="acres", 
        position = 'topleft')

# show map    
map    

Step 5: saving the map

The fact that these maps can be saved as stand-alone html widgets is pretty incredible. This means they can be treated effectively as a normal image file, but retain their interactivity. It also makes them very, very easy to embed in a web page.

I recently also discovered the mapshot() function that saves a simple static screenshot, which can be very nice for writing in formats that don’t support interactive graphics. Directly saving the map made here is a little ugly because of all the buttons and such, but just about all of those things can be easily removed if so desired.

Uncomment either chunk depending on your desired output format.

# # # save a stand-alone, interactive map as an html file
# library(htmlwidgets)
# saveWidget(widget = map, file = 'map.html', selfcontained = T)

# # # save a snapshot as a png file
# library(mapview)
# mapshot(map, file = 'map.png')

Related

Previous
comments powered by Disqus