'use strict'; angular.module('cluster',[]).factory('DynamicMarkerCluster', function () { var createClusterIcon = function (cluster) { var childCount = cluster.getChildCount(); var childMarkers = cluster.getAllChildMarkers(); var childCount = 0; childMarkers.forEach(function (marker) { if (marker.count) { childCount += marker.count; } else { childCount += 1; } }); var c = 'marker-cluster marker-cluster-'; if (childCount < 10) { c += 'small'; } else if (childCount < 100) { c += 'medium'; } else { c += 'large'; } return new L.DivIcon({ html: '
' + childCount + '
', className: c, iconSize: new L.Point(40, 40) }); }; var createLeafIcon = function (childCount) { var c = 'marker-cluster marker-cluster-'; if (childCount < 10) { c += 'small'; } else if (childCount < 100) { c += 'medium'; } else { c += 'large'; } return new L.DivIcon({ html: '
' + childCount + '
', className: c, iconSize: new L.Point(40, 40) }); }; var expandMarkersStub = function (keys) { var result = []; return { then: function (onSuccess, onFail) { onSuccess(result); } }; }; function DynamicMarkerCluster(options) { this.updating = false; this.updateTimeout = null; this.layer = new L.MarkerClusterGroup({ disableClusteringAtZoom: 16, removeOutsideVisibleBounds: true, singleMarkerMode: false, iconCreateFunction: createClusterIcon, maxClusterRadius: 55 }); this.expandMarkers = options.expandMarkers || expandMarkersStub; this.createClusterIcon = options.createClusterIcon || createClusterIcon; this.createLeafIcon = options.createClusterIcon || createClusterIcon; this.update = { add: [], remove: [], } } DynamicMarkerCluster.prototype.getLayer = function () { return this.layer; }; DynamicMarkerCluster.prototype.onBoundsChange = function (bounds) { if (this.updateTimeout) { clearTimeout(this.updateTimeout); this.updateTimeout = null; } var layer = this.layer; var self = this; this.updateTimeout = setTimeout(function () { if (layer && layer._map) { self.updateCluster(bounds, layer._map.getZoom()); } self.updateTimeout = null; }, 500); }; DynamicMarkerCluster.prototype.applyChanges = function (changes) { var expand = changes.expand || []; var add = changes.add || []; var remove = changes.remove || []; var clear = changes.clear; var layer = this.layer; this.updating = true; var self = this; if (clear) { layer.clearLayers(); } if (expand) { // async - wait till updates are loaded for atomic update this.expandMarkers(expand).then(function (expanded) { layer.removeLayers(remove); add.forEach(function(marker) { layer.addLayer(marker)}); //layer.addLayers(add); //layer.addLayers(expanded); expanded.forEach(function(marker) { layer.addLayer(marker)}); self.updating = false; }); } else if (add || remove) { layer.addLayers(add); layer.removeLayers(remove); this.updating = false; } }; DynamicMarkerCluster.prototype.updateCluster = function (bounds, zoom) { if (this.updating) { // console.log("skipping update"); return; } var n = 0; var expand = []; var remove = []; this.layer.eachLayer(function (marker) { if (marker.count) { if (marker.geoid && bounds.intersects(marker.bounds) && zoom > 8) { n++; expand.push(marker.geoid); remove.push(marker); } } }); this.applyChanges({expand: expand, remove: remove}); }; DynamicMarkerCluster.prototype.rebuildMapCluster = function (mapCounts, bounds, zoom) { var layer = this.layer; if (this.updating || !layer._map) { // console.log("skipping rebuild"); return; } var needsExpanding = []; var needsAdding = []; if (mapCounts) { mapCounts.forEach(function (count) { if ((bounds.intersects(count.bounds) && zoom > 7) || count.count == 1) { needsExpanding.push(count.geoid); } else { var marker = L.marker([count.lat, count.lng], {icon: createLeafIcon(count.count)}); marker.count = count.count; marker.geoid = count.geoid; marker.bounds = count.bounds; needsAdding.push(marker); } }); } this.applyChanges({add: needsAdding, clear: true, expand: needsExpanding}); }; return DynamicMarkerCluster; } );