Кластеризация меток на картах Google

Автор: | 05.02.2012

На страницах своего блога, я рассказывал о том, как можно использовать кластеризацию на Яндекс.Картах («Кластеризация меток на Яндекс.Картах – два решения» ).

Как работает кластеризация?

Вся область карты разбивается на прямоугольники фиксированного размера, все метки, попадающие в заданный прямоугольник при данном масштабе заменяются одной меткой с указанием количества исходных.

В этой заметке я покажу как использовать тот же подход, для вывода большого числа маркеров с использованием API Google Maps v3.

Для реализации кластеризации на Google Maps существует специальная javascript-библиотека MarkerClusterer.

Покажу на нескольких примерах, как ей пользоваться.

В начале нам нужно подготовить исходные данные для примеров.

Это координаты расположения терминалов QIWI (555 – штук) в Нижнем Новгороде (использовались и в заметке«Кластеризация меток на Яндекс.Картах – два решения»).

Файл Qiwi-terminal.sql

Используя PhpMyAdmin, добавляем их в нашу базу данных MySQL.

Для вывода данных из базы и формирование файла в формате JSON, мы будем использовать файл vivmarkers.php

Его исходный код:

<?php
 
include("config.php");
 
header('Content-Type: text/html; charset=utf-8');
 
if($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
 
$json = '{markers:['."n";
 
$query1= "SELECT * FROM qiwiterminal";
$result1 = mysql_query($query1);
 
if(mysql_num_rows($result1)>0)
{
while ($par1 = mysql_fetch_array($result1)){
 
$cname =  'Теминал QIWI';
 
$exp_str = explode(",", $par1['coords']);
 
$lat = $exp_str[0];
 
$lng = $exp_str[1];
 
$json.= "n".'{'.'"id": "'.$par1['id'].'",';
 
$json.= '"cname": "'.$cname.'",';
 
$json.= '"address": "'.$par1['address'].'",';
 
$json.= '"lat": "'.$lat.'",';
 
$json.=  '"lon": "'.$lng.'"';
 
$json.= '},';
 
}
 
$json = substr($json, 0,-1);
 
echo  $json;
 
echo '], ', "n",  '"status": "OK"', "n", '}';
 
}
else
{
echo '{"status": "false"}';
}
 
}
 
 
?>

Код файла config.php для подключения к базе данных:

<?php
 
$sdb_name = "localhost";
$user_name = "root";
$user_password = "";
$db_name = "gmap_bd";
 
// соединение с сервером базы данных
if(!$link = mysql_connect($sdb_name, $user_name, $user_password))
{
  echo "<br>Не могу соединиться с сервером базы данных<br>";
  exit();
}
 
// выбираем базу данных
if(!mysql_select_db($db_name, $link))
{
  echo "<br>Не могу выбрать базу данных<br>";
  exit();
}
 
mysql_query('SET NAMES utf8');
 
?>

Теперь когда исходные данные готовы рассмотрим реализацию примеров.

Первый пример кластеризации.

При загрузке карты мы видим, что маркеры сгруппированы в отдельные кластеры, с указанием числа входящих в них маркеров.

При каждом изменении масштаба, выполняется тот же алгоритм кластеризации.

С определенного уровня, мы наблюдаем отдельные маркеры.

Посмотреть пример в действии

Код файла my_primer_1.html:

<!DOCTYPE>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>MarkerClusterer v3 пример использования</title>
 
    <style type="text/css">
      body {
        margin: 0;
        padding: 10px 20px 20px;
        font-family: Arial;
        font-size: 16px;
      }
 
      #map-container {
        padding: 6px;
        border-width: 1px;
        border-style: solid;
        border-color: #ccc #ccc #999 #ccc;
        -webkit-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        -moz-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        box-shadow: rgba(64, 64, 64, 0.1) 0 2px 5px;
        width: 800px;
      }
 
      #map {
        width: 800px;
        height: 600px;
      }
 
    </style>
 
    <script src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
	<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer.js"></script>
 
 
	<script type="text/javascript">
      function initialize() {
        var center = new google.maps.LatLng(56.316667, 44.0);
 
        var map = new google.maps.Map(document.getElementById('map'), {
          zoom: 11,
          center: center,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        });
 
        var markers = [];
           $.getJSON("vivmarkers.php", function (json) {
        if (json.status == 'OK') {
            var markers = [];
 
            for (var i = 0; i < json.markers.length; i++) {
 
          var latLng = new google.maps.LatLng(json.markers[i].lon,
              json.markers[i].lat);
          var marker = new google.maps.Marker({
            position: latLng,
			title: json.markers[i].address
          });
          markers.push(marker);
        }
        var markerCluster = new MarkerClusterer(map, markers);
		 } else {
            alert('Произошла ошибка!');
        }
    });
 
      }
      google.maps.event.addDomListener(window, 'load', initialize);
    </script>
  </head>
  <body>
    <h3>MarkerClusterer v3 пример использования</h3>
    <div id="map-container"><div id="map"></div></div>
  </body>
</html>

Рассмотрим код файла подробно.

В начале мы задаем стили для отображения окна с картой, затем подключаем необходимые javascript-библиотеки: API Google Maps v3, jQuery и MarkerClusterer.

В функции initialize мы задаем начальные параметры нашей карты: координаты центра, уровень масштаба и тип карты.

После этого используя функцию getJSON, мы загружаем исходные данные о маркерах, передаваемые из файла vivmarkers.php в массив markers.

После этого, кластеризатору через метод new MarkerClusterer, мы передаем указательна карту и массив меток.

И он после обработки, выводит их на карту.

Во втором примере, при клике по маркеру, мы будем выводить балун с информацией.

Посмотреть пример в действии

Код файла my_primer_2.html:

<!DOCTYPE>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>MarkerClusterer v3 пример использования - информация в балуне</title>
 
    <style type="text/css">
      body {
        margin: 0;
        padding: 10px 20px 20px;
        font-family: Arial;
        font-size: 16px;
      }
 
      #map-container {
        padding: 6px;
        border-width: 1px;
        border-style: solid;
        border-color: #ccc #ccc #999 #ccc;
        -webkit-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        -moz-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        box-shadow: rgba(64, 64, 64, 0.1) 0 2px 5px;
        width: 800px;
      }
 
      #map {
        width: 800px;
        height: 600px;
      }
 
    </style>
 
    <script src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
	<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer.js"></script>
 
	<script type="text/javascript">
	var map;
 
function initialize() {
    var center = new google.maps.LatLng(56.316667, 44.0);
 
    map = new google.maps.Map(document.getElementById('map'), {
        zoom: 11,
        center: center,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    });
 
    var infowindow = new google.maps.InfoWindow({
        content: ''
    });
 
    $.getJSON("vivmarkers.php", function (json) {
        if (json.status == 'OK') {
            var markers = [];
 
            for (var i = 0; i < json.markers.length; i++) {
 
                var baloon = '<div><strong>' + json.markers[i].cname + '</strong><br>' + json.markers[i].address + '</div>'
 
                var marker = add_marker(json.markers[i].lon, json.markers[i].lat, json.markers[i].cname, baloon);
 
 
                markers.push(marker);
            }
            var markerCluster = new MarkerClusterer(map, markers);
        } else {
            alert('Произошла ошибка!');
        }
    });
}
 
function add_marker(lat, lng, title, box_html) {
 
    var infowindow = new google.maps.InfoWindow({
        content: box_html
    });
 
    var marker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lng),
        map: map,
        title: title
    });
 
    google.maps.event.addListener(marker, 'click', function () {
        infowindow.open(map, marker);
    });
 
    return marker;
}
 
$(document).ready(
function () {
    initialize();
    return true;
});
 
    </script>
  </head>
  <body>
    <h3>Пример использования кластеризации - информация в балуне</h3>
    <div id="map-container"><div id="map"></div></div>
  </body>
