Skip to content

Chapter 4: PMTiles Protocol#

Welcome back! In the last chapter, Chapter 3: Data Sources, we learned about the sources section in our map style. This section tells MapLibre GL JS where to find the geographic data it needs. We saw definitions like this:

// Inside the sources object...
"ky-streams": {
  type: "vector",
  url: "pmtiles://https://contig.us/data/pmtiles/ky_strm_line.pmtiles",
},
// ... and a raster-dem source also using pmtiles://
dem: {
  type: "raster-dem",
  url: "pmtiles://https://contig.us/data/tiles/pine-mtn/terrain.json",
},
// ... other sources ...

Notice the unusual pmtiles:// at the beginning of the URLs for some of our sources? This isn't a standard web protocol like http:// or https://. This is where the PMTiles Protocol comes in.

What Problem Does PMTiles Solve?#

Traditionally, map data is served as thousands, or even millions, of tiny files called "tiles." Each tile covers a small geographic area at a specific zoom level. When you pan or zoom on a map, the map library calculates which tiles are needed for the new view and fetches each one individually from a server.

Fetching thousands of small files can be less efficient than fetching one large file, especially if you're trying to self-host or use simple storage like Amazon S3 or DigitalOcean Spaces. Setting up a traditional tile server that can efficiently serve these individual tiles requires specific software and configuration.

The PMTiles Solution: One File to Rule Them All#

PMTiles is a clever solution that packages many map tiles (vector tiles or raster tiles) into a single file with a .pmtiles extension. This single file contains all the data for a specific area and range of zoom levels.

