Skip to content

walkerke/freestiler

Repository files navigation

freestiler freestiler logo

freestiler creates PMTiles vector tilesets from R and Python. Give it an sf object, a file on disk, or a DuckDB SQL query, and it writes a single .pmtiles file you can serve from anywhere. The tiling engine is written in Rust and runs in-process, so there's nothing else to install.

Installation

R

Install from r-universe:

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

Or install from GitHub:

# install.packages("devtools")
devtools::install_github("walkerke/freestiler")

Python

pip install freestiler

Published PyPI wheels currently target Python 3.9 through 3.14.

See the Python Setup article for more details.

Quick start

The main function is freestile(). Let's tile the North Carolina counties dataset that ships with sf:

library(sf)
library(freestiler)

nc <- st_read(system.file("shape/nc.shp", package = "sf"))

freestile(nc, "nc_counties.pmtiles", layer_name = "counties")

That's useful for checking your installation, but the same API handles much bigger data. Here we tile all 242,000 US block groups from tigris:

library(tigris)
options(tigris_use_cache = TRUE)

bgs <- block_groups(cb = TRUE)

freestile(
  bgs,
  "us_bgs.pmtiles",
  layer_name = "bgs",
  min_zoom = 4,
  max_zoom = 12
)

Viewing tiles

Use mapgl to view your tileset. PMTiles need HTTP range requests, so you'll want to start a local server first (e.g. npx http-server /tmp -p 8082 --cors -c-1):

library(mapgl)

maplibre(hash = TRUE) |>
  add_pmtiles_source(
    id = "bgs-src",
    url = "http://localhost:8082/us_bgs.pmtiles",
    promote_id = "GEOID"
  ) |>
  add_fill_layer(
    id = "bgs-fill",
    source = "bgs-src",
    source_layer = "bgs",
    fill_color = "navy",
    fill_opacity = 0.5,
    hover_options = list(
      fill_color = "#ffffcc",
      fill_opacity = 0.9
    )
  )

DuckDB queries

If your data lives in DuckDB, freestile_query() lets you filter, join, and transform with SQL before tiling:

freestile_query(
  query = "SELECT * FROM read_parquet('blocks.parquet') WHERE state = 'NC'",
  output = "nc_blocks.pmtiles",
  layer_name = "blocks"
)

For very large point datasets, the streaming pipeline avoids loading the full result into memory. On a recent run, freestile_query() streamed 146 million US job points from DuckDB into a 2.3 GB PMTiles archive in about 12 minutes:

freestile_query(
  query = "SELECT naics, state, ST_Point(lon, lat) AS geometry FROM jobs_dots",
  output = "us_jobs_dots.pmtiles",
  db_path = db_path,
  layer_name = "jobs",
  tile_format = "mvt",
  min_zoom = 4,
  max_zoom = 14,
  base_zoom = 14,
  drop_rate = 2.5,
  source_crs = "EPSG:4326",
  streaming = "always",
  overwrite = TRUE
)

Direct file input

You can tile spatial files without loading them into R first:

# GeoParquet
freestile_file("census_blocks.parquet", "blocks.pmtiles")

# GeoPackage, Shapefile, or other formats via DuckDB
freestile_file("counties.gpkg", "counties.pmtiles", engine = "duckdb")

Multi-layer tilesets

pts <- st_centroid(nc)

freestile(
  list(
    counties = freestile_layer(nc, min_zoom = 0, max_zoom = 10),
    centroids = freestile_layer(pts, min_zoom = 6, max_zoom = 14)
  ),
  "nc_layers.pmtiles"
)

Tile formats

freestiler defaults to MapLibre Tiles (MLT), a columnar encoding that produces smaller files for polygon and line data. Use tile_format = "mvt" when you need the widest viewer compatibility.

Learn more

About

A Rust-powered vector tile generator for R, Python, and DuckDB.

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md

Stars

Watchers

Forks

Packages

 
 
 

Contributors