Introduction #
Occasionally, one wishes to include a map on a website to illustrate geographic information. Although it is in principle possible to run the software serving the map data yourself, this often cannot be implemented for hosted websites because the hosting platform does not allow running arbitrary software. In this case, an external solution is usually used, making compliance with privacy regulations more complicated.
However, PMTiles makes it possible to include vector graphics-based maps on almost any site. The technical requirements are only:
- The hosting platform must support serving static files.
- The web server must support HTTP range requests.
- HTML code must be included in the website.
Tech Stack for Web Maps #
Raster and Vector Maps #
All map visualizations are composed of individual parts, the so-called tiles. They segment the world map based on geographic coordinates and zoom levels, as higher zoom levels show more details. Thus, only the map data of the currently shown section has to be loaded.
There are two types of tiles: raster and vector tiles. Raster tiles are simple bitmap graphics showing a small area of the map. They are created by a renderer from the raw map data.
Vector tiles, on the other hand, contain geographic information in a more ‘easily digestible’ format than the raw data and support quickly reading map sections. From them, the client (in the case of websites, the browser) renders the map. They have numerous advantages, such as:1
- Continuous zoom
- The appearance can be modified, for example, by custom color schemes or topics.
- Labels remain upright when rotating the map.
For the remainder of this article, vector tiles will be discussed.
The Stack #
Several steps are required to get from the raw map data to displaying a map in the browser.
flowchart LR
raw-data@{shape: lin-cyl, label: "OSM data"}
raw-data --> generator
generator(Generator)
generator -- writes --> tiles
tiles@{shape: lin-cyl, label: "Tile archive"}
client(Browser)
client --- cloud
cloud@{shape: cloud, label: "Internet"}
cloud -- Request --> tileserver
tileserver(Tile server)
tileserver -- Access --> tiles
- A generator software creates tiles from raw map data and saves them in an archive.
- The tile server takes requests for individual tiles from the browser and reads the tiles from the archive.
- The map renderer assembles the map from the individual tiles and displays it in the browser.
The MapLibre library is used as a renderer. This software, which runs in the browser, was forked from Mapbox when Mapbox switched to a non-free license. MapLibre has support for Mapbox Vector Tiles. This is the file format that is used to transfer vector tiles via the network to the browser. The tiles are split into different zoom levels, and each one contains only a small section of the map. It is theoretically possible to save the individual tiles to the file system. However, due to the duplication of the information contained in the tiles, this would require prohibitively large amounts of storage.2
Instead, the tiles are stored in an archive in a single file. One such format is MBTiles. A server software can read the requested tile from it for every client request.
OpenStreetMap is used as the data source. The data is collected by volunteers and can be used for any purpose. The map data for the entire world is available from Planet OSM. Regional extracts are provided by Geofabrik.
A tile generator creates the tiles that are then served by the tile server from this data source. Fortunately, this computationally expensive step can be skipped in most cases by using tiles that were generated by someone else and are available for download.
PMTiles #
A different archive format for vector tiles is PMTiles, developed by Protomaps. This format allows calculating the section of the file containing a specific tile on the client and fetching it with an HTTP range request. Specialized server software is thus no longer necessary; any HTTP server with support for range requests is sufficient.
Fetching tiles via HTTP range requests is implemented by a MapLibre plugin.
Putting Everything Together #
Map Tiles #
Vector tiles can be created from raw OpenStreetMap data, for example, with Tilemaker.3 Since this step is computationally expensive and not necessary in most cases due to the availability of ready-to-use vector tiles, it will not be discussed further.
Tiles are downloaded from Protomaps instead. The file comprising the entire planet has a size of 125 GB as of March 2026.
A geographic extract from that can be downloaded with the pmtiles command-line utility.4
To do that, the coordinates of the bounding box must be specified. For political subdivisions, these can be found easily with the Versatiles bounding box tool.
For instance, the coordinates of Middle Franconia are 10.069,48.86,11.602,49.789.
The command for downloading the map is thus:
pmtiles extract https://build.protomaps.com/20260329.pmtiles mittelfranken.pmtiles --bbox=10.069,48.86,11.602,49.789Here, the file name 20260320.pmtiles should be modified to reflect the current date.
In this case, the resulting file mittelfranken.pmtiles has a manageable size of 160 MB.
JavaScript Libraries #
These libraries are required for displaying the map in the browser:
- MapLibre GL JS as the renderer
- pmtiles to read the tile archive in the browser
- basemaps to create the map style programmatically in the browser5
basemaps can be omitted if the map style is made available as a static JSON file6 which can be obtained from maps.protomaps.com.
These libraries can be included from UNPKG. However, since we want to do without third-party servers, they are downloaded to be served by our own web server:
wget https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js
wget https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css
wget https://unpkg.com/pmtiles@latest/dist/pmtiles.js
wget https://unpkg.com/@protomaps/basemaps@latest/dist/basemaps.jsThe files are included in the HTML document:
<script src="/maps/maplibre-gl.js"></script>
<script src="/maps/pmtiles.js"></script>
<script src="/maps/basemaps.js"></script>
<link href="/maps/maplibre-gl.css" rel="stylesheet" />If required, the path must be modified according to the storage location on the web server.
Assets #
Additionally, assets (glyphs for MapLibre and sprites) are required for displaying the map.7
They can be obtained from the GitHub repository. The directories fonts and sprites are required.
HTML Code #
The map can be included in the website with this HTML code:
<!-- 1 -->
<div id="map" style="height: 30em; width: 100%"></div>
<script type="module">
// 2
const protocol = new pmtiles.Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
// 3
const origin = new URL(document.URL).origin;
const map = new maplibregl.Map({
container: "map",
style: {
version: 8,
sources: {
protomaps: {
type: "vector",
attribution: "<a href=\"https://github.com/protomaps/basemaps\">Protomaps</a> © <a href=\"https://openstreetmap.org\">OpenStreetMap</a>",
// 4
url: "pmtiles:///maps/mittelfranken.pmtiles",
},
},
// 5
layers: basemaps.layers("protomaps", basemaps.namedFlavor("light"), {lang:"en"}),
// 6
sprite: origin + "/maps/basemaps-assets/sprites/v4/light",
glyphs: origin + "/maps/basemaps-assets/fonts/{fontstack}/{range}.pbf",
},
zoom: 10,
center: [11.0772980, 49.4538720],
// 7
maxBounds: [
[
10.069,
48.86,
],
[
11.602,
49.789
],
],
});
</script>Remarks:
- The map is displayed in this
div. In this example, the size is specified with CSS. With the assignedid, it can be referenced by MapLibre so the map can be rendered inside. - Registering the pmtiles plugin so it can be used as the data source later
- The glyphs and sprites must be specified as an absolute path.8 To avoid hard-coding, the domain is read from the
documentJavaScript object.9 - The pmtiles file is specified as the data source with the
pmtilesprotocol. The path to the file can be absolute or relative. - The map style is defined by the rendering layers. They are provided by
basemapsand are available in several variants. English is set as the language for the map labels. - The absolute URL of the sprites and glyphs is composed from the origin and the storage location on the web server.
- The displayable map section is limited to the area for which tiles were downloaded.
Result #
The resulting map can look like this:
Extensibility #
In most cases, displaying a standard map is not the goal. Additional data can be added as GeoJSON10 and can be displayed in further map layers..
-
https://wiki.openstreetmap.org/wiki/Vector_tiles#Motivation ↩︎
-
https://docs.protomaps.com/guide/getting-started#_3-extract-any-area ↩︎
-
https://docs.protomaps.com/basemaps/maplibre#loading-styles-as-json ↩︎
-
https://developer.mozilla.org/de/docs/Web/API/Document/URL ↩︎
-
https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#addsource und https://maplibre.org/maplibre-style-spec/sources/#geojson ↩︎