| name | r-mapping |
| description | Use when code loads mapgl or freestiler, working with .pmtiles files, creating interactive maps in R, choosing between R mapping packages, or working with large spatial datasets for visualization |
R Mapping Ecosystem (Meta-Skill)
Overview
This is a meta-skill that helps you choose the right R mapping workflow. The R mapping stack consists of 2 specialized packages that work together.
Install both: install.packages(c("freestiler", "mapgl"))
API keys: Mapbox styles require MAPBOX_PUBLIC_TOKEN; MapLibre works without keys (CARTO/OpenFreeMap)
References
references/integration-patterns.md - How freestiler and mapgl work together
For per-package API docs, invoke the corresponding package skill (/r-freestiler, /r-mapgl).
When to Use Which Package
| Package | Use When | Skill |
|---|
| freestiler | Convert large spatial data (>10k features) to vector tiles | /r-freestiler |
| mapgl | Create interactive WebGL maps, add layers, build Shiny apps | /r-mapgl |
REQUIRED: After reading this skill, you MUST invoke the relevant sub-skill (r-freestiler or r-mapgl) before writing any code. If the task involves .pmtiles files, invoke r-freestiler. If the task involves map display, invoke r-mapgl. For pipelines, invoke both.
When NOT to Use
- Simple marker maps (use leaflet)
- Static print maps (use tmap or ggplot2 + sf)
- Quick exploration of small datasets (use tmap_mode("view") or maplibre_view())
- Non-spatial data visualization (use ggplot2)
Key Workflow Decisions
Dataset size determines your path:
| Features | Workflow | Reason |
|---|
| <10k | sf → mapgl directly | Fast enough without tiling |
| 10k-100k | freestiler → mapgl | Smooth performance with tiles |
| >100k or >1GB | freestiler with streaming | Memory-efficient processing |
Hosting context:
- Static hosting (GitHub Pages, Netlify): freestiler PMTiles → mapgl
- Shiny server: Can use sf directly with mapgl, but consider tiles for large data
Common Integration Patterns
Small Dataset: Direct to Map
library(mapgl)
library(sf)
nc <- st_read(system.file("shape/nc.shp", package = "sf"))
maplibre(style = carto_style("positron")) |>
fit_bounds(nc) |>
add_fill_layer(
id = "counties",
source = nc,
fill_color = interpolate(
column = "BIR74",
values = c(500, 10000),
stops = c("lightblue", "darkblue")
)
)
Large Dataset: Tile → Map Pipeline
library(freestiler)
library(mapgl)
freestile(
input = large_sf,
output = "tiles.pmtiles",
layer_name = "features"
)
view_tiles("tiles.pmtiles")
serve_tiles(path = dirname("tiles.pmtiles"))
maplibre() |>
add_pmtiles_source(
id = "src",
url = "http://localhost:8080/tiles.pmtiles"
) |>
add_fill_layer(
source = "src",
source_layer = "features",
fill_color = "steelblue"
)
Memory-Efficient Streaming (10M+ Features)
library(freestiler)
library(duckdb)
freestile_query(
query = "SELECT * FROM read_parquet('huge_spatial.parquet')",
output = "massive.pmtiles",
layer_name = "points",
streaming = "always"
)
Shiny App with Dynamic Filtering
library(shiny)
library(mapgl)
ui <- fluidPage(
selectInput("year", "Year", choices = 2010:2020),
maplibreOutput("map", height = "600px")
)
server <- function(input, output, session) {
output$map <- renderMaplibre({
maplibre(style = carto_style("positron")) |>
add_pmtiles_source(id = "data", url = "tiles.pmtiles") |>
add_circle_layer(
id = "points",
source = "data",
source_layer = "features"
)
})
observeEvent(input$year, {
maplibre_proxy("map") |>
set_filter(
"points",
list("==", list("get", "year"), input$year)
)
})
}
Decision Tree
Start: Need to map spatial data
├─ <10k features?
│ └─ YES → Use mapgl directly with sf source
├─ 10k-1M features?
│ └─ YES → freestiler (default settings) → mapgl
├─ >1M features OR >500MB file?
│ └─ YES → freestiler with streaming → mapgl
└─ Need static hosting?
└─ YES → freestiler PMTiles → mapgl (no server needed)
Critical Integration Points
- CRS must match: Transform to WGS84 before tiling:
st_transform(data, 4326)
- Layer names must align:
layer_name in freestiler = source_layer in mapgl
- Server for local development: Use
view_tiles() for quick preview, or serve_tiles() + mapgl for custom control
- File paths: PMTiles can use
file:// URLs with absolute paths in mapgl
Common Mistakes
- Wrong workflow order: Don't tile small datasets; don't pass huge sf objects to mapgl
- Missing streaming flag: Add
streaming = "always" for datasets >10M features
- Layer name mismatch:
freestile(layer_name = "x") requires add_*_layer(source_layer = "x")
- Server requirements: Python's http.server doesn't support PMTiles; use Node's http-server or freestiler's built-in server
- CRS assumptions: Always verify and transform to EPSG:4326
Advanced
See references/ for:
- integration-patterns.md: Production workflows, Shiny patterns, multi-layer maps, troubleshooting
For package-specific documentation, invoke the individual skills:
- r-freestiler: Tile generation, SQL queries, zoom strategies
- r-mapgl: Layer styling, Shiny integration, 3D terrain, interactive controls