</html>

Отличие от первого примера — это дополнительная фуккция add_marker, которая формирует маркер и добавляет содержимое в его балун, отображаемое при клике на маркере.

Также, мы вынесли объявление объекта карты map в глобальные переменные.

У библиотеки MarkerClusterer есть дополнительные параметры, которые позволяют управлять порядком кластеризации.

Основные из них: gridSize — размер ячейки сетки в пикселях, maxZoom — максимальный масштаб для кластеризации, styles — позволяет задавать стиль отображения кластеров.

Подробнее

Третий пример, задание своего стиля для отображения кластеров.

Посмотреть пример в действии

Рассмотрим код файла my_primer_3.html:

<!DOCTYPE>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>MarkerClusterer v3 пример использования - свой стиль маркеров</title>
 
    <style type="text/css">
      body {
        margin: 0;
        padding: 10px 20px 20px;
        font-family: Arial;
        font-size: 16px;
      }
 
      #map-container {
        padding: 6px;
        border-width: 1px;
        border-style: solid;
        border-color: #ccc #ccc #999 #ccc;
        -webkit-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        -moz-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        box-shadow: rgba(64, 64, 64, 0.1) 0 2px 5px;
        width: 800px;
      }
 
      #map {
        width: 800px;
        height: 600px;
      }
 
    </style>
 
    <script src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
	<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer.js"></script>
 
	<script type="text/javascript">
	var map;
 
	 var styles = [ [{
        url: '../images/znak30.png',
        height: 35,
        width: 30,
        anchor: [4, 0],
        textColor: '#f0ff3d',
        textSize: 10
      }, {
        url: '../images/znak40.png',
        height: 46,
        width: 40,
        anchor: [8, 0],
        textColor: '#ff0000',
        textSize: 11
      }, {
        url: '../images/znak50.png',
        width: 50,
        height: 58,
        anchor: [12, 0],
        textSize: 12
      }]];
 
	  var imageUrl = '../images/znak.png';
 
 
function initialize() {
    var center = new google.maps.LatLng(56.316667, 44.0);
 
    map = new google.maps.Map(document.getElementById('map'), {
        zoom: 11,
        center: center,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    });
 
    var infowindow = new google.maps.InfoWindow({
        content: ''
    });
 
	    $.getJSON("vivmarkers.php", function (json) {
        if (json.status == 'OK') {
 
            var markers = [];
 
			var markerImage = new google.maps.MarkerImage(imageUrl, new google.maps.Size(24, 28));
 
            for (var i = 0; i < json.markers.length; i++) {
 
                var baloon = '<div><strong>' + json.markers[i].cname + '</strong><br>' + json.markers[i].address + '</div>'
 
                var marker = add_marker(json.markers[i].lon, json.markers[i].lat, json.markers[i].cname, baloon, markerImage);
 
 
                markers.push(marker);
            }
 
var mcOptions = {gridSize: 50, maxZoom: 15, styles: styles[0]};
 
        markerClusterer = new MarkerClusterer(map, markers, mcOptions);
        } else {
            alert('Произошла ошибка!');
        }
    });
}
 
function add_marker(lat, lng, title, box_html, markerImage) {
 
    var infowindow = new google.maps.InfoWindow({
        content: box_html
    });
 
    var marker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lng),
        map: map,
        title: title,
		icon: markerImage
    });
 
    google.maps.event.addListener(marker, 'click', function () {
        infowindow.open(map, marker);
    });
 
    return marker;
}
 
$(document).ready(
function () {
    initialize();
    return true;
});
 
    </script>
  </head>
  <body>
    <h3>Пример использования кластеризации - свой стиль маркеров</h3>
    <div id="map-container"><div id="map"></div></div>
  </body>
</html>

