RTL поддержка

Поддержка RTL (right-to-left) в Kepler.gl затрагивает сразу несколько уровней: рендеринг карты через Mapbox GL JS, работу текстовых слоёв deck.gl, а также корректную обработку UI-слоя библиотеки и пользовательских данных, содержащих языки с направлением письма справа налево (арабский, иврит, персидский, урду).

Ключевая сложность заключается в том, что Kepler.gl не является самостоятельной картографической системой — он построен поверх Mapbox GL JS и deck.gl, поэтому RTL-поведение определяется сочетанием их возможностей и дополнительной конфигурацией.

Kepler.gl состоит из трёх основных слоёв:

  • UI (React-компоненты)
  • state management (Redux)
  • rendering (Mapbox GL JS + deck.gl)

RTL-логика распределяется следующим образом:

  • Mapbox GL JS отвечает за географическую визуализацию и подписи на карте
  • deck.gl отвечает за аналитические слои (точки, полигоны, heatmap и т.д.)
  • UI отвечает за отображение интерфейсных элементов (панели, фильтры)

RTL влияет на все три уровня, но критически важным является именно Mapbox GL JS, так как он контролирует текст на карте.

Подключение RTL-плагина Mapbox

Mapbox GL JS не включает поддержку арабского и других RTL-языков по умолчанию. Для корректного отображения требуется отдельный плагин:

npm install mapbox-gl-rtl-text

Далее он должен быть подключён до инициализации карты:

import mapboxgl from 'mapbox-gl';
import rtlTextPlugin from 'mapbox-gl-rtl-text';

mapboxgl.setRTLTextPlugin(
  'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js',
  null,
  true
);

Важный момент инициализации

RTL-плагин должен быть установлен до создания экземпляра Mapbox GL карты внутри Kepler.gl. В противном случае текстовые слои могут отрисоваться некорректно и не обновиться автоматически.

Интеграция с Kepler.gl

Kepler.gl позволяет передавать кастомную конфигурацию Mapbox через mapboxApiAccessToken и дополнительные параметры инициализации карты.

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

import KeplerGl from 'kepler.gl';
import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <KeplerGl
        id="map"
        mapboxApiAccessToken={process.env.MAPBOX_TOKEN}
        width={window.innerWidth}
        height={window.innerHeight}
      />
    </Provider>
  );
}

RTL-логика при этом внедряется на уровне глобальной инициализации Mapbox, а не через props Kepler.gl.

Отображение RTL-текста в слоях карты

Mapbox GL JS автоматически применяет RTL-обработку к следующим элементам:

  • подписи улиц
  • названия городов
  • POI (points of interest)
  • подписи слоёв с текстовыми свойствами

Однако в Kepler.gl часто используются кастомные слои deck.gl, где текст рендерится через TextLayer. В этом случае RTL не применяется автоматически.

Пример проблемного слоя deck.gl

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

const layer = new TextLayer({
  id: 'text-layer',
  data,
  getPosition: d => d.coordinates,
  getText: d => d.label,
  getSize: 16
});

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

Обработка RTL в deck.gl TextLayer

deck.gl не имеет встроенной полноценной поддержки bidi-алгоритма (bidirectional text algorithm). Поэтому требуется предварительная нормализация текста.

Использование bidi-библиотеки

Типичное решение — применение bidi-js или rtl-detect + преобразование строки:

import bidiFactory from 'bidi-js';

const bidi = bidiFactory();

function formatRTLText(text) {
  return bidi.from_string(text).toString();
}

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

const layer = new TextLayer({
  id: 'text-layer',
  data,
  getText: d => formatRTLText(d.label),
  getPosition: d => d.coordinates,
  getSize: 14
});

Определение направления текста

Перед обработкой важно определить, является ли текст RTL. Для этого применяется детектор языка:

import rtlDetect from 'rtl-detect';

function normalizeText(text) {
  if (rtlDetect.isRtlLang(text)) {
    return formatRTLText(text);
  }
  return text;
}

В Kepler.gl это особенно важно при смешанных датасетах, где одновременно присутствуют латиница и RTL-скрипты.

RTL и UI Kepler.gl

UI Kepler.gl по умолчанию не поддерживает полноценный RTL-layout. Это означает:

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

