Clusterer=function(map)
{   this.map=map;
    this.markers=[];
    this.clusters=[];
    this.timeout=null;
    this.currentZoomLevel=map.getZoom();
    this.maxVisibleMarkers=Clusterer.defaultMaxVisibleMarkers;
    this.gridSize=Clusterer.defaultGridSize;
    this.minMarkersPerCluster=Clusterer.defaultMinMarkersPerCluster;
    this.maxLinesPerInfoBox=Clusterer.defaultMaxLinesPerInfoBox;
    this.icon=Clusterer.defaultIcon;
    GEvent.addListener(map,'zoomend',Clusterer.MakeCaller(Clusterer.Display,this));
    GEvent.addListener(map,'moveend',Clusterer.MakeCaller(Clusterer.Display,this));
    GEvent.addListener(map,'infowindowclose',Clusterer.MakeCaller(Clusterer.PopDown,this));
};
Clusterer.defaultMaxVisibleMarkers=150;
Clusterer.defaultGridSize=5;
Clusterer.defaultMinMarkersPerCluster=5;
Clusterer.defaultMaxLinesPerInfoBox=10;
Clusterer.defaultIcon=new GIcon();
Clusterer.defaultIcon.image='http://www.acme.com/resources/images/markers/blue_large.PNG';
Clusterer.defaultIcon.shadow='http://www.acme.com/resources/images/markers/shadow_large.PNG';
Clusterer.defaultIcon.iconSize=new GSize(30,51);
Clusterer.defaultIcon.shadowSize=new GSize(56,51);
Clusterer.defaultIcon.iconAnchor=new GPoint(13,34);
Clusterer.defaultIcon.infoWindowAnchor=new GPoint(13,3);
Clusterer.defaultIcon.infoShadowAnchor=new GPoint(27,37);
Clusterer.prototype.SetIcon=function(icon)
{    this.icon=icon; };
Clusterer.prototype.SetMaxVisibleMarkers=function(n)
{    this.maxVisibleMarkers=n;};
Clusterer.prototype.SetMinMarkersPerCluster=function(n)
{    this.minMarkersPerCluster=n;};
Clusterer.prototype.SetMaxLinesPerInfoBox=function(n)
{    this.maxLinesPerInfoBox=n;};
Clusterer.prototype.AddMarker=function(marker,title)
{    if(marker.setMap!=null)
    marker.setMap(this.map);
    marker.title=title;
    marker.onMap=false;
    this.markers.push(marker);
    this.DisplayLater();};
