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 = 90; // previously 150
Clusterer.defaultGridSize = 5;
Clusterer.defaultMinMarkersPerCluster = 10; // previously 5
Clusterer.defaultMaxLinesPerInfoBox = 10;
Clusterer.defaultIcon = new GIcon();
Clusterer.defaultIcon.image = 'images/coffees/via_map/cluster.png';
Clusterer.defaultIcon.shadow = 'images/coffees/via_map/cluster_shadow.png';
Clusterer.defaultIcon.iconSize = new GSize(30, 37);
Clusterer.defaultIcon.shadowSize = new GSize(45, 38);
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.image = 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 tmpCurZoom = clusterer.map.getZoom() + 1;
	if (tmpCurZoom < 20) {
		clusterer.map.setCenter(cluster.marker.getPoint());
		clusterer.map.setZoom(tmpCurZoom);
	}
	/*
	var html='<table width="300" class="viaMapBubble">';
	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);
};