Skip to content

3D Buildings

Add a fill-extrusion layer on the Maptoolkit building source layer and tilt the map with pitch to see buildings in 3D. This view looks south from inside Central Park toward the Midtown Manhattan skyline.

The Street style already ships this layer and needs no code at all. Use this example for the other six styles, which draw buildings flat. See 3D Buildings and Terrain for what each library supports.

  • Filter on extrude. The building layer carries a boolean extrude flag marking the footprints meant to stand up. The Street style filters on the same flag. At this Midtown view it excludes about 120 of roughly 16,700 footprints, mostly building parts that would otherwise double up on the building they belong to.
  • Set fill-extrusion-base from min_height. Without it, anything that starts above ground level, such as a storey bridging a street, sinks into the ground.
  • Insert below the first symbol layer. Otherwise labels render behind the buildings and the map becomes hard to read at pitch.

Buildings are present from zoom 13 to 15, which is the maximum zoom of the tileset. Beyond 15 the tiles overzoom, so the geometry stays but no new detail arrives.

import * as maplibregl from 'https://cdn.jsdelivr.net/npm/maplibre-gl@6.9.0/dist/maplibre-gl.mjs';
import { MaptoolkitLogoControl } from 'https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-gl-logo/+esm';
const map = new maplibregl.Map({ container: 'map', attributionControl: { customAttribution: '', compact: false }, style: 'https://styles.maptoolkit.org/summer.json', center: [-73.9770, 40.7645], zoom: 14.3, pitch: 62, bearing: 200 }); // Midtown Manhattan, looking out from Central Park South

map.on('load', function () {
  // Insert the extrusion under the first symbol layer, so street and place
  // labels stay readable on top of the buildings instead of behind them.
  const firstSymbol = map.getStyle().layers.find(function (l) { return l.type === 'symbol'; });

  map.addLayer({
    id: 'buildings-3d',
    type: 'fill-extrusion',
    source: 'mtk',
    'source-layer': 'building',
    minzoom: 14,
    // Not every footprint is meant to stand up. Building parts and similar
    // carry extrude=false, and the Street style filters on the same flag.
    filter: ['==', ['get', 'extrude'], true],
    paint: {
      'fill-extrusion-color': '#cfd8dc',
      'fill-extrusion-height': ['get', 'height'],
      'fill-extrusion-base': ['get', 'min_height'],
      'fill-extrusion-opacity': 0.9
    }
  }, firstSymbol && firstSymbol.id);
});
map.addControl(new MaptoolkitLogoControl());
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.9.0/dist/maplibre-gl.css" />
  <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 'https://cdn.jsdelivr.net/npm/maplibre-gl@6.9.0/dist/maplibre-gl.mjs';
import { MaptoolkitLogoControl } from 'https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-gl-logo/+esm';
const map = new maplibregl.Map({ container: 'map', attributionControl: { customAttribution: '', compact: false }, style: 'https://styles.maptoolkit.org/summer.json', center: [-73.9770, 40.7645], zoom: 14.3, pitch: 62, bearing: 200 }); // Midtown Manhattan, looking out from Central Park South

map.on('load', function () {
  // Insert the extrusion under the first symbol layer, so street and place
  // labels stay readable on top of the buildings instead of behind them.
  const firstSymbol = map.getStyle().layers.find(function (l) { return l.type === 'symbol'; });

  map.addLayer({
    id: 'buildings-3d',
    type: 'fill-extrusion',
    source: 'mtk',
    'source-layer': 'building',
    minzoom: 14,
    // Not every footprint is meant to stand up. Building parts and similar
    // carry extrude=false, and the Street style filters on the same flag.
    filter: ['==', ['get', 'extrude'], true],
    paint: {
      'fill-extrusion-color': '#cfd8dc',
      'fill-extrusion-height': ['get', 'height'],
      'fill-extrusion-base': ['get', 'min_height'],
      'fill-extrusion-opacity': 0.9
    }
  }, firstSymbol && firstSymbol.id);
});
map.addControl(new MaptoolkitLogoControl());
  </script>
</body>
</html>