Think of it like putting all the photos for your collage (from the previous chapter's analogy) into one big, organized album file instead of having them scattered as individual pictures.

The PMTiles Protocol: How to Read the Single File#

Having all the tiles in one file is great, but MapLibre GL JS (or any web browser) doesn't automatically know how to open this special .pmtiles file and extract just the specific tile it needs for the current map view. It needs instructions.

The PMTiles Protocol is a special instruction manual, or a plugin, that you add to MapLibre GL JS. It tells MapLibre: "When you see a URL starting with pmtiles://, don't treat it like a normal web request. Use me to figure out how to get the data."

This plugin knows how to:

  1. Read the structure of the .pmtiles file (which is designed for efficient access).
  2. Calculate exactly which part of the file contains the data for the specific tile (z/x/y) that MapLibre is asking for.
  3. Use a standard web browser feature called HTTP Range Requests to ask the web server for only that specific part of the .pmtiles file, instead of downloading the whole thing.

So, the PMTiles format is the single file, and the PMTiles protocol is the special code that lets MapLibre read specific parts of that file efficiently over the web.

Our Goal: Understand and Implement the PMTiles Protocol Handler#

Our goal for this chapter is to see how we add this special PMTiles instruction manual (the protocol handler) to MapLibre GL JS so that it knows how to read our .pmtiles data sources.

Adding the PMTiles Protocol Handler#

To teach MapLibre GL JS how to understand pmtiles:// URLs, we need to import the PMTiles library and register its protocol handler with MapLibre.

You can see this right at the beginning of our map/main.js file, even before we create the map instance:

import "maplibre-gl/dist/maplibre-gl.css";
import "./style.css";
import maplibregl from "maplibre-gl";
import * as pmtiles from "pmtiles"; // <-- 1. Import the library

let protocol = new pmtiles.Protocol(); // <-- 2. Create an instance of the protocol handler
maplibregl.addProtocol("pmtiles", protocol.tile); // <-- 3. Register it with MapLibre

// ... rest of map/main.js code ...

Let's break down these few lines:

  1. import * as pmtiles from "pmtiles";: This line imports the necessary code from the pmtiles JavaScript library. We installed this library in the project setup.
  2. let protocol = new pmtiles.Protocol();: This creates a new instance of the Protocol class from the PMTiles library. This object contains the logic for handling PMTiles requests.
  3. maplibregl.addProtocol("pmtiles", protocol.tile);: This is the key step! We call a special function on maplibregl called addProtocol.
    • The first argument "pmtiles" tells MapLibre: "Whenever you see a URL that starts with pmtiles://..."
    • The second argument protocol.tile tells MapLibre: "...call this specific function (protocol.tile from the PMTiles library instance) to handle the request for that tile."

After these lines of code run, MapLibre GL JS now understands pmtiles:// URLs. When it encounters a source defined with url: "pmtiles://...", it will use the protocol.tile handler to fetch the data instead of its default web fetching methods.

How MapLibre Uses the PMTiles Protocol (Under the Hood)#

Let's see what happens when MapLibre GL JS needs a specific tile (e.g., zoom 13, column 1234, row 5678) for a source like "ky-streams" defined as:

"ky-streams": {
  type: "vector",
  url: "pmtiles://https://contig.us/data/pmtiles/ky_strm_line.pmtiles",
},

Here's a simplified flow:

sequenceDiagram
    participant MapLibre as MapLibre GL JS
    participant PMTilesHandler as PMTiles Protocol Handler
    participant Browser as Web Browser API
    participant WebServer as Web Server (contig.us)

    MapLibre->PMTilesHandler: "Need tile z=13, x=1234, y=5678" for source "ky-streams" (URL: pmtiles://...)
    PMTilesHandler->PMTilesHandler: Extract base URL: https://contig.us/data/pmtiles/ky_strm_line.pmtiles
    PMTilesHandler->PMTilesHandler: Calculate byte range for tile 13/1234/5678 within the .pmtiles file
    PMTilesHandler->Browser: Make HTTP request to https://...ky_strm_line.pmtiles with Range: bytes=start-end
    Browser->WebServer: Send HTTP GET request with Range header
    WebServer-->Browser: Send ONLY the requested byte range (the tile data)
    Browser-->PMTilesHandler: Pass the tile data back
    PMTilesHandler-->MapLibre: Provide the tile data
    MapLibre->MapLibre: Process and draw the tile data

The key point is that the PMTilesHandler figures out the exact location (byte range) of the required tile data within the single .pmtiles file and asks the server for only that part using the standard HTTP Range Request feature. The server doesn't need to know anything special about PMTiles; it just needs to be able to serve byte ranges of a file, which most standard web servers (Apache, Nginx, CDNs like Cloudflare or S3) can do.

This is why PMTiles is so powerful for simple hosting: you just put the .pmtiles file on a static web server, and the client-side PMTiles protocol handler in the browser knows how to efficiently get pieces of it as needed.

PMTiles in Our Data Sources#

After adding the protocol handler, we can use pmtiles:// URLs in our sources definition, as seen back in Chapter 3: Data Sources.

// Inside the style object's sources...
sources: {
  // ...
  "ky-streams": {
    type: "vector",
    // MapLibre now knows to use the pmtiles handler for this URL
    url: "pmtiles://https://contig.us/data/pmtiles/ky_strm_line.pmtiles",
  },
  // ...
  dem: {
    type: "raster-dem",
    // And for this one too (TileJSON is also often included in PMTiles)
    url: "pmtiles://https://contig.us/data/tiles/pine-mtn/terrain.json",
  },
  // ...
},

The url property for these sources now uses the pmtiles:// prefix, followed by the standard web URL (https://...) where the single .pmtiles file (or a TileJSON file for raster-dem) is located. MapLibre sees pmtiles://, hands the request to the PMTiles handler, and the handler fetches the necessary bytes from the web server.

Conclusion#

In this chapter, we learned about the PMTiles Protocol, a crucial component for efficiently loading map tiles packaged in a single .pmtiles file. We saw how PMTiles solves the problem of needing complex tile servers by allowing MapLibre GL JS to fetch only the necessary parts of a large file using standard HTTP Range Requests.

We also saw the simple, but essential, code snippet in map/main.js that registers the PMTiles protocol handler with MapLibre GL JS, enabling it to understand pmtiles:// URLs in the style's sources definitions.

Now that we know how to get the data onto the map using data sources and the PMTiles protocol, we're ready to tell MapLibre how to draw that data.

In the next chapter, we will explore Chapter 5: Map Layers, which are the instructions in the style that define the visual appearance of the data from our sources.


Generated by AI Codebase Knowledge Builder