// 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.new_markers = [];
    this.clusters = [];
    this.clusterMap = {};
    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;

    this.newMarkerStyles = null;
    this.markerStyles = null;

    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 = 100;
Clusterer.defaultGridSize = 12;

// { untilZoomLevel: value, untilDeeperZoomLevel: anotherValue, etc... } Use a large number for the last one
Clusterer.defaultMinMarkersPerCluster = {
    10: 2,
    13: 3,
    1000: 4
}

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


/**
 * Resets all markers and clusters attached to the clusterer
 */
Clusterer.prototype.reset = function()
{
    for (i = 0; i < this.markers.length; i++) {
        marker = this.markers[i];
        this.map.removeOverlay(marker);
    }

    for (i = 0; i < this.clusters.length; i++) {
        cluster = this.clusters[i];
        if (cluster != null) {
            this.map.removeOverlay(cluster.marker);
        }
    }

    this.markers = [];
    this.new_markers = [];
    this.clusters = [];
    this.clusterMap = {};
};


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

// Add markers that are considered new.
Clusterer.prototype.SetNewMarkers = function ( new_markers )
{
    this.new_markers = new_markers;
};

// 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, title )
{
    if ( marker.setMap != null )
        marker.setMap( this.map );

    marker.title = title;
    marker.onMap = false;
    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 ), document.all ? 50 : 0);
};


