Динамические стили на основе атрибутов

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

Основной механизм реализации динамических стилей — функция стиля (style function), которая вызывается для каждого объекта при рендеринге и получает доступ к самому объекту, текущему масштабу отображения и контексту отрисовки.


Базовая структура StyleFunction

В OpenLayers стиль может быть задан как функция:

import Style from 'ol/style/Style.js';
import Fill from 'ol/style/Fill.js';
import Stroke from 'ol/style/Stroke.js';
import CircleStyle from 'ol/style/Circle.js';

const styleFunction = (feature, resolution) => {
  const type = feature.get('type');

  let fillColor = '#3399CC';

  if (type === 'park') {
    fillColor = '#2ecc71';
  } else if (type === 'water') {
    fillColor = '#3498db';
  } else if (type === 'industrial') {
    fillColor = '#95a5a6';
  }

  return new Style({
    image: new CircleStyle({
      radius: 6,
      fill: new Fill({ color: fillColor }),
      stroke: new Stroke({ color: '#333', width: 1 })
    }),
    fill: new Fill({
      color: fillColor
    }),
    stroke: new Stroke({
      color: '#222',
      width: 1
    })
  });
};

Здесь ключевым элементом является доступ к feature.get('type'), позволяющий управлять визуализацией на основе семантики данных.


Использование атрибутов для категориальной стилизации

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

const categoryStyle = (feature) => {
  const category = feature.get('category');

  const styles = {
    school: new Style({
      image: new CircleStyle({
        radius: 7,
        fill: new Fill({ color: '#f1c40f' })
      })
    }),
    hospital: new Style({
      image: new CircleStyle({
        radius: 7,
        fill: new Fill({ color: '#e74c3c' })
      })
    }),
    shop: new Style({
      image: new CircleStyle({
        radius: 7,
        fill: new Fill({ color: '#9b59b6' })
      })
    })
  };

  return styles[category] || styles.shop;
};

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


Числовые атрибуты и градации

Когда атрибуты являются числовыми (например, население, интенсивность трафика, уровень загрязнения), применяется классификация значений.

const getColorByPopulation = (population) => {
  if (population > 1000000) return '#800026';
  if (population > 500000) return '#BD0026';
  if (population > 200000) return '#E31A1C';
  if (population > 100000) return '#FC4E2A';
  if (population > 50000) return '#FD8D3C';
  return '#FEB24C';
};

const populationStyle = (feature) => {
  const population = feature.get('population');

  return new Style({
    fill: new Fill({
      color: getColorByPopulation(population)
    }),
    stroke: new Stroke({
      color: '#555',
      width: 1
    })
  });
};

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


Учет масштаба при динамической стилизации

Рендеринг в OpenLayers зависит от resolution, что позволяет изменять стиль в зависимости от масштаба карты. Это особенно важно при работе с большим количеством объектов.

const scaleSensitiveStyle = (feature, resolution) => {
  const zoomLevel = resolutionToZoom(resolution);

  const baseSize = feature.get('importance') || 1;
  const radius = baseSize * Math.max(1, 20 - zoomLevel);

  return new Style({
    image: new CircleStyle({
      radius,
      fill: new Fill({ color: '#1abc9c' }),
      stroke: new Stroke({ color: '#2c3e50', width: 1 })
    })
  });
};

function resolutionToZoom(resolution) {
  const maxResolution = 156543.03392804097;
  return Math.log2(maxResolution / resolution);
}

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


Кэширование стилей для повышения производительности

При большом количестве объектов создание новых экземпляров Style на каждый рендер приводит к падению производительности. Решение — кэширование.

const styleCache = {};

const cachedStyleFunction = (feature) => {
  const status = feature.get('status');

  if (!styleCache[status]) {
    let color = '#95a5a6';

    if (status === 'active') color = '#2ecc71';
    if (status === 'inactive') color = '#e74c3c';
    if (status === 'pending') color = '#f1c40f';

    styleCache[status] = new Style({
      image: new CircleStyle({
        radius: 6,
        fill: new Fill({ color })
      })
    });
  }

  return styleCache[status];
};

