TextLayer

TextLayer — специализированный слой библиотеки Deck.gl, предназначенный для отображения текстовых подписей в геопространственных визуализациях. Слой позволяет размещать отдельные строки текста над картой, поверх объектов других слоёв и в произвольных координатах.

TextLayer используется для отображения:

  • названий городов;
  • подписей объектов инфраструктуры;
  • идентификаторов точек;
  • статистических значений;
  • динамически обновляемых данных;
  • аннотаций и пояснений на карте.

Внутри TextLayer применяется технология рендеринга символов через текстурный атлас (glyph atlas), что позволяет эффективно отображать тысячи подписей одновременно с использованием GPU.


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

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

Простейший пример:

const data = [
  {
    position: [37.6176, 55.7558],
    name: 'Москва'
  }
];

const textLayer = new TextLayer({
  id: 'cities',
  data,

  getPosition: d => d.position,
  getText: d => d.name
});

new Deck({
  initialViewState: {
    longitude: 37.6176,
    latitude: 55.7558,
    zoom: 5
  },

  controller: true,
  layers: [textLayer]
});

В результате над указанными координатами появится текстовая подпись.


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

Обычно каждый элемент массива содержит:

{
  position: [37.6176, 55.7558],
  name: 'Москва'
}

Либо более сложный набор данных:

{
  id: 1,
  city: 'Москва',
  population: 13000000,
  coordinates: [37.6176, 55.7558]
}

Настройка доступа к данным:

new TextLayer({
  data,

  getPosition: d => d.coordinates,
  getText: d => d.city
});

Свойство getText

Функция getText определяет текст, который будет отображён для каждого объекта.

Пример:

getText: d => d.name

Формирование строки из нескольких полей:

getText: d =>
  `${d.city} (${d.population})`

Использование вычисляемого значения:

getText: d =>
  `${d.temperature}°C`

Многострочный текст:

getText: d =>
  `${d.city}\n${d.population}`

Результат:

Москва
13000000

Deck.gl автоматически поддерживает перенос строк через символ \n.


Определение позиции текста

Положение подписи задаётся через accessor getPosition.

getPosition: d => d.position

Формат координат:

[longitude, latitude]

Пример:

[30.3158, 59.9391]

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

getPosition: d => [
  d.lng,
  d.lat,
  d.altitude
]

Например:

getPosition: d => [
  37.6176,
  55.7558,
  1000
]

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


Размер шрифта

Размер текста определяется свойством getSize.

getSize: 18

Или динамически:

getSize: d => d.population / 1000000

Пример:

new TextLayer({
  data,

  getText: d => d.city,
  getPosition: d => d.position,

  getSize: d =>
    Math.sqrt(d.population)
});

Крупные города будут отображаться более заметно.


Цвет текста

Для управления цветом используется getColor.

Формат:

[r, g, b]

или

[r, g, b, a]

Примеры:

getColor: [255, 255, 255]

Белый цвет.

getColor: [255, 0, 0]

Красный цвет.

Полупрозрачный текст:

getColor: [255, 255, 255, 150]

Динамический выбор:

getColor: d =>
  d.population > 5000000
    ? [255, 0, 0]
    : [0, 200, 255]

Выбор шрифта

Свойство fontFamily определяет используемый шрифт.

fontFamily: 'Arial'

Другие варианты:

fontFamily: 'Roboto'
fontFamily: 'Verdana'
fontFamily: 'Helvetica'

Пример:

new TextLayer({
  fontFamily: 'Roboto'
});

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


Толщина шрифта

Свойство fontWeight управляет насыщенностью.

fontWeight: 'normal'

Жирный вариант:

fontWeight: 'bold'

Числовые значения:

fontWeight: 300
fontWeight: 600
fontWeight: 700

Пример:

new TextLayer({
  fontWeight: 700
});

Масштабирование текста

По умолчанию размер текста зависит от масштаба карты.

Контроль осуществляется через sizeUnits.

Метры

sizeUnits: 'meters'

Размер будет измеряться в реальных метрах.

new TextLayer({
  getSize: 200,
  sizeUnits: 'meters'
});

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

Пиксели

sizeUnits: 'pixels'

Размер сохраняется визуально постоянным.

new TextLayer({
  getSize: 16,
  sizeUnits: 'pixels'
});

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

Общие единицы

sizeUnits: 'common'

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


Ограничение масштабирования

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

sizeMinPixels: 10

Даже при сильном уменьшении карты текст останется читаемым.

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

sizeMaxPixels: 48

Предотвращает чрезмерное увеличение подписей.

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

new TextLayer({
  getSize: d => d.population / 500000,

  sizeMinPixels: 10,
  sizeMaxPixels: 40
});

Выравнивание текста по горизонтали

Свойство getTextAnchor.

Возможные значения:

'start'
'middle'
'end'

Пример:

getTextAnchor: 'middle'

Точка координат будет располагаться в центре текста.

Схематично:

start

X Москва

middle

  Москва
     X

end

Москва X

Вертикальное выравнивание

Используется свойство getAlignmentBaseline.

Возможные варианты:

'top'
'center'
'bottom'

Пример:

getAlignmentBaseline: 'center'

Координата будет соответствовать центру текстового блока.


Смещение текста

Для смещения используется getPixelOffset.

Пример:

getPixelOffset: [20, -10]

Где:

[xOffset, yOffset]

Например, подпись можно вынести выше точки:

new TextLayer({
  getPosition: d => d.position,

  getPixelOffset: [0, -15]
});

Типичная комбинация с IconLayer:

layers: [
  iconLayer,
  textLayer
]

Текст располагается над иконкой.


