Customize a MapLibre map style
Mapricot's five styles are a place to start. A style is a JSON file that lists the map's layers in the order they're drawn, and says what each one draws and how: water as a blue fill, highways as orange lines, town names as gray text. Change the style and the map changes with it. Every style in this guide draws the same free tiles.
There are three ways to go, from the least work to the most:
- Change a few layers of one of Mapricot's styles: a color here, a layer hidden there.
- Build the style from a flavor, a set of 74 colors, the way Mapricot builds its own styles.
- Write the style yourself, layer by layer, for a map that draws only what you need.
The examples were tested with MapLibre GL JS 6.15 and @protomaps/basemaps 5.7.2 in Chromium. Here's Portland in the Light style as it comes, and with the first example's changes: bluer water, greener parks, sand-colored buildings, and no points of interest.
Before you start
You need a browser key with the Map tiles product. API keys shows how to make one. Add http://localhost:* to its allowed origins, so it works from your computer while you build.
Change a few layers
Make a folder, and in it a file called index.html, with your browser key in place of mapricot_pk_…:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My map style</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/dist/maplibre-gl.css">
</head>
<body style="margin: 0">
<div id="map" style="height: 100vh"></div>
<script type="module">
import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/+esm";
const response = await fetch("https://api.mapricot.com/tiles/v1/styles/light.json?key=mapricot_pk_…");
if (!response.ok) throw new Error(`The style didn't load: ${response.status}`);
const style = await response.json();
const colors = { water: "#5aa9d6", landuse_park: "#b7dca0", buildings: "#e6cfae" };
for (const layer of style.layers) {
if (colors[layer.id]) layer.paint["fill-color"] = colors[layer.id];
}
style.layers = style.layers.filter((layer) => layer.id !== "pois");
const map = new maplibregl.Map({ container: "map", style, center: [-122.68, 45.52], zoom: 13 });
map.addControl(new maplibregl.NavigationControl());
</script>
</body>
</html>
- The page fetches the Light style, changes it, and gives MapLibre the changed copy, so the map never draws the old colors. Any of the five styles works: put
dark,bright,grayscale, oroutdoorsin the URL in place oflight. colorssets the fill color of three layers by their ids.landuse_parkdraws parks, forests, cemeteries, and other green areas, each in its own shade of green. One color paints them all alike.- Filtering out the
poislayer hides the icons and names of shops, parks, stations, and other points of interest. - The style already has your key in its tiles URL, so nothing else in the page needs it.
Serve the folder with npx serve or python3 -m http.server, and open the address it prints. Opened straight from the file, the map stays blank, as Open the page explains.
Find a layer's id
These are the layers you'll change most often. All five styles have them, except that Grayscale has no pois.
| Layer | Draws |
|---|---|
background |
Everything under the other layers |
earth |
Land |
water |
Oceans, lakes, and wide rivers |
landuse_park |
Parks, forests, and other green areas |
buildings |
Buildings |
roads_highway, roads_major, roads_minor |
Motorways, main roads, and side streets |
pois |
Icons and names of points of interest |
places_locality |
Names of cities, towns, and villages |
places_country |
Names of countries |
To list every layer, add this to the script and open the browser's console:
map.on("load", () => console.log(map.getStyle().layers.map((layer) => layer.id)));
To find the layer behind something on the map, log every layer under a click:
map.on("click", (event) => {
for (const feature of map.queryRenderedFeatures(event.point)) {
console.log(feature.layer.id, feature.properties.kind);
}
});
A layer's type says which properties it has. Areas such as water are fills, with fill-color. Roads are lines, with line-color. Labels and icons such as pois are symbols, with text-color and icon-opacity. The MapLibre style spec lists them all. Roads are drawn twice, a wider _casing layer under each one, so change a road's casing too to change its edges.
Change it while the map is open
To change a layer after the map draws, such as from a button, use setPaintProperty and setLayoutProperty. The map at the top of this page switches with these calls:
map.setPaintProperty("water", "fill-color", "#5aa9d6");
map.setLayoutProperty("pois", "visibility", "none");
Make them once the map's load event has fired. They change only the open map: map.setStyle loads a fresh style, without them.
Build a style from a flavor
Mapricot builds its styles with the @protomaps/basemaps package. Its layers function writes a style's layers from a flavor: an object of 74 colors, one for each part of the map. Start from one of its flavors and change the colors you want, and every layer that uses a color follows it, bridges and tunnels included. Make index.html this way instead:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My map style</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/dist/maplibre-gl.css">
</head>
<body style="margin: 0">
<div id="map" style="height: 100vh"></div>
<script type="module">
import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/+esm";
import { layers, namedFlavor } from "https://cdn.jsdelivr.net/npm/@protomaps/basemaps@5.7.2/+esm";
const tiles = "https://api.mapricot.com/tiles/v1";
const flavor = {
...namedFlavor("light"),
water: "#5aa9d6",
park_a: "#b7dca0",
park_b: "#a5d18c",
buildings: "#e6cfae",
highway: "#f6c66b",
major: "#fbe3a6",
};
const map = new maplibregl.Map({
container: "map",
style: {
version: 8,
glyphs: `${tiles}/fonts/{fontstack}/{range}.pbf`,
sprite: `${tiles}/sprites/v4/light`,
sources: {
protomaps: { type: "vector", url: `${tiles}/planet.json?key=mapricot_pk_…` },
},
layers: layers("protomaps", flavor, { lang: "en" }),
},
center: [-122.68, 45.52],
zoom: 13,
});
map.addControl(new maplibregl.NavigationControl());
</script>
</body>
</html>
namedFlavor("light")is the flavor of Mapricot's Light style. The package also hasdark,white,grayscale, andblack.- The flavor's keys say what they color, such as
water,buildings,highway,major, andminor_afor roads, andcity_labelfor town names. LognamedFlavor("light")to see them all with their values. langsets the labels' language. With"de", Prague is Prag and Geneva is Genf. A place with no German name shows its English one, and a place whose local name is in another script, such as Tokyo, shows that name too.glyphsandspriteload Mapricot's fonts and icons. The icons come inlight,dark,white,grayscale, andblack, to suit the flavor's colors.- The source is Mapricot's tiles, the same as in its own styles. Its
planet.jsoncarries the credit to Mapricot and OpenStreetMap, so the map's corner shows it with no more code.
Write a style yourself
For a map that draws only what it needs, write the layers yourself. The figure-ground map is a whole style in two layers: a white background and the buildings in black. Each layer draws one of the tiles' layers of data, and most of those have a kind to filter on, such as park or forest in landuse:
| Data layer | Has |
|---|---|
earth |
Land |
water |
Oceans, lakes, rivers, and streams |
landuse |
Parks, forests, farmland, airports, hospitals, and more |
landcover |
Forest, grass, farmland, ice, and sand, at low zooms |
roads |
Roads, paths, railways, and ferries |
buildings |
Buildings, with their heights |
places |
Countries, regions, cities, towns, and neighborhoods |
pois |
Shops, parks, stations, peaks, and more |
boundaries |
Borders of countries and regions |
Protomaps' layer reference lists every kind, and Every road in a city and the rest of the gallery show more styles written this way. For labels, add the glyphs URL from the flavor example and set text-font to ["Noto Sans Regular"], ["Noto Sans Medium"], or ["Noto Sans Italic"].
Questions
What does a custom style cost? Nothing more than Mapricot's own. The tiles are free and not metered, whichever style draws them.
Can I host the style as a file? Yes. In the first example, add console.log(JSON.stringify(style)) after the changes, copy what it prints into style.json beside index.html, and pass style: "style.json" to the map in place of the fetched style. The file keeps the tiles URL with your key, so it works only from the origins the key allows.
Why did my changes go away? map.setStyle replaces the whole style, including changes made with setPaintProperty. Make the changes again after it, or change the style before you pass it, as the first example does.
Why does the console say Image "townhall" could not be loaded? No icon set has a town hall icon yet, so town halls show their names without one. Mapricot's own styles use the building icon instead. To do the same in a flavor style, change the pois layer before you pass the layers to the map:
const styleLayers = layers("protomaps", flavor, { lang: "en" });
const pois = styleLayers.find((layer) => layer.id === "pois");
pois.layout["icon-image"] = ["match", ["get", "kind"], "townhall", "building", pois.layout["icon-image"]];
Can Mapricot's styles show labels in another language? They show English. For another language, build the style from a flavor with lang, as above.
How do I add my own data on top? Add a source and layers of your own once the map loads. Markers and popups and clustering points show how.