| Title: | Create Vector Tiles from Spatial Data |
| Version: | 0.2.0 |
| Date: | 2026-06-24 |
| Description: | Create vector tile archives in 'PMTiles' format from 'sf' spatial data frames. Supports 'Mapbox Vector Tile' ('MVT') and 'MapLibre Tile' ('MLT') output formats. Uses a 'Rust' backend via 'extendr' for fast, in-memory tiling with zero external system dependencies. |
| License: | MIT + file LICENSE |
| URL: | https://walker-data.com/freestiler/, https://github.com/walkerke/freestiler/ |
| BugReports: | https://github.com/walkerke/freestiler/issues |
| Encoding: | UTF-8 |
| RoxygenNote: | 7.3.3 |
| Imports: | sf |
| Suggests: | arrow, DBI, duckdb, httpuv, jsonlite, mapgl, testthat (≥ 3.0.0), viridisLite, withr |
| Config/testthat/edition: | 3 |
| Config/rextendr/version: | 0.4.2.9000 |
| SystemRequirements: | Cargo (Rust's package manager), rustc >= 1.77.2 |
| Depends: | R (≥ 4.2) |
| NeedsCompilation: | yes |
| Packaged: | 2026-06-24 12:19:52 UTC; kylewalker |
| Author: | Kyle Walker [aut, cre] |
| Maintainer: | Kyle Walker <kyle@walker-data.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-06-24 14:50:02 UTC |
freestiler: Create Vector Tiles from Spatial Data
Description
Create vector tile archives in 'PMTiles' format from 'sf' spatial data frames. Supports 'Mapbox Vector Tile' ('MVT') and 'MapLibre Tile' ('MLT') output formats. Uses a 'Rust' backend via 'extendr' for fast, in-memory tiling with zero external system dependencies.
Author(s)
Maintainer: Kyle Walker kyle@walker-data.com
See Also
Useful links:
Report bugs at https://github.com/walkerke/freestiler/issues
Create vector tiles from spatial data
Description
Creates a PMTiles archive containing vector tiles from one or more sf data frames. Supports both Mapbox Vector Tile (MVT) and MapLibre Tile (MLT) formats, multi-layer output, feature dropping, point clustering, and feature coalescing.
Usage
freestile(
input,
output,
layer_name = NULL,
tile_format = "mvt",
min_zoom = 0L,
max_zoom = 14L,
base_zoom = NULL,
drop_rate = NULL,
cluster_distance = NULL,
cluster_maxzoom = NULL,
coalesce = FALSE,
simplification = TRUE,
generate_ids = TRUE,
overwrite = TRUE,
quiet = FALSE
)
Arguments
input |
An sf data frame, or a named list of sf/freestile_layer objects for multi-layer output. |
output |
Character. Path for the output .pmtiles file. |
layer_name |
Character. Name for the tile layer. If NULL, derived from the output filename. Only used for single-layer input. |
tile_format |
Character. Tile encoding format: |
min_zoom |
Integer. Minimum zoom level (default 0). |
max_zoom |
Integer. Maximum zoom level (default 14). |
base_zoom |
Integer. Zoom level at and above which all features are
present (no dropping). NULL (default) uses each layer's own max_zoom.
The drop-rate curve is also computed relative to base_zoom, so lowering
it produces gentler thinning at low zooms. Inspired by tippecanoe's
|
drop_rate |
Numeric. Exponential drop rate for feature thinning (e.g. 2.5). At each zoom level below base_zoom, features are retained at a rate of 1/drop_rate^(base_zoom - zoom). Points are thinned using spatial ordering; polygons/lines are thinned by area. NULL (default) disables drop-rate thinning. |
cluster_distance |
Numeric. Pixel distance for point clustering. Points
within this radius are merged into cluster features with a |
cluster_maxzoom |
Integer. Maximum zoom level for clustering. Above this zoom, individual points are shown. Default is max_zoom - 1. |
coalesce |
Logical. Whether to merge features with identical attributes within each tile (default FALSE). Lines sharing endpoints are merged; polygons are grouped into MultiPolygons. |
simplification |
Logical. Whether to snap geometries to the tile pixel grid at each zoom level (default TRUE). This provides zoom-adaptive simplification and prevents slivers between adjacent polygons. |
generate_ids |
Logical. Whether to assign sequential feature IDs (default TRUE). |
overwrite |
Logical. Whether to overwrite existing output file (default TRUE). |
quiet |
Logical. Whether to suppress progress messages (default FALSE). |
Details
Input data in any coordinate reference system (CRS) is automatically reprojected to WGS84 (EPSG:4326) before tiling.
Value
The output file path (invisibly).
Examples
## Not run:
library(sf)
nc <- st_read(system.file("shape/nc.shp", package = "sf"))
# Single layer
freestile(nc, "nc.pmtiles", layer_name = "counties")
# Multi-layer
pts <- st_centroid(nc)
freestile(
list(counties = nc, centroids = pts),
"nc_layers.pmtiles"
)
# With dropping and coalescing
freestile(nc, "nc_drop.pmtiles", drop_rate = 2.5, coalesce = TRUE)
# With point clustering
freestile(pts, "pts.pmtiles", cluster_distance = 50, cluster_maxzoom = 8)
## End(Not run)
Create vector tiles from a spatial file
Description
Reads a GeoParquet, GeoPackage, Shapefile, or other spatial file directly into the tiling engine. Input data in any coordinate reference system is automatically reprojected to WGS84 (EPSG:4326) before tiling.
Usage
freestile_file(
input,
output,
layer_name = NULL,
tile_format = "mvt",
min_zoom = 0L,
max_zoom = 14L,
base_zoom = NULL,
drop_rate = NULL,
cluster_distance = NULL,
cluster_maxzoom = NULL,
coalesce = FALSE,
simplification = TRUE,
overwrite = TRUE,
quiet = FALSE,
engine = "geoparquet"
)
Arguments
input |
Character. Path to the input spatial file. |
output |
Character. Path for the output .pmtiles file. |
layer_name |
Character. Name for the tile layer. If NULL, derived from the output filename. |
tile_format |
Character. |
min_zoom |
Integer. Minimum zoom level (default 0). |
max_zoom |
Integer. Maximum zoom level (default 14). |
base_zoom |
Integer. Zoom level at and above which all features are present. NULL (default) uses max_zoom. |
drop_rate |
Numeric. Exponential drop rate. NULL (default) disables. |
cluster_distance |
Numeric. Pixel distance for clustering. NULL disables. |
cluster_maxzoom |
Integer. Max zoom for clustering. Default max_zoom - 1. |
coalesce |
Logical. Whether to merge features with identical attributes (default FALSE). |
simplification |
Logical. Whether to snap geometries to the tile pixel grid (default TRUE). |
overwrite |
Logical. Whether to overwrite existing output (default TRUE). |
quiet |
Logical. Whether to suppress progress (default FALSE). |
engine |
Character. Backend engine: |
Details
The GeoParquet engine requires compilation with FREESTILER_GEOPARQUET=true.
The DuckDB engine uses the Rust DuckDB backend when included in the build
(enabled by default for native builds), or falls back to the R duckdb
package. Control backend selection with
options(freestiler.duckdb_backend = "auto"|"rust"|"r").
Value
The output file path (invisibly).
Examples
## Not run:
freestile_file("data.parquet", "output.pmtiles")
freestile_file("data.gpkg", "output.pmtiles", engine = "duckdb")
## End(Not run)
Create vector tiles with dynamic H3 hexagonal binning
Description
Aggregates points into H3 hexagons at zoom-appropriate resolutions and writes
a PMTiles archive in which low zooms show coarse hexagons, intermediate zooms
show progressively finer hexagons, and zooms at or above base_zoom show
individual points. Aggregations (count, sum, mean, etc.) are computed in
DuckDB via the H3 community extension; the function then assembles the
per-resolution hex layers and the raw-point layer via freestile().
Usage
freestile_h3(
input,
output,
agg = "count",
hex_layer_prefix = "h3",
point_layer_name = "points",
min_zoom = 0L,
max_zoom = 14L,
base_zoom = NULL,
h3_resolutions = NULL,
source_crs = NULL,
db_path = NULL,
tile_format = "mvt",
fade = FALSE,
fade_overlap = 1L,
overwrite = TRUE,
quiet = FALSE
)
Arguments
input |
An |
output |
Character. Path for the output |
agg |
Aggregation specification:
|
hex_layer_prefix |
Character. Prefix for the per-resolution hex MVT
layer names. Default |
point_layer_name |
Character. MVT layer name for raw points (default
|
min_zoom, max_zoom |
Integer. Global zoom range (default 0–14). |
base_zoom |
Integer or NULL. Zoom level at and above which raw points
take over from hex aggregations. Default |
h3_resolutions |
Optional override of the zoom -> H3 resolution
mapping. Accepts |
source_crs |
Character or NULL. CRS of geometry returned by SQL
input, e.g. |
db_path |
Character or NULL. Path to an existing DuckDB database file,
or |
tile_format |
Character. |
fade |
Logical. If |
fade_overlap |
Integer. Zooms of overlap on each side (only used when
|
overwrite |
Logical. Whether to overwrite an existing output file
(default |
quiet |
Logical. Suppress progress messages (default |
Details
Each distinct H3 resolution becomes its own MVT source-layer (named
"<hex_layer_prefix>_r<resolution>", e.g. "h3_r05"); raw points are
emitted as a separate source-layer (point_layer_name). With the default
fade = FALSE, per-layer zoom windows are disjoint so the rendered map
swaps between resolutions cleanly. With fade = TRUE, adjacent windows
overlap by fade_overlap zooms so the companion view_h3_tiles() helper
can cross-fade between resolutions.
DuckDB and the H3 community extension are required. With sf input the
data is written to DuckDB via a temporary Parquet (or dbWriteTable)
roundtrip; with character SQL input, the user query is wrapped in a
temporary view inside DuckDB.
Value
The output file path (invisibly).
Point volume at base_zoom
The raw-point layer is encoded without thinning: every input point is
written to every tile that contains it from base_zoom up (and from
base_zoom - fade_overlap when fade = TRUE). For very large inputs the
tiles at the first point zoom can get heavy. Raising base_zoom defers
points to higher zooms, where each tile covers less area and so holds
fewer points. Per-layer feature dropping for the points layer is on the
roadmap.
Antimeridian and polar cells
Hexagons that cross the antimeridian are split at +/-180 degrees so they render correctly on both sides of the dateline instead of as world-spanning slivers. The rare cells that contain a pole receive the same split and render approximately (the polygon edge follows the cell boundary vertices, so the polar cap itself is not filled).
See Also
Examples
## Not run:
library(sf)
pts <- st_as_sf(data.frame(
x = runif(50000, -100, -80),
y = runif(50000, 30, 45),
w = rnorm(50000, 100, 10)
), coords = c("x", "y"), crs = 4326)
freestile_h3(pts, "wind.pmtiles",
agg = c(n = "COUNT(*)", avg_w = "AVG(w)"),
min_zoom = 2, max_zoom = 12, base_zoom = 10)
view_h3_tiles("wind.pmtiles", agg_column = "n")
# Cross-fade between resolutions
freestile_h3(pts, "wind_fade.pmtiles",
agg = "count",
min_zoom = 2, max_zoom = 12, base_zoom = 10,
fade = TRUE)
## End(Not run)
Create a layer specification with per-layer zoom range
Description
Wraps an sf object with optional per-layer zoom range overrides for use in multi-layer tile generation.
Usage
freestile_layer(input, min_zoom = NULL, max_zoom = NULL)
Arguments
input |
An sf data frame. |
min_zoom |
Integer. Minimum zoom level for this layer. If NULL, uses the
global min_zoom from |
max_zoom |
Integer. Maximum zoom level for this layer. If NULL, uses the
global max_zoom from |
Value
A freestile_layer object (list with class attribute).
Examples
## Not run:
library(sf)
nc <- st_read(system.file("shape/nc.shp", package = "sf"))
roads <- st_read("roads.shp")
freestile(
list(
counties = freestile_layer(nc, min_zoom = 0, max_zoom = 10),
roads = freestile_layer(roads, min_zoom = 8, max_zoom = 14)
),
"layers.pmtiles"
)
## End(Not run)
Create vector tiles from a DuckDB SQL query
Description
Executes a SQL query via DuckDB's spatial extension and pipes the results
into the tiling engine. Uses the Rust DuckDB backend when included in the
build (enabled by default for native builds), or falls back to the R
duckdb package. Control backend selection with
options(freestiler.duckdb_backend = "auto"|"rust"|"r").
Usage
freestile_query(
query,
output,
db_path = NULL,
layer_name = NULL,
tile_format = "mvt",
min_zoom = 0L,
max_zoom = 14L,
base_zoom = NULL,
drop_rate = NULL,
cluster_distance = NULL,
cluster_maxzoom = NULL,
coalesce = FALSE,
simplification = TRUE,
overwrite = TRUE,
quiet = FALSE,
source_crs = NULL,
streaming = "auto"
)
Arguments
query |
Character. A SQL query that returns a geometry column. DuckDB
spatial functions like |
output |
Character. Path for the output .pmtiles file. |
db_path |
Character. Path to a DuckDB database file, or NULL (default) for an in-memory database. |
layer_name |
Character. Name for the tile layer. If NULL, derived from the output filename. |
tile_format |
Character. |
min_zoom |
Integer. Minimum zoom level (default 0). |
max_zoom |
Integer. Maximum zoom level (default 14). |
base_zoom |
Integer. Zoom level at and above which all features are present. NULL (default) uses max_zoom. |
drop_rate |
Numeric. Exponential drop rate. NULL (default) disables. |
cluster_distance |
Numeric. Pixel distance for clustering. NULL disables. |
cluster_maxzoom |
Integer. Max zoom for clustering. Default max_zoom - 1. |
coalesce |
Logical. Whether to merge features with identical attributes (default FALSE). |
simplification |
Logical. Whether to snap geometries to the tile pixel grid (default TRUE). |
overwrite |
Logical. Whether to overwrite existing output (default TRUE). |
quiet |
Logical. Whether to suppress progress (default FALSE). |
source_crs |
Character or NULL. CRS of the geometry returned by
|
streaming |
Character. DuckDB query execution mode: |
Details
When using the R fallback, source_crs must be supplied explicitly so the
query result can be interpreted or reprojected correctly. Pass
"EPSG:4326" if the SQL already returns WGS84 geometry, or the source CRS
string (for example "EPSG:4267") to have DuckDB reproject to WGS84 before
tiling. For file-based input where the CRS is embedded in the file, use
freestile_file() with engine = "duckdb" instead, which auto-detects the
source CRS.
Value
The output file path (invisibly).
Examples
## Not run:
# Query a GeoParquet file
freestile_query(
"SELECT * FROM read_parquet('data.parquet') WHERE pop > 50000",
"output.pmtiles"
)
# Query a Shapefile
freestile_query(
"SELECT * FROM ST_Read('counties.shp')",
"counties.pmtiles"
)
# Query with an existing DuckDB database
freestile_query(
"SELECT * FROM my_table WHERE region = 'West'",
"west.pmtiles",
db_path = "my_database.duckdb"
)
## End(Not run)
Read PMTiles metadata
Description
Reads the header and JSON metadata from a PMTiles file.
Usage
pmtiles_metadata(path)
Arguments
path |
Path to a |
Value
A list with header fields (zoom levels, bounds, tile format, etc.)
and a nested metadata element containing vector layer information,
or NULL on error.
Examples
## Not run:
meta <- pmtiles_metadata("my_tiles.pmtiles")
meta$min_zoom
meta$max_zoom
meta$metadata$vector_layers
## End(Not run)
Create vector tiles from spatial data (multi-layer support)
Description
Create vector tiles from spatial data (multi-layer support)
Usage
rust_freestile(
layers,
output_path,
tile_format,
global_min_zoom,
global_max_zoom,
base_zoom,
do_simplify,
generate_ids,
quiet,
drop_rate,
cluster_distance,
cluster_maxzoom,
do_coalesce
)
Arguments
layers |
List of layer lists, each containing: name, geometries, geom_types, prop_names, prop_types, prop_char_values, prop_num_values, prop_int_values, prop_lgl_values, min_zoom, max_zoom |
output_path |
Path for output .pmtiles file |
tile_format |
"mvt" or "mlt" |
global_min_zoom |
Minimum zoom level |
global_max_zoom |
Maximum zoom level |
do_simplify |
Whether to simplify geometries at lower zooms |
generate_ids |
Whether to generate sequential feature IDs |
quiet |
Whether to suppress progress messages |
drop_rate |
Exponential drop rate (negative = off) |
cluster_distance |
Pixel distance for clustering (negative = off) |
cluster_maxzoom |
Max zoom for clustering (negative = use max_zoom - 1) |
do_coalesce |
Whether to coalesce features with same attributes |
Create tiles from a file via DuckDB spatial (requires duckdb feature)
Description
Create tiles from a file via DuckDB spatial (requires duckdb feature)
Usage
rust_freestile_duckdb(
input_path,
output_path,
layer_name,
tile_format,
min_zoom,
max_zoom,
base_zoom,
do_simplify,
drop_rate,
cluster_distance,
cluster_maxzoom,
do_coalesce,
quiet
)
Arguments
input_path |
Path to the spatial file |
output_path |
Path for output .pmtiles file |
layer_name |
Layer name |
tile_format |
"mvt" or "mlt" |
min_zoom |
Minimum zoom level |
max_zoom |
Maximum zoom level |
base_zoom |
Base zoom level (negative = use max_zoom) |
do_simplify |
Whether to simplify geometries |
drop_rate |
Exponential drop rate (negative = off) |
cluster_distance |
Pixel distance for clustering (negative = off) |
cluster_maxzoom |
Max zoom for clustering (negative = use max_zoom - 1) |
do_coalesce |
Whether to coalesce features |
quiet |
Whether to suppress progress |
Create tiles from a DuckDB SQL query (requires duckdb feature)
Description
Create tiles from a DuckDB SQL query (requires duckdb feature)
Usage
rust_freestile_duckdb_query(
sql,
db_path,
output_path,
layer_name,
tile_format,
min_zoom,
max_zoom,
base_zoom,
do_simplify,
drop_rate,
cluster_distance,
cluster_maxzoom,
do_coalesce,
quiet,
streaming_mode
)
Arguments
sql |
SQL query that returns a geometry column |
db_path |
Path to DuckDB database (empty string = in-memory) |
output_path |
Path for output .pmtiles file |
layer_name |
Layer name |
tile_format |
"mvt" or "mlt" |
min_zoom |
Minimum zoom level |
max_zoom |
Maximum zoom level |
base_zoom |
Base zoom level (negative = use max_zoom) |
do_simplify |
Whether to simplify geometries |
drop_rate |
Exponential drop rate (negative = off) |
cluster_distance |
Pixel distance for clustering (negative = off) |
cluster_maxzoom |
Max zoom for clustering (negative = use max_zoom - 1) |
do_coalesce |
Whether to coalesce features |
quiet |
Whether to suppress progress |
streaming_mode |
"auto", "always", or "never" |
Create tiles from a GeoParquet file (requires geoparquet feature)
Description
Create tiles from a GeoParquet file (requires geoparquet feature)
Usage
rust_freestile_file(
input_path,
output_path,
layer_name,
tile_format,
min_zoom,
max_zoom,
base_zoom,
do_simplify,
drop_rate,
cluster_distance,
cluster_maxzoom,
do_coalesce,
quiet
)
Arguments
input_path |
Path to the GeoParquet file |
output_path |
Path for output .pmtiles file |
layer_name |
Layer name |
tile_format |
"mvt" or "mlt" |
min_zoom |
Minimum zoom level |
max_zoom |
Maximum zoom level |
base_zoom |
Base zoom level (negative = use max_zoom) |
do_simplify |
Whether to simplify geometries |
drop_rate |
Exponential drop rate (negative = off) |
cluster_distance |
Pixel distance for clustering (negative = off) |
cluster_maxzoom |
Max zoom for clustering (negative = use max_zoom - 1) |
do_coalesce |
Whether to coalesce features |
quiet |
Whether to suppress progress |
Read PMTiles header and metadata as a JSON string
Description
Read PMTiles header and metadata as a JSON string
Usage
rust_pmtiles_metadata(path)
Arguments
path |
Path to the .pmtiles file |
Serve PMTiles files via local HTTP server with CORS
Description
Start a local HTTP server to serve PMTiles files with CORS headers and HTTP
range request support. This allows PMTiles to be consumed by mapgl and
MapLibre GL JS. The server runs in the background and can be stopped with
stop_server().
Usage
serve_tiles(path, port = 8080)
Arguments
path |
Path to a directory containing PMTiles files, or a single PMTiles file. If a single file, its directory will be served. |
port |
Port number for the HTTP server. Default is 8080. |
Details
If a server is already running on the requested port, it is stopped first.
The server uses httpuv (a dependency of Shiny) to serve static files with
the CORS and range-request headers that PMTiles requires. Works well for
files up to ~1 GB. For larger files, consider an external server like
npx http-server /path --cors -c-1.
Value
Invisibly returns a list with url, port, and
dir. The server handle is stored internally so it can be stopped
with stop_server().
See Also
Examples
## Not run:
# Serve a directory
serve_tiles("/tmp/tiles")
# Serve a single file (its directory is served)
serve_tiles("us_bgs.pmtiles")
# Stop when done
stop_server()
## End(Not run)
Stop a local tile server
Description
Stop a local tile server
Usage
stop_server(port = NULL)
Arguments
port |
Port number to stop, or |
Value
Invisibly returns TRUE if a server was stopped.
See Also
Examples
## Not run:
serve_tiles("tiles/")
stop_server() # stop all
stop_server(8080) # stop specific port
## End(Not run)
View an H3 hexagonal-binning PMTiles archive
Description
Reads the metadata from a PMTiles archive produced by freestile_h3() and
builds a mapgl map with one fill layer per H3 resolution plus a circle
layer for raw points. Automatically detects whether the archive was built
with fade = FALSE (disjoint zoom windows, clean breaks) or fade = TRUE
(overlapping windows, cross-fade) and styles accordingly.
Usage
view_h3_tiles(
input,
agg_column = NULL,
stops = NULL,
palette = "viridis",
hex_opacity = 0.9,
point_color = "#0868ac",
point_radius = NULL,
background_style = NULL,
hex_layer_prefix = "h3",
point_layer_name = "points",
port = 8080
)
Arguments
input |
Path to a local |
agg_column |
Character or NULL. Aggregation column to drive the hex
color scale. If |
stops |
List with |
palette |
Character. Named palette used to generate default |
hex_opacity |
Numeric. Peak fill opacity for hex layers (default 0.9). In fade mode this is the opacity at each layer's center zoom. |
point_color |
Character. Color for the raw-point circles
(default |
point_radius |
mapgl expression or NULL. If |
background_style |
mapgl style passed to |
hex_layer_prefix |
Character. Prefix used when the archive was
written. Must match the value passed to |
point_layer_name |
Character. Name of the raw-points MVT layer in
the archive. Must match the value passed to |
port |
Integer. Port for the local PMTiles server (default 8080). |
Details
The default color scale is a documented quick-look ramp (5 evenly spaced
breaks across 1, 10, 100, 1000, 10000). For production maps, pass an
explicit stops = list(values = ..., colors = ...) derived from your data.
Embedding real aggregation statistics in PMTiles metadata is on the
roadmap.
Value
A mapgl map object.
See Also
Examples
## Not run:
view_h3_tiles("wind.pmtiles", agg_column = "n",
stops = list(values = c(1, 10, 100, 1000, 10000),
colors = viridisLite::viridis(5)))
## End(Not run)
Quickly view a PMTiles file on an interactive map
Description
Starts a local tile server (if needed) and creates an interactive mapgl map showing the tileset. Layer type and styling are auto-detected from the PMTiles metadata when possible.
Usage
view_tiles(
input,
layer = NULL,
layer_type = NULL,
color = NULL,
opacity = 0.5,
port = 8080,
promote_id = NULL
)
Arguments
input |
Path to a local |
layer |
Character. Source layer name to display. If |
layer_type |
Character. Map layer type: |
color |
Fill, line, or circle color. Default is |
opacity |
Numeric opacity (0–1). Default is 0.5. |
port |
Port for the local tile server. Default is 8080. |
promote_id |
Character. Property name to use as the feature ID for
hover interactivity. If |
Value
A mapgl map object (can be piped into further mapgl operations).
See Also
Examples
## Not run:
freestile(nc, "nc.pmtiles", layer_name = "counties")
view_tiles("nc.pmtiles")
# Override auto-detection
view_tiles("roads.pmtiles", layer_type = "line", color = "red")
# Point data
view_tiles("airports.pmtiles", layer_type = "circle", color = "orange")
## End(Not run)