Кастомизация результатов

Кастомизация результатов в Mapbox GL JS строится вокруг управления слоями (layers), источниками данных (sources) и выражениями (expressions), которые позволяют динамически изменять визуализацию объектов карты на основе их свойств, состояния карты и пользовательского взаимодействия. Основная идея заключается в том, что «результат» на карте — это не фиксированный набор объектов, а производное от стиля, данных и логики их отображения.

Внутренне Mapbox GL JS опирается на стиль в формате Style Specification, где визуализация разбита на:

  • источники данных (GeoJSON, vector tiles, raster)
  • слои, привязанные к источникам
  • правила отрисовки (paint и layout properties)

Каждый «результат» на карте — это объект, прошедший через фильтры слоя и выражения визуализации.

Источники:

map.addSource('places', {
  type: 'geojson',
  data: '/data/places.geojson'
});

Слои определяют, как именно будут отображены результаты:

map.addLayer({
  id: 'places-layer',
  type: 'circle',
  source: 'places',
  paint: {
    'circle-radius': 6,
    'circle-color': '#1978c8'
  }
});

Фильтрация результатов на уровне слоя

Фильтрация — базовый механизм кастомизации. Она определяет, какие объекты попадут в итоговую выборку отображения.

Фильтры используют логические выражения:

map.setFilter('places-layer', [
  'all',
  ['==', ['get', 'type'], 'restaurant'],
  ['>=', ['get', 'rating'], 4]
]);

Фильтрация позволяет:

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

Фильтры применяются на GPU-уровне, что обеспечивает высокую производительность даже при больших наборах данных.

Динамическая кастомизация через свойства стиля

Mapbox GL JS поддерживает runtime-изменение параметров слоёв:

map.setPaintProperty('places-layer', 'circle-color', '#ff3b30');
map.setPaintProperty('places-layer', 'circle-radius', 10);

Также можно изменять layout-параметры:

map.setLayoutProperty('places-layer', 'visibility', 'none');

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

Выражения (Expressions) как основа кастомизации

Expressions — ключевой инструмент для создания условной логики отображения.

Интерполяция значений

'circle-radius': [
  'interpolate',
  ['linear'],
  ['get', 'population'],
  1000, 4,
  100000, 20
]

Размер объекта становится зависимым от данных.

Условная логика

'circle-color': [
  'case',
  ['==', ['get', 'status'], 'active'], '#2ecc71',
  ['==', ['get', 'status'], 'inactive'], '#e74c3c',
  '#95a5a6'
]

Работа с текстом

'text-field': [
  'concat',
  ['get', 'name'],
  ' (',
  ['get', 'category'],
  ')'
]

Expressions позволяют строить сложные правила визуализации без дополнительного JavaScript-кода.

Кастомизация результатов поиска и выборки объектов

При работе с интерактивными сценариями часто требуется кастомизация «результатов» взаимодействия, например объектов под курсором.

map.on('click', 'places-layer', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['places-layer']
  });

  console.log(features);
});

Функция queryRenderedFeatures возвращает уже отфильтрованные и отрисованные результаты, что позволяет строить UI поверх карты.

Дополнительная кастомизация:

const features = map.querySourceFeatures('places', {
  sourceLayer: 'places-layer-source'
});

Разница:

  • queryRenderedFeatures — учитывает стиль и видимость
  • querySourceFeatures — работает с исходными данными

Управление плотностью и кластеризацией результатов

Для больших наборов данных применяется кластеризация:

map.addSource('points', {
  type: 'geojson',
  data: '/data/points.geojson',
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

Отображение кластеров:

map.addLayer({
  id: 'clusters',
  type: 'circle',
  source: 'points',
  filter: ['has', 'point_count'],
  paint: {
    'circle-color': '#51bbd6',
    'circle-radius': [
      'step',
      ['get', 'point_count'],
      15,
      100,
      25,
      750,
      35
    ]
  }
});

Индивидуальные результаты:

map.addLayer({
  id: 'unclustered-point',
  type: 'circle',
  source: 'points',
  filter: ['!', ['has', 'point_count']],
  paint: {
    'circle-color': '#f28cb1',
    'circle-radius': 6
  }
});

Кастомизация всплывающих результатов (popups)

Popup часто используется как финальное представление результата:

map.on('click', 'places-layer', (e) => {
  const feature = e.features[0];

  new mapboxgl.Popup()
    .setLngLat(feature.geometry.coordinates)
    .setHTML(`
      <div>
        <strong>${feature.properties.name}</strong><br/>
        ${feature.properties.description}
      </div>
    `)
    .addTo(map);
});

Кастомизация включает:

  • форматирование HTML
  • привязку к свойствам объекта
  • динамическое позиционирование

Кастомные иконки и визуальные результаты

Символьные слои позволяют полностью переопределять внешний вид результатов:

map.loadImage('/icons/restaurant.png', (error, image) => {
  map.addImage('restaurant-icon', image);

  map.addLayer({
    id: 'restaurants',
    type: 'symbol',
    source: 'places',
    layout: {
      'icon-image': 'restaurant-icon',
      'icon-size': 0.8,
      'text-field': ['get', 'name'],
      'text-offset': [0, 1.2]
    }
  });
});

Это позволяет превращать геоданные в семантически насыщенные визуальные элементы.

Data-driven styling и контекстная кастомизация

Кастомизация часто зависит от масштаба карты:

'circle-radius': [
  'interpolate',
  ['exponential', 1.5],
  ['zoom'],
  5, 2,
  10, 6,
  15, 12
]

Или комбинированные условия:

'circle-opacity': [
  'case',
  ['<', ['zoom'], 8], 0.5,
  1
]

Это позволяет формировать разные «результаты» для разных уровней детализации.

Кастомизация через слои fill, line и heatmap

Разные типы слоёв создают разные модели результата.

Fill-слои

map.addLayer({
  id: 'polygons',
  type: 'fill',
  source: 'regions',
  paint: {
    'fill-color': '#088',
    'fill-opacity': 0.4
  }
});

Line-слои

map.addLayer({
  id: 'routes',
  type: 'line',
  source: 'routes',
  paint: {
    'line-color': '#ffcc00',
    'line-width': 3
  }
});

Heatmap

map.addLayer({
  id: 'heat',
  type: 'heatmap',
  source: 'points',
  paint: {
    'heatmap-intensity': 1,
    'heatmap-radius': 20,
    'heatmap-color': [
      'interpolate',
      ['linear'],
      ['heatmap-density'],
      0, 'blue',
      1, 'red'
    ]
  }
});

Heatmap фактически агрегирует результаты в непрерывное поле плотности.

Сортировка визуальных результатов

Порядок слоёв влияет на итоговое восприятие данных:

map.moveLayer('places-layer', 'road-label');

Также применяется сортировка символов:

'text-allow-overlap': false,
'symbol-sort-key': ['get', 'priority']

Кастомизация через взаимодействие состояния карты

State-driven styling позволяет менять результаты в зависимости от состояния:

map.setFeatureState(
  { source: 'places', id: 123 },
  { selected: true }
);

Использование в стиле:

'circle-color': [
  'case',
  ['boolean', ['feature-state', 'selected'], false],
  '#ff0000',
  '#0000ff'
]

Интеграция кастомной логики через JavaScript

Хотя Mapbox GL JS предоставляет мощные декларативные инструменты, кастомизация часто дополняется JavaScript-логикой:

map.on('moveend', () => {
  const bounds = map.getBounds();

  const visible = map.queryRenderedFeatures({
    layers: ['places-layer']
  });

  updateUI(visible);
});

Это позволяет синхронизировать визуальные результаты карты с внешними интерфейсами.

Использование кастомных слоёв (Custom Layers)

Для полного контроля над результатами используется WebGL-слой:

const customLayer = {
  id: 'custom-layer',
  type: 'custom',
  renderingMode: '3d',

  onAdd(map, gl) {
    this.map = map;
    this.gl = gl;
  },

  render(gl, matrix) {
    // WebGL rendering logic
    this.map.triggerRepaint();
  }
};

map.addLayer(customLayer);

Это уровень полной переопределяемости отображения.

Локализация и языковая кастомизация результатов

map.setLayoutProperty('place-label', 'text-field', [
  'get',
  'name:ru'
]);

Или автоматическая локализация:

map.setLayoutProperty('country-label', 'text-field', [
  'coalesce',
  ['get', 'name_ru'],
  ['get', 'name_en']
]);

Ограничение и приоритизация результатов

Система приоритетов позволяет управлять перегрузкой карты:

'text-max-angle': 30,
'text-padding': 2,
'icon-allow-overlap': false

Это влияет на то, какие результаты остаются видимыми при плотной визуализации данных.

Кастомизация через взаимодействие с событиями

map.on('mouseenter', 'places-layer', () => {
  map.getCanvas().style.cursor = 'pointer';
});

map.on('mouseleave', 'places-layer', () => {
  map.getCanvas().style.cursor = '';
});

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