IconLayer

IconLayer — специализированный слой библиотеки Deck.gl, предназначенный для отображения большого количества иконок на карте или в произвольной геопространственной сцене. Слой активно применяется для визуализации точек интереса (POI), транспортных средств, объектов инфраструктуры, датчиков, пользователей, событий и любых других сущностей, представленных координатами.

В отличие от обычного использования HTML-маркеров или компонентов картографических библиотек, IconLayer выполняет рендеринг средствами WebGL на стороне GPU, что позволяет отображать десятки и сотни тысяч объектов с высокой производительностью.

Основные возможности слоя:

  • отображение иконок по географическим координатам;
  • использование атласов иконок (icon atlas);
  • индивидуальная настройка размера, цвета и угла поворота;
  • поддержка выделения объектов;
  • обработка событий мыши;
  • динамическое обновление данных;
  • высокая производительность при работе с большими наборами данных.

Подключение слоя

import {Deck} from '@deck.gl/core';
import {IconLayer} from '@deck.gl/layers';

Создание простейшего слоя:

const iconLayer = new IconLayer({
  id: 'icons',

  data: [
    {
      position: [37.6176, 55.7558]
    }
  ],

  getPosition: d => d.position,

  getIcon: () => ({
    url: '/images/marker.png',
    width: 128,
    height: 128,
    anchorY: 128
  }),

  getSize: 40
});

Принцип работы

Каждый объект из массива data преобразуется в отдельную иконку.

Схематично процесс выглядит следующим образом:

  1. Слой получает данные.
  2. Для каждого элемента вызываются аксессоры (getPosition, getIcon, getSize и другие).
  3. Deck.gl формирует внутренние буферы GPU.
  4. WebGL выполняет массовый рендеринг иконок за один или несколько проходов.

Пример структуры данных:

const places = [
  {
    id: 1,
    name: 'Airport',
    coordinates: [37.61, 55.75]
  },
  {
    id: 2,
    name: 'Station',
    coordinates: [37.59, 55.77]
  }
];

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

new IconLayer({
  id: 'places',

  data: places,

  getPosition: d => d.coordinates
});

Атлас иконок

Что такое Icon Atlas

Для достижения максимальной производительности Deck.gl обычно использует один общий файл изображения, содержащий множество иконок.

Такой файл называется атласом.

Например:

+-----------------------+
| hotel | bus | train   |
+-----------------------+
| car   | taxi | plane  |
+-----------------------+

Вместо загрузки множества отдельных файлов WebGL работает с одной текстурой.

Преимущества:

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

Параметр iconAtlas

Указывает путь к изображению атласа.

new IconLayer({
  iconAtlas: '/images/icons.png'
});

Параметр iconMapping

Описывает расположение каждой иконки внутри атласа.

Пример:

const iconMapping = {
  airport: {
    x: 0,
    y: 0,
    width: 128,
    height: 128,
    mask: true
  },

  train: {
    x: 128,
    y: 0,
    width: 128,
    height: 128,
    mask: true
  },

  hotel: {
    x: 256,
    y: 0,
    width: 128,
    height: 128,
    mask: true
  }
};

Подключение:

new IconLayer({
  iconAtlas: '/images/icons.png',
  iconMapping
});

Выбор иконки через getIcon

Наиболее распространённый способ выбора изображения — возврат имени из iconMapping.

Данные:

const data = [
  {
    type: 'airport',
    position: [37.61, 55.75]
  },
  {
    type: 'hotel',
    position: [37.62, 55.76]
  }
];

Слой:

new IconLayer({
  data,

  iconAtlas,
  iconMapping,

  getIcon: d => d.type
});

В результате каждая запись автоматически использует свою область в атласе.


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

Начиная с современных версий Deck.gl можно возвращать описание изображения напрямую.

getIcon: d => ({
  url: d.iconUrl,
  width: 128,
  height: 128,
  anchorY: 128
})

Пример данных:

[
  {
    iconUrl: '/icons/car.png',
    position: [37.6, 55.7]
  },
  {
    iconUrl: '/icons/bus.png',
    position: [37.7, 55.8]
  }
]

Такой подход удобен при динамической загрузке данных с сервера.


Позиционирование объектов

getPosition

Определяет координаты объекта.

getPosition: d => d.coordinates

Пример:

{
  coordinates: [37.6176, 55.7558]
}

Для картографических приложений используется формат:

[longitude, latitude]

Важно соблюдать именно этот порядок.

Правильно:

[37.6176, 55.7558]

Неправильно:

[55.7558, 37.6176]

Размер иконок

getSize

Задаёт размер отображения.

getSize: d => 40

Или:

getSize: d => d.importance * 10

Данные:

{
  importance: 5
}

Результат:

50

Масштабирование через sizeScale

Параметр применяется ко всем объектам слоя.

new IconLayer({
  getSize: d => d.size,

  sizeScale: 2
});

Фактический размер:

размер = getSize × sizeScale

Пример:

getSize: () => 20
sizeScale: 3

Результат:

60 пикселей

Ограничение размеров

sizeMinPixels

Минимальный размер.

sizeMinPixels: 16

sizeMaxPixels

Максимальный размер.

sizeMaxPixels: 100

Полный пример:

new IconLayer({
  sizeScale: 5,

  sizeMinPixels: 16,
  sizeMaxPixels: 80
});

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


