Google Maps API - Groupement de markers

Ajout de 900 markers à une carte, cluster, spiderfier et auto-boxing

Cette carte affiche les 878 villes (et cedex) françaises commençant par la lettre 'H'. Le code présenté ici est déjà utilisé en production pour une application qui gère 200 000 données géolocalisables. Cette application fournie une recherche multi-critères sur les différents attributs des données et permet ensuite de visualiser le résultat de la recherche sur une telle carte. Les utilisateurs apprécient cette fonctionnalité, mais ils ne savent pas qu'elle est relativement aisée à implémenter. Vous, en revanche, vous allez vous en rendre compte très vite. Enfin, sachez que jusqu'à 10 000 markers, les différents navigateurs se comportent bien sur la plupart des machines. Ensuite, nous arrivons à afficher 90 000 markers sur une bonne machine et sous Chrome, mais cela prend un peu de temps. Place au code maintenant !

Les librairies utilisées

Ce tutoriel utilise deux librairies spécifiques.

Marker Cluster V3

Cette librairie permet de regrouper les markers présents dans un même carré en un seul marker comme dans l'exemple ci-dessous.

Ici, la carte est divisée en carré de taille identique, la librairie compte le nombre de markers dans chaque carré et n'affiche finalement qu'un seul marker au centre du carré contenant le nombre de markers que ce cluster contient. Pour plus de lisibilité cette librairie a modifié l'icone qu'elle affiche (contraire à cette capture d'écran) et adopté un code couleur en fontion du nombre de markers contenus dans un cluster. Plus d'informations à cette adresse : Too Many Markers (en anglais).

La librairie peut être téléchargée (ou référencée) depuis cette URL : Marker Cluster (dans le dossier src)

Overlapping Marker Spiderfier V3

Cette librairie permet de gérer la situation ou plusieurs markers sont situés exactement au même endroit comme dans l'exemple ci-dessous

En cliquant sur le marker, la librairie représente sous la forme d'une toile d'araignée (d'ou son nom) tous les markers qui sont situés au même endroit. Cela fonctionne même avec 200 markers, même si ce n'est pas très pratique. Plus d'informations à cette adresse : OverlappingMarkerSpiderfier (en anglais).

La librairie peut être téléchargée (ou référencée) depuis cette URL : oms.min.js

Initialisation

La construction de la map est identique aux précédents exemples. On rajoute l'instantiation de la librairie Spiderfier via la classe OverlappingMarkerSpiderfier qu'on associe à notre map.

var options = {
	center: new google.maps.LatLng(47.90296, 1.90925),
	zoom: 6,
	mapTypeId: google.maps.MapTypeId.ROADMAP
};

var map = new google.maps.Map(document.getElementById("cluster"), options);

var oms = new OverlappingMarkerSpiderfier(map);

Ajout des markers

La méthode getCities() renvoit un tableau de tableaux de toutes les villes avec pour chaque entrée son nom, sa latitude et sa longitude (regardez le code source si besoin). Nous itérons ensuite sur ce tableau pour construire nos markers. Dans les options du marker, nous ne définissons pas la map associée comme nous l'avions fait précédement, cela sera fait par la suite. En revanche, un attribut city a été ajouté auquel on fourni le tableau correspondant à la ville pour laquelle on construit le marker. Il est possible d'ajouter n'importe quel attribut de cette façon. Cela s'avère très utile par la suite lorsque l'on doit gérer l'évènement click sur ce marker. Le marker est ajouté à un tableau qui nous servira un peu plus loin et également à l'objet oms qu'on a instantié.

var cities = getCities();
var markers = [];
for (var i = 0; i < cities.length; i++) {
	var markerOptions = {
		position: new google.maps.LatLng(cities[i][1], cities[i][2]),
		title: cities[i][0],
		city: cities[i]
	};
	var marker = new google.maps.Marker(markerOptions);
	markers.push(marker);
	oms.addMarker(marker);
}

Clusterisation

La création de nos cluster tient en une seule ligne. Il suffit d'instantier la classe MarkerClusterer, de lui fournir notre map, le tableau content tout nos markers (de type google.maps.Marker) et enfin un dernier paramètre très important, une liste d'options supplémentaires. Nous définissons uniquement l'option maxZoom qui indique à la librairie à partir de quel niveau de zoom celle-ci doit arrêter de faire des clusters. Prenons l'exemple de notre carte, si nous retirons ce paramètre alors en zoomant sur Honfleur nous verions un cluster de 10 markers sans possibilité d'accéder à ces markers. En précisant le paramètre, quand on arrive à un zoom de 9, le cluster est remplacé par un marker (ou par des markers dans d'autres situation). En cliquant dessus, la librarie Spiderfier prend le relais pour nous montrer les markers qui sont tous localisés au même point. (Mise à jour de 2016) Il faut maintenant préciser l'emplacement des images correspondant aux clusters via l'attribut imagePath.

var markerClusterer = new MarkerClusterer(map, markers, {
	maxZoom: 9, // maxZoom set when clustering will stop
	imagePath: 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m'
});

Auto-boxing de la map

Très pratique si vous utilisez ce tutoriel dans le cadre d'un outil de recherche, ce code permet de limiter le viewport (la zone visible de la map) à l'ensemble des markers que votre recherche a ramené. Ici le résultat, assez naturellement, limite le viewport à la France. Pour cela nous créons l'objet google.maps.LatLngBounds composé de deux objets google.maps.LatLng pour représenter un rectangle à partir des points situés dans les coins Sud-Ouest et Nord-Est de celui-ci. Ensuite nous parcourons notre tableau de markers et demandons à notre objet bounds de s'étendre afin de contenir la position de notre marker. Finalement, nous appelons la méthode fitBounds de notre map pour redéfinir correctement le viewport qui contiendra donc tous nos markers.

var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < markers.length; ++i) {
	bounds.extend(this.markers[i].position);
}
map.fitBounds(bounds);

