How to display averages instead of counts on folium MarkerClusters?

Viewed 1692

I am using the folium package in python to display MarkerClusters for my data.

When you are not zoomed in all the way the clusters look good but they seemingly show a Count for the number of sub markers within that cluster. I understand why this is the default behavior but for my purpose I would really like the clusters to display the Average of the values for each individual marker within the given cluster for a zoom level.

Here is my code as it exists right now:

folium_map = folium.Map(location=[33.97810188618428, -118.2155395906348])
mc = MarkerCluster()
for p in points:
    marker = build_folium_marker(p['f_name'], p['value'], p['lat'], p['lng'])
    mc.add_child(marker)
folium_map.add_children(mc)
folium_map.save('folium_marker_cluster_map.html')

In an ideal world MarkerCluster would take some param that lets you sent 'count' or 'average' but that doesn't seem to be the case. I am cautiously optimistic someone will be able to suggest a reasonably simple fix that doesn't involve forking leaflet (the js library folium is built on) and editing JS source. I can't be the first person to want to show a different metric on the MarkerClusters than the sum, specifically the average of values of markers within the cluster.

1 Answers

To customize marker cluster icon_create_function function is intended, the following example demonstrates how to override marker label to display custom value instead of default (count of markers in a cluster):

icon_create_function = '''
    function(cluster) {
        return L.divIcon({
             html: '<b>' + 123 + '</b>',
             className: 'marker-cluster marker-cluster-small',
             iconSize: new L.Point(20, 20)
        });
    }
'''

marker_cluster = MarkerCluster(icon_create_function=icon_create_function)

enter image description here

Now comes the turn of passing custom properties via marker, in Folium default marker does not support it but the following marker class could be introduced which extends Marker class:

class MarkerWithProps(Marker):
    _template = Template(u"""
        {% macro script(this, kwargs) %}
        var {{this.get_name()}} = L.marker(
            [{{this.location[0]}}, {{this.location[1]}}],
            {
                icon: new L.Icon.Default(),
                {%- if this.draggable %}
                draggable: true,
                autoPan: true,
                {%- endif %}
                {%- if this.props %}
                props : {{ this.props }} 
                {%- endif %}
                }
            )
            .addTo({{this._parent.get_name()}});
        {% endmacro %}
        """)
    def __init__(self, location, popup=None, tooltip=None, icon=None,
                 draggable=False, props = None ):
        super(MarkerWithProps, self).__init__(location=location,popup=popup,tooltip=tooltip,icon=icon,draggable=draggable)
        self.props = json.loads(json.dumps(props))

Now in Folium once marker object is instantiated (where population is a custom property)

marker = MarkerWithProps(
    location=marker_item['location'],
    props = { 'population': marker_item['population']}
)
marker.add_to(marker_cluster)

its custom properties could be accessed like this via JavaScript:

var markers = cluster.getAllChildMarkers();
var sum = 0;
for (var i = 0; i < markers.length; i++) {
  sum += markers[i].options.props.population;
}

To summarize, here is an example which demonstrates how to:

  • pass a custom properties via marker
  • calculate average value for custom marker property per cluster marker
  • display a custom label for a cluster marker

Example

#%%
import json
import folium
from folium import Marker
from folium.plugins import MarkerCluster
from jinja2 import Template


class MarkerWithProps(Marker):
    _template = Template(u"""
        {% macro script(this, kwargs) %}
        var {{this.get_name()}} = L.marker(
            [{{this.location[0]}}, {{this.location[1]}}],
            {
                icon: new L.Icon.Default(),
                {%- if this.draggable %}
                draggable: true,
                autoPan: true,
                {%- endif %}
                {%- if this.props %}
                props : {{ this.props }} 
                {%- endif %}
                }
            )
            .addTo({{this._parent.get_name()}});
        {% endmacro %}
        """)
    def __init__(self, location, popup=None, tooltip=None, icon=None,
                 draggable=False, props = None ):
        super(MarkerWithProps, self).__init__(location=location,popup=popup,tooltip=tooltip,icon=icon,draggable=draggable)
        self.props = json.loads(json.dumps(props))    



map = folium.Map(location=[44, -73], zoom_start=4)

marker_data =(
    {
        'location':[40.67, -73.94],
        'population': 200     
    },
    {
        'location':[44.67, -73.94],
        'population': 300     
    }
)

icon_create_function = '''
    function(cluster) {
        var markers = cluster.getAllChildMarkers();
        var sum = 0;
        for (var i = 0; i < markers.length; i++) {
            sum += markers[i].options.props.population;
        }
        var avg = sum/cluster.getChildCount();

        return L.divIcon({
             html: '<b>' + avg + '</b>',
             className: 'marker-cluster marker-cluster-small',
             iconSize: new L.Point(20, 20)
        });
    }
'''

marker_cluster = MarkerCluster(icon_create_function=icon_create_function)

for marker_item in marker_data:
    marker = MarkerWithProps(
        location=marker_item['location'],
        props = { 'population': marker_item['population']}
    )
    marker.add_to(marker_cluster)

marker_cluster.add_to(map)    

#m.save(os.path.join('results', '1000_MarkerCluster0.html'))
map

Result

enter image description here

Related