При работе с интерактивными картами в JavaScript основная нагрузка часто возникает не на рендеринге тайлов, а на управлении динамическими объектами: маркерами, линиями, полигонами, всплывающими окнами, оверлеями и пользовательскими слоями. В типичном сценарии такие объекты создаются и удаляются десятки или сотни раз в секунду при обновлении данных.
Каждое создание маркера или полигона в HERE Maps API включает:
Удаление объекта, в свою очередь, требует:
При интенсивных обновлениях (например, трекинг транспорта, визуализация IoT-данных, движение объектов в реальном времени) постоянное создание и уничтожение объектов приводит к:
Эта проблема решается применением паттерна пул объектов.
Пул объектов — это структура, в которой заранее создаётся ограниченное количество экземпляров тяжёлых объектов (маркер, полилиния, информационное окно), после чего они переиспользуются вместо постоянного пересоздания.
В контексте карт:
Ключевая идея: стоимость создания заменяется стоимостью конфигурации.
Наиболее распространённый сценарий — пул маркеров. В HERE Maps API маркеры являются относительно тяжёлыми объектами, особенно при использовании кастомных иконок и HTML-слоёв.
Структура пула обычно включает:
acquire и release.class MarkerPool {
constructor(map, factory, size = 100) {
this.map = map;
this.factory = factory;
this.pool = [];
this.inUse = new Set();
for (let i = 0; i < size; i++) {
const marker = this.factory();
marker.setVisibility(false);
this.pool.push(marker);
}
}
acquire() {
const marker = this.pool.pop();
if (!marker) return null;
this.inUse.add(marker);
marker.setVisibility(true);
return marker;
}
release(marker) {
marker.setVisibility(false);
this.inUse.delete(marker);
this.pool.push(marker);
}
}
Фабрика маркера может выглядеть так:
function createMarker(map) {
const marker = new H.map.Marker({ lat: 0, lng: 0 });
map.addObject(marker);
marker.setVisibility(false);
return marker;
}
Классическая ошибка — удаление объекта с карты:
map.removeObject(marker);
В модели пула такой подход заменяется:
pool.release(marker);
Объект не уничтожается, а:
release(marker) {
marker.setGeometry({ lat: 0, lng: 0 });
marker.setData(null);
marker.setVisibility(false);
this.inUse.delete(marker);
this.pool.push(marker);
}
В системах реального времени (например, GPS-трекинг) важно обновлять только геометрию объекта.
function updateVehicle(pool, id, coords) {
let marker = pool.inUse.get(id);
if (!marker) {
marker = pool.acquire();
pool.inUse.set(id, marker);
}
marker.setGeometry(coords);
}
Здесь добавляется дополнительный слой — индексирование активных объектов по идентификатору.
Полилинии (routes, trajectories) значительно тяжелее маркеров, так как содержат массив координат и требуют пересчёта геометрии.
Пул для линий должен учитывать:
class PolylinePool {
constructor(map, size = 20) {
this.map = map;
this.pool = [];
for (let i = 0; i < size; i++) {
const line = new H.map.Polyline([], {
style: { strokeColor: 'blue', lineWidth: 3 }
});
map.addObject(line);
line.setVisibility(false);
this.pool.push(line);
}
}
acquire() {
const line = this.pool.pop();
if (!line) return null;
line.setVisibility(true);
return line;
}
release(line) {
line.setGeometry([]);
line.setVisibility(false);
this.pool.push(line);
}
}
Оверлеи (HTML overlays, bubble windows) являются наиболее дорогими объектами из-за DOM.
Проблемы:
Пул позволяет держать фиксированное количество окон.
class InfoBubblePool {
constructor(map, size = 10) {
this.map = map;
this.pool = [];
for (let i = 0; i < size; i++) {
const bubble = new H.ui.InfoBubble({ lat: 0, lng: 0 }, {
content: ''
});
bubble.close();
this.pool.push(bubble);
}
}
acquire() {
return this.pool.pop() || null;
}
release(bubble) {
bubble.setContent('');
bubble.close();
this.pool.push(bubble);
}
}
Часто сброс всех свойств объекта при возврате в пул приводит к лишней нагрузке. Более эффективный подход — ленивый сброс.
Принцип:
release(marker) {
marker._id = null;
marker.setVisibility(false);
this.pool.push(marker);
}
При повторном использовании все значения перезаписываются полностью.
В сложных приложениях используется несколько уровней пулов:
Пример архитектуры:
Map
├── TrafficLayerPool
│ ├── VehicleMarkerPool
│ ├── IncidentMarkerPool
│
├── POILayerPool
│ ├── RestaurantMarkerPool
│ ├── HotelMarkerPool
│
└── RouteLayerPool
├── PolylinePool
├── ArrowOverlayPool
Такой подход позволяет:
При большом количестве точек (десятки тысяч объектов) пул используется совместно с кластеризацией.
Алгоритм:
function renderCluster(cluster, pool) {
const marker = pool.acquire();
marker.setGeometry(cluster.center);
marker.setData({
count: cluster.points.length
});
return marker;
}
Это снижает количество активных объектов в разы.
Ситуация, когда пул исчерпан, требует стратегии:
null;Наиболее стабильная стратегия — ограниченное расширение:
acquire() {
if (this.pool.length === 0) {
this.expand(10);
}
return this.pool.pop();
}
Распространённые проблемы:
inUse и
pool.Особенно критична ошибка двойного добавления:
map.addObject(marker);
map.addObject(marker); // приводит к дублированию слоёв
Использование пулов влияет на:
Наиболее заметный эффект наблюдается при сценариях:
Пул позволяет заменить модель:
create → render → destroy
на:
acquire → update → release → reuse
При интеграции с WebSocket или потоками данных:
socket.onmess age = (event) => {
const data = JSON.parse(event.data);
const marker = pool.acquire();
if (!marker) return;
marker.setGeometry(data.coords);
marker.setData(data);
};
Такой подход предотвращает деградацию производительности при всплесках данных.
Объекты внутри пула могут находиться в нескольких состояниях:
Чёткое управление состояниями предотвращает:
const STATE = {
IDLE: 0,
ACTIVE: 1,
RESETTING: 2
};
В приложениях, работающих часами или днями:
Однако требуется периодическая ревизия:
Пулы естественно интегрируются в слоистую архитектуру HERE Maps:
Такое разделение снижает связность и упрощает управление жизненным циклом объектов, которые отображаются на карте.