Skip to content

Reading PMTiles

This guide covers advanced topics for reading PMTiles archives.

Opening PMTiles Archives

PMTiles archives can be opened from any data source supported by Range Reader:

// Local file
RangeReader fileReader = FileRangeReader.builder()
    .path(Path.of("tiles.pmtiles"))
    .build();

// HTTP
RangeReader httpReader = HttpRangeReader.builder()
    .uri(URI.create("https://example.com/tiles.pmtiles"))
    .build();

// S3
RangeReader s3Reader = S3RangeReader.builder()
    .uri(URI.create("s3://bucket/tiles.pmtiles"))
    .build();

Reading Header Information

The header contains essential metadata about the tileset:

try (PMTilesReader reader = new PMTilesReader(rangeReader)) {
    PMTilesHeader header = reader.getHeader();

    // Tile format (MVT, PNG, JPEG, WEBP, etc.)
    String tileType = header.tileType();

    // Zoom level range
    int minZoom = header.minZoom();
    int maxZoom = header.maxZoom();

    // Geographic bounds (in E7 format: degrees * 10,000,000)
    double minLon = header.minLonE7() / 10_000_000.0;
    double minLat = header.minLatE7() / 10_000_000.0;
    double maxLon = header.maxLonE7() / 10_000_000.0;
    double maxLat = header.maxLatE7() / 10_000_000.0;

    System.out.printf("Bounds: [%.6f, %.6f, %.6f, %.6f]%n",
        minLon, minLat, maxLon, maxLat);
}

Reading Individual Tiles

Tiles are retrieved using the standard Z/X/Y addressing:

Optional<byte[]> tileData = reader.getTile(zoom, x, y);

if (tileData.isPresent()) {
    byte[] tile = tileData.get();
    // Process tile data...
} else {
    // Tile doesn't exist in the archive
}

Bulk Tile Operations

Reading a Tile Range

int zoom = 10;
for (int x = 880; x <= 890; x++) {
    for (int y = 410; y <= 420; y++) {
        Optional<byte[]> tile = reader.getTile(zoom, x, y);
        if (tile.isPresent()) {
            processTile(zoom, x, y, tile.get());
        }
    }
}

Parallel Processing

PMTilesReader is thread-safe for read operations:

IntStream.range(880, 891)
    .parallel()
    .forEach(x -> {
        IntStream.range(410, 421).forEach(y -> {
            reader.getTile(zoom, x, y).ifPresent(tile -> {
                processTile(zoom, x, y, tile);
            });
        });
    });

Performance Tips

  1. Use caching for cloud storage sources
  2. Enable block alignment for optimal read patterns
  3. Reuse readers instead of creating new instances
  4. Batch operations when processing multiple tiles

See Cloud Storage for detailed performance optimization strategies.

Error Handling

try (PMTilesReader reader = new PMTilesReader(rangeReader)) {
    Optional<byte[]> tile = reader.getTile(zoom, x, y);
    // Process tile...
} catch (UncheckedIOException e) {
    // Handle I/O errors (network issues, file not found, etc.)
    System.err.println("Failed to read PMTiles: " + e.getMessage());
} catch (Exception e) {
    // Handle other errors (invalid format, etc.)
    System.err.println("Error: " + e.getMessage());
}

Next Steps