// Clusterer.js - marker clustering routines for Google Maps apps
//
// Using these routines is very easy.
//
// 1) Load the routines into your code:
//
//        <script src="http://www.acme.com/javascript/Clusterer.js" type="text/javascript"></script>
//
// 2) Create a Clusterer object, passing it your map object:
//
//        var clusterer = new Clusterer( map );
//
// 3) Wherever you now do map.addOverlay( marker ), instead call
//    clusterer.AddMarker( marker, title ).  The title is just a
//    short descriptive string to use in the cluster info-boxes.
//
// 4) If you are doing any map.removeOverlay( marker ) calls, change those
//    to clusterer.RemoveMarker( marker ).
//
// That's it!  Everything else happens automatically.
//
//
// The current version of this code is always available at:
// http://www.acme.com/javascript/
//
//
// Copyright © 2005,2006 by Jef Poskanzer <jef@mail.acme.com>.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// For commentary on this license please see http://www.acme.com/license.html


// Constructor.
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 );


// Call this to change the cluster icon.
Clusterer.prototype.SetIcon = function ( icon )
    {
    this.icon = icon;
    };


// Changes the maximum number of visible markers before clustering kicks in.
Clusterer.prototype.SetMaxVisibleMarkers = function ( n )
    {
    this.maxVisibleMarkers = n;
    };


// Sets the minumum number of markers for a cluster.
Clusterer.prototype.SetMinMarkersPerCluster = function ( n )
    {
    this.minMarkersPerCluster = n;
    };


// Sets the maximum number of lines in an info box.
Clusterer.prototype.SetMaxLinesPerInfoBox = function ( n )
    {
    this.maxLinesPerInfoBox = n;
    };


// Call this to add a marker.
Clusterer.prototype.AddMarker = function ( marker, aD, last_i)
    {
    if ( marker.setMap != null )
	marker.setMap( this.map );

    marker.aD = aD;
    marker.onMap = false;
    // AnE: Nummerierung wegen Sidebar
    marker.last_i = last_i;
    
    this.markers.push( marker );
    this.DisplayLater();
    };


// Call this to remove a marker.
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 )
	{
	// When the zoom level changes, we have to remove all the clusters.
	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;
	}

    // Get the current bounds of the visible area.
    var bounds = clusterer.map.getBounds();

/*
    // AnE: rausgenommen, da durch das neue Bounds-Rechteck Marker in bestimmten
    // Faellen nicht angezeigt werden (Bsp.: Japan/Indien)
 
    // Expand the bounds a little, so things look smoother when scrolling
    // by small amounts.
    var sw = bounds.getSouthWest();
    alert('bounds sw: '+sw);
    var ne = bounds.getNorthEast();
    alert('bounds ne: '+ne);
    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 ) );
	}
    alert('new bounds sw lat: '+(sw.lat() - dy));
    alert('new bounds sw lng: '+(sw.lng() - dx));    
    alert('new bounds ne lat: '+(ne.lat() + dy));
    alert('new bounds ne lng: '+(ne.lng() + dx));        
*/

    // AnE, Sidebar generieren (nur sichtbare Marker)
    var mySidebar = document.getElementById("side_bar");
    var sidebarContent = '';
    
    // Partition the markers into visible and non-visible lists.
    var visibleMarkers = [];
    var nonvisibleMarkers = [];
    for ( i = 0; i < clusterer.markers.length; ++i )
	{
	marker = clusterer.markers[i];
	if ( marker != null )
	    if ( bounds.contains( marker.getPoint() ) )
        {
            visibleMarkers.push( marker );
            
            // AnE, Sidebar generieren (nur sichtbare Marker)
            // (last_i fuer Zugriff auf richtiges InfoWindow)
            sidebarContent += '<a href="javascript:myclick(' + marker.last_i + ')">' + marker.aD.lN + ', ' + marker.aD.fN + '<\/a>';
        }
	    else
		nonvisibleMarkers.push( marker );
	}
    // AnE
    var myCheckbox = document.getElementById('sidebar_aktualisieren');
    if (myCheckbox.checked)
        mySidebar.innerHTML = sidebarContent;

    // Take down the non-visible markers.
    for ( i = 0; i < nonvisibleMarkers.length; ++i )
	{
	marker = nonvisibleMarkers[i];
	if ( marker.onMap )
	    {
	    clusterer.map.removeOverlay( marker );
	    marker.onMap = false;
	    }
	}

    // Take down the non-visible clusters.
    for ( i = 0; i < clusterer.clusters.length; ++i )
	{
	cluster = clusterer.clusters[i];
	if ( cluster != null && ! bounds.contains( cluster.marker.getPoint() ) && cluster.onMap )
	    {
	    clusterer.map.removeOverlay( cluster.marker );
	    cluster.onMap = false;
	    }
	}

    // Clustering!  This is some complicated stuff.  We have three goals
    // here.  One, limit the number of markers & clusters displayed, so the
    // maps code doesn't slow to a crawl.  Two, when possible keep existing
    // clusters instead of replacing them with new ones, so that the app pans
    // better.  And three, of course, be CPU and memory efficient.
    if ( visibleMarkers.length > clusterer.maxVisibleMarkers )
	{
	// Add to the list of clusters by splitting up the current bounds
	// into a grid.
	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 );
    
    var for_counter_inner = 0;
    var for_counter_outer = 0;    
    
    var initialSwLat = bounds.getSouthWest().lat();
    var initialNeLat = bounds.getNorthEast().lat();
    var initialSwLng = bounds.getSouthWest().lng();
    var initialNeLng = bounds.getNorthEast().lng();
    
