Add a custom control programmatically
Adding a custom control programmatically allows developers to have more control is ideal for applications that require dynamic logic, event-driven behaviour, or a deeper integration with a framework like React.
Programmatic controls allow developers to register custom control elements manually by calling map.addControl() and providing a control implementation. Custom controls are instantiated using the MaptilerCustomControl class. The element that should be used can be provided either as the element itself, or as its CSS selector.
NPM module setup
npm install --save @maptiler/sdk bootstrap
import { Map, MapStyle, config, MaptilerCustomControl } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import 'bootstrap/dist/css/bootstrap.min.css';
config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
const map = new Map({
container: 'map', // container's id or the HTML element to render the map
style: MapStyle.STREETS,
navigationControl: false,
geolocateControl: false,
terrainExaggeration: 4,
});
//custom navigation controls
const compassControlElement = document.querySelector(".compass-control");
const navigationControl = new MaptilerCustomControl(
".navigation-controls",
(map, _, event) => {
if ((event.target).closest(".zoom-in-control")) {
map.zoomIn();
}
},
(map) => {
(compassControlElement.firstElementChild).style.transform = `rotateX(${String(map.getPitch())}deg) rotateZ(${String(-map.getBearing())}deg)`;
},
);
map.addControl(navigationControl);
document.querySelector(".zoom-out-control")?.addEventListener("click", () => map.zoomOut());
compassControlElement.addEventListener("click", () => map.resetNorthPitch());
//custom projection control
function toggleProjection() {
if (map.isGlobeProjection()) {
map.setProjection({ type: "mercator" });
} else {
map.setProjection({ type: "globe" });
}
}
const projectionControlElement = document.querySelector(".projection-control");
const projectionControl = new MaptilerCustomControl(projectionControlElement, toggleProjection, (map, element) => {
element.firstElementChild.textContent = map.isGlobeProjection() ? "map" : "globe";
element.title = map.isGlobeProjection() ? "Switch to Mercator projection" : "Switch to Globe projection";
});
map.addControl(projectionControl, "top-left");
//custom terrain control
function toggleTerrain() {
if (map.hasTerrain()) {
map.disableTerrain();
} else {
map.enableTerrain();
}
}
const terrainControlElement = document.createElement("div");
terrainControlElement.className = "btn btn-success m-2 terrain-control";
const terrainControlIcon = document.createElement("span");
terrainControlIcon.textContent = "terrain";
terrainControlIcon.classList.add("material-symbols-outlined");
terrainControlElement.appendChild(terrainControlIcon);
const terrainControl = new MaptilerCustomControl(terrainControlElement, toggleTerrain, (map) => {
terrainControlIcon.classList.toggle("icon-fill", map.hasTerrain());
terrainControlElement.title = map.hasTerrain() ? "Turn off Terrain" : "Turn on Terrain";
});
map.addControl(terrainControl, "top-left");
//custom control (center map)
const home = { lng: 15.4, lat: 49.8 };
map.addControl(
new MaptilerCustomControl(
".home-button",
(map) => {
map.easeTo({ center: home, zoom: 7 });
},
(map, element) => {
const center = map.getCenter();
const zoom = map.getZoom();
(element.firstElementChild).classList.toggle(
"icon-fill",
Math.abs(center.lng - home.lng) < Math.max(0, zoom - 6.8) * 1.5 && Math.abs(center.lat - home.lat) < Math.max(0, zoom - 6.8) / 2,
);
},
),
"bottom-right",
);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
<title>Add a custom control programmatically | NPM example</title>
<link rel="stylesheet" href="./style.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:FILL@0..1">
</head>
<body>
<div id="map"></div>
<div hidden>
<div class="btn-group-vertical m-2 navigation-controls">
<button class="btn btn-light zoom-in-control">
<span class="material-symbols-outlined">zoom_in</span>
</button>
<button class="btn btn-light zoom-out-control">
<span class="material-symbols-outlined">zoom_out</span>
</button>
<button class="btn btn-light compass-control">
<span class="material-symbols-outlined">navigation</span>
</button>
</div>
<button class="btn btn-primary m-2 projection-control">
<span class="material-symbols-outlined">globe</span>
</button>
<button class="btn btn-danger m-2 float-end home-button">
<span class="material-symbols-outlined">home</span>
</button>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
.maplibregl-control-container .btn {
pointer-events: auto;
display: block;
}
.material-symbols-outlined {
transition: font-variation-settings 1s ease-in-out;
font-variation-settings: 'FILL' 0;
}
.icon-fill {
font-variation-settings: 'FILL' 1;
}
Learn more
Check out the MaptilerCustomControl API reference to learn all the options to add custom controls to your map interface.