﻿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 = []; 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);
    }
    for (i = 0; i < nonvisibleMarkers.length; ++i) {
        marker = nonvisibleMarkers[i]; if (marker.onMap)
        { clusterer.map.removeOverlay(marker); marker.onMap = false; } 
    }
    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; } 
    }
    if (visibleMarkers.length > clusterer.maxVisibleMarkers) {
        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)
        { cluster = new Object(); cluster.clusterer = clusterer; cluster.bounds = new GLatLngBounds(new GLatLng(lat, lng), new GLatLng(lat + latInc, lng + lngInc)); cluster.markers = []; cluster.markerCount = 0; cluster.onMap = false; cluster.marker = null; clusterer.clusters.push(cluster); }
        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; } 
                } 
            } 
        }
        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; }
        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) {
            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) {
            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; GEvent.addListener(marker, 'click', Clusterer.MakeCaller(Clusterer.PopUp, cluster));
            } 
        } 
    }
    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;
        } 
    }
    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; } 
    }
    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; } 
}; 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);
};