/*
	console.log("initialSwLat: %f", initialSwLat);
	console.log("initialNeLat: %f", initialNeLat);
	console.log("initialSwLng: %f", initialSwLng);
	console.log("initialNeLng: %f", initialNeLng);		
*/
	
    // AnE: Bugfix
    // Problem: innere for-Schleife funktioniert nicht, wenn das bounds-Rechteck
    // eine Flaeche umschließt, die Bereiche links und rechts des Nullmeridians umfasst
    // Dadurch entsteht eine Abfrage fuer Lng, z.B. 88 <= -174, die nie erfuellt ist
    // Workaround: Differenz Lng ermitteln. Diese schrittweise zu SW-Lng aufaddieren
    // (Ersatz fuer innere for-Schleife)
    // in der inneren for-schleife dann zwischen lng < 180 und lng > 180 unterscheiden.
    // wenn lng > 180, dann lng = -180 + schrittweieser Offset
    //
    // Differenz Lng ermitteln
    // links und rechts des Nullmeridians
    if ((initialSwLng > 0 && initialNeLng < 0) || (initialSwLng < 0 && initialNeLng > 0) ||
		(initialSwLng > initialNeLng && initialSwLng > 0 && initialNeLng > 0)
		)
    {
//		console.log('links und rechts des Nullmeridians');
        var tmp = initialNeLng + 180;
        var diffLng = (180 - initialSwLng) + tmp;
    }
    // auf derselben Seite des Nullmeridians
    else
    {
//		console.log('auf derselben Seite des Nullmeridians');		
        var diffLng = initialNeLng - initialSwLng;
    }
    
	for ( var lat = bounds.getSouthWest().lat(); lat <= bounds.getNorthEast().lat(); lat += latInc )
	{
        // AnE
        for ( var lng = bounds.getSouthWest().lng(); lng <= (initialSwLng + diffLng) ; lng += lngInc )
        
		{
		cluster = new Object();
		cluster.clusterer = clusterer;
        
        useLat = lat;
        useLng = lng;
        
        // AnE
        if (lng > 180)
        {
            var tmp2 = lng - 180;
            useLng = -180 + tmp2;
        }
        
		cluster.bounds = new GLatLngBounds( new GLatLng( useLat, useLng ), new GLatLng( useLat + latInc, useLng + lngInc ) );
		cluster.markers = [];
		cluster.markerCount = 0;
		cluster.onMap = false;
		cluster.marker = null;
		clusterer.clusters.push( cluster );
        for_counter_inner++;
		}
        for_counter_outer++;
    }

	// Put all the unclustered visible markers into a cluster - the first
	// one it fits in, which favors pre-existing clusters.
	for ( i = 0; i < visibleMarkers.length; ++i )
	    {
	    marker = visibleMarkers[i];
	    if ( marker != null && ! marker.inCluster )
		{
		for ( j = 0; j < clusterer.clusters.length; ++j )
		    {
		    cluster = clusterer.clusters[j];
		    if ( cluster != null && cluster.bounds.contains( marker.getPoint() ) )
			{
			cluster.markers.push( marker );
			++cluster.markerCount;
			marker.inCluster = true;
			}
		    }
		}
	    }

	// Get rid of any clusters containing only a few markers.
	for ( i = 0; i < clusterer.clusters.length; ++i )
	    if ( clusterer.clusters[i] != null && clusterer.clusters[i].markerCount < clusterer.minMarkersPerCluster )
		{
		clusterer.ClearCluster( clusterer.clusters[i] );
		clusterer.clusters[i] = null;
		}

	// Shrink the clusters list.
	for ( i = clusterer.clusters.length - 1; i >= 0; --i )
	    if ( clusterer.clusters[i] != null )
		break;
	    else
		--clusterer.clusters.length;

	// Ok, we have our clusters.  Go through the markers in each
	// cluster and remove them from the map if they are currently up.
	for ( i = 0; i < clusterer.clusters.length; ++i )
	    {
	    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;
            // AnE: Label fuer einzelnen Marker auch wieder verstecken
            var markerLabelId = 'markerLabel'+marker.last_i;
            if (document.getElementById(markerLabelId))
            {
                markerLabel = document.getElementById(markerLabelId);
                markerLabel.style.visibility = 'hidden';
            }
			}
		    }
		}
	    }

        // Now make cluster-markers for any clusters that need one.
        for ( i = 0; i < clusterer.clusters.length; ++i )
        {
            cluster = clusterer.clusters[i];
          
            
            if ( cluster != null && cluster.marker == null )
            {
                // Figure out the average coordinates of the markers in this
                // cluster.
                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 );
                
                // unterschiedliche Marker-Groesse
                // (abhaengig von Anzahl Autoren im Cluster)
                // ueber getMarkerLabelOffset die Offsets je nach Icon fuer
                // die Positionierung der Anzahl holen (+ CSS-Klasse)
                var myIcon = clusterIcon1;
                var iconOffset = getMarkerLabelOffset(1);
                if (cluster.markers.length >= 10)
                {
                    var myIcon = clusterIcon2;
                    iconOffset = getMarkerLabelOffset(2);                    
                }
                if (cluster.markers.length >= 50)
                {
                    var myIcon = clusterIcon3;
                    iconOffset = getMarkerLabelOffset(3);
                }
                
                // AnE: benutzerdefiniertes Overlay
                var markerLabel = cluster.markers.length;                
                marker = new authorMarker(location, { icon: myIcon, authorText: markerLabel, authorClass: iconOffset.css, authorOffsetX: iconOffset.x, authorOffsetY: iconOffset.y, clickable: true} );
                
                cluster.marker = marker;
                GEvent.addListener( marker, 'click', Clusterer.MakeCaller( Clusterer.PopUp, cluster ) );
            }
        }
	} // ENDE Clustering!


    var marker_not_in_cluster = 0;
    var visibleIdentical = new Object();
    var viKey = '';
    var viLength = 0;
    // 1. Durchgang: Display the visible markers not already up and not in clusters.
    // Marker durchgehen und die sammeln, die an denselben Koordinaten liegen.
    // Fuer diese sind Anpassungen notwendig, siehe unten im 2. Durchgang
    for ( i = 0; i < visibleMarkers.length; ++i )
	{
	marker = visibleMarkers[i];
	if ( marker != null && ! marker.onMap && ! marker.inCluster )
        {
        // Koordinaten des Markers holen
        var vergleichPoint = marker.getLatLng();
        var vergleichPointLat = vergleichPoint.lat();
        var vergleichPointLng = vergleichPoint.lng();
        
        // key fuer Array
        viKey = vergleichPointLat+'-'+vergleichPointLng;
        viLength = 0;
        if (visibleIdentical[viKey])
            viLength = (visibleIdentical[viKey].length);
        if (viLength == 0)
        {
            visibleIdentical[viKey] = new Array();
        }
        // HTML-Code fuer Autoren-Zeile im Info-Window generieren
        // (wird gesammelt im 2. Durchgang zugewiesen, siehe unten)
        visibleIdentical[viKey][viLength] = generateAuthorHtml(marker.aD);
                                                                       
	    }
	} // 1. Durchgang: Display the visible markers not already up

    
    // 2. Durchgang: Display the visible markers not already up and not in clusters.
    // (Ausgabe der Marker und Anpassung Anzahlen und Info-
    // Window fuer Autoren mit identischen Koordinaten)
    for ( i = 0; i < visibleMarkers.length; ++i )
	{
        var visibleMarker = visibleMarkers[i];

        // Marker soll in Karte gesetzt werden
        if ( visibleMarker != null && ! visibleMarker.onMap && ! visibleMarker.inCluster )
        {
            clusterer.map.addOverlay( visibleMarker );
            if ( visibleMarker.addedToMap != null )
            visibleMarker.addedToMap();
            visibleMarker.onMap = true;

            vergleichPoint = visibleMarker.getLatLng();
            vergleichPointLat = vergleichPoint.lat();
            vergleichPointLng = vergleichPoint.lng();
            
            viKey = vergleichPointLat+'-'+vergleichPointLng;
            if (visibleIdentical[viKey])
            {
                var visibleIdenticalElement = visibleIdentical[viKey];
                
                // mehr als ein Author an dieser Koordinate
                if (visibleIdenticalElement.length > 1)
                {
                    // HTML-Code des Info-Windows anpassen
                    var identicalHtml= '';
                    identicalHtml += generateInfoWindowBegin();
                    for ( j = 0; j < visibleIdenticalElement.length; ++j )
                    {
                         identicalHtml += visibleIdenticalElement[j];
                    }
                    identicalHtml += generateInfoWindowEnd();
                    
                    // Beschriftung des Markers anpassen
                    visibleMarker.div_.innerHTML = visibleIdenticalElement.length;
                    
                    // HTML-Code fuer Info-Window ueber Event-Funktion registrieren
                    // (damit im Info-Window alle Autoren dieser Koordinate
                    // angezeigt werden)
                    reassignMarkerClickEvent(visibleMarker, identicalHtml);
                } // mehr als ein Author an dieser Koordinate
            }
        } // Marker soll in Karte gesetzt werden
	} // 2. Durchgang: Display the visible markers not already up



    // Display the visible clusters not already up.
    for ( i = 0; i < clusterer.clusters.length; ++i )
	{
	cluster = clusterer.clusters[i];
	if ( cluster != null && ! cluster.onMap && bounds.contains( cluster.marker.getPoint() ) )
	    {
	    clusterer.map.addOverlay( cluster.marker );
	    cluster.onMap = true;
	    }
	}

     
    // In case a cluster is currently popped-up, re-pop to get any new
    // markers into the infobox.
    Clusterer.RePop( clusterer );
    }; // ENDE Display


