pmtiles

R wrapper for the pmtiles command-line tool

PMTiles is a single-file archive format for tiled map data. This package provides R functions to work with PMTiles archives, including creating, inspecting, converting, and serving them for visualization.

Installation

Install from R-Universe:

install.packages('pmtiles', repos = c('https://walkerke.r-universe.dev', 'https://cloud.r-project.org'))

Or, install from GitHub:

# Using pak (recommended)
pak::pak("walkerke/pmtiles")

# Or using remotes
remotes::install_github("walkerke/pmtiles")

The package includes pre-compiled PMTiles binaries for:

No additional dependencies required!

Quick Start

Inspect a PMTiles archive

library(pmtiles)

# Show basic info
pm_show("path/to/file.pmtiles")

# Get metadata as JSON
metadata <- pm_show("path/to/file.pmtiles", metadata = TRUE)

# Get TileJSON
tilejson <- pm_show("path/to/file.pmtiles", tilejson = TRUE)

Quick visualization

The fastest way to visualize PMTiles (requires the mapgl package):

# Visualize local file
pm_view("path/to/file.pmtiles")

# Visualize remote file
pm_view("https://example.com/tiles.pmtiles")

# Customize the map
pm_view(
  "path/to/file.pmtiles",
  layer_type = "fill",
  fill_color = "#088",
  fill_opacity = 0.7,
  inspect_features = TRUE  # Enable hover tooltips
)

pm_view() automatically:

Serve PMTiles locally

Two approaches to serving PMTiles

pm_serve() - Serves raw .pmtiles files for PMTiles.js clients

pm_serve("file.pmtiles", port = 8080)

# Simple usage with mapgl's PMTiles protocol
library(mapgl)
mapboxgl() |>
  add_pmtiles_source(
    id = "tiles",
    url = "http://localhost:8080/file.pmtiles"
  ) |>
  add_fill_layer(
    id = "layer",
    source = "tiles",
    source_layer = "layer_name"
  )

# Or use with pm_view() for quick visualization
pm_view("http://localhost:8080/file.pmtiles")

# Stop the server when done
pm_stop_server(port = 8080)

pm_serve_zxy() - Serves Z/X/Y tile endpoints

pm_serve_zxy("~/pmtiles/directory")

# Use with standard tile URLs
mapboxgl() |>
  add_vector_source(
    id = "tiles",
    tiles = "http://localhost:8080/file/{z}/{x}/{y}.mvt"
  ) |>
  add_fill_layer(
    id = "layer",
    source = "tiles",
    source_layer = "layer_name"
  )

# Serve from cloud storage without downloading
pm_serve_zxy(
  bucket = r2_bucket("my-bucket", account_id = "your-cloudflare-account-id"),
  public_url = "http://localhost:8080"
)

# Run in background
server <- pm_serve_zxy(background = TRUE)
pm_stop_server(server)

When to use each approach

Use pm_serve():

Use pm_serve_zxy():

For very large files locally: Both approaches may have limitations. Consider:

# Example with Node's http-server
pm_view("http://localhost:8080/YOUR_TILES.pmtiles", inspect_features = TRUE)

Main Functions

Inspection & Info

Creation & Conversion

Editing

Serving & Visualization

Utilities

Examples

Convert MBTiles to PMTiles

pm_convert(
  input = "data.mbtiles",
  output = "data.pmtiles"
)

Extract a region

pm_extract(
  input = "world.pmtiles",
  output = "usa.pmtiles",
  bbox = c(-125, 24, -66, 49),  # [west, south, east, north]
  maxzoom = 10
)

Update metadata

pm_edit(
  input = "file.pmtiles",
  attribution = "© OpenStreetMap contributors",
  description = "Street map of Austin, TX"
)

Upload to cloud storage

Bucket helpers build the destination URL for each provider, so you never hand-assemble an s3://...?endpoint=... string. Credentials passed to a helper go only to the pmtiles subprocess — they are never written to your R session's environment.

