The idea of writing an article on the Clustering of Google Map came to me following a problem that I met when developing the application Moovenow. Moovenow, among others, displays a Google Map listing all sports facilities in France, all sports associations in France, as well as sporting events organized on the platform. This potentially represents more than 300,000 markers to display on a smartphone screen!
The problem is twofold: not only the loading time must not affect the user, but above all the information must be understandable by the user! Displaying 300,000 markers is not insignificant, especially since each marker does not represent the same type of data.
To address this issue, I decided to use the Google Map API. It is accessible on Xamarin thanks to a Nuget package.
To use the Google Map API, you need to create a Google Map API ID: https://developers.google.com/maps/documentation/android-api/signup?hl=en, API. Then add this key in the manifest as below, instead of [api_key_value]. You can also add the permissions and the theme Appcompat:
You may need to activate Multi Dex if your solution includes more than 64,000 methods. This is manifested by a “Java exit with code 2” error when compiling.
How to display a Google Map?
The Google Map API accessed via the previously added Nuget package allows us to easily display a Google Map. You only have to add a view named MapView to a Layout. You can configure this MapView as you want, but I will not go into these details here. If you are interested, everything is very well explained in the developer documentation. Here is the AXML file of the activity that will display the map:
And here is the code of this activity:
All you have to do is compile and deploy on your smartphone, and here is the result:
How to add markers that group together when they are too many in the same place?
Adding a marker to a map is very easy, in a single line of code:
But what interests us is to add many markers on a map. And when there is too many markers in the same place, we want to group them to keep a clean and legible map. This is called “Clustering”.
To do that, we will use the ClusterManager class provided by the Google Map API. And instead of adding MarkerOptions objects to our MapView, we will add ClusterItem to our ClusterManager. The latter will manage the marker or marker display on its own.
Here is the code of the ClusterItem class, which must imperatively implement the interface IClusterItem:
To set up the ClusterManager and add ClusterItems, we must return to the code of our Activity. For instance, I add spiral markers from the center of Grenoble, hence the few lines of mathematical code (but do not be afraid;):
As I said above, the ClusterManager does all the work on its own !!
There is nothing more to do. Compile, deploy on our phone, and have fun zooming in / out to see the markers come together:
How to customize the graphical rendering of markers and grouped markers?
We agree ! It is unthinkable to keep the default display of Google markers. You have to customize them to improve the branding of your application. No worries, this is done very well and we will see how.
To begin, understand this : all items displayed in a map are images. For simple markers, this is not a problem: the image is always the same. But for clustered markers, the text changes according to the number of marker grouped, and there it breaks a problem! But all is well, Google engineers thought about us 🙂
To modify the visual rendering of the clustered markers, we will create a composite view of an ImageView and a TextView, configure this view with the image of our choice and the text of our choice (here the number of markers grouped together) , And we will transform that view into an image so that the MapView displays it correctly through our ClusterManager. Here is the AXML code:
In order to make our ClusterManager use custom rendering, we need to create a ClusterRenderer. It is thanks to this renderer that we will be able to specify to the ClusterManager which image to display in which case. For example, when we need to display a simple marker, we want to display the picture named “marker”. And when it is necessary to display a clustered marker, we will display the picture “marker_cluster_grouped” + the text indicating the number of marker grouped. Everything is available in my Github if you want to recover the pictures.
Earlier I was telling you that Google engineers had thought about us. They put the IconGenerator class at your disposal to make it easier for us. Thanks to our IconGenerator, we can create the clustered marker image very easily from the previously created AXML. I encourage you to take a few minutes to understand everything with the code below:
Now that the ClusterRenderer is ready, we must configure our ClusterManager to use this renderer:
You can now admire the fruit of your work! It’s still more fun than the default images is not it?
How to customize InfoWindow?
InfoWindows are small windows that appear when a user clicks on a marker. By default, these windows display the title and description associated with the Marker. Do you remember ? They are in parameter of the ClusterItem class.
On the other hand, when a user clicks on a marker grouping, you do not need to display InfoWindow. Instead, you should center the map around this point.
How to display a default InfoWindow?
Here is a very quick example to illustrate my previous explanations. The code below allows the ClusterRenderer to modify the MarkerOptions which is about to be displayed on the map in order to enhance its title and description. Therefore, when the user clicks on a marker, an InfoWindow will appear to display the title and description:
How to display a custom InfoWindow?
Want to display a particular image in your InfoWindow? Or a RatingBar for example? No problem ! It is possible to customize the InfoWindow with our own view based on an AXML file. We will see step by step how to put this in place with our ClusterManager.
To start, here is the AXML file that I will use. It displays an image, title, text and RatingBar:
Here is the code for the InfoWindowAdapter, which allows to inflate our previously created AXML file:
Usually (except Clustering), to use a custom InfoWindow you need to create an InfoWindowAdapter and then configure your MapView to use this adapter. In Clustering, if you do this, the InfoWindow will appear even if the user clicks on a marker grouping. It’s not good !
That’s why the trick is to let our ClusterManager handle the InfoWindows. With the code below, we will tell our ClusterManager that it must use our custom InfoWindowAdapter previously created, for simple markers only. Then we will tell our MapView that it must use the ClusterManager as InfoWindowAdapter. These are the two lines of code 20 and 21.
Then, in order to manage the clics on the InfoWindow, we will implement the IOnClusterItemInfoWindowClickListener interface with our Activity:
Here’s the result ! A map with completely customized markers and an animated clustering 🙂
How to display specific information in the InfoWindow?
In the previous paragraph, we saw how to add a custom InfoWindow. But we have not seen how to change the information displayed according to each ClusterItem! If my markers represented people, I would like the InfoWindow to display their name, first name, and their score on 5 stars for example. This is what we will see in this paragraph.
For the example, I’ll only add a specific string to each marker. To do this, add a prefix to my ClusterItem class:
Editing the text displayed in the InfoWindow is done in the InfoWindowAdapter. Unfortunately, it only knows the Marker clicked, not the ClusterItems. We will therefore use a dictionary that lists all the Markers and their associated ClusterItem. To do this, go to the ClusterRenderer. Whenever it displays a Marker on the map, we will add the Marker-ClusterItem association to our dictionary:
Now that we have our dictionary linking the Markers to their associated ClusterItems, it is enough that our InfoWindowAdapter keeps a reference to this dictionary and it’s done!
Back in our Activity, create the dictionary and modify the constructors of the InfoWindowAdapter and the ClusterRenderer:
You can now compile and deploy to your phone. InfoWindows correctly display the specific information contained in a Clusteritem:
How to limit the cluster by marker type?
Still want to go further? No problem ! I explained it to you at the very beginning of my article, in Moovenow, we display infrastructure, associations, and sporting events on the map. So we have three different types of markers, that’s why we wanted three different groups! It’s done, it hurts the skull but you’ll see, we get there 🙂
For the example, I will add a categories of marker “favorites”. These markers will be yellow (and not green like the others). And the groupings of yellow and green markers will be independent. Similarly, the InfoWindows will be independent as well as the actions to be taken following a click on the InfoWindow.
Let’s first modify our Clusteritem class. If m_bIsFav is true, then the marker will be yellow. Otherwise, it will be green.
It is then necessary to modify the ClusterRenderer, because it takes care of the display of the images:
To customize the content of InfoWindows according to marker types, it’s as we did before:
Everything is fine ? Breathe a good blow, that’s where everything is played! In our Activity we will create multiple instances of ClusterManager in order to have independent clusters. Similarly, there will be multiple instances of ClusterRenderer. On the other hand, we keep a single instance of InfoWindowAdapter, which will be reused by the different ClusterManager and ClusterRenderer.
Also, since there are multiple instances of ClusterManager, you can no longer use the “m_map.SetOnCameraIdleListener (m_ClusterManager);” line of code. We need to manually trigger OnCameraIdle events from ClusterManagers. This is why the Activity must implement the IOnCameraIdleListener interface.
That is all :
Take your phones for the long-awaited result!
How to organize my code to manage different types of markers?
In the previous example, I used a ClusterItem for all my marker types (favorites and non-favorites). For an example, that’s enough. But for a sustainable and evolutionary code, I would advise rather to lean towards an abstract class, and a class implementing this abstract class for each type of marker. As a result, the InfoWindowAdapter and ClusterRenderer custom classes will only be created once, regardless of the number of marker types.
I hope this article has allowed you to advance in your projects! How did you successfully implement the Google Map API? What architecture did you use? Share your experiences 🙂