// AnE
function reassignMarkerClickEvent(marker, html)
{
    GEvent.clearListeners(marker, 'click');
    GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml(html);
    });
}


Clusterer.PopUp = function ( cluster )
{
    var clusterer = cluster.clusterer;
    
    var doZoom = false;
    
    // Unterscheidung
    // 1. bei mehr als ... Markern im Cluster zoomen
    if (cluster.markers.length >= maxAuthorsInInfoWindow)
    {
        doZoom = true;

        // Sonderfall abfangen: Es koennte sein, dass alle Marker dieselbe
        // Geo-Position haben. Dann bringt Zoomen nix. In dem Fall koennen wir
        // gleich das InfoWindow anzeigen
        // also Koordinaten pruefen
        var alleGleich = 0;
        for (var i = 0; i < cluster.markers.length; ++i)
        {
            var marker = cluster.markers[i];
            // Vergleichs-Point ist der Point des 1. Markers
            if (i == 0)
            {
                var vergleichPoint = marker.getLatLng();
                var vergleichPointLat = vergleichPoint.lat();
                var vergleichPointLng = vergleichPoint.lng();                                
            }
            // alle weiteren Marker
            else
            {
                actualPoint = marker.getLatLng();
                var actualPointLat = actualPoint.lat();
                var actualPointLng = actualPoint.lng();                                
                if (vergleichPointLat == actualPointLat && vergleichPointLng == actualPointLng)
                {
                    alleGleich++;
                }
            }// alle weiteren Marker
        } // for
        // alle haben dieselben Koordinaten
        if (alleGleich == (cluster.markers.length -1))
        {
            doZoom = false;
        }// alle haben dieselben Koordinaten
    } // 1. bei mehr als ... Markern im Cluster zoomen
    
    // wir muessen tatsaechlich zoomen
    if (doZoom)
    {
        // aktuelle Zoomstufe ermitteln
        var actualZoom = map.getZoom();
        map.setCenter(cluster.marker.getLatLng(), (actualZoom + 1));        
    }// wir muessen tatsaechlich zoomen
    
    // 2. ansonsten InfoWindow anzeigen
    else
    {
        // HTML-Code fuers InfoWindow
        var html = '';
        html += generateInfoWindowBegin();
        
        // Marker durchlaufen
        for ( var i = 0; i < cluster.markers.length; ++i )
        {
            var marker = cluster.markers[i];
            if ( marker != null )
            {        
                // HTML-Code fuer diesen Autor generieren
                html += generateAuthorHtml(marker.aD);        
            }
        } // Marker durchlaufen
        
        html += generateInfoWindowEnd();                    
        
        clusterer.map.closeInfoWindow();
        cluster.marker.openInfoWindowHtml( html );
        clusterer.poppedUpCluster = cluster;
    } // 2. ansonsten InfoWindow anzeigen
}; // ENDE PopUp


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;
	}
    };


// This returns a function closure that calls the given routine with the
// specified arg.
Clusterer.MakeCaller = function ( func, arg )
    {
    return function () { func( arg ); };
    };


// Augment GMarker so it handles markers that have been created but
// not yet addOverlayed.

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 );
    };

