Toggling Deck.gl Overlay Using MapLibre
We show how to toggle a deck.gl layer using a Clockwork Micro basemap and MapLibre. We will use a MapLibre popup for displaying information about the selected point.
Toggle Deck.gl Layer Overlay Using MapLibre
Why Deck.gl?
Deck.gl offers various layers for displaying data and even helps with transitions and animations. Manipulating the visuals on the layer is more straightforward and easier to understand than what mapLibre offers with the use of expressions.
Initialising the Project
We will initialise the map with Maplibre and a Clockwork Micro base map. Don't forget to replace the api key with your own.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/maplibre-gl@3.5.2/dist/maplibre-gl.css"
/>
<script src="https://unpkg.com/maplibre-gl@3.5.2/dist/maplibre-gl.js"></script>
<title>Toggle Deck.gl Layer | CWM</title>
<style>
body {
margin: 0;
padding: 0;
position: relative;
}
#map {
height: 100vh;
width: 100vw;
}
#toggle-button {
position: fixed;
top: 20px;
left: 20px;
background-color: rgb(130, 25, 191);
color: #f0ead6;
font-size: 1.2rem;
min-width: 70px;
border-radius: 5px;
border: none;
padding: 5px 10px;
transition: 0.3s;
}
#toggle-button:hover {
scale: 1.1;
box-shadow: 0 0 4px 4px gray;
}
</style>
</head>
<body>
<div id="map"></div>
<button id="toggle-button">Hide</button>
</body>
<script>
const url = "https://maps.clockworkmicro.com/streets/v1/style?x-api-key=";
const apiKey = "clockworkmicro_api_key"; // replace it with yours
let show = true; // Display the layer by default
let overlay; // creating the overlay variable here to be used for toggling
const map = new maplibregl.Map({
container: "map",
style: url + apiKey,
center: [2.345885, 48.860412],
zoom: 12,
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
</script>
</html>
We simply added the necessary initial code for displaying a base map that centers on districts of Paris, France. We will use the following sample data for five beautiful gardens one can visit in Paris. Insert the following js object below the previous code inside the script tag.
// 5 Beatiful gardens in Paris
const sampleData = {
type: "FeatureCollection",
name: "Jardins Des Paris",
crs: {
type: "name",
properties: { name: "urn:ogc:def:crs:OGC:1.3:CRS84" },
},
features: [
{
type: "Feature",
properties: {
name: "Jardins du Trocadéro",
district: 16,
},
geometry: {
type: "Point",
coordinates: [2.289207, 48.861561],
},
},
{
type: "Feature",
properties: {
name: "Jardin des Plantes",
district: 5,
},
geometry: {
type: "Point",
coordinates: [2.359823, 48.843995],
},
},
{
type: "Feature",
properties: {
name: "Jardins das Tulherias",
district: 1,
},
geometry: {
type: "Point",
coordinates: [2.327092, 48.863608],
},
},
{
type: "Feature",
properties: {
name: "Parc de Bercy",
district: 12,
},
geometry: {
type: "Point",
coordinates: [2.382094, 48.835962],
},
},
{
type: "Feature",
properties: {
name: "Jardin du Luxemburg",
district: 6,
},
geometry: {
type: "Point",
coordinates: [2.336975, 48.846421],
},
},
],
};
Adding the Deck.gl Layer
Deck.gl layers can be added into MapLibre projects as layers with the help of MapboxLayer. When we used this method for rendering our layers, we saw that creating a MapLibre popup required more effort than it should.
The recommended method for using deck.gl layers with MapLibre is to use the layer as a MapboxOverlay and add it as a control. But since we are adding the layer as an overlay now, we need to set the z-index values of our markers and popups to 2. Otherwise they will appear behind the overlay.
We will use the addControl method defined for a maplibre map for adding our overlay. When the interleaved field is set to true we saw that it prevents the popup from displaying on the map.
To do so, insert the following for the markers in the style tag:
.maplibregl-popup {
z-index: 2;
}
The following code snippet creates a Deck.gl ScatterPlotLayer and adds it to our map as control.
const layer = new deck.ScatterplotLayer({
id: "scatterplot-layer",
data: sampleData.features,
pickable: true,
opacity: 0.8,
stroked: true,
filled: true,
radiusScale: 6,
radiusMinPixels: 20,
radiusMaxPixels: 100,
lineWidthMinPixels: 5,
getPosition: (d) => d.geometry.coordinates,
getFillColor: (d) => [49, 130, 206],
getLineColor: (d) => [175, 0, 32],
onClick: (info) => {
const { coordinate, object } = info;
const description = `<div>
<p>
<strong>Name: </strong>${object.properties["name"]}
</p>
<strong>Disctrict: </strong>${object.properties["district"]}
</p>
</div>`;
new maplibregl.Popup()
.setLngLat(coordinate)
.setHTML(description)
.addTo(map);
},
});
// create the overlay
overlay = new deck.MapboxOverlay({
layers: [layer],
});
map.addControl(overlay);
Toggling the Layer
Simply changing fields dynamically as you would with any MapLibre layer is not recommended for deck.gl layers. We need to simply discard the old layer and create another one with updated fields. This does not come at a performance cost.
In order to keep our code dry we will handle adding the control through a function. We will also update the toggle button text according to the state of the layer. All we need to do now is, move the process that adds an overlay as a control to our map inside initializeOverlay()
function.
Insert the following code inside the script tag below all the other code we have written so far.
map.on("load", () => {
// Add the overlay
initializeOverlay();
const toggleButton = document.querySelector("#toggle-button");
toggleButton.addEventListener("click", () => {
if (show) {
map.removeControl(overlay);
toggleButton.innerText = "Show";
show = false;
} else {
// Recreate and add an overlay
initializeOverlay();
toggleButton.innerText = "Hide";
show = true;
}
});
});
That's all. The complete code is below.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/maplibre-gl@3.5.2/dist/maplibre-gl.css"
/>
<script src="https://unpkg.com/maplibre-gl@3.5.2/dist/maplibre-gl.js"></script>
<title>Toggle Deck.gl Layer | CWM</title>
<style>
body {
margin: 0;
padding: 0;
position: relative;
}
#map {
height: 100vh;
width: 100vw;
}
#toggle-button {
position: fixed;
top: 20px;
left: 20px;
background-color: rgb(130, 25, 191);
color: #f0ead6;
font-size: 1.2rem;
min-width: 70px;
border-radius: 5px;
border: none;
padding: 5px 10px;
transition: 0.3s;
}
#toggle-button:hover {
scale: 1.1;
box-shadow: 0 0 4px 4px gray;
}
/* <style> */
.maplibregl-popup {
z-index: 2;
}
</style>
</head>
<body>
<div id="map"></div>
<button id="toggle-button">Hide</button>
</body>
<script>
const url = "https://maps.clockworkmicro.com/streets/v1/style?x-api-key=";
const apiKey = "clockworkmicro_api_key"; // replace it with yours
let show = true; // Display the layer by default
let overlay; // creating the overlay variable here to be used for toggling
const map = new maplibregl.Map({
container: "map",
style: url + apiKey,
center: [2.345885, 48.860412],
zoom: 12,
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
// 5 Beatiful gardens in Paris
const sampleData = {
type: "FeatureCollection",
name: "Jardins Des Paris",
crs: {
type: "name",
properties: { name: "urn:ogc:def:crs:OGC:1.3:CRS84" },
},
features: [
{
type: "Feature",
properties: {
name: "Jardins du Trocadéro",
district: 16,
},
geometry: {
type: "Point",
coordinates: [2.289207, 48.861561],
},
},
{
type: "Feature",
properties: {
name: "Jardin des Plantes",
district: 5,
},
geometry: {
type: "Point",
coordinates: [2.359823, 48.843995],
},
},
{
type: "Feature",
properties: {
name: "Jardins das Tulherias",
district: 1,
},
geometry: {
type: "Point",
coordinates: [2.327092, 48.863608],
},
},
{
type: "Feature",
properties: {
name: "Parc de Bercy",
district: 12,
},
geometry: {
type: "Point",
coordinates: [2.382094, 48.835962],
},
},
{
type: "Feature",
properties: {
name: "Jardin du Luxemburg",
district: 6,
},
geometry: {
type: "Point",
coordinates: [2.336975, 48.846421],
},
},
],
};
// Instead of using the same overlay, deck.gl suggests
// creating another one, this doesn't come at a performance cost
// Add the overlay as a control
const initializeOverlay = () => {
const layer = new deck.ScatterplotLayer({
id: "scatterplot-layer",
data: sampleData.features,
pickable: true,
opacity: 0.8,
stroked: true,
filled: true,
radiusScale: 6,
radiusMinPixels: 20,
radiusMaxPixels: 100,
lineWidthMinPixels: 5,
getPosition: (d) => d.geometry.coordinates,
getFillColor: (d) => [49, 130, 206],
getLineColor: (d) => [175, 0, 32],
onClick: (info) => {
const { coordinate, object } = info;
const description = `<div>
<p>
<strong>Name: </strong>${object.properties["name"]}
</p>
<strong>Disctrict: </strong>${object.properties["district"]}
</p>
</div>`;
new maplibregl.Popup()
.setLngLat(coordinate)
.setHTML(description)
.addTo(map);
},
});
// create the overlay
overlay = new deck.MapboxOverlay({
layers: [layer],
});
map.addControl(overlay);
};
map.on("load", () => {
// Add the overlay
initializeOverlay();
const toggleButton = document.querySelector("#toggle-button");
toggleButton.addEventListener("click", () => {
if (show) {
map.removeControl(overlay);
toggleButton.innerText = "Show";
show = false;
} else {
initializeOverlay();
toggleButton.innerText = "Hide";
show = true;
}
});
});
</script>
</html>