В самом начале, мы определяем слиль для отображения кластеров

 var styles = [ [{
        url: '../images/znak30.png',
        height: 35,
        width: 30,
        anchor: [4, 0],
        textColor: '#f0ff3d',
        textSize: 10
      }, {
        url: '../images/znak40.png',
        height: 46,
        width: 40,
        anchor: [8, 0],
        textColor: '#ff0000',
        textSize: 11
      }, {
        url: '../images/znak50.png',
        width: 50,
        height: 58,
        anchor: [12, 0],
        textSize: 12
      }]];

Для разных значений размера ячеки сетки: 50, 40 и 30.

Иконку для отображения маркеров задает строка

var imageUrl = ‘../images/znak.png’;

Параметры для значка маркера строка

var markerImage = new google.maps.MarkerImage(imageUrl, new google.maps.Size(24, 28));

При вызове кластеризатора, мы передаем дополнительный параметр с опциями mcOptions:

var mcOptions = {gridSize: 50, maxZoom: 15, styles: styles[0]};

И в качестве дополнения, я покажу еще один пример использования кластеризатора, когда исходные данные о маркерах передаются в файле формата XML.

Посмотреть пример в действии

Созданием XML-файла с данными занимается скрипт vivmarkers_xml.php

Его код:

<?php
 
include("config.php");
 
header('Content-Type: text/xml; charset=utf-8');
 
if($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
 
$dom = new DOMDocument("1.0");
$node = $dom->createElement("markers");
$parnode = $dom->appendChild($node); 
 
// Select all the rows in the markers table
$query = "SELECT * FROM qiwiterminal";
$result = mysql_query($query);
if (!$result) {  
  die('Invalid query: ' . mysql_error());
} 
 
// Iterate through the rows, adding XML nodes for each
 
while ($row = @mysql_fetch_assoc($result)){  
$cname =  'Теминал QIWI';
$exp_str = explode(",", $row['coords']);
$lat = $exp_str[0];
$lng = $exp_str[1];
 
  // ADD TO XML DOCUMENT NODE  
  $node = $dom->createElement("marker");  
  $newnode = $parnode->appendChild($node);   
  $newnode->setAttribute("cname",$cname);
  $newnode->setAttribute("address", $row['address']);  
  $newnode->setAttribute("lat", $lat);  
  $newnode->setAttribute("lon", $lng); 
} 
 
echo $dom->saveXML();
 
}
 
?>

Для формирования XML-файла я использую DOM-функции PHP версии 5.

В PHP нужно инициализировать новый XML-документ и создать родительский узел “markers”.

Затем нужно подключиться к БД и произвести выбор необходимых данных при помощи запроса вида
SELECT * (выбрать все) к таблице с именем “qiwiterminal” и пройтись по всем результатам этой выборки.

Для каждой записи в таблице будет создаваться свой узел в XML-документе, атрибуты которого будут полями из соответствующей записи таблицы, — этот узел будет присоединяться к родительскому узлу. После этого Вы получите готовый XML-документ.

Код файла my_primer_4.html:

<!DOCTYPE>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>MarkerClusterer v3 пример использования - данные в XML</title>
 
    <style type="text/css">
      body {
        margin: 0;
        padding: 10px 20px 20px;
        font-family: Arial;
        font-size: 16px;
      }
 
      #map-container {
        padding: 6px;
        border-width: 1px;
        border-style: solid;
        border-color: #ccc #ccc #999 #ccc;
        -webkit-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        -moz-box-shadow: rgba(64, 64, 64, 0.5) 0 2px 5px;
        box-shadow: rgba(64, 64, 64, 0.1) 0 2px 5px;
        width: 800px;
      }
 
      #map {
        width: 800px;
        height: 600px;
      }
 
    </style>
 
    <script src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
	<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/src/markerclusterer.js"></script>
 
	<script type="text/javascript">
      function initialize() {
        var center = new google.maps.LatLng(56.316667, 44.0);
 
        var map = new google.maps.Map(document.getElementById('map'), {
          zoom: 11,
          center: center,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        });
 
        var markers = [];
 
	$.ajax({
    type: "GET",
    url: "vivmarkers_xml.php",
    dataType: "xml",
    success: function(xml) {
			$(xml).find("marker").each(function(){
	      	var lat = $(this).attr('lon');
		    var lng = $(this).attr('lat');				
		    var latLng = new google.maps.LatLng(parseFloat(lat), parseFloat(lng));
 
			var marker = new google.maps.Marker({
				position: latLng,
				title: $(this).attr('address')
			});
          markers.push(marker);
		           });
 
 
        var markerCluster = new MarkerClusterer(map, markers);
		}
		 });
 
      }
      google.maps.event.addDomListener(window, 'load', initialize);
    </script>
  </head>
  <body>
    <h3>Пример использования кластеризации - данные в XML</h3>
    <div id="map-container"><div id="map"></div></div>
  </body>
