Xamarin Android – Regrouper des markers Google Map avec le « Clustering »

L’idée d’écrire un article sur le Clustering de Google Map m’est venue suite à une problématique que j’ai rencontrée en développant l’application Moovenow. Moovenow, entre autre, affiche une carte Google Map répertoriant toutes les infrastructures sportives de France, toutes les associations sportives de France, ainsi que les événements sportifs organisés sur la plateforme. Cela représente potentiellement plus de 300 000 markers à afficher sur un écran de smartphone !

La problématique est double : non seulement le temps de chargement ne doit pas affecter l’utilisateur, mais surtout les informations doivent être compréhensibles par l’utilisateur ! Afficher 300 000 markers n’est pas anodin, d’autant plus que chaque marker ne représente pas le même type de donnée.

Pour répondre à cette problématique, j’ai décidé d’utiliser l’API de Google Map. Elle est accessible sur Xamarin grâce à un package Nuget.

 

Initialisation, configuration

Pour commencer, ajoutez les package Nuget Xamarin.Android.Support.Design et Xamarin.Android.Maps.Utils à votre projet Android.

Pour utiliser l’API Google Map, il vous faut créer un ID d’API Google Map : https://developers.google.com/maps/documentation/android-api/signup?hl=fr, paragraphe Obtenir une clef d’API. Ajoutez ensuite cette clef dans le manifest comme ci-dessous, à la place de [api_key_value]. Profitez-en également pour ajouter les permissions et le thème Appcompat :

Vous devrez peut être activer le Multi Dex si votre solution comprend plus de 64 000 méthodes. Cela se manifeste par une erreur « Java exit with code 2 » lors de la compilation.

 

Comment afficher une carte Google Map ?

L’API Google Map accessible via le package Nuget ajouté précédemment nous permet d’afficher très facilement une carte Google Map. Il faut simplement ajouter une vue nommée MapView à un Layout. Cette MapView est configurable à souhait, mais je ne rentrerai pas dans ces détails ici. Si cela vous intéresse, tout est très bien expliqué dans la documentation développeur. Voici le fichier AXML de l’activité qui va afficher la carte :

 

Et voici maintenant le code de cette activité :

Il n’y a plus qu’à compiler et déployer sur votre smartphone, et voici le résultat :
Google Map Android

 

Comment ajouter des markers qui se regroupent lorsqu’ils sont trop nombreux au même endroit ?

Ajouter un marker sur une carte se fait très facilement, en une seule ligne de code :

Mais ce qui nous intéresse, c’est d’ajouter de nombreux markers sur une carte. Et quand ces markers sont trop nombreux au même endroit, on veut les regrouper pour garder une carte propre et lisible. Cela s’appelle faire du « Clustering » (regroupement en français).

Pour faire cela, nous utiliserons la classe ClusterManager proposée par l’API de Google Map. Et au lieu d’ajouter des objets MarkerOptions à notre MapView, nous ajouterons des ClusterItem à notre ClusterManager. Ce dernier gérera tout seul l’affichage de marker ou de regroupement de marker.

Voici le code de la classe ClusterItem, qui doit impérativement implémenter l’interface IClusterItem :

 

Pour mettre en place le ClusterManager et y ajouter des ClusterItem, il faut retourner dans le code de notre Activité. Pour l’exemple, j’ajoute des markers en spirale en partant du centre de Grenoble, d’où les quelques lignes de code de mathématique (mais il ne faut pas prendre peur 😉 ) :

Comme je vous le disais plus haut, le ClusterManager fait tout le boulot tout seul !!

Il n’y a rien de plus à faire. Compilez, déployez sur notre téléphone, et amusez-vous à zoomer/dézoomer pour voir les markers se regrouper :

Google Map Android Clustering Markers

 

Comment personnaliser le rendu graphique des markers et markers regroupés ?

Nous sommes d’accord ! Il est impensable de garder l’affichage par défaut des markers de Google. Il faut les personnaliser pour améliorer l’image de marque de votre application. Pas d’inquiétude, cela se fait très bien et nous allons voir comment.

Pour commencer, comprenez que tous les éléments affichés dans une carte sont des images. Pour les markers simple, cela ne pause pas de problème : l’image est toujours la même. Mais pour les regroupements de markers, le texte change en fonction du nombre de marker regroupés, et là ça pause un problème ! Mais tout va bien, les ingénieurs de Google ont pensé à nous 🙂