Clusterer.prototype.RemoveMarker=function(marker)
{    for(var i=0;i<this.markers.length;++i)
    if(this.markers[i]==marker)
    {   if(marker.onMap)
        this.map.removeOverlay(marker);
        for(var j=0;j<this.clusters.length;++j)
        {   var cluster=this.clusters[j];
            if(cluster!=null)
            {   for(var k=0;k<cluster.markers.length;++k)
                if(cluster.markers[k]==marker)
                {   cluster.markers[k]=null;
                    --cluster.markerCount;
                    break;}
                if(cluster.markerCount==0)
                {   this.ClearCluster(cluster);
                    this.clusters[j]=null;
                }
                else if(cluster==this.poppedUpCluster)
                Clusterer.RePop(this);
            }
        }
        this.markers[i]=null;
        break;
    }
    this.DisplayLater();
};
Clusterer.prototype.DisplayLater=function()
{   if(this.timeout!=null)
    clearTimeout(this.timeout);
    this.timeout=setTimeout(Clusterer.MakeCaller(Clusterer.Display,this),50);
};
Clusterer.Display=function(clusterer)
{   var i,j,marker,cluster;
    clearTimeout(clusterer.timeout);
    var newZoomLevel=clusterer.map.getZoom();
    if(newZoomLevel!=clusterer.currentZoomLevel)
    {   for(i=0;i<clusterer.clusters.length;++i)
        if(clusterer.clusters[i]!=null)
        {   clusterer.ClearCluster(clusterer.clusters[i]);
            clusterer.clusters[i]=null;
        }
        clusterer.clusters.length=0;
        clusterer.currentZoomLevel=newZoomLevel;
    }
    var bounds=clusterer.map.getBounds();
    var sw=bounds.getSouthWest();
    var ne=bounds.getNorthEast();
    var dx=ne.lng()-sw.lng();
    var dy=ne.lat()-sw.lat();
    if(dx<300&&dy<150)
    {   dx*=0.10;
        dy*=0.10;
        bounds=new GLatLngBounds(new GLatLng(sw.lat()-dy,sw.lng()-dx),new GLatLng(ne.lat()+dy,ne.lng()+dx));
    }
    var visibleMarkers=[];
    var nonvisibleMarkers=[];
	//document.getElementById("sidebar_debug").innerHTML += "Nbre de markers passés au cluster="+clusterer.markers.length;
    for(i=0;i<clusterer.markers.length;++i)
    {    // séparation des markers qui sont dans la zone d'affichage de ceux endehors de la zone d'affichage
		marker=clusterer.markers[i];
        if(marker!=null)
        if(bounds.contains(marker.getPoint()))
        visibleMarkers.push(marker);
        else
        nonvisibleMarkers.push(marker);
    }
    //document.getElementById("sidebar_debug").innerHTML += " - Nbre de markers Visibles ="+visibleMarkers.length;
   	for(i=0;i<nonvisibleMarkers.length;++i)
    {   // suppression des markers en dehors de la zone d'affichege de la carte
		marker=nonvisibleMarkers[i];
        if(marker.onMap)
        {   clusterer.map.removeOverlay(marker);
            marker.onMap=false;
        }
    }
	//document.getElementById("sidebar_debug").innerHTML += " - Nbre de markers Clusterisés déjà existants avant zoom de la carte="+clusterer.clusters.length+"<br>";
    for(i=0;i<clusterer.clusters.length;++i)
    {   // supression des clusters qui sont en dehors de la zone d'affichage (cas ou l'on change le zoom)
		cluster=clusterer.clusters[i];
        if(cluster!=null&&!bounds.contains(cluster.marker.getPoint())&&cluster.onMap)
        {   clusterer.map.removeOverlay(cluster.marker);
            cluster.onMap=false;
        }
    }
    if(visibleMarkers.length>clusterer.maxVisibleMarkers)
    {   // groupage des markers en clusters
		var latRange=bounds.getNorthEast().lat()-bounds.getSouthWest().lat();
        var latInc=latRange/clusterer.gridSize;
        var lngInc=latInc/Math.cos((bounds.getNorthEast().lat()+bounds.getSouthWest().lat())/2.0*Math.PI/180.0);
        for(var lat=bounds.getSouthWest().lat();lat<=bounds.getNorthEast().lat();lat+=latInc)
        for(var lng=bounds.getSouthWest().lng();lng<=bounds.getNorthEast().lng();lng+=lngInc)
        {  // je prédéfinie tous mes clusters (clusterer.clusters) 
			cluster=new Object();
            cluster.clusterer=clusterer;
            cluster.bounds=new GLatLngBounds(new GLatLng(lat,lng),new GLatLng(lat+latInc,lng+lngInc));
            cluster.markers=[]; // aucun marker dans les clusters
            cluster.markerCount=0;
            cluster.onMap=false; // non visibles
            cluster.marker=null;
            clusterer.clusters.push(cluster); 
        }
        for(i=0;i<visibleMarkers.length;++i)
        {   // j'ajoute mes markers visibles aux clusters existants
			marker=visibleMarkers[i];
            if(marker!=null&&!marker.inCluster)
            {   for(j=0;j<clusterer.clusters.length;++j) // parcours des clusters existants
                {   cluster=clusterer.clusters[j];
                    if(cluster!=null&&cluster.bounds.contains(marker.getPoint()))
                    {  // si le marker est dans la zone couverte par le cluster je l'ajoute au cluster et je rends le cluster visible
						cluster.markers.push(marker);
                        ++cluster.markerCount;
                        marker.inCluster=true;
                    }
                }
            }
        }
        for(i=0;i<clusterer.clusters.length;++i)
        if(clusterer.clusters[i]!=null&&clusterer.clusters[i].markerCount<clusterer.minMarkersPerCluster) 
        {  // si le nombre de markers du cluster est inférieur au mini de marker par cluster je les sort du cluster et je remet le cluster à null
            clusterer.ClearCluster(clusterer.clusters[i]); 
            clusterer.clusters[i]=null;
        }
        for(i=clusterer.clusters.length-1;i>=0;--i)
        if(clusterer.clusters[i]!=null)
        break;
        else
        --clusterer.clusters.length;
        for(i=0;i<clusterer.clusters.length;++i)
        {  // si un des markers dèjà affiché sur la carte est dans un cluster, je le vire de l'affichage de la carte
			cluster=clusterer.clusters[i];
            if(cluster!=null)
            {   for(j=0;j<cluster.markers.length;++j)
                {   marker=cluster.markers[j];
                    if(marker!=null&&marker.onMap)
                    {   clusterer.map.removeOverlay(marker);
                        marker.onMap=false;
                    }
                }
            }
        }
        for(i=0;i<clusterer.clusters.length;++i)
        {   // si un cluster n'est pas null je l'affiche sur la carte
			cluster=clusterer.clusters[i];
            if(cluster!=null&&cluster.marker==null)
            {   var xTotal=0.0,yTotal=0.0;
                for(j=0;j<cluster.markers.length;++j)
                {   marker=cluster.markers[j];
                    if(marker!=null)
                    {   xTotal+=(+marker.getPoint().lng());
                        yTotal+=(+marker.getPoint().lat());
                    }
                }
                var location=new GLatLng(yTotal/cluster.markerCount,xTotal/cluster.markerCount);
    			
				marker=new GMarker(location,
                { icon:clusterer.icon
                }
                );
				cluster.marker=marker;
 				//document.getElementById("sidebar_debug").innerHTML += " - Icones du cluster " +i+ "(" +cluster.markerCount+" markers) = "+clusterer.icon.image+"<br>";
                GEvent.addListener(marker,'click',Clusterer.MakeCaller(Clusterer.PopUp,cluster));
            }
        }
    }
    for(i=0;i<visibleMarkers.length;++i)
    {   // affichage des markers visibles mais pas dans un cluster
		marker=visibleMarkers[i];
        if(marker!=null&&!marker.onMap&&!marker.inCluster)
        {   clusterer.map.addOverlay(marker);
            if(marker.addedToMap!=null)
            marker.addedToMap();
            marker.onMap=true;
        }
    }
    for(i=0;i<clusterer.clusters.length;++i)
    {   cluster=clusterer.clusters[i];
        if(cluster!=null&&!cluster.onMap&&bounds.contains(cluster.marker.getPoint()))
        {  	//document.getElementById("sidebar_debug").innerHTML += "ca passe ici ? Cluster n°"+i+"<br>";
			// ***********************************************************************************************************************************
			// modif par Pierrot *****************************************************************************************************************
			// Icone
			maxSizeClusterHeight=clusterer.iconSizeHeight*1;
			minSizeClusterHeight=clusterer.iconSizeHeight*0.40;
			maxSizeClusterWidth=clusterer.iconSizeWidth*1;
			minSizeClusterWidth=clusterer.iconSizeWidth*0.40;
			//document.getElementById("sidebar_debug").innerHTML += "minSizeClusterHeight="+Clusterer.iconSizeHeight+"Hauteur_Cluster="+minSizeClusterHeight*(1+ (cluster.markerCount/(visibleMarkers.length/10)))+"<br>";
			SizeClusterHeight=minSizeClusterHeight+maxSizeClusterHeight*(cluster.markerCount/(visibleMarkers.length/10));
			SizeClusterWidth=minSizeClusterWidth+maxSizeClusterWidth*(cluster.markerCount/(visibleMarkers.length/10));
			if (SizeClusterHeight > maxSizeClusterHeight) {SizeClusterHeight = maxSizeClusterHeight;}
			if (SizeClusterWidth > maxSizeClusterWidth) {SizeClusterWidth = maxSizeClusterWidth;}
			clusterer.icon.iconSize = new GSize(SizeClusterHeight,SizeClusterWidth);
			// Ombre	
			maxSizeClusterShadowHeight=clusterer.shadowSizeHeight*1;
			minSizeClusterShadowHeight=clusterer.shadowSizeHeight*0.40;
			maxSizeClusterShadowWidth=clusterer.shadowSizeWidth*1;
			minSizeClusterShadowWidth=clusterer.shadowSizeWidth*0.40;
			//document.getElementById("sidebar_debug").innerHTML += "minSizeClusterShadowHeight="+Clusterer.shadowSizeHeight+"Hauteur_ClusterShadow="+minSizeClusterShadowHeight*(1+ (cluster.markerCount/(visibleMarkers.length/10)))+"<br>";
			SizeClusterShadowHeight=minSizeClusterShadowHeight+maxSizeClusterShadowHeight*(cluster.markerCount/(visibleMarkers.length/10));
			SizeClusterShadowWidth=minSizeClusterShadowWidth+maxSizeClusterShadowWidth*(cluster.markerCount/(visibleMarkers.length/10));
			if (SizeClusterShadowHeight > maxSizeClusterShadowHeight) {SizeClusterShadowHeight = maxSizeClusterShadowHeight;}
			if (SizeClusterShadowWidth > maxSizeClusterShadowWidth) {SizeClusterShadowWidth = maxSizeClusterShadowWidth;}
			clusterer.icon.shadowSize = new GSize(SizeClusterShadowHeight,SizeClusterShadowWidth);
			// ***********************************************************************************************************************************
			// ***********************************************************************************************************************************
  			clusterer.map.addOverlay(cluster.marker);
        cluster.onMap=true;
        }
    }
    Clusterer.RePop(clusterer);
};
Clusterer.PopUp=function(cluster)
{   // construction du html de la popup en focntion des markers contenus dans le cluster
	var clusterer=cluster.clusterer;
    var html='<table width="300">';
    var n=0;
    for(var i=0;i<cluster.markers.length;++i)
    {   var marker=cluster.markers[i];
        if(marker!=null)
        {   ++n;
            html+='<tr><td>';
            if(marker.getIcon().smallImage!=null)
            html+='<img src="'+marker.getIcon().smallImage+'">';
            else
            html+='<img src="'+marker.getIcon().image+'" width="'+(marker.getIcon().iconSize.width/2)+'" height="'+(marker.getIcon().iconSize.height/2)+'">';
            html+='</td><td>'+marker.title+'</td></tr>';
            if(n==clusterer.maxLinesPerInfoBox-1&&cluster.markerCount>clusterer.maxLinesPerInfoBox)
            {   html+='<tr><td colspan="2">...and '+(cluster.markerCount-n)+' more</td></tr>';
                break;
            }
        }
    }
    html+='</table>';
    clusterer.map.closeInfoWindow();
    cluster.marker.openInfoWindowHtml(html);
    clusterer.poppedUpCluster=cluster;
};
Clusterer.RePop=function(clusterer)
{   if(clusterer.poppedUpCluster!=null)
    Clusterer.PopUp(clusterer.poppedUpCluster);
};
Clusterer.PopDown=function(clusterer)
{   clusterer.poppedUpCluster=null;
};
Clusterer.prototype.ClearCluster=function(cluster)
{   var i,marker;
    for(i=0;i<cluster.markers.length;++i)
    if(cluster.markers[i]!=null)
    {   cluster.markers[i].inCluster=false;
        cluster.markers[i]=null;
    }
    cluster.markers.length=0;
    cluster.markerCount=0;
    if(cluster==this.poppedUpCluster)
    this.map.closeInfoWindow();
    if(cluster.onMap)
    {   this.map.removeOverlay(cluster.marker);
        cluster.onMap=false;
    }
};
Clusterer.MakeCaller=function(func,arg)
{return function()
{   func(arg);
};
};
GMarker.prototype.setMap=function(map)
{this.map=map;
};
GMarker.prototype.addedToMap=function()
{this.map=null;
};
GMarker.prototype.origOpenInfoWindow=GMarker.prototype.openInfoWindow;
GMarker.prototype.openInfoWindow=function(node,opts)
{if(this.map!=null)
return this.map.openInfoWindow(this.getPoint(),node,opts);
else
return this.origOpenInfoWindow(node,opts);
};
GMarker.prototype.origOpenInfoWindowHtml=GMarker.prototype.openInfoWindowHtml;
GMarker.prototype.openInfoWindowHtml=function(html,opts)
{if(this.map!=null)
return this.map.openInfoWindowHtml(this.getPoint(),html,opts);
else
return this.origOpenInfoWindowHtml(html,opts);
};
GMarker.prototype.origOpenInfoWindowTabs=GMarker.prototype.openInfoWindowTabs;
GMarker.prototype.openInfoWindowTabs=function(tabNodes,opts)
{if(this.map!=null)
return this.map.openInfoWindowTabs(this.getPoint(),tabNodes,opts);
else
return this.origOpenInfoWindowTabs(tabNodes,opts);
};
GMarker.prototype.origOpenInfoWindowTabsHtml=GMarker.prototype.openInfoWindowTabsHtml;
GMarker.prototype.openInfoWindowTabsHtml=function(tabHtmls,opts)
{if(this.map!=null)
return this.map.openInfoWindowTabsHtml(this.getPoint(),tabHtmls,opts);
else
return this.origOpenInfoWindowTabsHtml(tabHtmls,opts);
};
GMarker.prototype.origShowMapBlowup=GMarker.prototype.showMapBlowup;
GMarker.prototype.showMapBlowup=function(opts)
{if(this.map!=null)
return this.map.showMapBlowup(this.getPoint(),opts);
else
return this.origShowMapBlowup(opts);
};