Для частичной адаптации используется CSS-override:

.kepler-gl {
  direction: rtl;
  text-align: right;
}

Однако это приводит к побочным эффектам:

  • ломается компоновка панелей
  • нарушается позиционирование drag-and-drop
  • модальные окна теряют выравнивание

Поэтому чаще применяется гибридный подход: RTL только для карты, UI остаётся LTR.

Mapbox style и RTL-слои

Mapbox стили поддерживают специальные настройки для RTL через layout.text-field.

Пример слоя символов:

{
  id: 'country-label',
  type: 'symbol',
  source: 'mapbox',
  'source-layer': 'country_label',
  layout: {
    'text-field': ['get', 'name_ar'],
    'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Regular']
  }
}

Kepler.gl может наследовать такие стили через кастомный style object.

Шрифты и Unicode

RTL-отображение напрямую зависит от наличия корректных шрифтов.

Рекомендуемые требования:

  • поддержка Arabic Unicode блоков
  • наличие glyph shaping (лигатуры)
  • fallback на Arial Unicode MS или Noto Sans Arabic

Mapbox style должен включать:

"text-font": ["Noto Sans Arabic Regular", "Arial Unicode MS Regular"]

Без этого текст может отображаться разрозненно (изолированные буквы вместо связанных форм).

Проблемы смешанных направлений текста

На практике наиболее сложная ситуация — смешанные строки:

  • арабский + цифры
  • арабский + английские термины
  • координаты + RTL подписи

Пример:

محطة 5 - Zone A

Без bidi-обработки результат может быть визуально некорректным.

Решение:

function smartFormat(text) {
  const hasRTL = rtlDetect.isRtlLang(text);
  if (!hasRTL) return text;

  return `\u202B${text}\u202C`;
}

Unicode маркеры:

  • \u202B RLE (right-to-left embedding)
  • \u202C pop directional formatting

RTL в фильтрах и таблицах Kepler.gl

Фильтры данных (data filters) и всплывающие таблицы требуют отдельной обработки:

  • сортировка строк должна учитывать locale
  • сравнение строк должно использовать Intl.Collator
const collator = new Intl.Collator('ar', { sensitivity: 'base' });

data.sort((a, b) => collator.compare(a.name, b.name));

Без этого арабские строки сортируются лексикографически по Unicode-коду, что даёт некорректный порядок.

Производительность RTL-обработки

Bidi-алгоритмы и преобразования строк могут стать узким местом при больших наборах данных (100k+ объектов).

Оптимизации:

  • кеширование преобразованных строк
  • мемоизация функций форматирования
  • предобработка данных на этапе ETL
const cache = new Map();

function cachedFormat(text) {
  if (cache.has(text)) return cache.get(text);

  const result = normalizeText(text);
  cache.set(text, result);

  return result;
}

Особенности при WebGL рендеринге

deck.gl использует WebGL, поэтому RTL-логика не может опираться на CSS direction для графических слоёв.

Это означает:

  • зеркалирование координат невозможно без ручного преобразования
  • текст рендерится как bitmap или SDF-глифы
  • порядок символов определяется до передачи в GPU

Любая RTL-логика должна быть завершена до стадии рендеринга.

Типичные ошибки интеграции

На практике часто встречаются следующие проблемы:

  • подключение RTL-плагина после инициализации карты
  • использование deck.gl TextLayer без bidi-обработки
  • отсутствие арабских шрифтов в style
  • попытка применить CSS direction к canvas-слоям
  • смешивание LTR/RTL без Unicode маркеров

Каждая из этих ошибок приводит к визуальным артефактам: перевёрнутому тексту, разорванным словам или неправильному порядку слов.

Гибридная стратегия поддержки RTL

Наиболее устойчивый подход в Kepler.gl:

  • Mapbox GL JS отвечает за RTL-карту и подписи
  • deck.gl получает уже нормализованный текст
  • UI остаётся LTR для стабильности интерфейса
  • данные проходят preprocessing слой с bidi-обработкой
  • используется кеширование преобразований

Такая схема минимизирует вмешательство в внутреннюю архитектуру Kepler.gl и снижает риск регрессий при обновлениях библиотек.