Coder Social home page Coder Social logo

leaflet.markercluster's Introduction

Leaflet.markercluster

Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.

Requires Leaflet 1.0.0

cluster map example

For a Leaflet 0.7 compatible version, use the leaflet-0.7 branch
For a Leaflet 0.5 compatible version, Download b128e950
For a Leaflet 0.4 compatible version, Download the 0.2 release

Table of Contents

Using the plugin

Include the plugin CSS and JS files on your page after Leaflet files, using your method of choice:

In each case, use files in the dist folder:

  • MarkerCluster.css
  • MarkerCluster.Default.css (not needed if you use your own iconCreateFunction instead of the default one)
  • leaflet.markercluster.js (or leaflet.markercluster-src.js for the non-minified version)

Building, testing and linting scripts

Install jake npm install -g jake then run npm install

  • To check the code for errors and build Leaflet from source, run jake.
  • To run the tests, run jake test.

Examples

See the included examples for usage.

The realworld example is a good place to start, it uses all of the defaults of the clusterer. Or check out the custom example for how to customise the behaviour and appearance of the clusterer

Usage

Create a new MarkerClusterGroup, add your markers to it, then add it to the map

var markers = L.markerClusterGroup();
markers.addLayer(L.marker(getRandomLatLng(map)));
... Add more layers ...
map.addLayer(markers);

Options

Defaults

By default the Clusterer enables some nice defaults for you:

  • showCoverageOnHover: When you mouse over a cluster it shows the bounds of its markers.
  • zoomToBoundsOnClick: When you click a cluster we zoom to its bounds.
  • spiderfyOnMaxZoom: When you click a cluster at the bottom zoom level we spiderfy it so you can see all of its markers. (Note: the spiderfy occurs at the current zoom level if all items within the cluster are still clustered at the maximum zoom level or at zoom specified by disableClusteringAtZoom option)
  • removeOutsideVisibleBounds: Clusters and markers too far from the viewport are removed from the map for performance.
  • spiderLegPolylineOptions: Allows you to specify PolylineOptions to style spider legs. By default, they are { weight: 1.5, color: '#222', opacity: 0.5 }.

You can disable any of these as you want in the options when you create the MarkerClusterGroup:

var markers = L.markerClusterGroup({
	spiderfyOnMaxZoom: false,
	showCoverageOnHover: false,
	zoomToBoundsOnClick: false
});

Customising the Clustered Markers

As an option to MarkerClusterGroup you can provide your own function for creating the Icon for the clustered markers. The default implementation changes color at bounds of 10 and 100, but more advanced uses may require customising this. You do not need to include the .Default css if you go this way. You are passed a MarkerCluster object, you'll probably want to use getChildCount() or getAllChildMarkers() to work out the icon to show.

var markers = L.markerClusterGroup({
	iconCreateFunction: function(cluster) {
		return L.divIcon({ html: '<b>' + cluster.getChildCount() + '</b>' });
	}
});

Check out the custom example for an example of this.

If you need to update the clusters icon (e.g. they are based on markers real-time data), use the method refreshClusters().

Customising Spiderfy shape positions

You can also provide a custom function as an option to MarkerClusterGroup to override the spiderfy shape positions. The example below implements linear spiderfy positions which overrides the default circular shape.

var markers = L.markerClusterGroup({
	spiderfyShapePositions: function(count, centerPt) {
                var distanceFromCenter = 35,
                    markerDistance = 45,
                    lineLength = markerDistance * (count - 1),
                    lineStart = centerPt.y - lineLength / 2,
                    res = [],
                    i;

                res.length = count;

                for (i = count - 1; i >= 0; i--) {
                    res[i] = new Point(centerPt.x + distanceFromCenter, lineStart + markerDistance * i);
                }

                return res;
            }
});

All Options

Enabled by default (boolean options)

  • showCoverageOnHover: When you mouse over a cluster it shows the bounds of its markers.
  • zoomToBoundsOnClick: When you click a cluster we zoom to its bounds.
  • spiderfyOnMaxZoom: When you click a cluster at the bottom zoom level we spiderfy it so you can see all of its markers. (Note: the spiderfy occurs at the current zoom level if all items within the cluster are still clustered at the maximum zoom level or at zoom specified by disableClusteringAtZoom option).
  • removeOutsideVisibleBounds: Clusters and markers too far from the viewport are removed from the map for performance.
  • animate: Smoothly split / merge cluster children when zooming and spiderfying. If L.DomUtil.TRANSITION is false, this option has no effect (no animation is possible).

