Стилизация на основе масштаба

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

Связь между масштабом и разрешением можно выразить через текущий view, где увеличение масштаба карты соответствует уменьшению значения resolution. Это фундаментальный момент, так как вся логика условной стилизации строится не на «zoom» как на уровне абстракции, а на числовом значении разрешения.


Основной механизм: style function с resolution

Наиболее гибкий способ реализации масштабозависимой стилизации — использование функции стиля:

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

const vectorLayer = new VectorLayer({
  source: vectorSource,
  style: function (feature, resolution) {
    return new Style({
      image: new Circle({
        radius: resolution < 10 ? 8 : 4,
        fill: new Fill({
          color: resolution < 10 ? 'red' : 'blue'
        }),
        stroke: new Stroke({
          color: '#333',
          width: 1
        })
      })
    });
  }
});

В этом примере resolution используется как индикатор масштаба: при уменьшении значения (приближении) увеличивается размер символа и изменяется цвет. Такой подход обеспечивает полную управляемость отрисовки без привязки к фиксированным зум-уровням.


Использование пороговых значений resolution

Часто стилизация строится не на непрерывной зависимости, а на дискретных диапазонах:

style: function (feature, resolution) {
  if (resolution > 50) {
    return new Style({
      image: new Circle({
        radius: 3,
        fill: new Fill({ color: '#999' })
      })
    });
  }

  if (resolution > 10) {
    return new Style({
      image: new Circle({
        radius: 6,
        fill: new Fill({ color: '#666' })
      })
    });
  }

  return new Style({
    image: new Circle({
      radius: 10,
      fill: new Fill({ color: '#000' })
    })
  });
}

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


Привязка к zoom вместо resolution

Несмотря на то что OpenLayers оперирует resolution, иногда удобнее работать с уровнем zoom:

style: function (feature, resolution) {
  const zoom = map.getView().getZoom();

  if (zoom < 5) {
    return new Style({
      image: new Circle({
        radius: 3,
        fill: new Fill({ color: 'gray' })
      })
    });
  }

  if (zoom < 10) {
    return new Style({
      image: new Circle({
        radius: 6,
        fill: new Fill({ color: 'orange' })
      })
    });
  }

  return new Style({
    image: new Circle({
      radius: 10,
      fill: new Fill({ color: 'red' })
    })
  });
}

Однако такой подход менее универсален, поскольку разные проекции и настройки view могут давать отличающиеся соответствия между zoom и реальным масштабом отображения.


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

OpenLayers предоставляет встроенные свойства ограничения отображения стилей и слоёв:

const style = new Style({
  image: new Circle({
    radius: 8,
    fill: new Fill({ color: 'blue' })
  }),
  minResolution: 5,
  maxResolution: 100
});

При использовании этих параметров объект автоматически исчезает или появляется в зависимости от текущего разрешения карты. Это позволяет разгрузить функцию стиля и передать часть логики в конфигурацию.

Аналогичные свойства поддерживаются на уровне слоя:

const layer = new VectorLayer({
  source: vectorSource,
  minResolution: 2,
  maxResolution: 200,
  style: baseStyle
});

Масштабозависимая типографика

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

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

style: function (feature, resolution) {
  const name = feature.get('name');

  const fontSize = resolution < 10 ? '14px' : '10px';

  return new Style({
    text: new Text({
      text: name,
      font: `${fontSize} sans-serif`,
      fill: new Fill({ color: '#000' }),
      stroke: new Stroke({ color: '#fff', width: 3 })
    })
  });
}

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

if (resolution > 20) {
  return null;
}

Оптимизация: кэширование стилей

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

Оптимизация заключается в кэшировании:

const styleCache = {};

function getStyle(resolution) {
  if (styleCache[resolution]) {
    return styleCache[resolution];
  }

  const style = new Style({
    image: new Circle({
      radius: resolution < 10 ? 8 : 4,
      fill: new Fill({
        color: resolution < 10 ? 'red' : 'blue'
      })
    })
  });

  styleCache[resolution] = style;
  return style;
}

style: function (feature, resolution) {
  return getStyle(Math.round(resolution));
}

В реальных приложениях чаще используется нормализация resolution по диапазонам, а не точные значения.


Масштабозависимое отображение типов геометрий

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

style: function (feature, resolution) {
  const geometryType = feature.getGeometry().getType();

  if (geometryType === 'LineString') {
    return new Style({
      stroke: new Stroke({
        color: resolution < 20 ? 'red' : 'blue',
        width: resolution < 20 ? 3 : 1
      })
    });
  }

  if (geometryType === 'Polygon') {
    return new Style({
      fill: new Fill({
        color: resolution < 10 ? 'rgba(0,0,255,0.3)' : 'rgba(0,0,255,0.1)'
      }),
      stroke: new Stroke({
        color: '#333',
        width: 1
      })
    });
  }
}

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


Условная агрегация и кластеризация как часть масштабной стилизации

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

style: function (feature, resolution) {
  const size = feature.get('features')?.length;

  if (size > 1) {
    return new Style({
      image: new Circle({
        radius: 10 + size,
        fill: new Fill({ color: 'rgba(255,0,0,0.5)' })
      }),
      text: new Text({
        text: String(size),
        fill: new Fill({ color: '#fff' })
      })
    });
  }

  return new Style({
    image: new Circle({
      radius: resolution < 10 ? 6 : 3,
      fill: new Fill({ color: 'green' })
    })
  });
}

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


Логическая декомпозиция стилей по масштабам

При разработке сложных карт применяется разделение логики стилизации:

  • базовый стиль объекта
  • стиль для среднего масштаба
  • стиль для детального масштаба
  • стиль для агрегации
function baseStyle(feature) {
  return new Style({
    fill: new Fill({ color: '#ccc' }),
    stroke: new Stroke({ color: '#666', width: 1 })
  });
}

function mediumScaleStyle(feature) {
  return new Style({
    fill: new Fill({ color: '#999' }),
    stroke: new Stroke({ color: '#444', width: 1.5 })
  });
}

function detailedStyle(feature) {
  return new Style({
    fill: new Fill({ color: '#666' }),
    stroke: new Stroke({ color: '#000', width: 2 })
  });
}

style: function (feature, resolution) {
  if (resolution > 50) return baseStyle(feature);
  if (resolution > 10) return mediumScaleStyle(feature);
  return detailedStyle(feature);
}

Такое разделение улучшает читаемость кода и упрощает поддержку визуальной логики.


Влияние масштаба на производительность рендеринга

Чем ниже значение resolution (то есть чем ближе пользователь к объектам), тем выше нагрузка на рендеринг: увеличивается количество видимых объектов, активируется отрисовка текста, усложняется геометрия символов.

Масштабозависимая стилизация используется не только для визуальных эффектов, но и для управления производительностью:

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

Композиция условий масштаба с атрибутами объекта

На практике масштаб редко используется изолированно. Он комбинируется с атрибутами объекта:

style: function (feature, resolution) {
  const population = feature.get('population');

  if (resolution < 10 && population > 1000000) {
    return new Style({
      image: new Circle({
        radius: 12,
        fill: new Fill({ color: 'red' })
      })
    });
  }

  if (resolution < 20 && population > 500000) {
    return new Style({
      image: new Circle({
        radius: 8,
        fill: new Fill({ color: 'orange' })
      })
    });
  }

  return new Style({
    image: new Circle({
      radius: 4,
      fill: new Fill({ color: 'gray' })
    })
  });
}

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