Basemaps for Maplibre GL JS + Protomaps
Basemapkit generates customizable styles compatible with Maplibre GL JS that relies on the Protomaps Planet schemas when it comes to vector layers and feature properties. You can download your own PMtiles copy of the planet on the official Protomaps build page.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
On an existing ES project:
npm install basemapkit
The following example instantiates a Maplibre Map
, then initializes the Protomaps protocol and then generates a style with Basemapkit:
import "maplibre-gl/dist/maplibre-gl.css";
import maplibregl from "maplibre-gl";
import { Protocol } from "pmtiles";
import { getStyle, getStyleList } from "basemapkit";
// Adds the Protomaps protocol:
maplibregl.addProtocol("pmtiles", new Protocol().tile);
// Build the Basemapkit style
const style = getStyle(
// One of the main syle:
"avenue",
{
// URL to the pmtiles
pmtiles: "https://my-s3-bucket.com/planet.pmtiles",
// URL to the sprites (for POIs)
sprite: "https://raw.github.com/jonathanlurie/phosphor-mlgl-sprite/refs/heads/main/sprite/phosphor-diecut",
// URL to the glyphs (for labels)
glyphs: "https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf";
// Language (you can ommit to use the platform language)
lang: "en",
});
// Instantiate the Map:
const map = new maplibregl.Map({
container: "map",
center: [0, 0],
zoom: 3,
// Add the Basemapkit style:
style,
});
If using a traditional tile.json
and z/x/y
tiles instead of http-range-requesting a pmtiles
file, then replace the option pmtiles
by tilejson
, as in the example below:
const style = getStyle(
// One of the main syle:
"avenue",
{
// URL to the tile.json
tilejson: "https://example.com/tile.json",
// URL to the sprites (for POIs)
sprite: "https://raw.github.com/jonathanlurie/phosphor-mlgl-sprite/refs/heads/main/sprite/phosphor-diecut",
// URL to the glyphs (for labels)
glyphs: "https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf";
// Language (you can ommit to use the platform language)
lang: "en",
});
This can get particularly handy when using the Protomaps CLI or Martin to serve z/x/y
vector tiles.
Basemakit styles are compatible with Protomaps languages properties and uses @protomaps/basemaps
under the hood.
The only addition from Basemapkit is the capability to detect the end user's platform language, so if the lang
option is omitted, it will automatically use the language set by the user at the browser or OS level.
Here is the list of supported languages:
"ar" | "cs" | "bg" | "da" | "de" | "el" | "en" | "es" | "et" | "fa" | "fi" | "fr" | "ga" | "he" | "hi" | "hr" | "hu" | "id" | "it" | "ja" | "ko" | "lt" | "lv" | "ne" | "nl" | "no" | "mr" | "mt" | "pl" | "pt" | "ro" | "ru" | "sk" | "sl" | "sv" | "tr" | "uk" | "ur" | "vi" | "zh-Hans" | "zh-Hant"
Basemapkit can feature hillshading and/or terrain bumps when a terrain tileset is provided. The terrain tiles can be encoded as "mapbox"
(default) or "terrarium"
, in either PNG or WebP format. Then, the terrain tileset can be packed as pmtiles (``options.terrain.pmtiles) or left as individual tiles, using a *tiles.json* as entry point (
options.terrain.tilejson`).
![]() |
![]() |
![]() |
![]() |
![]() |
Here is how to add hillshading but keep the terrain flat. Those settings are actually the default when the terrain tiles are provided.
const style = getStyle(
"avenue",
{
pmtiles: "",
sprite: "...",
glyphs: "...";
lang: "...",
/**
* The terrain options:
*/
terrain: {
/**
* The public URL of a pmtiles files for raster terrain, encoded on RGB channels of either PNG or WebP. To use if sourcing tiles directly with
* range-request using the `pmtiles`'s protocol. Alternatively, the option `tileJson` can be used and will take precedence.
*/
pmtiles: "https://my-s3-bucket.com/terrain.pmtiles";
/**
* Enable or disable the hillshading. Enabled by default if one of the source options `terrain.pmtiles` or `terrain.tilejson` is provided.
* It cannot be enabled if none of the source option is provided.
*/
hillshading: true,
/**
* The terrain exaggeration is disabled by default, making the terrain flat even if one of the source options `terrain.pmtiles` or `terrain.tilejson` is provided.
* A value of `1` produces at-scale realistic terrain elevation.
* It cannot be enabled if none of the source option is provided.
*/
exaggeration: 0,
/**
* Encoding of the terrain raster data. Can be "mapbox" or "terrarium". Default: "mapbox"
*/
encoding: "mapbox",
}
});
Alternatively, if the terrain tiles are refered to with a tiles.json
:
const style = getStyle(
"avenue",
{
pmtiles: "",
sprite: "...",
glyphs: "...";
lang: "...",
/**
* The terrain options:
*/
terrain: {
/**
* The public URL to a tile JSON file for raster terrain tiles, encoded on RGB channels of either PNG or WebP. To use if classic z/x/y MVT tiles are served through
* Maplibre's Martin or the pmtiles CLI. Will take precedence on the option `pmtiles` if both are provided.
*/
tilejson: "https://example.com/terrain-tile.json";
}
});
There are options to hide the points of interests and labels. By default, both are shown, meaning the options goes like this:
getStyle(
"avenue",
{
pmtiles: "...",
sprite: "...",
glyphs: "...",
hidePOIs: false,
hideLabels: false,
});
So by default, London looks like this:
But POIs can be hidden by doing this:
getStyle(
"avenue",
{
pmtiles: "...",
sprite: "...",
glyphs: "...",
hidePOIs: true,
hideLabels: false,
});
Then the same locations looks like this:
Alternatively, the labels can be hidden, this includes POIs' labels, so only POIs' icons will be shown by doing this:
getStyle(
"avenue",
{
pmtiles: "...",
sprite: "...",
glyphs: "...",
hidePOIs: false,
hideLabels: true,
});
And finally, both labels and POIs can be hidden, resulting in a somewhat mysterious map:
getStyle(
"avenue",
{
pmtiles: "...",
sprite: "...",
glyphs: "...",
hidePOIs: true,
hideLabels: true,
});
Note that the corresponding layers are removed from the style and not just made invisible. If hiding POIs or label, the options sprite
and glyph
are unnecessary.
In addition to language and hiding POIs/labels, Basmapkit exposes some methods to modify the colors of the original style (avenue
) to create presets. When the style is generated with some non-default colorEdit
, a brand new Maplibre style is created and can be directly injected into a Maplibre Map
instance's .setStyle()
method, or even written as a static json file.
buildStyle({
pmtiles: "...",
sprite: "...",
glyphs: "...",
terrain: {...},
hidePOIs: false,
hideLabels: false,
// At the moment, "avenue" is the only style to start from
baseStyleName: "avenue",
colorEdit: {
// Invert the colors:
negate: false,
// In the range [-1, 1]:
brightness: 0,
// In the range [-1, 1]:
brightnessShift: 0,
// In the range [-1, 1]:
exposure: 0,
contrast: [
// intensity in the range [-1, 1]:
0,
// Midpoint in [0, 255]
127
],
// Rotate around the hue wheel, in range [0, 360]
hueRotation: 0,
// In the range [-1, 1]
// with -1 being gray levels and 1 being extra boosted colors
saturation: 0,
// Color blending with a multiply method
multiplyColor: [
// Color to multiply with
"#ff0000",
// blending factor in [0, 1]
// with 0 being the original color and 1 being the the color above
0
],
// Linear color blending
mixColor: [
// Color to blend with
"#ff0000",
// blending factor in [0, 1]
// with 0 being the original color and 1 being the the color above
0
]
}
}
);
For instance, let's create a TMNT toxic N.Y.C. kind of map:
{
"baseStyleName": "avenue",
"lang": "en",
"hidePOIs": true,
"hideLabels": false,
"colorEdit": {
"negate": true,
"brightness": 0.4,
"brightnessShift": 0,
"exposure": 0.8,
"contrast": [
0.4,
160
],
"hueRotation": 80,
"saturation": 0.12,
"multiplyColor": [
"#ff00ff",
0.6
],
"mixColor": [
"#00ff00",
0.3
]
}
}
You can live play with these on basemapkit.jnth.io and selecting the style 🖌️ custom 🎨
.
And from this "color editor" were created the built-in styles available below...
Some custom colorEdit
recipes are already built in Basemapkit and can be accessed directly from the getStyle()
function.
This one is the default, with all the colorEdit
options set to default:
// Create the style
const style = getStyle("avenue", options);
// Create the style
const style = getStyle("avenue-pop", options);
// Create the style
const style = getStyle("avenue-night", options);
// Create the style
const style = getStyle("avenue-bright", options);
// Create the style
const style = getStyle("avenue-saturated", options);
// Create the style
const style = getStyle("avenue-warm", options);
// Create the style
const style = getStyle("avenue-vintage", options);
// Create the style
const style = getStyle("avenue-bnw", options);
// Create the style
const style = getStyle("avenue-blueprint", options);