</html>

Здесь отличие от первого примера в AJAX запросе данных в формате XML, а также в обработке полученных данных.

Еще несколько ссылок на примеры использования MarkerClusterer идущие вместе с ним:

A simple example of MarkerClusterer (100 markers);
An example of MarkerClusterer v3;
An example of MarkerClusterer v3 speed test

Загрузить саму библиотеку со всеми примерами можно здесь.

Кластеризация меток на картах Google: 10 комментариев

  1. Dmitry

    Я бы еще порекомендовал добавить в описание MarkerClustererPlus, текущяа версия

    http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.7/docs/reference.html

    Т.к. он и быстрее, и у него есть то, чего нет у обычного кластерера — вынос из кластера массива меток

    removeMarkers(markers:Array., opt_nodraw?:boolean)

    Иначе если Вы будете удалять по одной метке — получите сильную задержку, я так понимаю из-за отрисовки после изменения содержимого кластера, т.е. в случае удаления целого массива, перерисовка производится после удаления, а не после каждого маркера 🙂 Это только догадки, хотя можно посмотреть код

  2. Рома

    Отличная штука! Я так понимаю — если я хочу, чтобы в кластерах отображались объекты недвижимости, то данные будут браться из базы данных недвижимости? Каким образом будут ставиться метки? Может есть где почитать — где кроме Панорамио можно ставить метки на карте?
    Спасибо!

  3. chunya

    отличная статья, благодаря ей нанес на карту членов ассоциации с кластеризацией!

    спасибо автору!

  4. Иван

    Не встречались ли с такой задачей:
    Есть скажем 2 группы меток (отображаются ввиде списка рядом с картой): по несколько меток в группе А и несколько в группе Б.
    При нажатии на группу А отображаются все метки этой группы, а если выбрать одну из меток в группе А, то отобажается только она. Тоже самое с Группой Б.
    Есть решение?

  5. Валерия

    Спасибо громаадное за статью!! Очень помогла! Благодарю автора!!!

  6. Андрій

    Статья — как и другие — на высоте, спасибо автору

    У меня возник такой вопрос:
    в примере 2 (среди отметок — справа, клик по числу 38, потом при увеличении к отметкам примерно по адресу Казанское шоссе, дом 10х3) есть кластер из 3 отметок, но при самом большом зуме он так на отдельные отметки не расспадается. Думаю, это из-за того, что все 3 — находяться на одном адресе…

    Вот как их показать отдельно? отключить кластеризацию?

  7. Андрій

    у меня это (пока в разработке, не пинайте)
    http://mapeteua/infrastruktura
    выбрать «Місцеве самоврядування»
    примерно по центру — кластер с числом 24, клик
    улица «Листопадова» (слева) кластер с числом 12, клик

    по адресу Листопадова, 5 (это горсовет) получается как бы одинарная иконка (или 12), вот как можна вывести их отдельно?

  8. Дмитрий

    Спасибо Андрею за вопрос, тоже стало интересно, как разделить метки, которые находятся по одному адресу?..

  9. lesta

    Подскажите пожалуйста, никак не могу подружить данные скрипты с версией query 1.7
    Уже совсем измучалась

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *