Skip to contents

mapgl wraps Maplibre GL JS and Mapbox GL JS, giving you GPU-accelerated interactive maps that render directly in the viewer or in knitted HTML. This vignette shows how to map earthquake data with circle layers, data-driven styling, and a heatmap.

Fetch the data

library(eqr)
library(dplyr)
library(mapgl)

quakes <- get_quakes(
  start_time = Sys.time() - 30 * 86400,
  end_time   = Sys.time(),
  min_mag    = 2.5
) |>
  mutate(datetime = as.POSIXct(time / 1000, origin = "1970-01-01", tz = "UTC"))

Circle layer with data-driven styling

add_circle_layer() accepts Maplibre expressions for any paint property. interpolate() builds a linear interpolation expression that scales radius by magnitude and colors circles by depth.

maplibre(
  style      = carto_style("dark-matter"),
  center     = c(-98, 38),
  zoom       = 2,
  projection = "globe"
) |>
  set_fog(
    space_color    = "#0a0a1a",
    star_intensity = 0.3,
    horizon_blend  = 0.1
  ) |>
  add_circle_layer(
    id     = "quakes",
    source = quakes,
    circle_radius = interpolate(
      column = "mag",
      type   = "linear",
      values = c(2.5, 5, 7),
      stops  = c(3, 8, 18)
    ),
    circle_color = interpolate(
      column = "depth",
      type   = "linear",
      values = c(0, 50, 150, 300),
      stops  = c("#ffffb2", "#fecc5c", "#fd8d3c", "#bd0026")
    ),
    circle_opacity      = 0.75,
    circle_stroke_color = "#ffffff",
    circle_stroke_width = 0.5,
    tooltip = "place",
    popup   = c("mag", "depth", "datetime")
  ) |>
  add_continuous_legend(
    legend_title = "Depth (km)",
    values       = c(0, 50, 150, 300),
    colors       = c("#ffffb2", "#fecc5c", "#fd8d3c", "#bd0026")
  )

Hover over a point for the location name; click for magnitude, depth, and time.

Heatmap

A heatmap shows overall seismicity density without the visual clutter of individual points. Weight each event by magnitude so larger quakes contribute more to the kernel.

maplibre(
  style      = carto_style("dark-matter"),
  center     = c(-98, 38),
  zoom       = 2,
  projection = "globe"
) |>
  set_fog(
    space_color    = "#0a0a1a",
    star_intensity = 0.3,
    horizon_blend  = 0.1
  ) |>
  add_heatmap_layer(
    id     = "quakes-heat",
    source = quakes,
    heatmap_weight = interpolate(
      column = "mag",
      type   = "linear",
      values = c(2.5, 7),
      stops  = c(0.1, 1)
    ),
    heatmap_radius    = 20,
    heatmap_opacity   = 0.85,
    heatmap_color = list(
      "interpolate",
      list("linear"),
      list("heatmap-density"),
      0,   "rgba(0,0,0,0)",
      0.2, "#4575b4",
      0.4, "#74add1",
      0.6, "#fee090",
      0.8, "#f46d43",
      1,   "#d73027"
    )
  )

Filtering by magnitude

Use filter to show only the largest events — useful as an overlay on the heatmap to highlight notable earthquakes without redrawing the source.

maplibre(
  style      = carto_style("dark-matter"),
  center     = c(-98, 38),
  zoom       = 2,
  projection = "globe"
) |>
  set_fog(
    space_color    = "#0a0a1a",
    star_intensity = 0.3,
    horizon_blend  = 0.1
  ) |>
  add_heatmap_layer(
    id     = "quakes-heat",
    source = quakes,
    heatmap_weight = interpolate(
      column = "mag",
      type   = "linear",
      values = c(2.5, 7),
      stops  = c(0.1, 1)
    ),
    heatmap_radius  = 20,
    heatmap_opacity = 0.8
  ) |>
  add_circle_layer(
    id     = "quakes-m4plus",
    source = quakes,
    filter = list(">=" , "mag", 4),
    circle_radius = interpolate(
      column = "mag",
      type   = "linear",
      values = c(4, 7),
      stops  = c(5, 20)
    ),
    circle_color        = "#bd0026",
    circle_opacity      = 0.8,
    circle_stroke_color = "#ffffff",
    circle_stroke_width = 1,
    tooltip = "place",
    popup   = c("mag", "depth", "datetime")
  )

Global seismicity — last 7 days

Pull the full global extent by overriding the default US bounding box. M2.5+ worldwide over a week typically returns 1,000–2,000 events, well within the 20,000-event cap.

quakes_global <- get_quakes(
  start_time = Sys.time() - 7 * 86400,
  end_time   = Sys.time(),
  min_mag    = 2.5,
  min_lat    = -90,  max_lat = 90,
  min_lng    = -180, max_lng = 180
) |>
  mutate(datetime = as.POSIXct(time / 1000, origin = "1970-01-01", tz = "UTC"))

The globe spins automatically on load — drag to take control, and it stops.

library(htmlwidgets)

maplibre(
  style      = carto_style("dark-matter"),
  center     = c(0, 20),
  zoom       = 1.8,
  projection = "globe"
) |>
  set_fog(
    space_color    = "#0a0a1a",
    star_intensity = 0.3,
    horizon_blend  = 0.1
  ) |>
  add_circle_layer(
    id     = "quakes-global",
    source = quakes_global,
    circle_radius = interpolate(
      column = "mag",
      type   = "linear",
      values = c(2.5, 5, 7),
      stops  = c(2, 6, 16)
    ),
    circle_color = interpolate(
      column = "depth",
      type   = "linear",
      values = c(0, 70, 300, 700),
      stops  = c("#ffffb2", "#fecc5c", "#fd8d3c", "#bd0026")
    ),
    circle_opacity      = 0.8,
    circle_stroke_color = "#ffffff",
    circle_stroke_width = 0.4,
    tooltip = "place",
    popup   = c("mag", "depth", "datetime")
  ) |>
  add_continuous_legend(
    legend_title = "Depth (km)",
    values       = c(0, 70, 300, 700),
    colors       = c("#ffffb2", "#fecc5c", "#fd8d3c", "#bd0026")
  ) |>
  onRender("
    function(el) {
      var map = el.map;
      var spinning = true;

      function spin() {
        if (!spinning) return;
        var center = map.getCenter();
        center.lng -= 0.05;
        map.setCenter(center);
        requestAnimationFrame(spin);
      }

      map.on('load', spin);
      map.on('mousedown', function() { spinning = false; });
      map.on('touchstart', function() { spinning = false; });
    }
  ")