#gis #specification #back-ends #url #bucket #tile #mmap #sync

pmtiles

Implementation of the PMTiles v3 spec with multiple sync and async backends

25 releases (14 breaking)

0.14.0 Jun 4, 2025
0.12.0 May 26, 2025
0.11.0 Sep 20, 2024
0.10.0 May 2, 2024
0.0.5 Oct 22, 2022

#8 in Geospatial

Download history 469/week @ 2025-02-19 439/week @ 2025-02-26 533/week @ 2025-03-05 389/week @ 2025-03-12 242/week @ 2025-03-19 254/week @ 2025-03-26 351/week @ 2025-04-02 686/week @ 2025-04-09 428/week @ 2025-04-16 210/week @ 2025-04-23 467/week @ 2025-04-30 467/week @ 2025-05-07 387/week @ 2025-05-14 912/week @ 2025-05-21 709/week @ 2025-05-28 599/week @ 2025-06-04

2,674 downloads per month
Used in 4 crates (3 directly)

MIT/Apache

4.5MB
1.5K SLoC

PMTiles (for Rust)

GitHub crates.io version docs.rs docs crates.io version CI build Codecov

This crate implements the PMTiles v3 spec, originally created by Brandon Liu for Protomaps.

Features

  • Opening and validating PMTile archives
  • Querying tiles
  • Backends supported:
    • Async mmap (Tokio) for local files
    • Async http and https (Reqwest + Tokio) for URLs
    • Async s3 (Rust-S3 + Tokio) for S3-compatible buckets
  • Creating PMTile archives

Plans & TODOs

  • Documentation and example code
  • Support conversion to and from MBTiles + x/y/z
  • Support additional backends (sync mmap and http at least)
  • Support additional async styles (e.g., async-std)

PRs welcome!

Usage examples

Reading from a local PMTiles file

use bytes::Bytes;
use pmtiles::async_reader::AsyncPmTilesReader;

async fn get_tile(z: u8, x: u64, y: u64) -> Option<Bytes> {
  let file = "example.pmtiles";
  // Use `new_with_cached_path` for better performance
  let reader = AsyncPmTilesReader::new_with_path(file).await.unwrap();
  reader.get_tile(z, x, y).await.unwrap()
}

Reading from a URL with a simple directory cache

This example uses a simple hashmap-based cache to optimize reads from a PMTiles source. The same caching is available for all other methods. Note that HashMapCache is a rudimentary cache without eviction. You may want to build a more sophisticated cache for production use by implementing the DirectoryCache trait.

use bytes::Bytes;
use pmtiles::async_reader::AsyncPmTilesReader;
use pmtiles::cache::HashMapCache;
use pmtiles::reqwest::Client;  // Re-exported Reqwest crate

async fn get_tile(z: u8, x: u64, y: u64) -> Option<Bytes> {
  let cache = HashMapCache::default();
  let client = Client::builder().use_rustls_tls().build().unwrap();
  let url = "https://protomaps.github.io/PMTiles/protomaps(vector)ODbL_firenze.pmtiles";
  let reader = AsyncPmTilesReader::new_with_cached_url(cache, client, url).await.unwrap();
  reader.get_tile(z, x, y).await.unwrap()
}

Reading from an S3 bucket with a directory cache

AWS client configuration is fairly none-trivial to document here. See AWS SDK documentation for more details.

use bytes::Bytes;
use pmtiles::async_reader::AsyncPmTilesReader;
use pmtiles::aws_sdk_s3::Client; // Re-exported AWS SDK S3 client
use pmtiles::cache::HashMapCache;

async fn get_tile(client: Client, z: u8, x: u64, y: u64) -> Option<Bytes> {
  let cache = HashMapCache::default();
  let bucket = "https://s3.example.com".to_string();
  let key = "example.pmtiles".to_string();
  let reader = AsyncPmTilesReader::new_with_cached_client_bucket_and_path(cache, client, bucket, key).await.unwrap();
  reader.get_tile(z, x, y).await.unwrap()
}

Writing to a PMTiles file

use pmtiles::{PmTilesWriter, TileType};
use std::fs::File;

let file = File::create("example.pmtiles").unwrap();
let mut writer = PmTilesWriter::new(TileType::Mvt).create(file).unwrap();
writer.add_tile(0, 0, 0, &[/*...*/]).unwrap();
writer.finalize().unwrap();

Development

  • This project is easier to develop with just, a modern alternative to make. Install it with cargo install just.
  • To get a list of available commands, run just.
  • To run tests, use just test.

License

Licensed under either of

Test Data License

Some PMTile fixtures copied from official PMTiles repository.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~1–21MB
~316K SLoC