Display 3D Buildings with Roof Shapes in MapLibre GL JS
On this page
This example shows the center of Vienna with 3D buildings, their roof shapes and 3D trees. Three parts work together:
- The 3D scene says which buildings and trees to draw and how. The Street and Summer
styles carry one in their metadata. For other styles, pass your own with the control’s
sceneoption. - The Scene3D Control reads the scene and adds its layers to the map.
- 3D Layers (
@maptoolkit/maplibre-3d-layers, with three.js) draws the buildings with their roofs and the trees. It comes with the control: the control’s standalone build loads it from next to itself once the map reaches zoom 12, so the page loads only the control.
Use it for city maps, tourism and real estate maps, or any map that people look at tilted.
import * as maplibregl from "maplibre-gl";
import { Scene3dControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-scene3d-control@0.3.3/dist/maplibre-scene3d-control.standalone.js";
import { LogoControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-logo-control@1.1.2/dist/maplibre-logo-control.js";
const API_KEY = "YOUR_API_KEY";
const map = new maplibregl.Map({
container: "map",
// Street and Summer are the Maptoolkit styles with a 3D scene.
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center: [16.3731, 48.2085], // St. Stephen's Cathedral, Vienna
zoom: 16,
pitch: 70,
maxPitch: 85,
canvasContextAttributes: { antialias: true }, // smoother edges on distant trees
attributionControl: { compact: false },
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
// The standalone build loads the 3D library it ships (three.js included) once the map reaches zoom 12.
map.addControl(new Scene3dControl({ apiKey: API_KEY }));
map.addControl(new LogoControl({ variant: "com" }));<!DOCTYPE html>
<html lang="en">
<head>
<title>Display 3D Buildings with Roof Shapes - MapLibre GL JS</title>
<meta property="og:description" content="Show 3D buildings with roof shapes, city models and 3D trees with the Maptoolkit Scene3D Control." />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.16.0/dist/maplibre-gl.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-logo-control@1.1.2/dist/maplibre-logo-control.css" />
<!-- MapLibre GL JS 6 and the plugins are ES modules. The import map tells the browser
where to load "maplibre-gl" from. -->
<script type="importmap">
{
"imports": {
"maplibre-gl": "https://cdn.jsdelivr.net/npm/maplibre-gl@6.16.0/dist/maplibre-gl.mjs"
}
}
</script>
<style>
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
#map { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script type="module">
import * as maplibregl from "maplibre-gl";
import { Scene3dControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-scene3d-control@0.3.3/dist/maplibre-scene3d-control.standalone.js";
import { LogoControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-logo-control@1.1.2/dist/maplibre-logo-control.js";
const API_KEY = "YOUR_API_KEY";
const map = new maplibregl.Map({
container: "map",
// Street and Summer are the Maptoolkit styles with a 3D scene.
style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
center: [16.3731, 48.2085], // St. Stephen's Cathedral, Vienna
zoom: 16,
pitch: 70,
maxPitch: 85,
canvasContextAttributes: { antialias: true }, // smoother edges on distant trees
attributionControl: { compact: false },
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
// The standalone build loads the 3D library it ships (three.js included) once the map reaches zoom 12.
map.addControl(new Scene3dControl({ apiKey: API_KEY }));
map.addControl(new LogoControl({ variant: "com" }));
</script>
</body>
</html>Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.
How it works
Maptoolkit styles name their 3D scene in the style metadata. The Street and Summer styles carry one; the other styles do not, and the control then adds nothing. The control reads the scene, adds the buildings tileset with your API key, and puts the buildings and trees below the labels.
The buildings come in two parts. A fill-extrusion layer raises building footprints to their
height from zoom 12. From zoom 15, buildings with a roof shape mapped in OpenStreetMap are drawn
as 3D bodies with that roof. Vienna also has an official 3D city model, so around the cathedral
you see its buildings instead of the OpenStreetMap ones. The trees are 3D models placed on the
single trees and forests of the basemap, from zoom 15.
The control needs MapLibre GL JS 6, which is an ES module. The import map points maplibre-gl to
its module build, and the control’s standalone build loads the 3D library from next to itself.
With npm and a bundler you need no import map: install @maptoolkit/maplibre-scene3d-control
and maplibre-gl, and import the control from its package name.
pitch: 70 matters here. In the Maptoolkit styles the 3D buildings fade in as you tilt the map,
so from straight above they are barely visible.
Without a valid API key the buildings tileset answers 403. The control then keeps the style’s
own buildings, so the map still shows buildings, but without roof shapes and city models.
Next steps
- Scene3D Control for all options, methods and events, and how to show a scene of your own.
- 3D Layers to draw the buildings yourself with your own colors, or to place your own glTF models.
- Display Buildings in 3D for plain extruded buildings from the basemap, without the plugin.