Markers clustering: custom icon, groups and add them dynamically

Hi!

I’m working now with markers. I’m getting all my info from a database.
I was able to add as many markers as I have into the database with my custom icon.
Also, I was able to put those markers into a cluster if I add them in the code, following this example.

var points = [
      {
        coordinates: [-0.13389631465156526, 51.510387047712356],
        properties: {
            id: 1,
            name: 'Checkpoint A'
        }
    },
...
];

So, my issues are the following:

1
If I’m trying to add new markers to the cluster from my database, this never shows on the map.
That’s how I’m doing it:

var points = [];
...
points.push({
  coordinates: [data.long, data.lat],
 properties: {
       id: i,
       name: data.office
 }
});

I tried using a refreshMarkers(); just after the push, but doesn’t work.

2
Is it possible to have two or more groups of clusters?
For example, I have two types of stores: pizza and drinks, but I don’t want to mix them in the same cluster. Can I have a cluster per each group of stores? Even if there are closing stores, there will be 2 clusters because are different stores.

3
Is it possible to change the marker icon using clusters?

Hello!
Ad.1 To update a cluster with some new points, you need to do few things:

  1. push some new points to an array:
//we are adding a new point to an array
points.push({
   coordinates: [-0.1389855570008649, 51.512649424212185],
   properties: {
        id: 12,
       name: 'Checkpoint L
   }
});
  1. update a GeoJSON object with a new points array like this:
    //here we are creating a GeoJSON source with all points as features
             geoJson = {
                 type: 'FeatureCollection',
                 features: points.map(function(point) {
                     return {
                         type: 'Feature',
                         geometry: {
                             type: 'Point',
                             coordinates: point.coordinates
                         },
                         properties: point.properties
                     };
                 })
             };
  2. get an existing source from the map and overwrite it with new GeoJSON object
    //here we are overwriting source which was added just after the map was loaded
    //after this operation, new point should be visible on the map
     map.getSource('point-source').setData(geoJson);
2 Likes

Ad. 2
Yes, that’s possible. In the ‘Markers clustering’ example which you are referring to, we show how to add a single group of markers which we want to cluster. So basically you need to perform operations of adding sources and layers twice (or more).
Let’s say we have a two categories of places in your database: pizza and drinks. Here’s what you need to do:

  1. You need to sort all the places into to separate arrays (pizza places go to the first one and drinks two the second one).
  2. Create two separate GeoJSON objects - just like I showed in the second step of my previous response - one using array with pizza places (let’s call it pizzaGeojson), the second one with drinks (drinksGeojson).
  3. Add two sources to the map with GeoJSON objects which you just created. You can do it this way:
            map.addSource('pizza-source', {
                type: 'geojson',
                data: pizzaGeojson,
                cluster: true,
                clusterMaxZoom: 14,
                clusterRadius: 50
            });
            map.addSource('drinks-source', {
                type: 'geojson',
                data: drinksGeojson,
                cluster: true,
                clusterMaxZoom: 14,
                clusterRadius: 50
            });
  1. Now you need to add two separate layers with sources which you just added to the map
            map.addLayer({
                id: 'pizza-places',
                type: 'circle',
                source: 'pizza-source',
                ...
            });
           map.addLayer({
                id: 'drinks-places',
                type: 'circle',
                source: 'drinks-source',
                ...
            });
  1. Remember that you also need to apply “refreshing” behaviour (meaning declustering on zoom in and clustering on zoom out - basically what’s inside refreshMarkers() function) to both sources.
2 Likes

Ad.3
And that’s also possible. Here’s a code snippet with comments:


        map.on('load', function() {
            //first you need to load an image which you want to use as an icon (JPG or PNG)
            map.loadImage('my-icon.png', function(error, image) {
                //when it's loaded you can add it to the map
                map.addImage('my-icon', image);

                //now as normally you would do for clustering - add GeoJSON source
                map.addSource('point-source', {
                    type: 'geojson',
                    data: geoJson,
                    cluster: true,
                    clusterMaxZoom: 14,
                    clusterRadius: 50
                });

                //and here we are adding a Layer which uses a loaded image as it's layout
                map.addLayer({
                    id: 'clusters',
                    type: 'symbol',
                    source: 'point-source',
                    filter: ['has', 'point_count'],
                    layout: {
                        'icon-image': 'my-icon',
                        'icon-size': 0.1,
                        'icon-allow-overlap': true
                    }
                });

                (...)
2 Likes

michalbialecki, thank you for the first 2 answers, they work at all!
I forgot to ask something about each marker: how can I customize what happens when I do click on any of them? I want to show a div over the map with details about each marker.
Also, when I update the info of each group, everything is ok but the “first-show”. The info is updated (I mean if I remove/add new elements to the clusters), but the map looks exactly the same until I do zoom-in or zoom-out, then the markers are updated on the map. Any way to update the markers/clusters on the map automatically?


About the 3rd question, I was able to modify the counter icon (the one that shows how many markers are), but not to modify the marker’s icon itself.

This is how I’m creating the layers:

map.addLayer({
        id: 'clusters-groupA',
        type: 'circle',
        source: 'groupA-source',
        filter: ['has', 'point_count'],
        paint: {
          'circle-color': [
            'step',
            ['get', 'point_count'],
            '#EC619F',
            4,
            '#008D8D',
            7,
            '#004B7F'
          ],
          'circle-radius': [
            'step',
            ['get', 'point_count'],
            15,
            4,
            20,
            7,
            25
          ],
          'circle-stroke-width': 1,
          'circle-stroke-color': 'white',
          'circle-stroke-opacity': 1
        }
      });
      map.addLayer({
        id: 'cluster-count-groupA',
        type: 'symbol',
        source: 'groupA-source',
        filter: ['has', 'point_count'],
        layout: {
          'text-field': '{point_count_abbreviated}',
          'text-size': 16
        },
        paint: {
          'text-color': 'white'
        }
      });

I have the following markers and clusters on the map:

image

I was able to modify the cluster (color, steps, border, etc.)
How can I modify the “flag” icon?
Can I replace the icon by a text? For example, put inside the black circle a info from the array/GeoJson?

Hey!
Regarding your question about customizing markers - single markers are created in a completely different way comparing to clusters. Clusters (as I already showed you) are created as a layer which we display on the map. Single markers are HTML elements, which you can create using tt.Marker type. As an example of marker customization you can take a look at this tutorial: https://developer.tomtom.com/maps-sdk-web-js/tutorials-use-cases/how-add-and-customize-location-marker.
If you want to make it “clickable” you can always attach an event listener to this HTML element which you are passing as a parameter to tt.Marker() contructor, or you can add it to already existing marker by doing something like this:

//let’s say that “marker” was already created
marker.getElement().addEventListener(‘click’, function() { (…) });

I followed this tutorial and works pretty well for single markers.
But what I want to do is both customize and make “clickable” every “single-marker” inside the cluster.
As I said I was able to modify the count-circle but not the markers inside.
I want to replace the “flag-icon” of the markers inside the cluster with either a custom image or a text. Is this possible?
Regarding make them “clickable”, I tried to catch them by class using jquery but didn’t work.
The “issue” with both things is that the clusters will be added dynamically, so I cannot make a specific function or listener, it must be generic and then inside check for point’s properties.