Векторные слои в OpenLayers опираются на операции поиска, фильтрации и пересечения геометрий. При увеличении количества объектов линейный перебор становится узким местом: сложность операций возрастает до (O(n)), что критично для интерактивных карт с тысячами и десятками тысяч объектов.
Основой ускорения служит пространственный индекс. В экосистеме JavaScript де-факто стандартом для 2D-индексации является RBush — реализация R-tree с оптимизированной вставкой и быстрым поиском по bounding box.
OpenLayers внутри использует геометрические операции через
ol/extent и ol/geom. Однако без индекса каждая
операция выбора или отрисовки вынуждена проверять все фичи.
R-tree решает проблему за счёт иерархического разбиения пространства:
Сложность поиска уменьшается до порядка (O(n)) в среднем случае.
RBush строит сбалансированное дерево, где каждый узел хранит bounding box:
[minX, minY, maxX, maxY]
Каждый объект либо является листом, либо агрегатом других узлов.
Ключевые операции:
ins ert(item) — добавление элементаsearch(bbox) — поиск пересеченийremove(item) — удалениеclear() — очистка индексаОсобенность реализации — bulk-loading, позволяющая быстро построить индекс из массива без поэлементной вставки.
В OpenLayers векторные источники позволяют подключать собственные
стратегии индексации через ol/source/Vector и обработку
геометрий.
Типовая схема:
Пример базовой интеграции:
import RBush from 'rbush';
import VectorSource from 'ol/source/Vector';
const index = new RBush();
function featureToItem(feature) {
const extent = feature.getGeometry().getExtent();
return {
minX: extent[0],
minY: extent[1],
maxX: extent[2],
maxY: extent[3],
feature
};
}
const source = new VectorSource({
features: []
});
function rebuildIndex(features) {
index.clear();
const items = features.map(featureToItem);
index.load(items);
}
Интерактивные операции (click, hover) в больших слоях являются наиболее чувствительными к производительности.
Без индекса:
containsCoordinate для каждой геометрииС RBush:
function getFeaturesAtCoordinate(coord) {
const searchExtent = [
coord[0], coord[1],
coord[0], coord[1]
];
const candidates = index.search({
minX: searchExtent[0],
minY: searchExtent[1],
maxX: searchExtent[2],
maxY: searchExtent[3]
});
return candidates
.map(c => c.feature)
.filter(f => f.getGeometry().intersectsCoordinate(coord));
}
Такой подход радикально снижает число геометрических проверок.
При изменении данных ключевой проблемой становится синхронизация индекса.
Стратегии:
Подходит для:
Минус — дорогостоящая операция при больших наборах.
Используется при потоковых данных:
function addFeature(feature) {
const item = featureToItem(feature);
index.insert(item);
}
function removeFeature(item) {
index.remove(item);
}
Важно поддерживать ссылку на объект RBush-узла для удаления.
Кластеризация точечных данных часто реализуется через пространственные индексы.
Алгоритм:
function cluster(extent) {
const neighbors = index.search(extent);
const clusterCenter = neighbors.reduce(
(acc, item) => {
const c = item.feature.getGeometry().getCoordinates();
acc.x += c[0];
acc.y += c[1];
return acc;
},
{ x: 0, y: 0 }
);
clusterCenter.x /= neighbors.length;
clusterCenter.y /= neighbors.length;
return {
center: [clusterCenter.x, clusterCenter.y],
size: neighbors.length
};
}
Такой подход масштабируется значительно лучше, чем перебор всех точек.
Ключевые факторы эффективности RBush:
RBush использует оптимизированный bulk-loading алгоритм, минимизирующий перекос структуры.
Чем точнее bounding box соответствует геометрии, тем меньше ложных попаданий.
Частые вставки ухудшают баланс дерева. Приоритет отдаётся пакетной загрузке.
Для плотных данных имеет смысл:
При создании собственного ol/source/Source индекс может
стать внутренним механизмом хранения:
class IndexedSource {
constructor() {
this.index = new RBush();
this.features = [];
}
addFeature(feature) {
this.features.push(feature);
this.index.insert(featureToItem(feature));
}
getInExtent(extent) {
return this.index.search({
minX: extent[0],
minY: extent[1],
maxX: extent[2],
maxY: extent[3]
}).map(i => i.feature);
}
}
Такой слой фактически превращает источник данных в пространственно индексированную структуру, сокращая нагрузку на рендеринг и взаимодействия.
RBush эффективен в сценариях:
Ограничения:
| Подход | Производительность | Гибкость |
|---|---|---|
| линейный поиск | низкая | высокая |
| RBush | высокая | средняя |
| GeoJSON full scan | очень низкая | высокая |
| серверный индекс | максимальная | зависит от API |
RBush занимает промежуточную позицию между простотой и скоростью, оставаясь полностью клиентским решением.
Типичная архитектура:
Схема потока:
Такая модель снижает нагрузку на CPU при работе с большими наборами геоданных и сохраняет отзывчивость интерфейса даже при сложных слоях.