Создание кастомных deck.gl слоёв

В основе визуализации Kepler.gl лежит слойная модель deck.gl, где каждый слой представляет независимую единицу рендеринга данных. Расширяемость достигается за счёт возможности подключения пользовательских классов слоёв, наследуемых от базовых реализаций deck.gl. Это позволяет внедрять специализированные визуализации без изменения ядра приложения.

Kepler.gl использует декларативное описание слоёв через состояние визуализации (visState), где каждый слой описывается набором параметров: типом, источником данных, стилями и фильтрами. Пользовательские слои интегрируются в эту систему через регистрацию и последующее использование в конфигурации карты.


Базовый контракт пользовательского слоя deck.gl

Любой кастомный слой в deck.gl реализуется через наследование от класса Layer. В зависимости от сложности задачи используются:

  • Layer — базовый абстрактный слой
  • CompositeLayer — композиция нескольких вложенных слоёв
  • готовые геометрические слои (ScatterplotLayer, PathLayer, PolygonLayer) как основа расширения

Ключевые методы, определяющие поведение слоя:

  • initializeState — инициализация состояния
  • updateState — реакция на изменение props
  • renderLayers — композиция дочерних слоёв (для CompositeLayer)
  • getColor, getPosition и другие accessor-функции
  • draw — низкоуровневый рендер (WebGL)

Создание кастомного слоя на основе CompositeLayer

Наиболее распространённый подход — композиция стандартных слоёв deck.gl с дополнительной логикой обработки данных.

import {CompositeLayer, ScatterplotLayer} from '@deck.gl/layers';

class HeatScatterLayer extends CompositeLayer {
  initializeState() {
    this.state = {
      maxRadius: 1000
    };
  }

  renderLayers() {
    const {data, getPosition, getWeight} = this.props;

    return new ScatterplotLayer({
      id: `${this.id}-scatter`,
      data,
      getPosition,
      getRadius: d => getWeight(d),
      radiusUnits: 'meters',
      opacity: 0.8,
      pickable: true
    });
  }
}

HeatScatterLayer.layerName = 'HeatScatterLayer';

Такой слой позволяет переопределять логику отображения без вмешательства в низкоуровневый WebGL-код.


Интеграция кастомного слоя в Kepler.gl

Kepler.gl ожидает регистрацию кастомных слоёв через расширение конфигурации. Основной механизм — добавление слоя в список доступных типов и маппинг его в систему визуализации.

import KeplerGl from 'kepler.gl';
import {addDataToMap} from 'kepler.gl/actions';

const customLayers = {
  heat_scatter: HeatScatterLayer
};

Далее слой подключается через состояние:

const mapConfig = {
  version: 'v1',
  config: {
    visState: {
      layers: [
        {
          id: 'custom-layer-1',
          type: 'heat_scatter',
          config: {
            dataId: 'dataset_1',
            label: 'Heat Scatter',
            columns: {
              lat: 'latitude',
              lng: 'longitude'
            }
          }
        }
      ]
    }
  }
};

Привязка кастомного слоя к системе Kepler.gl

Kepler.gl не использует прямую инстанциацию слоёв. Вместо этого применяется фабрика слоёв, где тип слоя сопоставляется с классом.

import KeplerGl from 'kepler.gl';
import {LayerManager} from 'kepler.gl';

const layerClasses = {
  heat_scatter: HeatScatterLayer
};

function getLayer(type) {
  return layerClasses[type];
}

В реальной интеграции используется расширение KeplerGlSchema и middleware Redux, через которое слой регистрируется в runtime.


Расширение базового Layer для низкоуровневого рендеринга

В случаях, когда требуется максимальный контроль над WebGL-пайплайном, используется наследование от Layer.

import {Layer} from '@deck.gl/core';

class PulsingPointLayer extends Layer {
  initializeState() {
    const {gl} = this.context;

    this.setState({
      time: 0
    });
  }

  draw({uniforms}) {
    const {gl} = this.context;

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

    // кастомный WebGL рендеринг
  }

  updateState({props, oldProps}) {
    if (props.time !== oldProps.time) {
      this.setState({time: props.time});
    }
  }
}

PulsingPointLayer.layerName = 'PulsingPointLayer';

Такой подход требует ручного управления шейдерами и состоянием WebGL-контекста.