Clusterer.Display = function ( clusterer )
{
    var i, j, marker, cluster;

    clearTimeout( clusterer.timeout );
   

    var newZoomLevel = clusterer.map.getZoom();
    if( !clusterer.gridOffset ||
        newZoomLevel != clusterer.currentZoomLevel ) {
        //N: This is the offset we will use for the grid until the zoom level changes. In this way we can keep the clusters

        bounds = clusterer.map.getBounds();
        ne = bounds.getNorthEast();
        sw = bounds.getSouthWest();

        clusterer.gridOffset = new GLatLng(ne.lat(), sw.lng()); //clusterer.map.getCenter();

        var sz =  clusterer.map.getSize();
        clusterer.cellSize = sz.width / clusterer.gridSize;

        clusterer.gridPos = function(latLng) {
            var o = clusterer.map.fromLatLngToDivPixel(clusterer.gridOffset);
            var p = clusterer.map.fromLatLngToDivPixel(latLng);

            return { 
                x: Math.floor((p.x - o.x)/clusterer.cellSize),
                y: Math.floor((p.y - o.y)/clusterer.cellSize)
            };
        }

        clusterer.mapPos = function(gridPos) {
            var o = clusterer.map.fromLatLngToDivPixel(clusterer.gridOffset);
            
            var p = new GPoint(gridPos.x*clusterer.cellSize + o.x, gridPos.y*clusterer.cellSize + o.y);
            return clusterer.map.fromDivPixelToLatLng(p);
        }

    /*:N*/
    }

    
    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;
        clusterer.clusterMap = {};
    }

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

    // Expand the bounds a little, so things look smoother when scrolling
    // by small amounts.
    // N: By three grid cell each direction;
    var expandCells = 3;

    var sw = clusterer.gridPos(bounds.getSouthWest());
    var ne = clusterer.gridPos(bounds.getNorthEast());

    if( clusterer.currentZoomLevel > 5 ) {
        sw = clusterer.mapPos({
            x: sw.x - expandCells,
            y: sw.y + expandCells
        });
        ne = clusterer.mapPos({
            x: ne.x + expandCells,
            y: ne.y - expandCells
        });
        bounds = new GLatLngBounds(sw, ne);
    }


    var prevBounds = clusterer.prevBounds;
    if( prevBounds ) {
        //Prevent double refresh if nothing has changed
        
        if( prevBounds.equals(bounds) ) {            
            return;
        }

    }

    clusterer.prevBounds = bounds;


    /*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.15;
        dy *= 0.15;
        bounds = new GLatLngBounds(
            new GLatLng( sw.lat() - dy, sw.lng() - dx ),
            new GLatLng( ne.lat() + dy, ne.lng() + dx ) );
    }*/

    // 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 );
            else
                nonvisibleMarkers.push( marker );
    }   

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


    //N: Take down the non-visible clusters.
    for( i = 0; i < clusterer.clusters.length; i++ ) {
        cluster = clusterer.clusters[i];
        if( cluster != null && cluster.onMap && !bounds.contains( cluster.marker.getPoint()) ) {
            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 ( /*N:*/ true  /*:N*/ || visibleMarkers.length > clusterer.maxVisibleMarkers )
    {

        var clusters = clusterer.clusterMap; //{};
        
        //sw = bounds.getSouthWest();
        //ne = bounds.getNorthEast();

        //var g0 = clusterer.gridPos(new GLatLng(ne.lat(), sw.lng()));        
        //xWidth = clusterer.gridPos(ne).x - g0.x + 1;

        for( i = 0, n = visibleMarkers.length; i < n; i++ ) {

            marker = visibleMarkers[i];
            if( !marker.inCluster ) {
                var pos = clusterer.gridPos(marker.getPoint());

                //var index = (pos.x - g0.x) + (pos.y - g0.y)*xWidth;
                var index = pos.x  + "," + pos.y;               
            
                cluster = clusters[index];
                if( !cluster ) {

                    ne = clusterer.mapPos({
                        x: pos.x + 1,
                        y: pos.y
                    });
                    sw = clusterer.mapPos({
                        x: pos.x,
                        y: pos.y + 1
                    });
                
                    cluster = {};
                    cluster.clusterer = clusterer;
                    cluster.bounds = new GLatLngBounds(sw, ne);
                    cluster.center = clusterer.mapPos({
                        x: pos.x + 0.5,
                        y: pos.y + 0.5
                    });

                    cluster.pos = pos;
                
                    cluster.markers = [];
                    cluster.markerCount = 0;
                    cluster.onMap = false;
                    cluster.marker = null;
                    clusters[index] = cluster;
                }

                ++cluster.markerCount;
                cluster.markers.push(marker);
                marker.inCluster = true;
            }
        }

        var cs = [];
        for( var c in clusters ) {
            if( clusters[c] ) {
                clusters[c].index = cs.length;
                cs.push(clusters[c]);
            }
        }
        clusterer.clusters = cs;                      


        var minMarkers = 2;        
        for( var level in clusterer.minMarkersPerCluster ) {           
            if( level > clusterer.currentZoomLevel ) {
                minMarkers = clusterer.minMarkersPerCluster[level];
               
                break;
            }
        }

        // 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 < minMarkers )
            {
                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;
                    }
                }
            }
        }

        function isNewMarker(marker)
        {
            var length = clusterer.new_markers.length;
            if (length == 0) return false;
            
            for(var i = 0; i < length; i++) {
                if(clusterer.new_markers[i] == marker) return true;
            }
            return false;

        }

        function updateCluster(cluster) {
            // Figure out the average coordinates of the markers in this
            // cluster.

            // N: ..and the bounds spanned by the markers
            var hasNewItems = false;
            var mne = null, msw = null;
            var xTotal = 0.0, yTotal = 0.0;
            for ( j = 0; j < cluster.markers.length; ++j )
            {
                marker = cluster.markers[j];
                if ( marker != null )
                {
                    if (hasNewItems == false) {
                        hasNewItems = isNewMarker(marker);
                    }
                    
                    pos = marker.getPoint();
                    if( !msw ) {
                        msw = pos;
                        mne = pos;
                    } else {
                        if( pos.lng() < msw.lng() ) {
                            msw = new GLatLng(msw.lat(), pos.lng());
                        }
                        if( pos.lng() > mne.lng() ) {
                            mne = new GLatLng(mne.lat(), pos.lng());
                        }
                        if( pos.lat() < msw.lat() ) {
                            msw = new GLatLng(pos.lat(), msw.lng());
                        }
                        if( pos.lat() > mne.lat() ) {
                            mne = new GLatLng(pos.lat(), mne.lng());
                        }
                    }

                    xTotal += ( pos.lng() );
                    yTotal += ( pos.lat() );
                }
            }

            //N: Weighted average between cell center and marker
            //density
            var bias = 0;
            var p1 =  cluster.center;
            var p2 = new GLatLng( yTotal / cluster.markerCount, xTotal / cluster.markerCount );
            cluster.location =
            new GLatLng( p1.lat() * bias + p2.lat()*(1-bias),
                p1.lng() * bias + p2.lng()*(1-bias));

            //cluster.location = new GLatLng( yTotal / cluster.markerCount, xTotal / cluster.markerCount );
            //marker = new GMarker( cluster.location, { icon: clusterer.icon } );
            cluster.minBounds = new GLatLngBounds(msw, mne);


            //N: Use these markers            
            styles =  (hasNewItems == true) ? clusterer.newMarkerStyles : clusterer.markerStyles;
            for( j = 0; j < styles.length; j++ ) {
                if(styles[j].limit > cluster.markerCount ) {
                    break;
                }
                cluster.markerStyle = styles[j];
            }
            

            marker = new ClusterMarker_(cluster.location, cluster.markerCount, cluster.markerStyle, cluster.minBounds);
            cluster.marker = marker;
            GEvent.addListener( marker, 'click', Clusterer.MakeCaller( Clusterer.PopUp, cluster ) );
        }
             

        // 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 )
            {
                updateCluster(cluster);
            }
        }


        // The cmax ell distance to search for overlapping markers
        var range = 1;

        //N: Do this until no markers overlap (too much)        
        while( true ) {
            
            var changed = 0;
            // N: Merge overlapping markers
            for ( i = 0; i < clusterer.clusters.length; i++ ) {
                cluster = clusterer.clusters[i];
                if ( cluster ) {
                    var p1 = clusterer.map.fromLatLngToDivPixel(cluster.location);
                                        
                    for( var j_x = cluster.pos.x - range; j_x <= cluster.pos.x + range; j_x++ ) {
                        for( var j_y = cluster.pos.y - range; j_y <= cluster.pos.y + range; j_y++ ) {
                            
                            if( j_x != cluster.pos.x || j_y != cluster.pos.y ) {
                                
                                var otherIndex = j_x + "," + j_y;

                                var other = clusterer.clusterMap[otherIndex];
                                if( other && other.marker ) {
                                    
                                    var p2 = clusterer.map.fromLatLngToDivPixel(other.location);
                                    var dist = Math.sqrt(Math.pow(p1.x-p2.x, 2) + Math.pow(p1.y-p2.y, 2));
                                    if( dist < 0.75 * (cluster.markerStyle.width / 2 + other.markerStyle.width / 2) ) {
                                        var others = [];                                        
                                        for( var k = 0; k < other.markers.length; k++ ) {
                                            var marker = other.markers[k];
                                            var pm = clusterer.map.fromLatLngToDivPixel(marker.getPoint());
                                            var distm = Math.sqrt(Math.pow(p1.x-p2.x, 2) + Math.pow(p1.y-p2.y, 2));

                                            if( distm < 1000 * cluster.markerStyle.width ) {
                                                cluster.markers.push(marker);
                                                ++cluster.markerCount;
                                                --other.markerCount;
                                            } else {
                                                others.push(marker);
                                            }
                                        }
                                        ++changed;
                                        other.markers = others;
                                        if( other.markerCount == 0 ) {
                                            clusterer.clusters[other.index] = null;
                                            clusterer.clusterMap[otherIndex] = null;
                                        } else {
                                            updateCluster(other);
                                        }
                                        updateCluster(cluster);
                                    }
                                }
                            }
                        }
                    }

                    //Take single markers very close to clusters, and add them to avoid confusing single markers
                    if( !document.all ) {
                        
                        //Slows down IEs and is not THAT important
                        for ( j = 0; j < visibleMarkers.length; j++ )
                        {
                            marker = visibleMarkers[j];
                            if ( marker != null && ! marker.onMap && ! marker.inCluster )
                            {
                                var p2 = clusterer.map.fromLatLngToDivPixel(marker.getPoint());
                                var dist = Math.sqrt(Math.pow(p1.x-p2.x, 2) + Math.pow(p1.y-p2.y, 2));

                                if( dist < 3 * (cluster.markerStyle.width / 2) ) {
                                    marker.inCluster = true;
                                    ++cluster.markerCount;
                                    cluster.markers.push(marker);
                                    ++changed;
                                    updateCluster(cluster);
                                }
                            }
                        }
                    }
                }
            }

            if( !changed ) {
                break;
            }
        }
        
    }
    
    

    // Display the visible markers not already up and not in clusters.
    for ( i = 0; i < visibleMarkers.length; ++i )
    {
        marker = visibleMarkers[i];
        if ( marker != null && ! marker.onMap && ! marker.inCluster )
        {
            clusterer.map.addOverlay( marker );
            if ( marker.addedToMap != null )
                marker.addedToMap();
            marker.onMap = true;
        }
    }

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


