Interactive Maps with tmap and Shiny

Session 7: Introduction to tmap.mapgl

Martijn Tennekes

Beyond Leaflet

tmap modes — recap

Mode Package Backend JS library
"plot" tmap grid (none)
"view" tmap leaflet Leaflet
"mapbox" tmap.mapgl mapgl Mapbox GL JS
"maplibre" tmap.mapgl mapgl MapLibre GL JS

The "view" mode (Leaflet) is great for most interactive maps. The mapgl modes extend this with large-data performance, 3D visualisation, and a globe CRS.

Mapbox vs MapLibre

  • Mapbox GL JS — widely used JS library for high-performance web mapping
    • Adopted a proprietary licence in December 2020
    • Free for personal use, but requires an API key; usage limits apply
  • MapLibre GL JSopen-source fork created immediately after Mapbox’s licence change
    • Fully free, no API key required, community-maintained
    • Backed by organisations including AWS, Microsoft, and Meta

For most purposes during this course, MapLibre is the preferred choice.

Why use tmap.mapgl?

✅ Advantages over "view" mode:

  • Better performance for large datasets (GPU rendering; WebGL also exists in "view" via leafgl, but more restricted)
  • 3D polygon extrusion with tm_polygons_3d() — Session 8
  • Globe CRS — no projection distortion for world maps
  • Some basemaps are rendered in 3D (terrain, buildings)

⚠️ Trade-offs:

  • Layer blending (new feature tmap 4.4) cannot be supported
  • Some tmap features behave slightly differently or are not yet supported
  • "mapbox" requires a free API key

Getting started

Installation

install.packages("tmap.mapgl")

# Or development version:
remotes::install_github("r-tmap/tmap.mapgl")

tmap.mapgl depends on the mapgl package, which is installed automatically.

MapLibre — no API key needed

library(tmap)
library(tmap.mapgl)

tmap_mode("maplibre")

tm_shape(World) +
  tm_polygons("well_being",
    fill.scale = tm_scale_continuous(values = "pu_gn"))

Once tmap.mapgl is loaded, four modes are available and rtm() rotates through them all.

Switching between modes with rtm() and ttm()

library(tmap)
library(tmap.mapgl)

ttm()             # toggles plot <-> view (both loaded by default)

tmap_mode("maplibre")  # switch to maplibre

ttm()             # now toggles maplibre <-> view (previous mode)

rtm()             # rotates: plot -> view -> mapbox -> maplibre -> ...

Mapbox — API key required

# Set your Mapbox API key once per session (or in .Renviron)
mapbox_key <- "your_key_here"
Sys.setenv(MAPBOX_TOKEN = mapbox_key)

tmap_mode("mapbox")

tm_shape(World) +
  tm_polygons("well_being",
    fill.scale = tm_scale_continuous(values = "pu_gn"))

Get a free key at account.mapbox.com.

Mode-specific options

tm_maplibre() and tm_mapbox()

Just as tm_view() controls Leaflet options, use tm_maplibre() / tm_mapbox() for mapgl-specific options:

tmap_mode("maplibre")

tm_shape(World) +
  tm_polygons("well_being") +
  tm_maplibre(
    pitch = 0,    # camera pitch (0 = top-down, 60 = oblique)
    zoom = 2      # initial zoom level
  )

Background maps via tmap_providers()

In the mapgl modes, background maps are set via tm_basemap() — the same as in "view" mode. The available providers are queried with tmap_providers():

tmap_mode("maplibre")
tmap_providers()    # lists providers for the active mode

tm_shape(NLD_muni) +
  tm_polygons("employment_rate") +
tm_basemap("CartoDB.Positron")

MapLibre/Mapbox JS use the term “style” for their background maps. In tmap, these are exposed as basemaps via tm_basemap() — keeping them separate from tmap’s own style system (tmap_style()).

CRS: the globe view

The default CRS in "maplibre" mode is the globe — no projection distortion. This makes it ideal for world maps:

tmap_mode("maplibre")

# World choropleth — globe CRS by default
tm_shape(World) +
  tm_polygons("HPI")

# Or a bubble map
tm_shape(World) +
  tm_polygons() +
tm_shape(metro) +
  tm_bubbles(size = "pop2020")

CRS: the globe view (cont.)

In the map viewer, two controls appear at the top right:

  • Globe icon — toggles between globe CRS and Web Mercator (EPSG:3857)
  • Navigation arrows (compass rose) — resets the bearing to north-up; drag to rotate

These controls let users switch projection and orientation interactively.

Same tmap code, different mode

The same map specification works across modes — just switch the mode:

tm <- tm_shape(NLD_muni) +
  tm_polygons(
    fill = "employment_rate",
    fill.scale = tm_scale_intervals(style = "kmeans"),
    hover = "name",
    popup = tm_popup(vars = c("name", "employment_rate"))
  )

tmap_mode("view");    tm   # Leaflet
tmap_mode("maplibre"); tm  # MapLibre GL

Recap

  • tmap.mapgl adds "mapbox" and "maplibre" modes to tmap
  • Load with library(tmap.mapgl) — no other changes needed
  • "maplibre" is free; "mapbox" requires a free API key
  • Use rtm() to rotate through all available modes; ttm() toggles between current and previous
  • Background maps via tm_basemap() + tmap_providers() (not style = anymore)
  • Default CRS in maplibre is the globe — toggle in-map between globe and Web Mercator
  • New features: draggable legends, interactive categorical legends, tm_polygons_3d() (Session 8)
  • More: tmap.mapgl package