# Cloudflare R2 (recommended host: no egress fees)
# Account ID: R2 dashboard > Account Details panel
# Keys: R2 dashboard > Manage R2 API Tokens
pm_upload(
  input = "data.pmtiles",
  remote = "data.pmtiles",
  bucket = r2_bucket(
    "my-tiles",
    account_id = "your-cloudflare-account-id",
    access_key = Sys.getenv("R2_ACCESS_KEY_ID"),
    secret_key = Sys.getenv("R2_SECRET_ACCESS_KEY")
  )
)

# Amazon S3 (uses your standard AWS credential chain)
pm_upload(
  "data.pmtiles", "data.pmtiles",
  bucket = s3_bucket("my-tiles", region = "us-east-1")
)

# Google Cloud Storage (Application Default Credentials)
pm_upload(
  "data.pmtiles", "data.pmtiles",
  bucket = gcs_bucket("my-tiles")
)

# Azure Blob Storage
pm_upload(
  "data.pmtiles", "data.pmtiles",
  bucket = azure_bucket(
    "tiles-container",
    storage_account = "myaccount",
    access_key = Sys.getenv("AZURE_STORAGE_KEY")
  )
)

# Any other S3-compatible service (MinIO, DigitalOcean Spaces, Backblaze B2, ...)
# The region is inferred from recognized endpoints (here: nyc3); for other
# services, pass region = explicitly
pm_upload(
  "data.pmtiles", "data.pmtiles",
  bucket = s3_compatible_bucket(
    "my-tiles",
    endpoint = "https://nyc3.digitaloceanspaces.com"
  )
)

The same helpers work everywhere a bucket is accepted: pm_show(), pm_extract(), pm_tile(), and pm_serve_zxy(). See ?r2_bucket for provider-by-provider setup instructions.

Working with Remote PMTiles

Many functions support remote URLs:

# Inspect remote archive
pm_show("https://example.com/tiles.pmtiles", metadata = TRUE)

# Extract from remote source
pm_extract(
  input = "https://example.com/world.pmtiles",
  output = "local-region.pmtiles",
  bbox = c(-98, 30, -97, 31)
)

# Visualize remote tiles
pm_view("https://example.com/tiles.pmtiles")

Creating PMTiles

With tippecanoe (Recommended)

The pm_create() function provides a comprehensive R wrapper for tippecanoe, allowing you to create PMTiles directly from sf objects or GeoJSON files.

Note: You must install tippecanoe separately:

git clone https://github.com/felt/tippecanoe.git
cd tippecanoe
make -j
make install

Unfortunately, this workflow is not supported for Windows users.

library(sf)
library(pmtiles)

# Simple: Create from sf object
my_data <- st_read("data.geojson")
pm_create(my_data, "output.pmtiles", max_zoom = 14)

# Advanced: High-quality parcel tiles
pm_create(
  "parcels.geojson",
  "parcels.pmtiles",
  layer_name = "parcels",
  min_zoom = 10,
  max_zoom = 18,
  full_detail = 15,
  preserve_input_order = TRUE,
  no_tiny_polygon_reduction = TRUE,
  coalesce_densest_as_needed = TRUE,
  coalesce_fraction_as_needed = TRUE,
  simplification = 1,
  detect_shared_borders = TRUE,
  other_options = c("-pf", "-pk", "-ai")
)

# Point clustering for markers
pm_create(
  points_sf,
  "clustered_points.pmtiles",
  max_zoom = 14,
  cluster_distance = 10,
  cluster_maxzoom = "g",
  generate_ids = TRUE
)

# With attribute filtering
pm_create(
  roads_sf,
  "roads.pmtiles",
  include = c("name", "highway", "surface"),
  drop_densest_as_needed = TRUE,
  simplification = 10
)

Other Tools

Requirements

License

Related Projects

Credits

This package wraps the go-pmtiles command-line tool by Protomaps.