Fill-extrusion для зданий

MapLibre GL JS поддерживает трёхмерную визуализацию зданий через слой типа fill-extrusion, который преобразует двумерные полигоны в объёмные геометрические формы на основе атрибутов высоты и базовой отметки. Этот механизм используется для отображения городской застройки, анализа плотности, визуального моделирования инфраструктуры и создания картографических сцен с глубиной.

Слой fill-extrusion строится поверх векторных тайлов и интерпретирует полигональные геометрии как объёмные объекты. Каждому полигону назначается:

  • height — итоговая высота здания
  • base — базовая высота (например, уровень земли или подиум)
  • color — цвет поверхности
  • opacity — прозрачность
  • дополнительные параметры освещения и смещения

Геометрия экструзии формируется как вертикальное «вытягивание» полигона между значениями base и height.

Источники данных для 3D зданий

Ключевое требование — наличие атрибутов высоты в векторных данных. Чаще всего используются OpenStreetMap-совместимые схемы:

  • height — фактическая высота здания
  • building:levels — количество этажей
  • min_height — подиум или нависающая структура
  • roof:height — дополнительная высота крыши

При отсутствии height часто применяется оценка:

height ≈ building:levels × 3

(где 3 метра — усреднённая высота этажа)

Базовая конфигурация слоя fill-extrusion

Структура слоя в стиле MapLibre GL JS описывается через JSON-стиль:

{
  "id": "3d-buildings",
  "type": "fill-extrusion",
  "source": "composite",
  "source-layer": "building",
  "minzoom": 15,
  "filter": ["==", "extrude", "true"],
  "paint": {
    "fill-extrusion-color": "#aaa",
    "fill-extrusion-height": ["get", "height"],
    "fill-extrusion-base": ["get", "min_height"],
    "fill-extrusion-opacity": 0.8
  }
}

Инициализация карты и подключение слоя

Основная конфигурация карты включает источник тайлов и добавление слоя после загрузки стиля:

import maplibregl from "maplibre-gl";

const map = new maplibregl.Map({
  container: "map",
  style: "https://demotiles.maplibre.org/style.json",
  center: [37.6173, 55.7558],
  zoom: 15,
  pitch: 60,
  bearing: -20
});

map.on("load", () => {
  map.addLayer({
    id: "3d-buildings",
    source: "openmaptiles",
    "source-layer": "building",
    type: "fill-extrusion",
    minzoom: 15,
    filter: ["==", "extrude", "true"],
    paint: {
      "fill-extrusion-color": "#b8b8b8",
      "fill-extrusion-height": ["get", "height"],
      "fill-extrusion-base": ["get", "min_height"],
      "fill-extrusion-opacity": 0.9
    }
  });
});

Важные свойства fill-extrusion

fill-extrusion-height

Определяет итоговую высоту объекта. Поддерживает:

  • прямое значение из атрибута
  • выражения интерполяции
  • вычисления на основе уровней

Пример интерполяции:

"fill-extrusion-height": [
  "interpolate",
  ["linear"],
  ["zoom"],
  15, 0,
  16, ["*", ["get", "height"], 0.5],
  17, ["get", "height"]
]

fill-extrusion-base

Используется для создания многоуровневых объектов (например, мосты или здания с подиумом):

"fill-extrusion-base": ["get", "min_height"]

Если значение отсутствует, база считается равной нулю.

fill-extrusion-color

Поддерживает как статические цвета, так и data-driven стили:

"fill-extrusion-color": [
  "interpolate",
  ["linear"],
  ["get", "height"],
  0, "#d9d9d9",
  100, "#a6a6a6",
  300, "#6b6b6b"
]

fill-extrusion-opacity

Регулирует прозрачность слоя. Значение важно при наложении нескольких 3D слоёв и при включённом освещении сцены.

Освещение и восприятие глубины

3D-эффект усиливается настройкой источника света:

map.setLight({
  anchor: "viewport",
  color: "#ffffff",
  intensity: 0.6
});