Clusterer.PopUp = function ( 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;
    }
};


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








//////////////////////////



function ClusterMarker_(latlng, count, style, bounds) {
    
    var index = 0;
    /*var dv = count;
    while (dv !== 0) {
        dv = parseInt(dv / 10, 10);
        index ++;
    }

    if (styles.length < index) {
        index = styles.length;
    }*/
    this.url_ = style.url;
    this.height_ = style.height;
    this.width_ = style.width;
    

    this.textColor_ = style.opt_textColor;
    this.anchor_ = style.opt_anchor;
    this.latlng_ = latlng;
    this.index_ = index;
    this.style_ = style;
    this.text_ = count;
    this.hide_text_ = (style.hide_text == undefined) ? false : style.hide_text;
    this.bounds = bounds;
}


ClusterMarker_.prototype = new GOverlay();

ClusterMarker_.prototype.getPoint = function() {
    return this.latlng_;
}


/**
 * Initialize cluster marker.
 * @private
 */
ClusterMarker_.prototype.initialize = function (map) {

    this.map_ = map;
    var div = document.createElement("div");
    var latlng = this.latlng_;
    var pos = map.fromLatLngToDivPixel(latlng);
    pos.x -= parseInt(this.width_ / 2, 10);
    pos.y -= parseInt(this.height_ / 2, 10);
    
    var mstyle = "";
    if (document.all) {
        mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
    } else {
        mstyle = "background:url(" + this.url_ + ");";
    }
    if (typeof this.anchor_ === "object") {
        if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
            mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
        } else {
            mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
        }
        if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
            mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
        } else {
            mstyle += 'width:' + this.width_ + 'px;text-align:center;';
        }
    } else {
        mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
        mstyle += 'width:' + this.width_ + 'px;text-align:center;';
    }
    var txtColor = this.textColor_ ? this.textColor_ : 'black';
    // RKK: Forced font change from Arial, 11px to Arial Narrow, 12px
    div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
    pos.x + "px;color:" + txtColor +  ";position:absolute;font-size:12px;" +
    'font-family:Arial Narrow,sans-serif;font-weight:bold';
    div.innerHTML =  (this.hide_text_ == true) ? '&nbsp;' : this.text_;
    map.getPane(G_MAP_MAP_PANE).appendChild(div);


    var bounds = this.bounds;
    GEvent.addDomListener(div, "click", function () {        
        var zoom = map.getBoundsZoomLevel(bounds, map.getSize());                
        // Don't zoom too much. It's confusing
        //map.setCenter(bounds.getCenter(), zoom);
        map.setCenter(bounds.getCenter(), zoom - 1);
    });
    
    GEvent.addDomListener(div, "mouseover", function() {
        //TODO: i18n
        div.title = "Klik her for at se flere steder";
    });
    this.div_ = div;
};

/**
 * Remove this overlay.
 * @private
 */
ClusterMarker_.prototype.remove = function () {
    if( this.div_ ) {
        this.div_.parentNode.removeChild(this.div_);
    }
};

/**
 * Copy this overlay.
 * @private
 */
ClusterMarker_.prototype.copy = function () {
    return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.style_, this.bounds);
};

/**
 * Redraw this overlay.
 * @private
 */
ClusterMarker_.prototype.redraw = function (force) {
    if(this.div_ ) {
       
        if (!force) {
            return;
        }
        var pos = this.map_.fromLatLngToDivPixel(this.latlng_);
        pos.x -= parseInt(this.width_ / 2, 10);
        pos.y -= parseInt(this.height_ / 2, 10);
        this.div_.style.top =  pos.y + "px";
        this.div_.style.left = pos.x + "px";
    }
};

/**
 * Hide this cluster marker.
 */
ClusterMarker_.prototype.hide = function () {
    if( this.div_ ) {
        this.div_.style.display = "none";
    }
};

/**
 * Show this cluster marker.
 */
ClusterMarker_.prototype.show = function () {
    if( this.div_ ) {
        this.div_.style.display = "";
    }
};

/**
 * Get whether the cluster marker is hidden.
 * @return {Boolean}
 */
ClusterMarker_.prototype.isHidden = function () {
    if( this.div_ ) {
        return this.div_.style.display === "none";
    }
};
