Написать пост

Дьявол в деталях: интерактивная карта на OpenLayers. Часть 2

Аватарка пользователя Алевтина Морозова

Рассказали, как сделать интерактивную карту на сайте с помощью JS-библиотеки OpenLayers: как добавить остановки и отслеживать юзеров.

Обложка поста Дьявол в деталях:  интерактивная карта на OpenLayers. Часть 2

В первой части статьи от Антона Ефременкова, Senior web developer ITentika, вы уже поняли, почему был выбран данный стек и с чем пришлось столкнуться на стадии POC.

Однако это было только начало пути. При разработке новых модулей возникли следующие сложности, обусловленные использованием OpenLayers.

Проблемы остановок

В этот раз нам нужно было отобразить на карте остановки автобусов, троллейбусов и других транспортных средств.

Дьявол в деталях:  интерактивная карта на OpenLayers. Часть 2 1
Остановки на карте OpenLayers

Да, опять работа с маркерами. И да, опять некоторая логика, связанная с ними. К примеру, в дальнейшем маркер остановки соответствовал типу транспортного средства, а при клике на сам маркер вызывался попап с детальной информацией и некоторыми кнопками.

Используя тестовые данные, мы “накидали” на карту штук 500 остановок и решили посмотреть, насколько шустро они отобразятся на карте:

Судя по трекеру FPS в левом верхнем углу карты, OpenLayers не испытывает каких-то проблем с производительностью на таком объёме данных. И тогда мы решили посмотреть, что будет, если мы отобразим количество остановок, близкое к данным продакшн-сервера. Что-то около 10 тысяч:

Как видно по трекеру FPS, на этом моменте жизнь нашей страницы остановилась. Причём, по мере приближении карты страница оживает. Но тем не менее, сдавать фичу в таком виде мы не имели права.

У нас было несколько гипотез, объясняющих такое поведение карты, но ни одна из них не оправдалась. Кластеризацию маркеров на карте применить мы не смогли, потому как это шло вразрез с требованиями, поэтому мы устроили очередное небольшое исследование. Довольно скоро выяснилось, что слой, на котором мы располагали маркеры ТС (VectorLayer) просто не предназначен для отображения большого количества элементов, и для такого случая рекомендуют использовать слой другого типа – VectorImageLayer. У этого слоя есть свои ограничения, но для нашего случая он вполне подошёл, что позволило отобразить все 10к остановок без особой боли и перейти к последней задаче нашего проекта, вызывавшей ещё бОльшие опасения.

Проблемы трека движения ТС

В этой части проекта мы должны были показать путь, пройденный транспортным средством для дальнейшего сравнения с маршрутом, по которому это ТС должно было двигаться. Для этого мы должны отобразить геометрические объекты двух видов:

  • ломаную линию, привязанную к графу
    дорожной сети (собственно, тот самый путь, пройденный ТС)
  • координаты, фактически переданные
    транспортным средством
Дьявол в деталях:  интерактивная карта на OpenLayers. Часть 2 2

Если учесть интервал, с которым мы получаем координаты ТС, а также максимальный диапазон времени, за который мы должны уметь строить такой трек, у нас получалось что-то около 86 тысяч маркеров, одновременно отображаемых на карте.

Помните проблемы перформанса, о которых я писал ранее?)

Естественно, мы готовились к худшему. Но тяга к экспериментам взяла своё, и мы решили проверить, насколько безнадёжно страница умрёт, если мы отобразим все 86к координат на слое VectorLayer:

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

Итоги

Дальнейшая разработка шла уже без сюрпризов, и библиотека OpenLayers раз за разом показывала себя с хорошей стороны. Поэтому, резюмируя, я могу сказать, что эту библиотеку действительно можно использовать в проектах любого размера (особенно если у вас не будет другого выбора – подобно нашей ситуации), и со своей задачей она справится.

Но всё же, давайте пофантазируем на тему “а что было бы, если бы мы могли использовать Leaflet, а не OpenLayers?”

Прежде всего, Leaflet, в отличие от OpenLayers, умеет отрисовывать различные маркеры в DOM’e. Поэтому код подключения карты и стилизации маркеров транспортных средств уместился бы в этих двух блоках:

			const map = L.map('map').setView(L.latLng(coordinates), 12);
const wmsLayer =
  L.tileLayer.wms(url, { layers: 'default' }).addTo(map);

for (const vehicle of vehicles) {
  new L.marker(L.latLng([vehicle.latitude, vehicle.longitude]),
  {
    icon: new L.DivIcon({
      className: 'vehicle-marker',
      html: getMarkerHtml(vehicle),
    })
  }).addTo(map);
}
		
			function getMarkerHtml(vehicle) {
  return `
    
      ${vehicle.boardNumber}
      ${getStatusPart(vehicle.vehicleStatus)}
      ${vehicle.hasEquipFault ?
          '' : ''
      }
    
    `;
}

function getStatusPart(status) {
  if (status < 4) return '';

  const cssClass = `vh-status ${getStatusClass(status)}`;  
  return ``;
}
		

Ну и само собой, тут используется некоторое количество обычный CSS-стилей. В любом случае, мы работаем с обычной вёрсткой, поэтому в плане стилизации наши руки развязаны, да и код получается понятнее. А результат на выходе тот же самый, только получен он значительно быстрее:

Дьявол в деталях:  интерактивная карта на OpenLayers. Часть 2 3

Если говорить об отображении действительно большом количестве объектов на карте, то, само собой, выводить их в DOM не нужно – опять-таки, это убьёт страницу. И тут Leaflet проявляет свойственную ему гибкость: при необходимости, маркеры могут быть отрисованы на canvas’e:

			const icon = L.icon({
  iconUrl: './assets/marker_stop.svg',
  iconSize: [12, 12],
  iconAnchor: [8, 8]
});

const map = L.map('map').setView(L.latLng(coordinates), 12);
const markersCanvas = new L.MarkersCanvas();
const wmsLayer =
  L.tileLayer.wms(url, { layers: 'default' }).addTo(map);

let markers = [];
for (const point of points) {
  markers.push(createMarker(point));
}

markersCanvas.addMarkers(markers);
markersCanvas.addTo(map);

function createMarker(point) {
  return new L.marker(L.latLng([point.y, point.x]), {
    icon: icon,
  });
}
		

Даже если отобразить на карте те же 10к остановок, никаких значительных проблем в производительности мы не встретим:

А этот код нарисует на карте трек движения ТС и все имеющиеся координаты:

			const map = L.map('map').setView(L.latLng(coordinates), 12);
const markersCanvas = new L.MarkersCanvas();
const wmsLayer = 
  L.tileLayer.wms(url, { layers: 'default' }).addTo(map);

let markers = [];
for (const point of trackData.rawPoints) {
  markers.push(createMarker(point));
}
markersCanvas.addTo(map);
markersCanvas.addMarkers(markers);

const coords = tracks.flatMap(t => JSON.parse(t.track));
const polyline = new L.Polyline(coords, {
  color: '#8C8C8C',
  weight: 3,
  opacity: 1,
  smoothFactor: 1
}).addTo(map);
		

Всё ещё никаких значительных проблем с производительностью:

Возможно, по совокупности этих факторов эта библиотека и является самой популярной в своём секторе. Если вам можно её использовать, конечно же =)

Следите за новыми постами
Следите за новыми постами по любимым темам
630 открытий2К показов