DEV Community

Cover image for Effectively switching mapbox-gl styles
dqunbp
dqunbp

Posted on • Edited on

Effectively switching mapbox-gl styles

Hi there!

I wish write about known common problem with mapbox-gl.
At the moment there are some proposals for a solution and some workarounds.

In this article, we see the typical scenario and going to go to implement it.

Conditions of the problem

Let's describe a simple case:

  • We need to add to the map our own GeoJSON source and corresponding layer, before the text labels.
  • When switching the base style, our layer should remain on the map.

We use a straightforward approach with mapbox-gl events.

Define the source

// constants.js
const GEOJSON_FEATURE = {
  type: "Feature",
  properties: {},
  geometry: {
    type: "Polygon",
    coordinates: [
      [
        [-66.96466, 44.8097],
        [-67.79035274928509, 47.066248887716995],
        [-69.23708614772835, 47.44777598732787],
        [-71.08482, 45.3052400000002],
        [-70.64573401557249, 43.090083319667144],
        [-66.96466, 44.8097],
      ],
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

Create the map

// index.js
mapboxgl.accessToken = <your token here>;
var map = new mapboxgl.Map({
  container: "map",
  style: "mapbox://styles/mapbox/streets-v11",
  center: [-68.13734351262877, 45.137451890638886],
  zoom: 5,
});
Enter fullscreen mode Exit fullscreen mode

Improve addLayer function

If we use beforeId that not exists already on the map, we get an error
Let's create addLayerBefore function to escape this

function addLayerBefore(addLayerFn, layer, beforeId) {
// check beforeId defined and exists on the map
const beforeLayer = Boolean(beforeId) && map.getLayer(beforeId);
  if (beforeLayer && beforeId === beforeLayer.id) addLayerFn(layer, beforeId);
  else {
    console.warn(
      `Not found layer with id '${beforeId}'.\nLayer '${layer.id}' added without before.`
    );
    addLayerFn(layer);
  }
}
Enter fullscreen mode Exit fullscreen mode

Define add data function

Add's GeoJSON source with fill layer, before waterway-label in current example it places polygon before the map text labels

function addMaineLayer() {
  map.addSource("maine", {
    type: "geojson",
    data: {
      type: "Feature",
      geometry: GEOJSON_FEATURE
  });

  // define the function to add layer
  const addLayer = (layer, beforeId) => map.addLayer(layer, beforeId);

  addLayerBefore(
    addLayer,
    {
      id: "maine",
      type: "fill",
      source: "maine",
      layout: {},
      paint: {
        "fill-color": "#088",
        "fill-opacity": 0.8
      }
    },
    "waterway-label"
  );
}
Enter fullscreen mode Exit fullscreen mode

Setup events

We should call addMaineLayer function when:

  • map initially loads with load event
  • map changes the style with styledata event
map.on("load", function () {
  // add layer to the map on load
  addMaineLayer();

  const layerList = document.getElementById("menu");
  const inputs = layerList.getElementsByTagName("input");

  function switchLayer(layer) {
    // addMaineLayer fn will be called once on layer switched
    map.once("styledata", addMaineLayer);
    const layerId = layer.target.id;
    map.setStyle("mapbox://styles/mapbox/" + layerId);
  }

  // set toggle base style events
  for (let i = 0; i < inputs.length; i++) {
    inputs[i].onclick = switchLayer;
  }
});
Enter fullscreen mode Exit fullscreen mode

Let's check the result

That is!
In the next article we going to go implement this with RxJS!

Top comments (0)