PMTiles Quick Start¶
Get started with Tileverse PMTiles in under 5 minutes.
Installation¶
First, add the dependencies:
<dependencies>
<dependency>
<groupId>io.tileverse.pmtiles</groupId>
<artifactId>tileverse-pmtiles</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>io.tileverse.storage</groupId>
<artifactId>tileverse-storage-all</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
Reading Tiles from a Local File¶
PMTilesReader.open(URI) is a one-line entry point that opens the parent {@link Storage}, gets a RangeReader for the leaf, and bundles them so that closing the reader releases both. The same call works for any URI scheme (file:, http(s):, s3:, gs:, Azure URLs).
import io.tileverse.pmtiles.PMTilesReader;
import io.tileverse.pmtiles.PMTilesHeader;
import java.net.URI;
import java.nio.file.Path;
import java.util.Optional;
public class QuickStart {
public static void main(String[] args) throws Exception {
URI uri = Path.of("world.pmtiles").toUri();
try (PMTilesReader reader = PMTilesReader.open(uri)) {
// Read the header to get metadata
PMTilesHeader header = reader.getHeader();
System.out.println("Tile Format: " + header.tileType());
System.out.println("Min Zoom: " + header.minZoom());
System.out.println("Max Zoom: " + header.maxZoom());
// Get a specific tile (zoom=10, x=885, y=412)
Optional<ByteBuffer> tileData = reader.getTile(10, 885, 412);
if (tileData.isPresent()) {
System.out.printf("Tile found! Size: %d bytes%n", tileData.get().remaining());
} else {
System.out.println("Tile not found");
}
}
}
}
Reading from HTTP¶
try (PMTilesReader reader = PMTilesReader.open(URI.create("https://example.com/tiles.pmtiles"))) {
Optional<ByteBuffer> tile = reader.getTile(10, 885, 412);
// Process tile...
}
Reading from S3¶
For backends that need configuration (region, credentials, endpoint overrides), open a Storage explicitly with the configured Properties, then ask it for a reader. This is the general-purpose two-resource pattern.
import io.tileverse.storage.RangeReader;
import io.tileverse.storage.Storage;
import io.tileverse.storage.StorageFactory;
import java.net.URI;
import java.util.Properties;
Properties props = new Properties();
props.setProperty("storage.s3.region", "us-west-2");
URI bucket = URI.create("s3://my-bucket/");
URI leaf = URI.create("s3://my-bucket/world.pmtiles");
try (Storage storage = StorageFactory.open(bucket, props);
RangeReader s3Reader = storage.openRangeReader(leaf);
PMTilesReader reader = new PMTilesReader(s3Reader)) {
Optional<ByteBuffer> tile = reader.getTile(10, 885, 412);
// Process tile...
}
With Caching¶
For better performance, especially with cloud storage, wrap the reader with caching before handing it to PMTilesReader:
import io.tileverse.storage.cache.CachingRangeReader;
try (Storage storage = StorageFactory.open(URI.create("s3://my-bucket/"), props);
RangeReader baseReader = storage.openRangeReader(URI.create("s3://my-bucket/world.pmtiles"));
// Wrap the base reader with caching
RangeReader cachedReader = CachingRangeReader.builder(baseReader)
.maximumSize(1000) // Cache up to 1000 ranges
.withBlockAlignment() // Optimize reads
.build();
PMTilesReader reader = new PMTilesReader(cachedReader)) {
Optional<ByteBuffer> tile = reader.getTile(10, 885, 412);
}
Decoded Vector and Raster Tiles¶
PMTilesReader.getTile(...) returns the raw on-disk tile bytes. Most callers want a decoded model — the PMTilesVectorTileStore and PMTilesRasterTileStore wrappers handle the decoding step (and validate that the archive's tile type matches):
import io.tileverse.pmtiles.PMTilesReader;
import io.tileverse.pmtiles.store.PMTilesRasterTileStore;
import io.tileverse.tiling.pyramid.TileIndex;
import io.tileverse.tiling.store.TileData;
import java.awt.image.RenderedImage;
import java.util.Optional;
try (PMTilesReader reader = PMTilesReader.open(URI.create("s3://my-bucket/imagery.pmtiles"))) {
PMTilesRasterTileStore store = new PMTilesRasterTileStore(reader);
var tile = store.matrixSet().getTileMatrix(10).tile(TileIndex.xyz(885, 412, 10)).orElseThrow();
Optional<TileData<RenderedImage>> decoded = store.loadTile(tile);
decoded.ifPresent(td -> {
RenderedImage img = td.data(); // ready for ImageIO.write, GridCoverage2D, etc.
});
}
WebP decoding is bundled with tileverse-pmtiles (an ImageIO plugin pulled in transitively). PNG and JPEG ship with the JDK. See Reading PMTiles for the vector store equivalent.
Processing Multiple Tiles¶
PMTilesReader.open(URI) is the simplest entry point — it opens the parent Storage and RangeReader for you, and closing the reader releases both.
// Get tiles for a specific area
int zoom = 10;
int minX = 880, maxX = 890;
int minY = 410, maxY = 420;
try (PMTilesReader reader = PMTilesReader.open(URI.create("s3://my-bucket/world.pmtiles"))) {
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
Optional<ByteBuffer> tile = reader.getTile(zoom, x, y);
if (tile.isPresent()) {
System.out.printf("Tile %d/%d/%d: %d bytes%n",
zoom, x, y, tile.get().remaining());
}
}
}
}
Next Steps¶
- Reading PMTiles: Learn more about reading operations
- Cloud Storage: Deep dive into cloud storage integration
- Storage Guide and Range Reader Reference: Understand the underlying data access layer