Использование шейдеров в кастомных слоях

Для сложных визуализаций применяется программирование вершинных и фрагментных шейдеров через shadertools.

import {Layer} from '@deck.gl/core';

class GlowLayer extends Layer {
  getShaders() {
    return {
      vs: `
        attribute vec3 positions;
        void main(void) {
          gl_Position = project_position_to_clipspace(positions);
        }
      `,
      fs: `
        precision highp float;
        void main(void) {
          gl_FragColor = vec4(1.0, 0.5, 0.0, 0.7);
        }
      `
    };
  }
}

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


Передача данных и accessor-функции

Кастомные слои используют accessor-паттерн для извлечения данных из объектов массива:

class CustomLayer extends CompositeLayer {
  renderLayers() {
    const {data, getValue} = this.props;

    return new ScatterplotLayer({
      data,
      getPosition: d => d.coordinates,
      getRadius: d => getValue(d),
      getFillColor: [255, 100, 0]
    });
  }
}

Accessor-функции позволяют отделить структуру данных от логики рендеринга, обеспечивая совместимость с Kepler.gl datasets.


Управление состоянием слоя в Kepler.gl visState

Kepler.gl хранит состояние слоёв в Redux-структуре visState.layers. Каждый слой представляет сериализуемый объект:

  • id
  • type
  • config
  • visualChannels
  • dataId

При создании кастомного слоя важно обеспечить сериализуемость всех параметров, так как состояние может сохраняться и восстанавливаться.

const layerConfig = {
  id: 'custom_1',
  type: 'heat_scatter',
  config: {
    opacity: 0.6,
    visConfig: {
      radius: 10
    }
  }
};

Взаимодействие кастомных слоёв с фильтрами Kepler.gl

Фильтрация данных в Kepler.gl происходит на уровне visState.filters. Кастомный слой автоматически получает отфильтрованный набор данных, если использует стандартный pipeline.

renderLayers() {
  const {filteredData} = this.props;

  return new ScatterplotLayer({
    data: filteredData,
    getPosition: d => d.position
  });
}

При необходимости можно переопределять поведение фильтрации, используя updateTriggers и кастомные селекторы Redux.


Оптимизация производительности кастомных слоёв

При работе с большими наборами данных ключевое значение имеют:

  • минимизация пересоздания слоёв
  • использование updateTriggers
  • кэширование вычисляемых значений
  • переход на TypedArray для числовых данных
new ScatterplotLayer({
  data,
  updateTriggers: {
    getRadius: props.radiusScale
  }
});

Дополнительно применяются стратегии spatial indexing и GPU instancing для снижения нагрузки на CPU.


Композиция нескольких кастомных слоёв

CompositeLayer позволяет объединять несколько визуализаций в единый слой:

class MultiVizLayer extends CompositeLayer {
  renderLayers() {
    return [
      new ScatterplotLayer({...}),
      new PathLayer({...}),
      new TextLayer({...})
    ];
  }
}

Такой подход используется для создания сложных картографических представлений, где один слой отвечает за несколько визуальных каналов.


Встраивание кастомных слоёв в архитектуру Redux Kepler.gl

Kepler.gl использует Redux middleware для управления слоями. Кастомные слои должны быть совместимы с action-потоками:

  • addLayer
  • updateLayer
  • removeLayer

Слой регистрируется как часть схемы приложения и становится доступен через UI-конфигурацию или программный API.

dispatch(addLayer({
  ...layerConfig,
  type: 'heat_scatter'
}));

Согласование кастомных слоёв с системой визуальных каналов Kepler.gl

Kepler.gl использует визуальные каналы (visual channels), такие как:

  • color
  • size
  • height
  • stroke

Кастомный слой может подключаться к этим каналам через декларацию:

visualChannels: {
  colorField: {name: 'intensity'},
  sizeField: {name: 'weight'}
}

Это обеспечивает совместимость с UI-конфигурацией Kepler.gl без изменения интерфейса.


Расширение поведения pickable и interaction layer

Интерактивность реализуется через pickable и события:

new ScatterplotLayer({
  pickable: true,
  onClick: info => {
    console.log(info.object);
  }
});

Kepler.gl интегрирует picking через единый event manager, поэтому кастомные слои автоматически участвуют в системе интеракции при соблюдении интерфейса deck.gl.