Skip to content

Roof shapes ​

RoofsLayer draws real roofs (gabled, hipped, mansard, dome, …) on top of your own fill-extrusion buildings, from any vector source that follows Overture's building schema:

ts
import { RoofsLayer } from 'maplibre-landmarks';

map.addLayer(
  new RoofsLayer({
    id: 'roofs',
    source: 'buildings', // needs feature ids (numeric ids or promoteId)
    sourceLayer: 'building',
    extrusionLayer: 'buildings-3d', // your walls; shortened by the roof drawn on each building
  }),
);

Every shape ​

extrusionLayer also takes several layers (e.g. buildings and building parts in separate source layers): each layer's own source-layer is read, and every one gets roofs and shortened walls. sourceLayer is then only needed for layers without one.

AttributeMeaning
height, min_heightBuilding / part top (incl. roof) and bottom, metres
roof_shapegabled, saltbox, hipped, half_hipped, hipped_and_gabled, gambrel, mansard, skillion, round, bellcast_gable, butterfly, crosspitched, sawtooth, pyramidal, cone, dome, onion (plus OSM aliases, below)
roof_heightRoof alone; default from a 30° pitch (or the radius for domes)
roof_anglePitch in degrees, used when there is no roof_height (OSM roof:angle; not in Overture)
roof_direction, roof_orientationBearing the roof faces; along / across the longest side
roof_color, roof_materialRoof colour; material picks a colour when no colour is tagged
facade_color, facade_materialWalls, with wallColors: true; toned down (60 % saturation, lightness kept within 30–88 %) so tagged colours stay calm
has_partsOutline drawn by its parts: no roof of its own

Draw walls only for outlines without parts, or each outline's box hides its parts' roofs: add filter: ['!=', ['get', 'has_parts'], true] to your extrusion layer.

Other sources map their names with fields, e.g. fields: { roof_shape: 'roof:shape' }. Buildings without a roof shape keep their flat extrusion. Options: minZoom (15), maxBuildings (2000), wallColors (false), gableColor (#d9d4ce), farCutoff (true: pitched views roof buildings only up to about three screen heights away), onError.

Buildings without a height take it from num_floors (3 m a floor, FLOOR_M) plus a tagged roof_height, and their base from min_floor. Give the wall layer the same heights so roofs sit on their walls:

ts
import { buildingBase, buildingHeight } from 'maplibre-landmarks';

map.addLayer({
  id: 'walls',
  type: 'fill-extrusion',
  source: 'overture',
  'source-layer': 'building',
  paint: { 'fill-extrusion-height': buildingHeight(), 'fill-extrusion-base': buildingBase() },
});

Overture's official tiles (no build step). Each Overture release publishes worldwide building tiles (PMTiles, z5–14) with these attributes and numeric feature ids, buildings and building parts in separate layers. Most roof tags sit on parts, so give both wall layers:

ts
import { addProtocol, type FillExtrusionLayerSpecification } from 'maplibre-gl';
import { Protocol } from 'pmtiles';
addProtocol('pmtiles', new Protocol().tile);

map.addSource('overture-buildings', {
  type: 'vector',
  url: 'pmtiles://https://overturemaps-extras-us-west-2.s3.amazonaws.com/tiles/2026-09-23.1/buildings.pmtiles',
});
const walls = {
  type: 'fill-extrusion',
  source: 'overture-buildings',
  minzoom: 14,
  paint: {
    'fill-extrusion-color': '#d9d4ce',
    'fill-extrusion-height': ['coalesce', ['get', 'height'], 10],
    'fill-extrusion-base': ['coalesce', ['get', 'min_height'], 0],
  },
} satisfies Omit<FillExtrusionLayerSpecification, 'id' | 'source-layer'>;
map.addLayer({
  ...walls,
  id: 'buildings-3d',
  'source-layer': 'building',
  filter: ['!=', ['get', 'has_parts'], true],
}); // outlines drawn by their parts are skipped
map.addLayer({ ...walls, id: 'building-parts-3d', 'source-layer': 'building_part' });
map.addLayer(
  new RoofsLayer({
    id: 'roofs',
    source: 'overture-buildings',
    extrusionLayer: ['buildings-3d', 'building-parts-3d'],
  }),
);

Tiles are large in dense cities (about 1.6 MB for a z14 tile of central Paris) and served from Overture's US-West bucket; for production, cut your region with pmtiles extract and serve it from your own CDN. Replace the release (2026-09-23.1) with a current one from Overture's docs. Data © Overture Maps Foundation and OpenStreetMap contributors (ODbL).

Your own roof tiles ​

Overture's tiles drop polygons smaller than a pixel, even at their highest zoom: a lantern's columns or a spire's pinnacles go missing and the parts above them float. Tiling Overture's data yourself keeps them (and gives smaller, faster tiles):

bash
# Buildings and parts of one area from Overture's GeoParquet (DuckDB with spatial + httpfs)
duckdb -c "INSTALL spatial; LOAD spatial; INSTALL httpfs; LOAD httpfs; SET s3_region='us-west-2';
COPY (SELECT id AS gers, height, min_height, num_floors, min_floor, roof_shape, roof_height,
  roof_direction, roof_orientation, roof_color, roof_material, facade_color, facade_material,
  has_parts, geometry
  FROM read_parquet('s3://overturemaps-us-west-2/release/2026-09-23.0/theme=buildings/type=building/*')
  WHERE bbox.xmin < 2.38 AND bbox.xmax > 2.27 AND bbox.ymin < 48.89 AND bbox.ymax > 48.83)
TO 'building.geojsonl' WITH (FORMAT GDAL, DRIVER 'GeoJSONSeq');"
# …the same for type=building_part (without has_parts) into building_part.geojsonl, then:
tippecanoe -o roofs.pmtiles -Z14 -z16 --generate-ids --no-tiny-polygon-reduction \
  --no-feature-limit --no-tile-size-limit -L building:building.geojsonl \
  -L building_part:building_part.geojsonl

The demo loads such a file with ?roofs=/roofs.pmtiles (put it in demo/public/).

Your own tiles. Any vector source with these attributes (or its own names mapped with fields) and feature ids works; Overture keeps only 14 roof shapes and no roof_angle, so a source built from raw OSM tags can draw more. See Roof attributes.

Full attribute and shape reference: Roof attributes.

OSM aliases. _ and - spellings are interchangeable. Drawn as the nearest shape: gabled_height_moved, gabled_irregular → saltbox; double_saltbox, quadruple_saltbox → mansard; pitched → gabled; lean_to, monopitch, shed → skillion; pyramid → pyramidal; Overture's spherical → dome. flat, many and unknown values get no roof. sawtooth draws ~8 m teeth across the building; roof_height (or roof_angle) applies to one tooth.

Roof shapes for dome and onion, and the material colours, are derived from OSM Buildings (BSD-2-Clause; see LICENSE).

MIT licensed. Models: Open Landmarks; map data © OpenStreetMap contributors and Overture Maps Foundation.