Other options

  • animateAddingMarkers: If set to true (and animate option is also true) then adding individual markers to the MarkerClusterGroup after it has been added to the map will add the marker and animate it into the cluster. Defaults to false as this gives better performance when bulk adding markers. addLayers does not support this, only addLayer with individual Markers.
  • disableClusteringAtZoom: If set, at this zoom level and below, markers will not be clustered. This defaults to disabled. See Example. Note: you may be interested in disabling spiderfyOnMaxZoom option when using disableClusteringAtZoom.
  • maxClusterRadius: The maximum radius that a cluster will cover from the central marker (in pixels). Default 80. Decreasing will make more, smaller clusters. You can also use a function that accepts the current map zoom and returns the maximum cluster radius in pixels.
  • polygonOptions: Options to pass when creating the L.Polygon(points, options) to show the bounds of a cluster. Defaults to empty, which lets Leaflet use the default Path options.
  • singleMarkerMode: If set to true, overrides the icon for all added markers to make them appear as a 1 size cluster. Note: the markers are not replaced by cluster objects, only their icon is replaced. Hence they still react to normal events, and option disableClusteringAtZoom does not restore their previous icon (see #391).
  • spiderLegPolylineOptions: Allows you to specify PolylineOptions to style spider legs. By default, they are { weight: 1.5, color: '#222', opacity: 0.5 }.
  • spiderfyDistanceMultiplier: Increase from 1 to increase the distance away from the center that spiderfied markers are placed. Use if you are using big marker icons (Default: 1).
  • iconCreateFunction: Function used to create the cluster icon. See the default implementation or the custom example.
  • spiderfyShapePositions: Function used to override spiderfy default shape positions.
  • clusterPane: Map pane where the cluster icons will be added. Defaults to L.Marker's default (currently 'markerPane'). See the pane example.

Chunked addLayers options

Options for the addLayers method. See #357 for explanation on how the chunking works.

  • chunkedLoading: Boolean to split the addLayers processing in to small intervals so that the page does not freeze.
  • chunkInterval: Time interval (in ms) during which addLayers works before pausing to let the rest of the page process. In particular, this prevents the page from freezing while adding a lot of markers. Defaults to 200ms.
  • chunkDelay: Time delay (in ms) between consecutive periods of processing for addLayers. Default to 50ms.
  • chunkProgress: Callback function that is called at the end of each chunkInterval. Typically used to implement a progress indicator, e.g. code in RealWorld 50k. Defaults to null. Arguments:
    1. Number of processed markers
    2. Total number of markers being added
    3. Elapsed time (in ms)

Events

Leaflet events like click, mouseover, etc. are just related to Markers in the cluster. To receive events for clusters, listen to 'cluster' + '<eventName>', ex: clusterclick, clustermouseover, clustermouseout.

Set your callback up as follows to handle both cases:

markers.on('click', function (a) {
	console.log('marker ' + a.layer);
});

markers.on('clusterclick', function (a) {
	// a.layer is actually a cluster
	console.log('cluster ' + a.layer.getAllChildMarkers().length);
});

Additional MarkerClusterGroup Events

  • animationend: Fires when marker clustering/unclustering animation has completed
  • spiderfied: Fires when overlapping markers get spiderified (Contains cluster and markers attributes)
  • unspiderfied: Fires when overlapping markers get unspiderified (Contains cluster and markers attributes)

Methods

Group methods

Adding and removing Markers

addLayer, removeLayer and clearLayers are supported and they should work for most uses.

Bulk adding and removing Markers

addLayers and removeLayers are bulk methods for adding and removing markers and should be favoured over the single versions when doing bulk addition/removal of markers. Each takes an array of markers. You can use dedicated options to fine-tune the behaviour of addLayers.

These methods extract non-group layer children from Layer Group types, even deeply nested. However, be noted that:

  • chunkProgress jumps backward when addLayers finds a group (since appending its children to the input array makes the total increase).
  • Groups are not actually added into the MarkerClusterGroup, only their non-group child layers. Therfore, hasLayer method will return true for non-group child layers, but false on any (possibly parent) Layer Group types.

If you are removing a lot of markers it will almost definitely be better to call clearLayers then call addLayers to add the markers you don't want to remove back in. See #59 for details.

Getting the visible parent of a marker

If you have a marker in your MarkerClusterGroup and you want to get the visible parent of it (Either itself or a cluster it is contained in that is currently visible on the map). This will return null if the marker and its parent clusters are not visible currently (they are not near the visible viewpoint)

var visibleOne = markerClusterGroup.getVisibleParent(myMarker);
console.log(visibleOne.getLatLng());

Refreshing the clusters icon

If you have customized the clusters icon to use some data from the contained markers, and later that data changes, use this method to force a refresh of the cluster icons. You can use the method:

  • without arguments to force all cluster icons in the Marker Cluster Group to be re-drawn.
  • with an array or a mapping of markers to force only their parent clusters to be re-drawn.
  • with an L.LayerGroup. The method will look for all markers in it. Make sure it contains only markers which are also within this Marker Cluster Group.
  • with a single marker.
markers.refreshClusters();
markers.refreshClusters([myMarker0, myMarker33]);
markers.refreshClusters({id_0: myMarker0, id_any: myMarker33});
markers.refreshClusters(myLayerGroup);
markers.refreshClusters(myMarker);

The plugin also adds a method on L.Marker to easily update the underlying icon options and refresh the icon. If passing a second argument that evaluates to true, the method will also trigger a refreshCluster on the parent MarkerClusterGroup for that single marker.

// Use as many times as required to update markers,
// then call refreshClusters once finished.
for (i in markersSubArray) {
	markersSubArray[i].refreshIconOptions(newOptionsMappingArray[i]);
}
markers.refreshClusters(markersSubArray);

// If updating only one marker, pass true to
// refresh this marker's parent clusters right away.
myMarker.refreshIconOptions(optionsMap, true); 

Other Group Methods

  • hasLayer(layer): Returns true if the given layer (marker) is in the MarkerClusterGroup.
  • zoomToShowLayer(layer, callback): Zooms to show the given marker (spiderfying if required), calls the callback when the marker is visible on the map.

Clusters methods

The following methods can be used with clusters (not the group). They are typically used for event handling.

Getting the bounds of a cluster

When you receive an event from a cluster you can query it for the bounds.

markers.on('clusterclick', function (a) {
	var latLngBounds = a.layer.getBounds();
});

You can also query for the bounding convex polygon. See example/marker-clustering-convexhull.html for a working example.

markers.on('clusterclick', function (a) {
	map.addLayer(L.polygon(a.layer.getConvexHull()));
});

Zooming to the bounds of a cluster

When you receive an event from a cluster you can zoom to its bounds in one easy step. If all of the markers will appear at a higher zoom level, that zoom level is zoomed to instead. zoomToBounds takes an optional argument to pass options to the resulting fitBounds call.

See marker-clustering-zoomtobounds.html for a working example.

markers.on('clusterclick', function (a) {
	a.layer.zoomToBounds({padding: [20, 20]});
});

Other clusters methods

  • getChildCount: Returns the total number of markers contained within that cluster.
  • getAllChildMarkers(storage: array | undefined, ignoreDraggedMarker: boolean | undefined): Returns an array of all markers contained within this cluster (storage will be used if provided). If ignoreDraggedMarker is true and there is currently a marker dragged, the dragged marker will not be included in the array.
  • spiderfy: Spiderfies the child markers of this cluster
  • unspiderfy: Unspiderfies a cluster (opposite of spiderfy)

Handling LOTS of markers

The Clusterer can handle 10,000 or even 50,000 markers (in chrome). IE9 has some issues with 50,000.

Note: these two examples use the chunkedLoading option set to true in order to avoid locking the browser for a long time.

License

Leaflet.markercluster is free software, and may be redistributed under the MIT-LICENSE.

Build Status

Sub-plugins

Leaflet.markercluster plugin is very popular and as such it generates high and diverse expectations for increased functionalities.

If you are in that case, be sure to have a look first at the repository issues in case what you are looking for would already be discussed, and some workarounds would be proposed.

Check also the below sub-plugins:

Plugin Description Maintainer
Leaflet.FeatureGroup.SubGroup Creates a Feature Group that adds its child layers into a parent group when added to a map (e.g. through L.Control.Layers). Typical usage is to dynamically add/remove groups of markers from Marker Cluster. ghybs
Leaflet.MarkerCluster.LayerSupport Brings compatibility with L.Control.Layers and other Leaflet plugins. I.e. everything that uses direct calls to map.addLayer and map.removeLayer. ghybs
Leaflet.MarkerCluster.Freezable Adds the ability to freeze clusters at a specified zoom. E.g. freezing at maxZoom + 1 makes as if clustering was programmatically disabled. ghybs
Leaflet.MarkerCluster.PlacementStrategies Implements new strategies to position clustered markers (eg: clock, concentric circles, ...). Recommended to use with circleMarkers. Demo adammertel / UNIVIE
Leaflet.MarkerCluster.List Displays child elements in a list. Suitable for mobile devices. Demo adammertel / UNIVIE

leaflet.markercluster's People

Contributors

adammertel avatar awinograd avatar bertyhell avatar boldtrn avatar ckrahe avatar cyrille37 avatar danzel avatar dergutewolf avatar elitemastereric avatar frankrowe avatar ghybs avatar ivansanchez avatar joaogarin avatar jperelli avatar kontrollanten avatar lucaswerkmeister avatar mindplay-dk avatar mlazowik avatar mourner avatar mrcheeze avatar olive380 avatar originalsin avatar rdenniston avatar sanjpareek avatar schwanksta avatar tmcw avatar wildhoney avatar ykzeng avatar z3ut avatar zverev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leaflet.markercluster's Issues

Exception on panning with spiderfied group

I have a cluster group layer that I clear and re-populate with every pan. The layer is set up as so:

featuresLayer = new L.MarkerClusterGroup({ spiderfyOnMaxZoom: false, showCoverageOnHover: true, zoomToBoundsOnClick: false });

After the initial map + clustered markers display (which works fine) I catch the Leaflet map "moveend" event which of course is fired at the end of panning. Inside this event handler I'm calling featuresLayer.clearLayers() followed by N featuresLayer.addLayer(a_newly_created_marker);

Panning and zooming works perfectly - the the new markers and groups are displayed dynamically as expected ... until I try panning with a spiderfied group, which causes an exception:

leaflet.js:6Uncaught TypeError: Cannot set property '_leaflet_pos' of null
n.DomUtil.setPositionleaflet.js:6
n.Marker.n.Class.extend._setPosleaflet.js:6
L.MarkerCluster.include.L.DomUtil.TRANSITION._animationUnspiderfyleaflet.markercluster-src.js:1510
L.MarkerCluster.include.unspiderfyleaflet.markercluster-src.js:1310
L.MarkerClusterGroup.include._unspiderfyleaflet.markercluster-src.js:1611
L.MarkerClusterGroup.L.FeatureGroup.extend.addLayerleaflet.markercluster-src.js:47
doBoundsRPCsearch:1070
handleMovesearch:1139
n.Mixin.Events.fireEventleaflet.js:6
n.Map.Drag.n.Handler.extend._onDragEndleaflet.js:6
n.Mixin.Events.fireEventleaflet.js:6
n.Draggable.n.Class.extend._onUpleaflet.js:6
n.DomEvent.addListener.e.(anonymous function).u

After this the markers/groups are all gone but I can still see one artifact - just the "legs" of the spider, and the map can no longer be panned.

Fire a popup for a cluster group

In our leaflet map, we have a table next to it that lists each of the points. When a row in the table is clicked, we would like to have a popup displayed over the cluster the point is in (or zoom to the point in the cluster and have a popup).

Before it worked fine because we didn't have clustering.

Is there a way to find the cluster of a given marker and then show a popup where the cluster is?

Expand only visible clusters after zoom, expand others on panning

While clusters/markers that are outside the buffer zone are hidden and it helps performance, I think it would be nice to try still not expanding the clusters in the buffer zone on zoom in, and only expand clusters which bounds intersect with the screen after zooming, and expand remaining clusters at the end of each panning (it would still look nice). This way you won't have lots of markers spread across an area 9 times the screen. The performance hit isn't noticeable on desktop screen but it is on iPad, for example.

Clusterer greys out map on Samsung I9001 with Gingerbread

I have a weird issue. When clusterer is enabled on my map, on first load 'tile' layer initializes and then disappears. (I have tried different tiles but the issue is still there)

The tile layer will only reappear if the map is zoomed in or out using the buttons of zoom control. (I have tried to programmaticaly zoom in or out but it doesn't fix the problem).

I have the same issue on the realworld example page on my Samsung I9001 with 2.3.5 Android.

If the clusterer is disabled and markers are normally displayed on the map, then the tiles layer is displayed normally.

Build script

Steal the leaflet build script and integrate it. Also include a prebuilt .js/css file in the repo

how to manage markers on each layer

I have to manage about 30000 markers, i don't know how to show or hide some of them without reload all the marker data .could you show me how to do it?
(I notice that , when the markerClusterGroup render the markers , it sets the opacity to 1 )

Thanks a bundle

make dblclick propagate to map

I have a big map with no weird stuff:

markers = new L.MarkerClusterGroup({ 
    spiderfyOnMaxZoom: false, 
    showCoverageOnHover: false, 
    zoomToBoundsOnClick: false 
});

However, when I doubleclick on a marker, I espect it to zoom just as if I would have clicked anywhere else. Is it possible to make the 'dblclick' event propagate to the underlaying map?

Or even better: have an option to zoom on click, but not zoomToBoundsOnClick, just a regular 1 step zoom similar to a double click.

Ability to have all markers uncluster at specific zoom level

(Reported via email from Dan Cowan @ Brightmind)

In my project I need a slightly different behavior at maximum zoom levels. Rather than clustering or spiderifying I'd like it to place markers back in their original positions. I can achieve this by having a regular marker layer for max zoom and the clustering layer for other zoom levels and switching the layers out using the zoomend listener. This is a rather inelegant solution though which wastes memory and processing power in the client, not ideal for mobile devices.

Next I'll look at modifying your plugin to see what I can do. I was wondering if you have any tips or suggestions about where to start? If this works, and looks like a worthwhile feature I'll be more than happy to contribute any code back to your project.

Once again, thanks for the effort on a great plugin.

Add hasLayer() to the markercluster object

When adding new markers to a map, it'd be great to be able to easily check if it already has that layer. It's pretty simple to do without this function but it would be a nice added feature.

Update Leaflet to 0.4.4

There's one annoying Chrome bug with tiles flickering on map borders that was fixed. Also, you can remove Leaflet files from lib folder and just include Leaflet through CDN.

Clustering decisions change after clearing and re-adding markers

Using the same example as #49, http://pastebin.com/qqr29pCN , I notice that the marker reload in response to panning generates a very different set of clusters from those created on the initial load. Looking at the JS of the example, you can see that the panning operation causes a MarkerClusterGroup.clearLayers() call to be made, then all the original markers re-added.

I've seen this on my own maps as well, often resulting in clusters stacked on top of one another where the original cluster has been split. A demonstration:
-Navigate here: http://www.ezgeo.net/search?state=VT&county=Windham&county_search_type=Building

  • Note near the center of the map the cluster of four markers. Also note the cluster of 22 markers to the SE.
  • Pan the map ever so slightly. This will cause the MarkerClusterGroup to be cleared, a new query performed, and the new markers being added to the MarkerClusterGroup..
  • The cluster that formerly contained four markers has been split into a cluster of three + one stand-alone. The cluster that formerly contained 22 markers has been split into three overlapping clusters of 9, 9, 3, and one stand-alone marker.

Sensible defaults for clusterer

My guess is that the majority of users of this plugin will want experience done in the "everything" example (plus the zoom-to-cluster/spiderfier stuff) out of the box, and they'll have to copy/paste lots of code.

I'd like to propose implementing sensible defaults for the plugin, so that user gets the "everything" functionality out of the box and can turn individual features off and add custom functionality easily.

Similar to how it's done in Leaflet, for example, for default map controls, you can do this with MarkerClusterGroup options, enabling things on hover/click by default but allowing this:

var markers = new L.MarkerClusterGroup({
    spiderfyOnMaxZoom: false,
    zoomToBoundsOnClick: false,
    showCoverageOnHover: false,
});

markers.on('click', function () { ... });

L.geoJson to MarkerCluster crashing on update to newest build

I had been using an older version of markercluster with an app that pulls GeoJSON from CartoDB. Everything worked fine, but I wanted to take advantage of the singleMarkerMode in the newer version. After I updated, map.addLayer(clusters) crashes my app. It's not giving me a javascript error, just freezing my browser. Not sure how to narrow the issue down. When I switch to just adding the points (around 8,000), they load fine. Is there an issue with the way I'm creating these clusters and/or creating the markers?

$.getJSON(svcGeoUrl, function(data) {
    var clusters = new L.MarkerClusterGroup({showCoverageOnHover: false});
    var points = L.geoJson(data.features, {
        pointToLayer: function(feature, latlng) {
            var marker = L.marker(latlng);
            clusters.addLayer(marker);
            return clusters;
        }
    }).addTo(map);
    map.fitBounds(points.getBounds());
    map.panBy([160,0]);
});

I also tried it this way:

$.getJSON(svcGeoUrl, function(data) {
    var clusters = new L.MarkerClusterGroup({showCoverageOnHover: false});
    var points = L.geoJson(null, {
            pointToLayer: function (feature, latlng) {
                var marker = L.marker(latlng);
                clusters.addLayer(marker);
                return clusters;
            }
        });
    $.each(data.features, function(fid, feature) {
        points.addData(feature);
    });
    map.addLayer(clusters);
});

Thanks a lot in advance!

Checking markers in clusters for a particular attribute

The example here is, I have 100 markers on the page, with a priority attrtibute, which I am using to define which pin image/style they have. I now want to do something similar with the clusters.

For example, if there are 2 attributes in the currently viewed clustered pins, normal and high, I want to colour the cluster based on the priority, so if any of the markers in a cluster have the 'high' attribute, I want to apply a certain class to the cluster, and another if not.

Is there a way currently to access the markers in a cluster at the point the styles are applied to that cluster?

Coverage display gets stuck on map if the cluster layer is removed while it's active.

I've come across a bug that if the clusterMarker layer is removed from the map while a coverage is being displayed, it gets stuck on the map. On a drag event i'm clearing the markers from the map to load a new set and in doing this will sometimes get these orphans.

The three coverages seen in this picture are from previous clusters before dragging the map.

To reproduce

  • Hover over cluster to display coverage
  • drag the map which triggers on dragend map.removeLayer(clusterLayer)
  • keep mouse hovering over the cluster marker
  • New markers are loaded clearing the current ones

Result

  • New markers are loaded
  • Old coverage is left behind on the map

.clearLayers() not working

Hi,

My app utilizes lazy loading for markers via AJAX, I tried calling .clearLayers() in my MarkerClusterGroup instance but the clusters don't clear from the layer. Can't call myself a JavaScript guru but from what I can see in the MarkerClusterGroup.removeLayer() function the clusters should be recursively cleared from the map so why aren't they? Is this a possible bug or you just haven't implemented it yet?

thanks,
hezron

Try generate additional parent levels in the tree on startup

If we generate some extra parent levels in the cluster tree initially it will allow the tree to be more tree like (currently it is just 1 level), which should give better performance as less nodes/markers need to be visited to work out what to draw.

Should do something smartish to work it out, like while there are > 100 clusters in the top level, generate another top level.

Clusters sometimes stay hidden after expansion

I've not yet managed to come up with a consistent way to reproduce this, but it's quite easy - open the "everything" example and zoom quickly back and forth for a while until you see that a part of the clusters is missing. Looking at the DOM, they are there and even react on hover but have opacity: 0. Maybe it's something to do with your setTimeout hacks.

Poor performance when retrieving points via AJAX

Hey,
I found a funny behaviour when retrieving points via AJAX and have their markers clustered.

I'm loading the points (a GeoJSON feature collection with about 370 points) from a REST service, and this is (a part of) my code:

$.getJSON(POIS_URL + "?limit=0", function(pois) {
    for (var i in pois.features) {
        var marker = new L.Marker(new L.LatLng(pois.features[i].geometry.coordinates[1], pois.features[i].geometry.coordinates[0]));
        marker.bindPopup(pois.features[i].properties.name);
        markers.addLayer(marker);
    }
});

If I run this code, my CPU usage goes up to 100% for nearly 10 seconds, and after that everything is displayed correctly.

Now comes the funny part:
if I make a synchronius request, everything is just as fast as one would expect.
I simply put $.ajaxSetup( { "async": false } ); in front of the code:

$.ajaxSetup( { "async": false } );
$.getJSON(POIS_URL + "?limit=0", function(pois) {
    for (var i in pois.features) {
        var marker = new L.Marker(new L.LatLng(pois.features[i].geometry.coordinates[1], pois.features[i].geometry.coordinates[0]));
        marker.bindPopup(pois.features[i].properties.name);
        markers.addLayer(marker);
    }
});
$.ajaxSetup( { "async": true } );

Is this a bug, or is my code just rubbish? I guess it should work with asynchronious requests as well. Since I'm not sure, I just post it here, maybe someone else can reproduce this behaviour. I've tested it using Chromium 18.0.1025.168 and Firefox 14.0.1 on Ubuntu 12.04.1, and both browsers act the same way, Firefox is even a bit slower than Chromium.

Regards

Poor performance on iOS while pinch-zooming

Not sure why this happens but when the number of cluster on the screen gets to more than 10-20, pinch-zoom starts being very laggy. Usual markers don't seem to cause such performance hit. Needs some research (maybe this issue belongs to Leaflet and not here but lets take a look first).

Disappearing cluster

In the current 'realworld' example, if I zoom out and start to pan, the cluster of 388 disapears about 4 times out of 10, it almost looks like it happens more the further out I zoom, it usually reappears with another pan or zoom. Interestingly, it doesn't happen in the 'custom' example, that I could see anyway.

Map is null when removing all layers and adding new markers

I have an AJAX form when it changes it should update the map by removing all the markers and clusters, and place the new markers on the map, however once the markers are placed, I get an error logged saying map is null which is on line 340 (bounds = map.getPixelBounds(),).

Should this need to be fixed because I don't know what causes it to become null. This is my code:

var map, markers, objects = [], groupLayer;
var addMarkers = function(markerObj)
{
    if(typeof groupLayer !== 'undefined')
    {
        map.removeLayer(markers);
        map.removeLayer(groupLayer);
        objects = [];   
    }

    console.log(typeof map); // always says 'object'

    markers = new L.MarkerClusterGroup(
    {
        maxClusterRadius: 120,
        iconCreateFunction: function(count) 
        {
            return new L.DivIcon({ html: '<div>' + count + '</div>', className: 'leaflet-cluster', iconSize: new L.Point(46, 46) });
        },
        spiderfyOnMaxZoom: false, showCoverageOnHover: false, zoomToBoundsOnClick: true
    });

    $.each(markerObj, function(index, obj)
    {
        var profileLocation = new L.LatLng(obj.lat, obj.lng),
            locationMarker = new L.Marker(profileLocation);

        markers.addLayer(locationMarker);
        objects.push(locationMarker);
    });

    groupLayer = new L.FeatureGroup(objects);

    map.addLayer(markers).fitWorld();   
};

var profileMarkers = // object of markers

map = new L.Map('map');

var cloudmade = new L.TileLayer('http://{s}.tile.cloudmade.com/<API_KEY>/998/256/{z}/{x}/{y}.png', 
    {
        maxZoom: 18
    });

var defaultLocation = new L.LatLng(51.300, -1.8900);

map.setView(defaultLocation, 15).addLayer(cloudmade)

addMarkers(profileMarkers);

$(':input').change(function()
{
    // ajax goes here, returns object of markers
   addMarkers(response.markers);
});

What could be the problem for this?

Add class factories for classes (Leaflet 0.4-like)

L.markerClusterGroup = function (options) {
    return new L.MarkerClusterGroup(options);
};

You can also update the examples accordingly, e.g.:

map.addLayer(new L.Polygon(a.layer.getConvexHull()));
// turns to
L.polygon(a.layer.getConvexHull()).addTo(map);

If cluster is spiderfied and the cluster layer get its markers updated, the position on the markers is incorrect

This may or may not be related to #53

I have a set of filters on my map to limit which markers are shown.

If I limit the markers to only one marker is showing which would otherwise be clustered, the position is accurate as seen here. If I have both markers in the cluster, spiderfy, and then update the filters so that only one marker is left, the location of that marker is the position of where it spiderfied to instead of the location original location as seen here. The lines still showing is what's referenced in #53 and why i think they might be related.

When my filters change, I'm

  • Removing the cluster layer from the map
  • Re-initializing the cluster layer with new L.MarkerClusterGroup();
  • Loop through markers which should remain on the map and add their layers to the cluster layer
  • Once done, add the marker cluster layer to the map again

Below is the code

//Clear the current marker cluster group
me.map.removeLayer(me.markerCluster);
me.markerCluster = new L.MarkerClusterGroup();

//Go through items and update which are on the map
for(var id in me.markers)
{   
    //Add it to the layer group
    me.markerCluster.addLayer(me.markers[id]);
}   

me.map.addLayer(me.markerCluster);

Let me know if anymore information is needed!

Utilize leaflet layering

It would be fantastic if the marker clusterer supported layers like the stock leaflet layer functionality so users can enable/disable layers of markers and have the clusters update accordingly.

Potential way to make initial clustering more than 100 times faster

Hey Dave,

Spent several hours today figuring out how the clustering algorithm works, profiling and thinking about ways to optimize it, and I think I have a really great idea about it.

As you know most of the time is spent in MarkerClusterGroup._cluster method. I put several checkpoints to see how many times certain actions happened. Here's a snapshot from the 50k example and highest zoom level:

point to cluster comparisons: 62604635
merges into existing clusters: 41857
point to point comparisons for new clusters: 2133408
clusters created: 3977
unclustered points pushed: 4166 

As you see, there are ~63 million point to cluster comparisons, of which only 0.07% actually result in a merge into a cluster and most other iterations are wasted.

Lets suppose that instead of saving created clusters into a simple array (there are only 4k created), we put it into a grid with square cells of clusterRadius size. After this, to check if a particular point lies near a cluster, you only have to go through 9 cells (cell corresponding to a point and 8 surrounding ones) of a grid instead of going through ALL the clusters. As each cell will have ~1 cluster or no clusters in average, the amount of point to cluster comparisons will drop from 63 million to 50000*9 = 450000 max, and that's 140 times better.

Because we only create 4k clusters, keeping the grid vs pushing to an array won't have noticeable overhead, while the comparison drop mentioned above will have a massive impact.

Similarly, in this example there are ~2 million point to point comparison for creating new clusters from unclustered points, while there's only ~4k unclustered points and created cluster total. If we use a similar approach and maintain a grid for the unclustered points to make all comparisons, its amount should drop to 8000 (number of _clusterOne calls) * 9 = 72000 vs 2.1 million, which is 30 times better.

What do you think?

Cluster only 5+ close markers

I apologize if this has already been addressed but I didn't see it mentioned: Now it seems that any time two markers or more are close to one another, they get clustered together. Is there a way to change this so clusters will only be formed if say we have 5 markers that are close together? Or 10? Or any number we choose?

Removing cluster layer from map during zoom crashes

Calling removeLayer() on the map to remove the cluster marker layer causes the crash when the cluster tries to merge split clusters.

Right now, due to the way we have our data setup and the amount of markers being dealt with, on every zoom end or drag end, the markers are re-retrieved and the clusterGroup is recreated. This works fine zooming in and out until the cluster group is removed during a zoom animation.

On my map, I don't show the cluster group when the map is below zoom level 5. When I go from 4, showing clusters, to 5, now showing none, the map crashes when it tries merge split clusters.

Error shown for line 252 of the -src file

Uncaught TypeError: Cannot read property '_zoom' of null

Use requestAnimFrame instead of setTimeout

In Leaflet source, L.Util.requestAnimFrame(fn, context) is used everywhere in place of setTimeout. It does practically the same but executes much faster. Try it!

Also, if you need to request the same update in several different places, you can do cancelAnimFrame before the request, so that multiple actions only trigger one reflow/repaint and update call. E.g. this is used on Leaflet move while panning, because browser can't update the screen as fast as the mouse moves.

I'll probably write a post about this trick in a future Leaflet blog.

Better cluster center strategy

Since sending the #30 pull, I noticed that it's not the best approach too. There are some consideration to take into account. Take a look at the image comparison here: http://imgur.com/a/iadup

The current approach (after mergin the pull), illustrated on the first image, is to calculate weighted centers during clustering (and use them immediately). As cluster centers are easily shifted on each point/cluster added, the resulting clusters can contain children spread across large areas (instead of containing clusters roughly 2*maxRadius from each other). So its seems quite flawed.

The original approach (2nd image) was to continuously calculate centers as cluster bounds during clustering. As centers are recalculated during clustering, it has the same flaw as the first approach, although less pronounced as centers shift not as much.

I thought about how to overcome the mentioned flaw and came up a new approach (3rd image) โ€” while clustering, use only the first cluster point as cluster center and do not change it. This way there are no shifts, and cluster areas are more consistent and don't grow out of control. After the clustering, we can then choose other centers for clusters on display. E.g. this image shows first point centers while clustering, but with weighted centers when showing.

I'm now leaning towards the last option, although it's not without flaws too, e.g. coverage areas often collide, but I think that this is related to the greedy algorithm in general, not in approach to choosing centers, and can't be fixed easily. You can check out how this works with this commit https://github.com/mourner/Leaflet.markercluster/commit/6616abd444296cc5a77463a3c8cf8b62fd6f0e94

Let me know what you think!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.