Векторная стилизация в OpenLayers опирается на два ключевых параметра
отображения карты: текущий масштаб и разрешение (resolution). Внутри
механизма рендеринга именно resolution является базовой
величиной, через которую OpenLayers определяет, как изменяется
визуальное представление объектов при масштабировании.
Связь между масштабом и разрешением можно выразить через текущий
view, где увеличение масштаба карты соответствует
уменьшению значения resolution. Это фундаментальный момент,
так как вся логика условной стилизации строится не на «zoom» как на
уровне абстракции, а на числовом значении разрешения.
Наиболее гибкий способ реализации масштабозависимой стилизации — использование функции стиля:
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 используется как индикатор
масштаба: при уменьшении значения (приближении) увеличивается размер
символа и изменяется цвет. Такой подход обеспечивает полную
управляемость отрисовки без привязки к фиксированным зум-уровням.
Часто стилизация строится не на непрерывной зависимости, а на дискретных диапазонах:
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' })
})
});
}
Такой подход используется для управления визуальной плотностью данных: на дальних масштабах объекты упрощаются, на ближних — становятся более детализированными.
Несмотря на то что 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 и реальным масштабом отображения.
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' })
})
});
}
Такой подход формирует многоуровневую визуальную иерархию, где масштаб определяет уровень детализации, а данные — визуальную значимость объектов.