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, or outdoors in the URL in place of light.
  • colors sets the fill color of three layers by their ids. landuse_park draws parks, forests, cemeteries, and other green areas, each in its own shade of green. One color paints them all alike.
  • Filtering out the pois layer 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 has dark, white, grayscale, and black.
  • The flavor's keys say what they color, such as water, buildings, highway, major, and minor_a for roads, and city_label for town names. Log namedFlavor("light") to see them all with their values.
  • lang sets 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.
  • glyphs and sprite load Mapricot's fonts and icons. The icons come in light, dark, white, grayscale, and black, to suit the flavor's colors.
  • The source is Mapricot's tiles, the same as in its own styles. Its planet.json carries 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.