Цвет иконок

getColor

Позволяет менять цвет программно.

getColor: [255, 0, 0]

Либо:

getColor: d => {
  if (d.status === 'error') {
    return [255, 0, 0];
  }

  return [0, 200, 0];
}

Данные:

[
  {
    status: 'ok'
  },
  {
    status: 'error'
  }
]

Формат цветов

Используется RGBA.

[255, 0, 0]

Красный.

[0, 255, 0]

Зелёный.

[0, 0, 255]

Синий.

[255, 255, 255]

Белый.

[255, 255, 255, 128]

Полупрозрачный белый.


Прозрачность

Отдельная настройка альфа-канала:

getColor: [0, 120, 255, 100]

Либо:

getColor: d => [
  0,
  120,
  255,
  d.opacity
]

Поворот иконок

getAngle

Позволяет задавать угол вращения.

getAngle: d => d.heading

Данные:

{
  heading: 90
}

Применяется для:

  • самолётов;
  • автомобилей;
  • кораблей;
  • дронов;
  • маршрутов движения.

Пример:

new IconLayer({
  getAngle: d => d.direction
});

Якорные точки

Параметры задаются внутри описания иконки.

{
  url: '/marker.png',

  width: 128,
  height: 128,

  anchorX: 64,
  anchorY: 128
}

anchorX

Горизонтальная точка привязки.

anchorY

Вертикальная точка привязки.

Для классического маркера:

anchorX: width / 2
anchorY: height

Точка карты совпадает с кончиком маркера.


Подписи к иконкам

Сам IconLayer не отображает текст.

Обычно используется совместно с TextLayer.

layers: [
  iconLayer,
  textLayer
]

Пример:

new TextLayer({
  data,

  getText: d => d.name,

  getPosition: d => d.position
});

Поддержка выбора объектов

pickable

Включение взаимодействия.

new IconLayer({
  pickable: true
});

После этого становятся доступны события.


Обработка кликов

new IconLayer({
  pickable: true,

  onClick: info => {
    console.log(info.object);
  }
});

Получение данных объекта:

onClick: ({object}) => {
  if (object) {
    console.log(object.name);
  }
}

Наведение мыши

onHover: info => {
  console.log(info.object);
}

Пример отображения всплывающей подсказки:

onHover: ({object, x, y}) => {
  if (!object) {
    return;
  }

  showTooltip({
    x,
    y,
    text: object.name
  });
}

Автоматическое выделение

autoHighlight

Deck.gl способен самостоятельно подсвечивать объект под курсором.

new IconLayer({
  pickable: true,
  autoHighlight: true
});

Цвет подсветки

highlightColor

highlightColor: [255, 255, 0]

Жёлтая подсветка.

Пример:

new IconLayer({
  pickable: true,

  autoHighlight: true,

  highlightColor: [255, 255, 0, 120]
});

Управление видимостью

visible

visible: false

Пример:

new IconLayer({
  visible: isIconsVisible
});

Обновление данных

При изменении массива данных слой автоматически пересоздаёт необходимые буферы.

const layer = new IconLayer({
  data: vehicles
});

Обновление:

deck.setProps({
  layers: [
    new IconLayer({
      id: 'vehicles',
      data: updatedVehicles
    })
  ]
});

Производительность

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

Наиболее эффективный вариант:

iconAtlas
iconMapping

Вместо:

getIcon: d => ({
  url: d.image
})

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


Минимизация пересоздания данных

Плохо:

data.map(item => ({
  ...item
}))

при каждом рендере.

Лучше:

const data = useMemo(
  () => vehicles,
  [vehicles]
);

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

Позволяет обновлять только необходимые атрибуты.

new IconLayer({
  getColor: d => colorMap[d.type],

  updateTriggers: {
    getColor: colorMap
  }
});

При изменении цветов не пересчитываются остальные параметры.


Совместное использование с другими слоями

Наиболее распространённые комбинации:

IconLayer + ScatterplotLayer

[
  scatterLayer,
  iconLayer
]

Круг показывает область интереса, иконка — конкретный объект.


IconLayer + TextLayer

[
  iconLayer,
  textLayer
]

Используется для подписей объектов.


IconLayer + PathLayer

[
  pathLayer,
  iconLayer
]

Маршрут отображается линией, а транспорт — иконкой.


IconLayer + GeoJsonLayer

[
  geoJsonLayer,
  iconLayer
]

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


Практический пример: отображение транспорта

Структура данных:

const vehicles = [
  {
    id: 1,
    type: 'bus',
    heading: 45,
    position: [37.617, 55.755]
  },

  {
    id: 2,
    type: 'tram',
    heading: 180,
    position: [37.625, 55.760]
  }
];

Создание слоя:

const vehicleLayer = new IconLayer({
  id: 'vehicles',

  data: vehicles,

  pickable: true,

  iconAtlas: '/images/transport.png',

  iconMapping,

  getPosition: d => d.position,

  getIcon: d => d.type,

  getSize: 32,

  getAngle: d => d.heading,

  autoHighlight: true,

  onClick: ({object}) => {
    if (!object) {
      return;
    }

    console.log(object.id);
  }
});

В результате формируется высокопроизводительный слой, способный отображать тысячи движущихся объектов с индивидуальными иконками, ориентацией, интерактивностью и поддержкой аппаратного ускорения через WebGL.