Кэширование особенно эффективно при дискретных наборах значений.


Сложные правила стилизации с несколькими атрибутами

В реальных сценариях стиль зависит от комбинации параметров.

const complexStyle = (feature) => {
  const type = feature.get('type');
  const priority = feature.get('priority');
  const active = feature.get('active');

  let color = '#bdc3c7';
  let radius = 5;

  if (type === 'server') color = '#3498db';
  if (type === 'router') color = '#9b59b6';

  if (priority === 'high') radius = 10;
  if (priority === 'low') radius = 4;

  if (!active) color = '#7f8c8d';

  return new Style({
    image: new CircleStyle({
      radius,
      fill: new Fill({ color }),
      stroke: new Stroke({
        color: '#2c3e50',
        width: 1
      })
    })
  });
};

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


Использование выражений через ol/style/expressions

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

import { Style, Fill, Stroke } from 'ol/style.js';

const expressionStyle = {
  'fill-color': [
    'case',
    ['==', ['get', 'type'], 'park'], '#2ecc71',
    ['==', ['get', 'type'], 'water'], '#3498db',
    '#bdc3c7'
  ],
  'stroke-color': '#333',
  'stroke-width': 1
};

Такой подход уменьшает количество JavaScript-логики и переносит часть вычислений в систему выражений OpenLayers.


Динамическая работа с иконками

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

import Icon from 'ol/style/Icon.js';

const iconStyleFunction = (feature) => {
  const status = feature.get('status');

  const iconMap = {
    online: 'icons/online.png',
    offline: 'icons/offline.png',
    unknown: 'icons/unknown.png'
  };

  return new Style({
    image: new Icon({
      src: iconMap[status] || iconMap.unknown,
      scale: 0.8
    })
  });
};

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


Условная визуализация линий и полигонов

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

const roadStyle = (feature) => {
  const traffic = feature.get('traffic');

  let color = '#2ecc71';
  let width = 2;

  if (traffic === 'moderate') {
    color = '#f1c40f';
    width = 3;
  }

  if (traffic === 'heavy') {
    color = '#e74c3c';
    width = 4;
  }

  return new Style({
    stroke: new Stroke({
      color,
      width
    })
  });
};

Подобные схемы применяются в навигационных системах и аналитике дорожной сети.


Стилизация на основе диапазонов и нормализация данных

При работе с непрерывными данными часто требуется нормализация значений перед визуализацией.

const normalize = (value, min, max) => {
  return (value - min) / (max - min);
};

const heatStyle = (feature) => {
  const value = feature.get('value');

  const intensity = normalize(value, 0, 100);

  const red = Math.floor(255 * intensity);
  const green = Math.floor(255 * (1 - intensity));

  return new Style({
    fill: new Fill({
      color: `rgba(${red}, ${green}, 50, 0.7)`
    })
  });
};

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


Комбинирование стилей через массив Style

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

const multiStyle = (feature) => {
  const base = new Style({
    stroke: new Stroke({
      color: '#000',
      width: 2
    })
  });

  const highlight = new Style({
    image: new CircleStyle({
      radius: 8,
      fill: new Fill({ color: '#ffcc00' })
    })
  });

  if (feature.get('selected')) {
    return [base, highlight];
  }

  return [base];
};

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


Синхронизация атрибутов с внешними источниками данных

Динамические стили часто зависят от данных, обновляемых в реальном времени. Атрибуты объектов могут меняться через WebSocket или API-запросы, после чего слой перерисовывается.

feature.set('status', 'active');
feature.changed();

Вызов changed() принудительно инициирует перерасчет стиля, что критично для интерактивных систем мониторинга.


Оптимизация при большом количестве правил

При усложнении логики стилизации важно минимизировать количество вычислений внутри функции стиля. Распространенный подход — предварительная нормализация данных и подготовка вычисляемых полей в момент загрузки GeoJSON.

features.forEach((f) => {
  const value = f.get('rawValue');
  f.set('category', value > 50 ? 'high' : 'low');
});

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