Effectively switching mapbox-gl styles
Tags: mapboxgl
Published: 2020-09-11 14:53:26

Hi there!

I wish write about [known](https://stackoverflow.com/questions/36168658/mapbox-gl-setstyle-removes-layers) common problem with `mapbox-gl`.
At the moment there are [some](https://github.com/mapbox/mapbox-gl-js/issues/4006) [proposals](https://github.com/mapbox/mapbox-gl-js/issues/4000) for a solution and some [workarounds](https://bl.ocks.org/tristen/0c0ed34e210a04e89984).

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
```javascript
// 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],
      ],
    ],
  },
};
```
## Create the map
```javascript
// 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,
});
```
## 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
```javascript
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);
  }
}
```

### 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
```javascript
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"
  );
}
```

### Setup events
We should call addMaineLayer function when:
- map initially loads with `load` event
- map changes the style with `styledata` event

```javascript
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;
  }
});
```

### Let's check the result
{% codesandbox mapbox-switch-style-qs8c5 %}

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