Свет влияет на затенение граней экструдированных объектов, создавая визуальное разделение фасадов.

Фильтрация зданий

Для отображения только определённых типов объектов используется фильтр:

"filter": [
  "all",
  ["==", "building", "yes"],
  ["has", "height"]
]

Распространённые стратегии фильтрации:

  • исключение нежилых объектов
  • отображение только высоких зданий
  • разделение по типу использования

Работа с отсутствующими высотами

Реальные данные часто неполные, поэтому применяются fallback-стратегии:

"fill-extrusion-height": [
  "case",
  ["has", "height"],
  ["get", "height"],
  ["*", ["get", "building:levels"], 3]
]

При этом важно учитывать, что значения уровней могут быть неточными.

Производительность и масштабирование

Слой fill-extrusion относится к GPU-ускоряемым и хорошо масштабируется, однако есть ограничения:

  • большое количество полигонов увеличивает нагрузку на вершинный шейдер
  • сложные выражения в paint-параметрах замедляют рендеринг
  • высокая плотность зданий на малых zoom требует оптимизации фильтров

Оптимизационные практики:

  • скрытие слоя до zoom ≥ 14–15
  • использование simplification геометрии в тайлах
  • ограничение отображения по классу зданий
"minzoom": 15

Взаимодействие с наклоном и вращением карты

3D-здания становятся визуально корректными только при включённом наклоне:

map.setPitch(60);
map.setBearing(-30);

Без pitch экструдированные объекты визуально теряют объём и воспринимаются как смещённые плоские фигуры.

Типичные артефакты визуализации

Z-fighting

Возникает при совпадении высот или дублировании полигонов. Решение — небольшое смещение base:

"fill-extrusion-base": ["+", ["get", "min_height"], 0.1]

Пропущенные здания

Причины:

  • отсутствие extrude: true
  • фильтрация на уровне слоя
  • отсутствие атрибута height

«Плоские» здания

Причина — height = 0 или отсутствие данных, из-за чего экструзия не формируется.

Комбинация с другими слоями

Fill-extrusion часто используется вместе с:

  • символами (labels) поверх зданий
  • слоями дорог (line)
  • слоями воды (fill)
  • 2.5D рельефом (terrain)

Порядок слоёв критичен: 3D-здания должны находиться выше базовых полигонов, но ниже UI-символов.

Data-driven стилизация фасадов

Для создания различий между типами зданий применяется привязка к атрибутам:

"fill-extrusion-color": [
  "match",
  ["get", "building"],
  "residential", "#c9c9c9",
  "commercial", "#b0b0ff",
  "industrial", "#d0b0a0",
  "#aaaaaa"
]

Это позволяет визуально сегментировать городскую среду без изменения геометрии.

Продвинутые выражения высоты

При работе с неполными данными используется комбинирование источников:

"fill-extrusion-height": [
  "coalesce",
  ["get", "height"],
  ["*", ["get", "building:levels"], 3],
  10
]

Такая конструкция обеспечивает устойчивую деградацию качества данных.

Использование градиентов и визуальной глубины

Градиентная окраска усиливает восприятие масштаба:

"fill-extrusion-color": [
  "interpolate",
  ["linear"],
  ["get", "height"],
  0, "#eeeeee",
  50, "#cccccc",
  200, "#888888"
]

При сочетании с освещением формируется псевдо-фотореалистичный эффект городской застройки.

Интеграция с пользовательскими данными

Fill-extrusion поддерживает динамическое обновление:

map.setFeatureState(
  { source: "openmaptiles", sourceLayer: "building", id: 123 },
  { height: 120 }
);

Это используется для:

  • симуляции роста города
  • визуализации аналитики
  • интерактивных дашбордов

Влияние pitch и zoom на восприятие масштаба

  • при zoom < 14 здания неразличимы
  • при zoom 15–17 формируется городская плотность
  • при pitch > 60 усиливается эффект «городского каньона»

Zoom также влияет на интерполяцию высот, что позволяет постепенно «вырастать» зданиям при приближении карты.