Pour modifier le rendu visuel des regroupements de markers, nous allons créer une vue composée d’une ImageView et d’une TextView, configurer cette vue avec l’image de notre choix et le text de notre choix (ici le nombre de markers regroupés), et nous transformerons cette vue en image afin que la MapView l’affiche correctement par le biais de notre ClusterManager. Voici le code AXML :

 

Pour que notre ClusterManager utilise le rendu visuel personnalisé, nous devons créer un ClusterRenderer. C’est grâce à ce renderer que nous pourrons spécifier au ClusterManager quelle image afficher dans quel cas. Par exemple, quand il faut afficher un marker simple, nous voulons afficher l’image nommée « marker ». Et quand il faut afficher un regroupement de marker, il faut afficher l’image « marker_cluster_grouped » + le texte indiquant le nombre de marker regroupés. Tout est disponible dans mon Github si vous souhaitez récupérer les images.

Tout à l’heure je vous disais que les ingénieurs de Google avaient pensé à nous. Ils mettent à votre disposition la classe IconGenerator afin de nous faciliter la tâche. Grâce à notre IconGenerator, nous pouvons créer l’image de regroupement de marker très facilement à partir du AXML créé précédement. Je vous laisse prendre quelques minutes pour tout comprendre avec le code ci-dessous :

 

Maintenant que le ClusterRenderer est prêt, il faut configurer notre ClusterManager pour qu’il utilise ce renderer :

 

Vous pouvez maintenant admirer le fruit de votre travail ! C’est quand même plus sympa que les images par défaut n’est-ce pas ?

Google Map Android Clustering Markers

 

Comment personnaliser les InfoWindow ?

Les InfoWindows sont les petites fenêtres qui apparaissent lorsqu’un utilisateur clique sur un marker. Par défaut, ces fenêtres affichent le titre et la description associés au Marker. Vous vous rappelez ? Ils sont en paramètre de la classe ClusterItem.

En revanche, lorsqu’un utilisateur clique sur un regroupement de marker, il ne faut pas afficher d’InfoWindow. Au lieu de cela, il faut centrer la carte autour de ce point.

Comment afficher une InfoWindow par défaut ?

Voici un exemple très rapide pour illustrer mes explications précédentes. Le code ci-dessous permet au ClusterRenderer de modifier le MarkerOptions qui est sur le point d’être affiché sur la carte afin de valoriser son titre et sa description. De ce fait, lorsque l’utilisateur cliquera sur un marker, une InfoWindow apparaîtra afin d’afficher le titre et la description :

Google Map Android Clustering Markers

 

Comment afficher une InfoWindow personnalisée ?

Vous souhaitez afficher une image particulière dans votre InfoWindow ? Ou une RatingBar par exemple ? Aucun problème ! Il est possible de personnaliser l’InfoWindow avec notre propre vue basée sur un fichier AXML. Nous allons voir pas à pas comment mettre cela en place avec notre ClusterManager.

Pour commencer, voici le fichier AXML que je vais utiliser. Il affiche une image, un titre, du texte et une RatingBar :

 

Voici maintenant le code pour l’InfoWindowAdapter, qui permet de valoriser notre fichier AXML créé précédemment :

 

En temps normal (hors Clustering) pour utiliser une InfoWindow personnalisée, il faut créer un InfoWindowAdapter puis configurer notre MapView pour qu’elle utilise cet adapter. En Clustering, si nous faisons cela, l’InfoWindow apparaitra même si l’utilisateur clique sur un regroupement de marker. Ce n’est pas bon !

C’est pourquoi l’astuce consiste à laisser notre ClusterManager gérer les InfoWindows. Avec le code ci-dessous, nous allons dire à notre ClusterManager qu’il doit utiliser notre InfoWindowAdapter personnalisé créé précédemment, pour les markers simples uniquement. Puis nous allons dire à notre MapView qu’elle doit utiliser le ClusterManager comme InfoWindowAdapter. Ce sont les deux lignes de code 20 et 21.

Ensuite, afin de gérer les cliques sur l’InfoWindow, nous allons implémenter l’interface IOnClusterItemInfoWindowClickListener avec notre Activité :

 

Et voici le résultat ! Une carte avec des markers complètement personnalisés et un clustering animé 🙂

Google Map Android Clustering Markers

 

Comment afficher des informations spécifiques dans l’InfoWindow ?

