Яндекс.Карты, статья №3: Кластерер и большое количество маркеров

Яндекс.Карты, статья №3: Кластерер и большое количество маркеров

Всем привет!

Как вы помните, мы уже писали, что делать, если у вас получается много маркеров в Google Maps. Сегодня я хочу рассказать, что делать, если маркеров много на ваших Яндекс.Картах.

Сейчас я работаю над проектом, где мне нужно выводить много маркеров на карту, для которой используется API от Яндекс.Карт.

В процессе работы выяснилось, что для Яндекс.Карт, в отличии от Google Maps, готового кластерера нет. Поэтому мы написали свой, взяв за основу кластерер от Гуглокарт.

Предыстория

Как я уже сказал, в Яндекс.Картах нет встроенного механизма для кластеризации маркеров, как, собственно, и в Google Maps. Зато для карт Гугла есть куча плагинов, и есть из чего выбрать. В Яндекс.Картах же есть активные области и диспетчер объектов, но работа с ними у меня не заладилась.

В итоге я решил, что надо бы сделать кластерер для Яндекс.Карт. За основу был взят кластерер для Google Maps, который Женя Котельников, автор сервиса «Линчелка», переписал под API от Яндекс.Карт. За это ему отдельное спасибо ;-)

Кластерер для Яндекс.Карт

Подключаем Яндекс.Карты, затем сам кластерер:

<script type="text/javascript" src="http://api-maps.yandex.ru/1.1/index.xml?key="></script>
<script type="text/javascript" src="/js/map.yandex.clusterer.js"></script>

Создаем объект кластерера:

...
var cluster = new PlacemarkClusterer(map, null);
...

Добавляем в него маркеры (по одному или массивом):

...
cluster.addPlacemark(marker); // один маркер
...
cluster.addPlacemarks(markers); // сразу несколько
...

Теперь посмотрим на настройки для кластерера:

...
var cluster = new PlacemarkClusterer(map, null, {
gridSize: 60, // размер ячейки кластера
maxZoom: 16, // уровень зума, после которого показываются все маркеры
style: {
url: 'cluster.png', // адрес иконки группы маркеров кластера
height: 50, // высота
width: 50 // ширина
}
});
...

Для удаления маркера из кластера используется метод cluster.removePlacemark(placemark), а чтобы удалить все маркеры — cluster.clearPlacemarks():

...
cluster.removePlacemark(marker); // удалить указанный маркер из кластера
...
cluster.clearPlacemarkes(); // удалить все маркеры их кластера
...

Остальные методы можно посмотреть в самом файле. Скачать плагин можно ниже:

Скачать

Есть идеи для улучшения? Вопросы? Пожелания?

Не забудьте рассказать друзьям о нашем плагине — красивые кнопочки сразу после статьи там именно для этого! Заранее спасибо!

Расскажите друзьям

Оцените статью:
  • 1
  • 2
  • 3
  • 4
  • 5

Комментарии — 21

