Векторные слои в OpenLayers предоставляют механизм отображения геометрии через стили, которые могут вычисляться не только статически, но и на основе атрибутов каждого объекта. Такой подход превращает карту в систему визуальной аналитики, где внешний вид каждого элемента напрямую зависит от его данных.
Основной механизм реализации динамических стилей — функция стиля
(style function), которая вызывается для каждого объекта
при рендеринге и получает доступ к самому объекту, текущему масштабу
отображения и контексту отрисовки.
В 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
})
})
});
};
Такая конструкция позволяет моделировать сложные визуальные состояния без изменения исходных данных.
В современных версиях 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)`
})
});
};
Такой подход позволяет визуализировать плотности и интенсивности в непрерывной шкале.
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');
});
После этого стиль опирается на уже готовый атрибут, снижая нагрузку на рендеринг.