Gestion des clicks sur les markers

Dans l'exemple précédent nous avons vu comment afficher une fenêtre d'information lorsque l'on click sur un marker. Nous reprenons ici l'objet google.maps.InfoWindow qui représente notre fenêtre mais cette fois-ci nous ne définissons pas tout de suite son contenu. De plus, plutôt que d'ajouter un listener sur notre marker nous ajoutons un listener global via notre objet oms. Ainsi nous n'aurons qu'un seul listener au lieu de 878, une petite économie de mémoire et de performance. La méthode addListener prend un type d'évènement et une fonction à exécuter dont le premier paramètre est le marker et le second l'évènement google à l'origine de l'action. Dans son traitement, nous pouvons dynamiquement définir le contenu du marker (via l'objet city que nous avions passé à sa construction) et l'afficher.

var infoWindow = new google.maps.InfoWindow();
oms.addListener('click', function(marker, event) {
	infoWindow.setContent(marker.city[0] + ", " + marker.city[1] + ", " + marker.city[2]);
	infoWindow.open(map, marker);
});

Un dernier point pour l'élégance

Sur la carte de ce tutoriel, zoomez légèrement avec la molette de votre souris vers Vannes. Juste au-dessus de Lorient il doit y avoir un cluster de 10 villes. Si vous cliquez dessus vous vous rendez compte qu'il contient une petite ville nommée Hennebont et la liste de ses cedex. Tous les markers du cluster sont donc exactement localisés au même point. Sans le code qui suit le zoom aurait donc été poussé à son maximum (21), c'est-à-dire à deux rues visibles sur tout le viewport. Ce n'est pas forcément très beau. Ce code ajoute un listener sur notre objet markerClusterer pour l'évènement clusterclick. Il définit une fonction qui calcule la taille du viewport du cluster puis si le zoom qui en résulte est supérieur à 14 redéfinit le zoom de la map à 14. Ainsi on obtient le résultat visible ci-dessous au lieu de voir deux rues sans contexte véritable. Bien sûr cela n'empèche pas l'utilisateur de zoomer lui-même avec sa molette au-delà du niveau 14.

google.maps.event.addListener(markerClusterer, 'clusterclick', function(cluster) {
	map.fitBounds(cluster.getBounds());
	if (map.getZoom() > 14) {
		map.setZoom(14);
	}
});

Avec le code

Sans le code

Code complet

Voici le code complet qui vous permettra de tester cet exemple dans une page HTML. Notez qu'il vous faudra remplir le tableau cities par vous-même. Soit en le copiant depuis le code source de cette page, soit en utilisant vos propres données.

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
		<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
		<script type="text/javascript" src="https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/src/markerclusterer.js"></script>
		<script type="text/javascript" src="https://jawj.github.com/OverlappingMarkerSpiderfier/bin/oms.min.js"></script>
	</head>

	<body>
		<div id="map" style="width:100%; height:100%"></div>
	</body>
</html>
<script type="text/javascript">
	var options = {
		center: new google.maps.LatLng(47.90296, 1.90925),
		zoom: 6,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	var map = new google.maps.Map(document.getElementById("map"), options);
	
	var oms = new OverlappingMarkerSpiderfier(map);
	
	var cities = [];
	var markers = [];
	for (var i = 0; i < cities.length; i++) {
		var markerOptions = {
			position: new google.maps.LatLng(cities[i][1], cities[i][2]),
			title: cities[i][0],
			city: cities[i]
		};
		var marker = new google.maps.Marker(markerOptions);
		markers.push(marker);
		oms.addMarker(marker);
	}
	
	var markerClusterer = new MarkerClusterer(map, markers, {
		maxZoom: 9, // maxZoom set when clustering will stop
		imagePath: 'https://cdn.rawgit.com/googlemaps/js-marker-clusterer/gh-pages/images/m'
	});

	var bounds = new google.maps.LatLngBounds();
	for (var i = 0; i < markers.length; ++i) {
		bounds.extend(this.markers[i].position);
	}
	map.fitBounds(bounds);
	
	var infoWindow = new google.maps.InfoWindow();
	oms.addListener('click', function(marker, event) {
		infoWindow.setContent(marker.city[0] + ", " + marker.city[1] + ", " + marker.city[2]);
		infoWindow.open(map, marker);
	});
	
	google.maps.event.addListener(markerClusterer, 'clusterclick', function(cluster) {
		map.fitBounds(cluster.getBounds());
		if (map.getZoom() > 14) {
			map.setZoom(14);
		}
	});
</script>

A suivre : Leaflet - Première Carte Voir le tutoriel

comments powered by Disqus