MapSnippets logoMapSnippets logo

3D Building Extrusions with MapTiler SDK

MapTiler SDK

Add 3D building height extrusions using a fill-extrusion layer from the vector tile source.

progress_activityLoading map...
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const map = new maptilersdk.Map({
  container: 'map',
  style: maptilersdk.MapStyle.STREETS,
  center: [2.3488, 48.8534],
  zoom: 15.5,
  pitch: 60,
  bearing: -17,
  canvasContextAttributes: { antialias: true }
});
map.on('load', () => {
  const style = map.getStyle();
  const src = Object.keys(style.sources).find(s => map.getSource(s).type === 'vector');
  if (!src) return;
  // Insert above basemap but below labels so labels stay on top.
  const labelLayerId = style.layers.find(l => l.type === 'symbol' && l.layout && l.layout['text-field'])?.id;
  map.addLayer({
    id: 'buildings-3d',
    type: 'fill-extrusion',
    source: src,
    'source-layer': 'building',
    minzoom: 14,
    filter: ['!=', ['get', 'hide_3d'], true],
    paint: {
      'fill-extrusion-color': '#9C27B0',
      'fill-extrusion-height': [
        'interpolate', ['linear'], ['zoom'],
        14, 0,
        16, ['coalesce', ['get', 'render_height'], 0]
      ],
      'fill-extrusion-base': ['coalesce', ['get', 'render_min_height'], 0],
      'fill-extrusion-opacity': 0.8
    }
  }, labelLayerId);
});

How it works

MapTiler vector tiles include a building source layer with height attributes. The snippet detects the vector source at runtime rather than hardcoding a source ID, making it resilient to style changes.

The fill-extrusion layer type renders polygons with vertical height. The render_height and render_min_height properties from the building data define the top and base of each extrusion.

The SDK is built on MapLibre GL JS 5, so two protections from the MapLibre example apply here too: the ['!=', ['get', 'hide_3d'], true] filter excludes features the tiles mark as not-for-3D (without it you can hit a TypeError inside loadTile), and ['coalesce', ..., 0] defends against null render_height / render_min_height values.

Passing labelLayerId as the second argument to addLayer() inserts the 3D layer below the first label layer, so road and POI labels stay visible on top.

Set pitch and bearing in the constructor to view the 3D effect. Without pitch, extrusions are not visible.

Key APIs

API Description Docs
map.addLayer() Add a new layer to the style docs.maptiler.com/sdk-js/api/
fill-extrusion Layer type for 3D polygon rendering docs.maptiler.com/sdk-js/api/
map.getStyle() Get current style to inspect sources docs.maptiler.com/sdk-js/api/

FAQ

Why detect the source dynamically instead of hardcoding it?

MapTiler style source IDs can change between style versions. Runtime detection using type === 'vector' ensures the code works with any MapTiler style.

Can I color buildings by height?

Yes. Replace the static color with an expression like ['interpolate', ['linear'], ['get', 'render_height'], 0, '#aaa', 50, '#9C27B0'].

Common mistakes

  • Forgetting to set pitch: extrusions are invisible from directly above
  • Hardcoding the source ID: it varies by style version
  • Adding the layer before the load event: the sources are not available yet
  • Passing raw ['get', 'render_height'] without coalesce: null values crash loadTile under MapLibre 5.x (which the SDK bundles)
  • Omitting the beforeId argument to addLayer(): the extrusion covers labels and icons
auto_awesome

Build maps using MapTiler SDK with AI

Generate code like this 3d building extrusions with maptiler sdk example using the MapTiler SDK skill in your IDE.