Dans le paragraphe précédent, nous avons vu comment ajouter une InfoWindow personnalisée. Mais nous n’avons pas vu comment modifier les informations affichées en fonction de chaque ClusterItem ! Si mes markers représentaient des personnes, je voudrai que l’InfoWindow affiche leur nom, prénom, et leur score sur 5 étoiles par exemple. C’est ce que nous allons voir dans ce paragraphe.

Pour l’exemple, je vais simplement ajouter une chaîne de caractère spécifique à chaque marker. Pour cela, il faut ajouter une prorpiété à ma classe ClusterItem :

 

La modification du texte affiché dans les InfoWindow se fait dans le InfoWindowAdapter. Malheureusement, ce dernier n’a connaissance que du Marker cliqué, et non des ClusterItems. Nous allons donc utiliser un dictionnaire qui répertorie tous les Markers et leur ClusterItem associé. Pour cela, rendez-vous dans le ClusterRenderer. A chaque fois que ce dernier afficher un Marker sur la carte, on va ajouter l’association Marker-ClusterItem à notre dictionnaire :

 

Maintenant que nous avons notre dictionnaire reliant les Markers à leur ClusterItems associés, il suffit que notre InfoWindowAdapter garde une référence à ce dictionnaire et le tour est joué !

 

De retour dans notre Activité, créez le fameux dictionnaire et modifiez les constructeurs de l’InfoWindowAdapter et du ClusterRenderer :

Vous pouvez maintenant compiler et déployer sur votre téléphone. Les InfoWindows affichent correctement l’information spécifique contenue dans un Clusteritem :

Google Map Android Clustering Markers

 

Comment limiter le cluster par type de marker ?

Encore envie d’aller plus loin ? Pas de problème ! Je vous l’expliquais au tout début de mon article, dans Moovenow, on affiche des infrastructures, des associations, et des événements sportifs sur la carte. Nous avons donc trois types de markers différents, et nous voulions donc trois regroupements différents ! Ça se fait, ça fait mal au crâne mais vous verrez, on y arrive 🙂

Pour l’exemple, je vais rajouter une catégories de marker « favoris ». Ces markers seront jaunes (et non vert comme les autres). Et les regroupements de markers jaunes et verts seront indépendant. De même, les InfoWindows seront indépendants ainsi que les actions à faire suite à un clique sur l’InfoWindow.

Modifions d’abord notre classe Clusteritem. Si m_bIsFav est vrai, alors le marker sera jaune. Sinon, il sera vert.

 

Il faut ensuite modifier le ClusterRenderer, car c’est lui qui s’occupe de l’affichage des images :

 

Pour personnaliser le contenu des InfoWindows en fonction des types de marker, c’est comme tout à l’heure :

 

Tout va bien ? Respirez un bon coup, c’est là que tout se joue ! Dans notre Activité, on va créer plusieurs instances de ClusterManager afin d’avoir des regroupements indépendants. De même, il y aura plusieurs instances de ClusterRenderer. En revanche, on garde une seule instance de InfoWindowAdapter, qui sera réutilisée par les différents ClusterManager et ClusterRenderer.

De plus, comme il y a plusieurs instances de ClusterManager, on ne peut plus utiliser la ligne de code « m_map.SetOnCameraIdleListener(m_ClusterManager); ». Nous devons déclencher manuellement les événements OnCameraIdle des ClusterManagers. C’est pour cela que l’Activité doit implémenter l’interface IOnCameraIdleListener.

Voilà tout :

 

A vos téléphones pour le résultat tant attendu !

Google Map Android Clustering Markers

 

Comment organiser mon code pour gérer différents types de markers ?

Dans l’exemple précédent, j’ai utilisé un ClusterItem pour tous mes types de markers (favoris et non favoris). Pour un exemple, c’est suffisant. Mais pour un code durable et évolutif, je conseillerai plutôt de se pencher vers une classe abstraite, et une classe implémentant cette classe abstraite pour chaque type de marker. De ce fait, les classes personnalisées InfoWindowAdapter et ClusterRenderer ne seront créées qu’une seule fois, quelque-soit le nombre de types de markers.

 

Google Map Android Clustering Markers

 

 

J’espère que cet article vous a plut et vous a permis d’avancer dans vos projets ! Comment avez-vous réussi à implémenter l’API Google Map ? Quelle architecture avez-vous utilisé ? Partagez vos expériences 🙂