function MarkerClusterer(map, opt_markers, opt_options){
this.extend(MarkerClusterer, google.maps.OverlayView);
this.map_=map;
this.markers_=[];
this.clusters_=[];
this.sizes=[42, 48, 56, 62, 68];
this.styles_=[];
this.ready_=false;
var options=opt_options||{};
var MapOptions=$("#map_options");
var gridSize=Number(MapOptions.find(".clusters-grid").html());
this.gridSize_=options['gridSize']||gridSize;
var minimumClusterSize=Number(MapOptions.find(".clusters-min").html());
this.minClusterSize_=options['minimumClusterSize']||minimumClusterSize;
this.maxZoom_=options['maxZoom']||null;
this.styles_=options['styles']||[];
this.imagePath_=options['imagePath'] ||
this.MARKER_CLUSTER_IMAGE_PATH_;
this.imageExtension_=options['imageExtension'] ||
this.MARKER_CLUSTER_IMAGE_EXTENSION_;
this.zoomOnClick_=true;
if(options['zoomOnClick']!=undefined){
this.zoomOnClick_=options['zoomOnClick'];
}
this.averageCenter_=false;
if(options['averageCenter']!=undefined){
this.averageCenter_=options['averageCenter'];
}
this.setupStyles_();
this.setMap(map);
this.prevZoom_=this.map_.getZoom();
var that=this;
google.maps.event.addListener(this.map_, 'zoom_changed', function(){
var maxZoom=that.map_.mapTypes[that.map_.getMapTypeId()].maxZoom;
var zoom=that.map_.getZoom();
if(zoom < 0||zoom > maxZoom){
return;
}
if(that.prevZoom_!=zoom){
that.prevZoom_=that.map_.getZoom();
that.resetViewport();
}});
google.maps.event.addListener(this.map_, 'idle', function(){
that.redraw();
});
if(opt_markers&&opt_markers.length){
this.addMarkers(opt_markers, false);
}}
MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_PATH_=$("#map_options").find(".clusters-url").html();
MarkerClusterer.prototype.MARKER_CLUSTER_IMAGE_EXTENSION_='png';
MarkerClusterer.prototype.extend=function(obj1, obj2){
return (function(object){
for (var property in object.prototype){
this.prototype[property]=object.prototype[property];
}
return this;
}).apply(obj1, [obj2]);
};
MarkerClusterer.prototype.onAdd=function(){
this.setReady_(true);
};
MarkerClusterer.prototype.draw=function(){};
MarkerClusterer.prototype.setupStyles_=function(){
if(this.styles_.length){
return;
}
for (var i=0, size; size=this.sizes[i]; i++){
this.styles_.push({
url: this.imagePath_ + (i + 1) + '.' + this.imageExtension_,
height: size,
width: size
});
}};
MarkerClusterer.prototype.fitMapToMarkers=function(){
var markers=this.getMarkers();
var bounds=new google.maps.LatLngBounds();
for (var i=0, marker; marker=markers[i]; i++){
bounds.extend(marker.getPosition());
}
this.map_.fitBounds(bounds);
};
MarkerClusterer.prototype.setStyles=function(styles){
this.styles_=styles;
};
MarkerClusterer.prototype.getStyles=function(){
return this.styles_;
};
MarkerClusterer.prototype.isZoomOnClick=function(){
return this.zoomOnClick_;
};
MarkerClusterer.prototype.isAverageCenter=function(){
return this.averageCenter_;
};
MarkerClusterer.prototype.getMarkers=function(){
return this.markers_;
};
MarkerClusterer.prototype.getTotalMarkers=function(){
return this.markers_.length;
};
MarkerClusterer.prototype.setMaxZoom=function(maxZoom){
this.maxZoom_=maxZoom;
};
MarkerClusterer.prototype.getMaxZoom=function(){
return this.maxZoom_||this.map_.mapTypes[this.map_.getMapTypeId()].maxZoom;
};
MarkerClusterer.prototype.calculator_=function(markers, numStyles){
var index=0;
var count=markers.length;
var dv=count;
while (dv!==0){
dv=parseInt(dv / 10, 10);
index++;
}
index=Math.min(index, numStyles);
return {
text: count,
index: index
};};
/**
* Set the calculator function.
*
* @param {function(Array, number)} calculator The function to set as the
*     calculator. The function should return a object properties:
*     'text' (string) and 'index' (number).
*
*/
MarkerClusterer.prototype.setCalculator=function(calculator){
this.calculator_=calculator;
};
/**
* Get the calculator function.
*
* @return {function(Array, number)} the calculator function.
*/
MarkerClusterer.prototype.getCalculator=function(){
return this.calculator_;
};
MarkerClusterer.prototype.addMarkers=function(markers, opt_nodraw){
for (var i=0, marker; marker=markers[i]; i++){
this.pushMarkerTo_(marker);
}
if(!opt_nodraw){
this.redraw();
}};
MarkerClusterer.prototype.pushMarkerTo_=function(marker){
marker.isAdded=false;
if(marker['draggable']){
var that=this;
google.maps.event.addListener(marker, 'dragend', function(){
marker.isAdded=false;
that.repaint();
});
}
this.markers_.push(marker);
};
MarkerClusterer.prototype.addMarker=function(marker, opt_nodraw){
this.pushMarkerTo_(marker);
if(!opt_nodraw){
this.redraw();
}};
MarkerClusterer.prototype.removeMarker_=function(marker){
var index=-1;
if(this.markers_.indexOf){
index=this.markers_.indexOf(marker);
}else{
for (var i=0, m; m=this.markers_[i]; i++){
if(m==marker){
index=i;
break;
}}
}
if(index==-1){
return false;
}
this.markers_.splice(index, 1);
return true;
};
MarkerClusterer.prototype.removeMarker=function(marker, opt_nodraw){
var removed=this.removeMarker_(marker);
if(!opt_nodraw&&removed){
this.resetViewport();
this.redraw();
return true;
}else{
return false;
}};
MarkerClusterer.prototype.removeMarkers=function(markers, opt_nodraw){
var removed=false;
for (var i=0, marker; marker=markers[i]; i++){
var r=this.removeMarker_(marker);
removed=removed||r;
}
if(!opt_nodraw&&removed){
this.resetViewport();
this.redraw();
return true;
}};
MarkerClusterer.prototype.setReady_=function(ready){
if(!this.ready_){
this.ready_=ready;
this.createClusters_();
}};
MarkerClusterer.prototype.getTotalClusters=function(){
return this.clusters_.length;
};
MarkerClusterer.prototype.getMap=function(){
return this.map_;
};
MarkerClusterer.prototype.setMap=function(map){
this.map_=map;
};
MarkerClusterer.prototype.getGridSize=function(){
return this.gridSize_;
};
MarkerClusterer.prototype.setGridSize=function(size){
this.gridSize_=size;
};
MarkerClusterer.prototype.getMinClusterSize=function(){
return this.minClusterSize_;
};
MarkerClusterer.prototype.setMinClusterSize=function(size){
this.minClusterSize_=size;
};
MarkerClusterer.prototype.getExtendedBounds=function(bounds){
var projection=this.getProjection();
var tr=new google.maps.LatLng(bounds.getNorthEast().lat(),
bounds.getNorthEast().lng());
var bl=new google.maps.LatLng(bounds.getSouthWest().lat(),
bounds.getSouthWest().lng());
var trPix=projection.fromLatLngToDivPixel(tr);
trPix.x +=this.gridSize_;
trPix.y -=this.gridSize_;
var blPix=projection.fromLatLngToDivPixel(bl);
blPix.x -=this.gridSize_;
blPix.y +=this.gridSize_;
var ne=projection.fromDivPixelToLatLng(trPix);
var sw=projection.fromDivPixelToLatLng(blPix);
bounds.extend(ne);
bounds.extend(sw);
return bounds;
};
MarkerClusterer.prototype.isMarkerInBounds_=function(marker, bounds){
return bounds.contains(marker.getPosition());
};
MarkerClusterer.prototype.clearMarkers=function(){
this.resetViewport(true);
this.markers_=[];
};
MarkerClusterer.prototype.resetViewport=function(opt_hide){
for (var i=0, cluster; cluster=this.clusters_[i]; i++){
cluster.remove();
}
for (var i=0, marker; marker=this.markers_[i]; i++){
marker.isAdded=false;
if(opt_hide){
marker.setMap(null);
}}
this.clusters_=[];
};
MarkerClusterer.prototype.repaint=function(){
var oldClusters=this.clusters_.slice();
this.clusters_.length=0;
this.resetViewport();
this.redraw();
window.setTimeout(function(){
for (var i=0, cluster; cluster=oldClusters[i]; i++){
cluster.remove();
}}, 0);
};
MarkerClusterer.prototype.redraw=function(){
this.createClusters_();
};
MarkerClusterer.prototype.distanceBetweenPoints_=function(p1, p2){
if(!p1||!p2){
return 0;
}
var R=6371;
var dLat=(p2.lat() - p1.lat()) * Math.PI / 180;
var dLon=(p2.lng() - p1.lng()) * Math.PI / 180;
var a=Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c=2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d=R * c;
return d;
};
MarkerClusterer.prototype.addToClosestCluster_=function(marker){
var distance=40000;
var clusterToAddTo=null;
var pos=marker.getPosition();
for (var i=0, cluster; cluster=this.clusters_[i]; i++){
var center=cluster.getCenter();
if(center){
var d=this.distanceBetweenPoints_(center, marker.getPosition());
if(d < distance){
distance=d;
clusterToAddTo=cluster;
}}
}
if(clusterToAddTo&&clusterToAddTo.isMarkerInClusterBounds(marker)){
clusterToAddTo.addMarker(marker);
}else{
var cluster=new Cluster(this);
cluster.addMarker(marker);
this.clusters_.push(cluster);
}};
MarkerClusterer.prototype.createClusters_=function(){
if(!this.ready_){
return;
}
var mapBounds=new google.maps.LatLngBounds(this.map_.getBounds().getSouthWest(),
this.map_.getBounds().getNorthEast());
var bounds=this.getExtendedBounds(mapBounds);
for (var i=0, marker; marker=this.markers_[i]; i++){
if(!marker.isAdded&&this.isMarkerInBounds_(marker, bounds)){
this.addToClosestCluster_(marker);
}}
};
function Cluster(markerClusterer){
this.markerClusterer_=markerClusterer;
this.map_=markerClusterer.getMap();
this.gridSize_=markerClusterer.getGridSize();
this.minClusterSize_=markerClusterer.getMinClusterSize();
this.averageCenter_=markerClusterer.isAverageCenter();
this.center_=null;
this.markers_=[];
this.bounds_=null;
this.clusterIcon_=new ClusterIcon(this, markerClusterer.getStyles(),
markerClusterer.getGridSize());
}
Cluster.prototype.isMarkerAlreadyAdded=function(marker){
if(this.markers_.indexOf){
return this.markers_.indexOf(marker)!=-1;
}else{
for (var i=0, m; m=this.markers_[i]; i++){
if(m==marker){
return true;
}}
}
return false;
};
Cluster.prototype.addMarker=function(marker){
if(this.isMarkerAlreadyAdded(marker)){
return false;
}
if(!this.center_){
this.center_=marker.getPosition();
this.calculateBounds_();
}else{
if(this.averageCenter_){
var l=this.markers_.length + 1;
var lat=(this.center_.lat() * (l-1) + marker.getPosition().lat()) / l;
var lng=(this.center_.lng() * (l-1) + marker.getPosition().lng()) / l;
this.center_=new google.maps.LatLng(lat, lng);
this.calculateBounds_();
}}
marker.isAdded=true;
this.markers_.push(marker);
var len=this.markers_.length;
if(len < this.minClusterSize_&&marker.getMap()!=this.map_){
marker.setMap(this.map_);
}
if(len==this.minClusterSize_){
for (var i=0; i < len; i++){
this.markers_[i].setMap(null);
}}
if(len >=this.minClusterSize_){
marker.setMap(null);
}
this.updateIcon();
return true;
};
Cluster.prototype.getMarkerClusterer=function(){
return this.markerClusterer_;
};
Cluster.prototype.getBounds=function(){
var bounds=new google.maps.LatLngBounds(this.center_, this.center_);
var markers=this.getMarkers();
for (var i=0, marker; marker=markers[i]; i++){
bounds.extend(marker.getPosition());
}
return bounds;
};
Cluster.prototype.remove=function(){
this.clusterIcon_.remove();
this.markers_.length=0;
delete this.markers_;
};
Cluster.prototype.getSize=function(){
return this.markers_.length;
};
Cluster.prototype.getMarkers=function(){
return this.markers_;
};
Cluster.prototype.getCenter=function(){
return this.center_;
};
Cluster.prototype.calculateBounds_=function(){
var bounds=new google.maps.LatLngBounds(this.center_, this.center_);
this.bounds_=this.markerClusterer_.getExtendedBounds(bounds);
};
Cluster.prototype.isMarkerInClusterBounds=function(marker){
return this.bounds_.contains(marker.getPosition());
};
Cluster.prototype.getMap=function(){
return this.map_;
};
Cluster.prototype.updateIcon=function(){
var zoom=this.map_.getZoom();
var mz=this.markerClusterer_.getMaxZoom();
if(zoom > mz){
for (var i=0, marker; marker=this.markers_[i]; i++){
marker.setMap(this.map_);
}
return;
}
if(this.markers_.length < this.minClusterSize_){
this.clusterIcon_.hide();
return;
}
var numStyles=this.markerClusterer_.getStyles().length;
var sums=this.markerClusterer_.getCalculator()(this.markers_, numStyles);
this.clusterIcon_.setCenter(this.center_);
this.clusterIcon_.setSums(sums);
this.clusterIcon_.show();
};
function ClusterIcon(cluster, styles, opt_padding){
cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
this.styles_=styles;
this.padding_=opt_padding||0;
this.cluster_=cluster;
this.center_=null;
this.map_=cluster.getMap();
this.div_=null;
this.sums_=null;
this.visible_=false;
this.setMap(this.map_);
}
ClusterIcon.prototype.triggerClusterClick=function(){
var markerClusterer=this.cluster_.getMarkerClusterer();
google.maps.event.trigger(markerClusterer, 'clusterclick', this.cluster_);
if(markerClusterer.isZoomOnClick()){
this.map_.fitBounds(this.cluster_.getBounds());
}};
ClusterIcon.prototype.onAdd=function(){
this.div_=document.createElement('DIV');
if(this.visible_){
var pos=this.getPosFromLatLng_(this.center_);
this.div_.style.cssText=this.createCss(pos);
this.div_.innerHTML=this.sums_.text;
}
var panes=this.getPanes();
panes.overlayMouseTarget.appendChild(this.div_);
var that=this;
google.maps.event.addDomListener(this.div_, 'click', function(){
that.triggerClusterClick();
});
};
ClusterIcon.prototype.getPosFromLatLng_=function(latlng){
var pos=this.getProjection().fromLatLngToDivPixel(latlng);
pos.x -=parseInt(this.width_ / 2, 10);
pos.y -=parseInt(this.height_ / 2, 10);
return pos;
};
ClusterIcon.prototype.draw=function(){
if(this.visible_){
var pos=this.getPosFromLatLng_(this.center_);
this.div_.style.top=pos.y + 'px';
this.div_.style.left=pos.x + 'px';
}};
ClusterIcon.prototype.hide=function(){
if(this.div_){
this.div_.style.display='none';
}
this.visible_=false;
};
ClusterIcon.prototype.show=function(){
if(this.div_){
var pos=this.getPosFromLatLng_(this.center_);
this.div_.style.cssText=this.createCss(pos);
this.div_.style.display='';
}
this.visible_=true;
};
ClusterIcon.prototype.remove=function(){
this.setMap(null);
};
ClusterIcon.prototype.onRemove=function(){
if(this.div_&&this.div_.parentNode){
this.hide();
this.div_.parentNode.removeChild(this.div_);
this.div_=null;
}};
ClusterIcon.prototype.setSums=function(sums){
this.sums_=sums;
this.text_=sums.text;
this.index_=sums.index;
if(this.div_){
this.div_.innerHTML=sums.text;
}
this.useStyle();
};
ClusterIcon.prototype.useStyle=function(){
var index=Math.max(0, this.sums_.index - 1);
index=Math.min(this.styles_.length - 1, index);
var style=this.styles_[index];
this.url_=style['url'];
this.height_=style['height'];
this.width_=style['width'];
this.textColor_=style['textColor'];
this.anchor_=style['anchor'];
this.textSize_=style['textSize'];
this.backgroundPosition_=style['backgroundPosition'];
};
ClusterIcon.prototype.setCenter=function(center){
this.center_=center;
};
ClusterIcon.prototype.createCss=function(pos){
var style=[];
if(document.all&&document.namespaces){
style.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
'sizingMethod=scale,src="' + this.url_ + '");');
}else{
style.push('background-image:url(' + this.url_ + ');');
var backgroundPosition=this.backgroundPosition_ ? this.backgroundPosition_:'0 0';
style.push('background-position:' + backgroundPosition + ';');
}
if(typeof this.anchor_==='object'){
if(typeof this.anchor_[0]==='number'&&this.anchor_[0] > 0 &&
this.anchor_[0] < this.height_){
style.push('height:' + (this.height_ - this.anchor_[0]) +
'px; padding-top:' + this.anchor_[0] + 'px;');
}else{
style.push('height:' + this.height_ + 'px; line-height:' + this.height_ +
'px;');
}
if(typeof this.anchor_[1]==='number'&&this.anchor_[1] > 0 &&
this.anchor_[1] < this.width_){
style.push('width:' + (this.width_ - this.anchor_[1]) +
'px; padding-left:' + this.anchor_[1] + 'px;');
}else{
style.push('width:' + this.width_ + 'px; text-align:center;');
}}else{
style.push('height:' + this.height_ + 'px; line-height:' +
this.height_ + 'px; width:' + this.width_ + 'px; text-align:center;');
}
var txtColor=this.textColor_ ? this.textColor_:'white';
var txtSize=this.textSize_ ? this.textSize_:11;
style.push('cursor:pointer; top:' + pos.y + 'px; left:' +
pos.x + 'px; color:' + txtColor + '; position:absolute; font-size:' +
txtSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
return style.join('');
};
window['MarkerClusterer']=MarkerClusterer;
MarkerClusterer.prototype['addMarker']=MarkerClusterer.prototype.addMarker;
MarkerClusterer.prototype['addMarkers']=MarkerClusterer.prototype.addMarkers;
MarkerClusterer.prototype['clearMarkers']=MarkerClusterer.prototype.clearMarkers;
MarkerClusterer.prototype['fitMapToMarkers']=MarkerClusterer.prototype.fitMapToMarkers;
MarkerClusterer.prototype['getCalculator']=MarkerClusterer.prototype.getCalculator;
MarkerClusterer.prototype['getGridSize']=MarkerClusterer.prototype.getGridSize;
MarkerClusterer.prototype['getExtendedBounds']=MarkerClusterer.prototype.getExtendedBounds;
MarkerClusterer.prototype['getMap']=MarkerClusterer.prototype.getMap;
MarkerClusterer.prototype['getMarkers']=MarkerClusterer.prototype.getMarkers;
MarkerClusterer.prototype['getMaxZoom']=MarkerClusterer.prototype.getMaxZoom;
MarkerClusterer.prototype['getStyles']=MarkerClusterer.prototype.getStyles;
MarkerClusterer.prototype['getTotalClusters']=MarkerClusterer.prototype.getTotalClusters;
MarkerClusterer.prototype['getTotalMarkers']=MarkerClusterer.prototype.getTotalMarkers;
MarkerClusterer.prototype['redraw']=MarkerClusterer.prototype.redraw;
MarkerClusterer.prototype['removeMarker']=MarkerClusterer.prototype.removeMarker;
MarkerClusterer.prototype['removeMarkers']=MarkerClusterer.prototype.removeMarkers;
MarkerClusterer.prototype['resetViewport']=MarkerClusterer.prototype.resetViewport;
MarkerClusterer.prototype['repaint']=MarkerClusterer.prototype.repaint;
MarkerClusterer.prototype['setCalculator']=MarkerClusterer.prototype.setCalculator;
MarkerClusterer.prototype['setGridSize']=MarkerClusterer.prototype.setGridSize;
MarkerClusterer.prototype['onAdd']=MarkerClusterer.prototype.onAdd;
MarkerClusterer.prototype['draw']=MarkerClusterer.prototype.draw;
Cluster.prototype['getCenter']=Cluster.prototype.getCenter;
Cluster.prototype['getSize']=Cluster.prototype.getSize;
Cluster.prototype['getMarkers']=Cluster.prototype.getMarkers;
ClusterIcon.prototype['onAdd']=ClusterIcon.prototype.onAdd;
ClusterIcon.prototype['draw']=ClusterIcon.prototype.draw;
ClusterIcon.prototype['onRemove']=ClusterIcon.prototype.onRemove;