Метки на картах это просто. Кластеризация
от aNNiMON
Если фотографий много, то отображать их все сразу на карте — не самая лучшая идея, как в плане UX, так и в плане производительности. Поэтому давайте сгруппируем фотографии и покажем их количество.
Для группировки в OpenLayers есть кластер ol.source.Cluster. Из коробки он работает с точками (ol.geom.Point), так что для нашей задачи его будет несложно добавить. Задаём ему дистанцию и откуда брать точки (photosSource), а также переименуем layerThumbs в layerClusters:
clusterStyle похож на photoStyle, только рисует не иконки, а окружность с заливкой и обводкой, а поверх неё текст.
Так как отдельных фотографий теперь на карте нет, то выбирать можно только эти кружочки — придётся переписать функцию выбора:
При клике на карту собираем массив объектов, попадающих в область выбора и показываем детали уже для них.
📄 Исходный код
Можно показать и отдельные фотографии, если объект в кластере только один:
Можно усложнить логику выбора, добавив плавное приближение к фотографиям при нажатии на кластер:
Если выбрали круг с 2+ элементами и при этом есть ещё куда увеличивать карту, то перебираем все координаты в выбранном кластере и формируем из них виртуальную область extent:
затем в течение 400мс приближаем карту к этой области так, чтобы остались отступы по краям в 150 пикселей:
Демо:
📄 Исходный код
Для группировки в OpenLayers есть кластер ol.source.Cluster. Из коробки он работает с точками (ol.geom.Point), так что для нашей задачи его будет несложно добавить. Задаём ему дистанцию и откуда брать точки (photosSource), а также переименуем layerThumbs в layerClusters:
- const clusterSource = new ol.source.Cluster({
- distance: 30,
- minDistance: 10,
- source: photosSource
- });
- const layerClusters = new ol.layer.Vector({
- source: clusterSource,
- style: clusterStyle,
- updateWhileAnimating: true,
- updateWhileInteracting: true,
- });
- const map = new ol.Map({
- // ..
- layers: [layerOSM, layerClusters],
- // ..
- });
- function clusterStyle(feature, resolution) {
- const features = feature.get('features');
- const size = features.length;
- const key = `cl-${size}`;
- if (!cache[key]) {
- cache[key] = new ol.style.Style({
- zIndex: 110,
- image: new ol.style.Circle({
- radius: 12,
- stroke: new ol.style.Stroke({color: '#8AFFD9'}),
- fill: new ol.style.Fill({color: '#229D75'})
- }),
- text: new ol.style.Text({
- text: `${size}`,
- fill: new ol.style.Fill({color: '#fff'})
- }),
- });
- }
- return cache[key];
- }
Так как отдельных фотографий теперь на карте нет, то выбирать можно только эти кружочки — придётся переписать функцию выбора:
- map.on('click', event => {
- layerClusters.getFeatures(event.pixel).then(clickedFeatures => {
- if (!clickedFeatures.length) return;
- const features = clickedFeatures[0].get('features');
- const details = features.map(f => photoDetails(f));
- document.getElementById('photo-details').innerHTML = details.join();
- });
- });
📄 Исходный код
Можно показать и отдельные фотографии, если объект в кластере только один:
- function clusterStyle(feature, resolution) {
- const features = feature.get('features');
- const size = features.length;
- if (size === 1) return thumbStyle(features[0], resolution);
- // ..
Можно усложнить логику выбора, добавив плавное приближение к фотографиям при нажатии на кластер:
- map.on('click', event => {
- layerClusters.getFeatures(event.pixel).then(clickedFeatures => {
- if (!clickedFeatures.length) return;
- const features = clickedFeatures[0].get('features');
- if (features.length > 1 && !isMaximumZoom(map.getView())) {
- // Zoom in
- const extent = ol.extent.boundingExtent(
- features.map(r => r.getGeometry().getCoordinates()),
- );
- map.getView().fit(extent, {duration: 400, padding: [150, 150, 150, 150]});
- } else {
- // Show photo details
- const details = features.map(f => photoDetails(f));
- document.getElementById('photo-details').innerHTML = details.join();
- }
- });
- });
- function isMaximumZoom(view) {
- const maxZoom = view.getMaxZoom();
- const currentZoom = view.getZoom();
- return currentZoom >= maxZoom;
- }
- const extent = ol.extent.boundingExtent(
- features.map(r => r.getGeometry().getCoordinates()),
- );
- map.getView().fit(extent, {duration: 400, padding: [150, 150, 150, 150]});;
Демо:
📄 Исходный код