Pitch toggle control for Mapbox GL JS

Government types tend to want a button for everything, and I try to dissuade them from that as much as possible. However, getting a Mapbox GL JS map to do the tilter-whirl thing requires a complex UI interaction. A little help is in order.

Tom MacWright has a good blog post to get you started. Your JavaScript class will require onAdd and onRemove events, which fire when the control is added to or removed from the map. If you want the control to look like the regular GL JS buttons, you’ll need to add the corresponding classes to your container and button.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
export default class PitchToggle {

constructor({bearing = -20, pitch = 70, minpitchzoom = null}) {
this._bearing = bearing;
this._pitch = pitch;
this._minpitchzoom = minpitchzoom;
}

onAdd(map) {
this._map = map;
let _this = this;

this._btn = document.createElement('button');
this._btn.className = 'mapboxgl-ctrl-icon mapboxgl-ctrl-pitchtoggle-3d';
this._btn.type = 'button';
this._btn['aria-label'] = 'Toggle Pitch';
this._btn.onclick = function() {
if (map.getPitch() === 0) {
let options = {pitch: _this._pitch, bearing: _this._bearing};
if (_this._minpitchzoom && map.getZoom() > _this._minpitchzoom) {
options.zoom = _this._minpitchzoom;
}
map.easeTo(options);
_this._btn.className = 'mapboxgl-ctrl-icon mapboxgl-ctrl-pitchtoggle-2d';
} else {
map.easeTo({pitch: 0, bearing: 0});
_this._btn.className = 'mapboxgl-ctrl-icon mapboxgl-ctrl-pitchtoggle-3d';
}
};


this._container = document.createElement('div');
this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
this._container.appendChild(this._btn);

return this._container;
}

onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}

}

In an ES6 class, class declarations via constructor let you set configurable values used by the rest of the class. Here we’re setting default values for pitch and bearing, and the minpitchzoom sets a minimum zoom when switching to 3D so the citizens won’t have a building footprint flying up their arse unexpectedly.

Add it to your map via:

1
map.addControl(new PitchToggle({minpitchzoom: 11}));

You’ll also want something inside that button. Here I’m making two somethings, depending on the state.

1
2
3
4
5
6
.mapboxgl-ctrl-pitchtoggle-3d {     
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCI+ICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjM1ZW0iIHN0eWxlPSJmb250LXNpemU6IDE0cHg7IGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1hbmNob3I6IG1pZGRsZTsiPjNEPC90ZXh0Pjwvc3ZnPg==);
}
.mapboxgl-ctrl-pitchtoggle-2d {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzMCI+ICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjM1ZW0iIHN0eWxlPSJmb250LXNpemU6IDE0cHg7IGZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLEFyaWFsLEhlbHZldGljYSxzYW5zLXNlcmlmOyBmb250LXdlaWdodDogYm9sZDsgdGV4dC1hbmNob3I6IG1pZGRsZTsiPjJEPC90ZXh0Pjwvc3ZnPg==);
}

These are base64 encoded SVG’s, using b64.io. The original SVG looks like this, with a hat tip to Lea Verou.

1
2
3
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<text x="50%" y="50%" dy=".35em" style="font-size: 14px; font-family: 'Helvetica Neue',Arial,Helvetica,sans-serif; font-weight: bold; text-anchor: middle;">3D</text>
</svg>

The code is on Github. Note as I’m using a few ES6 features, you’ll want to run it through something like Babel. If that isn’t a part of your build process already, it really should be.