mihdan
О, чудо! Премного благодарен. В свое время отказался от Яндекс-карт из-за отсутствия кластеризации в них.
V@s3K
Извините, чем не устрил http://api.yandex.ru/maps/jsapi/doc/dg/tasks/how-to-group-objects.xml ?
По-моему он делает ровно то же самое, что вы написали.
#
V@s3K
Shock
Как раз вчера написал свой Clusterer для Я.карт. Спасибо =)
#
Shock
Shock
V@s3K, оно делает не то же самое. Кластерер - берет кучу иконок, которые стоят рядом и показывает их как одну. А по вашей ссылке - работа со всеми элементами из группы с помощью интерфейса группы
V@s3K
Оу, тогда беру свои слова назад. Полезно. Какая лицензия?
#
V@s3K
Shock
Автор, а как вы предлагаете работать с разными объектами? Делать разные Кластереры для каджого объекта?
Shock
Кстати, могу подсказать очень удобную идею обратного прохода массива:
// У вас:
for (var i = len - 1; i >= 0; --i) {
// Альтернатива:
for (var i = len; i--;) {
Евгений Котельников
Что вы имеете в виду под "разными объектами"? Сейчас скрипт работает только с метками на карте, если вам нужно для чего-то другого, то feel free, как говорится, модифицировать. Подозреваю, что обобщить для любого оверлея будет несложно.
#
Евгений Котельников  
Shock
У меня есть кафешки, парки, банки. Я не хочу, чтобы кафешки и банки схлопывались в один значёк.
Не понимаю привычки некоторых программистов на вопрос "а можно ли сделать так?" отвечать "лезь в исходники". Я ведь не требую фич, просто интересна идея.

Кстати, почему вы решили, что
placemark._$iconContainer.css('display', 'none');
// будет лучше(быстрее?) в итоге, чем
map.removeOverlay(placemark)


Просто предполжение, тесты, статья какая-нить или в мануале написано?
Евгений Котельников
Это прямая калька того, как сделано в гугловом кластерере (в гуглокартах есть встроенные методы show/hide/isHidden, здесь их пришлось таким вот изощрённым образом реализовывать самому). Видимо, у его разработчика были основания не удалять маркер из DOM, а скрывать его.

Про кафешки, банки и парки. Да, нужно будет делать по отдельному объекту кластерера для каждого типа маркеров. Только не забудьте реализовать эти самые show, hide и isHidden для кастомных маркеров.
#
Евгений Котельников  
properr
круто)
Гость_1
@Shock вопрос к вам: ваш альтернативный метод обхода массива подразумевает, что после обхода, вы этим массивом уже пользоваться не собираетесь, верно? Если нет, то я не понимаю эту конструкцию, ведь она будет удалять свойства из массива.
#
Гость_1
Дмитрий
По поводу улучшения: в оригинальном (для Гуглокарт) пастеризаторе есть возможность задавать разные иконки для разных групп маркеров (в зависимости от их количества).
Планируется ли такая возможность для карт Яндекса?
#
Дмитрий
Сергей
Большое спасибо за идею с массивом, очень помогло
Гость_1
@Shock спасибо, присмотрелся, понял как работает.
#
Гость_1
Shock
Обычно массив обходится прямым путём:

var array = ['zero', 'one', 'two', 'three'];
for (var i = 0, l = array.length; i < l; i++) {
  var item = array[i];
  console.log(i, array[i]);
}


Каждую итерацию i увеличивается на единицу и мы можем обратиться к соответствующему элементу массива. Результат будет такой:
0, zero
1, one
2, two
3, three


Я предлагаю почти такой же способ, но идти мы начинаем с последнего элемента.


var array = ['zero', 'one', 'two', 'three'];
for (var i = array.length; i--;) {
  var item = array[i];
  console.log(i, array[i]);
}


Результат будет такой:
3, three
2, two
1, one
0, zero


Такой способ можно использовать, когда нужен обратный порядок, или порядок не важен (например, нам нужно посчитать сумму всех элементов массива - начинать считать можно и с начала и с конца).

Механизм работы очень простой. i = array.length - присваиваем переменной i длину массива, в данном случае 4. Проверяем, не является ли это значение "нулевым". Если не является, но уменьшаем на единицу и выполняем блок кода. Таким образом, оно начнёт проходить с элемента array.length-1, а закончит элементом 1-1, то есть нулевым.

Естественно, элементы массива удаляться не будут. Мы не меняем ни массив ни свойство array.length
not_so_good
что-то не работает ваша приблуда у меня.
при клике на кластер происходит приближение к месту первой метки в кластере. и так до упора. постоянно видна пиктограмма кластера с неизменным кол-вом меток в нём. а самих меток так и не видно.
с чем это может быть связано?
#
not_so_good
not_so_good
я делаю выборку меток в аякс запросе
после того как получаю нужные мне маркеры
я делаю так

clusterer.clearPlacemarks();
clusterer.addPlacemarks(markers);

так вот выше преведнные код вызывает у меня проблемы, которые я описал в предыдущем комменте
но если убрать строчку clusterer.clearPlacemarks(); то всё работает как надо
только вот мне всё же нужна после каждого нового запроса.. стирать старые маркеры
#
not_so_good
not_so_good
приходится делать так

clusterer.clearPlacemarks();
clusterer = new PlacemarkClusterer(map, markers);

так же всё будет работать, если в Вашем плагине закомментить строчку

mcfn_.disable();

которая как, я понял, зачем-то отключает обновление вьюпорта при удалении всех маркеров, а при добавлении новых этого почему-то неделается.
может оно и логично, что нужно заново создавать объект clusterer, если очистил его полностью
а может я что-то не вкурил. не люблю в чужом коде копаться чёрт возьми
#
not_so_good
Александр
А не подскажите линк на работающий пример?
#
Александр
Hamsterman
Хочу для каждой метки задавать свой стиль (иконку). Не подскажете как это сделать, сейчас после кластеризации стили меток теряются, хотя дескрипшены остаются..?

Новый комментарий

как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть
как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть

metin2 pvp metin2 pvp serverler pvp serverler