Skylines in 3D

Midtown Manhattan, Chicago's Loop, and three more downtowns, with every building raised to its height in OpenStreetMap and shaded from low to tall. Drag with the right mouse button, or two fingers, to tilt and turn the map, and click a building for its height.

Low 1,000 ft+

Build it yourself

The tiles carry OpenStreetMap's building heights, so one fill-extrusion layer on top of Mapricot's light style turns a flat city into a 3D one. It was tested with MapLibre GL JS 6.15 in Chromium.

What you need

Nothing but a web server to serve the page: the map tiles need no key.

Make the page

Make a folder, and in it index.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Buildings in 3D</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/dist/maplibre-gl.css">
  </head>
  <body style="margin: 0">
    <div id="map" style="height: 100vh"></div>
    <script type="module">
      import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/+esm";

      const map = new maplibregl.Map({
        container: "map",
        style: "https://tiles.mapricot.com/styles/light.json",
        center: [-73.9845, 40.7536],
        zoom: 15.4,
        pitch: 55,
        bearing: -28,
      });
      map.addControl(new maplibregl.NavigationControl({ visualizePitch: true }));
      await map.once("load");

      const labels = map.getStyle().layers.find((layer) => layer.type === "symbol").id;
      const height = ["coalesce", ["get", "height"], 10];
      map.addLayer(
        {
          id: "buildings-3d",
          type: "fill-extrusion",
          source: "protomaps",
          "source-layer": "buildings",
          paint: {
            "fill-extrusion-height": height,
            "fill-extrusion-base": ["coalesce", ["get", "min_height"], 0],
            "fill-extrusion-color": ["interpolate", ["linear"], height, 0, "#cde2fb", 61, "#6da7ec", 152, "#256abf", 305, "#0d366b"],
            "fill-extrusion-opacity": 0.9,
          },
        },
        labels,
      );
    </script>
  </body>
</html>
  • pitch tilts the map and bearing turns it, so the buildings show their sides. The navigation control's compass shows the tilt too, with visualizePitch.
  • The new layer reads the buildings layer of the style's own protomaps source, so it needs no source of its own.
  • height and min_height are in meters. A building with no height in OpenStreetMap is drawn 10 meters tall, about three floors. min_height lifts parts that start above the ground, such as a tower on a podium.
  • The color goes from pale blue for low buildings to navy at 305 meters, 1,000 feet.
  • Adding the layer before the style's first label layer keeps the street and place names on top of the buildings.

Open it

Serve the folder with npx serve or python3 -m http.server, and open the address it prints. Opened straight from the file, the map stays blank, as Open the page explains. You should see Midtown Manhattan from the south, tilted. Drag with the right mouse button, or two fingers, to turn it.

To fly between cities like the map above, call map.flyTo({ center, zoom: 15.4, pitch: 55, bearing }) from a button. The map tiles docs list the tiles' other layers.

Data