Поворот текста

Свойство getAngle задаёт угол поворота.

getAngle: 0
getAngle: 45
getAngle: 90

Динамический вариант:

getAngle: d => d.direction

Пример:

new TextLayer({
  getAngle: d => d.rotation
});

Угол измеряется в градусах.


Поддержка прозрачности

Прозрачность задаётся альфа-каналом.

getColor: [255, 255, 255, 100]

Полностью непрозрачный текст:

getColor: [255, 255, 255, 255]

Полностью скрытый:

getColor: [255, 255, 255, 0]

Часто применяется для анимаций появления и исчезновения.


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

TextLayer поддерживает механизм picking.

Включение:

pickable: true

Обработка события:

new TextLayer({
  pickable: true,

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

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

{
  city: 'Москва',
  population: 13000000
}

Всплывающие подсказки

Совместное использование с Deck Tooltip:

new Deck({
  getTooltip: ({object}) =>
    object && {
      text: `${object.city}`
    }
});

Слой:

new TextLayer({
  pickable: true
});

При наведении появляется информационная подсказка.


Работа с большим количеством подписей

TextLayer оптимизирован для отображения большого объёма данных.

Пример:

const cities = new Array(10000)
  .fill(null)
  .map((_, i) => ({
    name: `City ${i}`,
    position: [
      Math.random() * 360 - 180,
      Math.random() * 180 - 90
    ]
  }));

Отображение:

new TextLayer({
  data: cities,

  getPosition: d => d.position,
  getText: d => d.name
});

Рендеринг остаётся быстрым благодаря:

  • текстурному атласу;
  • пакетной отрисовке;
  • использованию WebGL;
  • вычислениям на GPU.

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

Изменение массива автоматически приводит к обновлению слоя.

textLayer = new TextLayer({
  id: 'cities',
  data: updatedData,

  getPosition: d => d.position,
  getText: d => d.name
});

В React:

const layer = new TextLayer({
  data,

  getPosition: d => d.position,
  getText: d => d.name
});

После изменения состояния компонент перерисует слой.


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

TextLayer + ScatterplotLayer

layers: [
  new ScatterplotLayer({
    data,
    getPosition: d => d.position,
    getRadius: 5000
  }),

  new TextLayer({
    data,
    getPosition: d => d.position,
    getText: d => d.name,

    getPixelOffset: [0, -15]
  })
]

Точки получают подписи.


TextLayer + IconLayer

layers: [
  iconLayer,

  new TextLayer({
    data,

    getPosition: d => d.position,
    getText: d => d.label,

    getPixelOffset: [0, -25]
  })
]

Подходит для отображения маркеров и названий объектов.


TextLayer + ColumnLayer

layers: [
  columnLayer,

  new TextLayer({
    data,

    getPosition: d => [
      d.lng,
      d.lat,
      d.height
    ],

    getText: d => d.value.toString()
  })
]

Текст размещается над трёхмерными колонками.


Параметры генерации атласа символов

characterSet

Deck.gl может заранее определить набор символов.

characterSet: 'auto'

Автоматический режим.

Явный список:

characterSet:
  'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

Поддержка кириллицы:

characterSet:
  'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'

Полезно для оптимизации памяти.


fontSettings

Настройки создания текстурного атласа.

Пример:

fontSettings: {
  fontSize: 64,
  buffer: 4
}

Параметры:

Параметр Назначение
fontSize Размер символов в атласе
buffer Отступ между символами
sdf Использование Signed Distance Field
radius Радиус генерации SDF

Signed Distance Field (SDF)

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

Включение:

fontSettings: {
  sdf: true
}

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

  • более качественное масштабирование;
  • уменьшение артефактов;
  • высокая резкость текста на разных уровнях zoom.

Особенно полезно для крупных картографических приложений и систем мониторинга.


Частые проблемы

Подписи не отображаются

Причины:

getText: d => d.title

при отсутствии поля:

{
  name: 'Москва'
}

Необходимо проверить структуру данных.


Неверные координаты

Ошибочный порядок:

[latitude, longitude]

Правильно:

[longitude, latitude]

Слишком маленький текст

Следует увеличить:

getSize

или установить:

sizeMinPixels

Текст перекрывает объект

Используется смещение:

getPixelOffset: [0, -20]

Размытый текст

Рекомендуется включить SDF:

fontSettings: {
  sdf: true
}

или увеличить размер атласа:

fontSettings: {
  fontSize: 64
}

Практический пример

import {TextLayer} from '@deck.gl/layers';

const layer = new TextLayer({
  id: 'cities',

  data: [
    {
      city: 'Москва',
      population: 13000000,
      position: [37.6176, 55.7558]
    },
    {
      city: 'Санкт-Петербург',
      population: 5600000,
      position: [30.3158, 59.9391]
    }
  ],

  pickable: true,

  getPosition: d => d.position,

  getText: d =>
    `${d.city}\n${d.population.toLocaleString()}`,

  getSize: d =>
    Math.sqrt(d.population) / 150,

  getColor: [255, 255, 255],

  getTextAnchor: 'middle',

  getAlignmentBaseline: 'bottom',

  getPixelOffset: [0, -10],

  sizeUnits: 'pixels',

  sizeMinPixels: 12,

  sizeMaxPixels: 40,

  fontFamily: 'Roboto',

  fontWeight: 600,

  fontSettings: {
    sdf: true
  }
});

Данный пример демонстрирует большинство возможностей TextLayer: динамический текст, масштабирование размера, поддержку кириллицы, взаимодействие с пользователем, выравнивание, настройку шрифта и качественный рендеринг текста при любых уровнях масштабирования карты.