
/* FROM: http://www.acme.com/javascript/Clusterer2.jsm */
Clusterer = function (map, mgr) {
	this.map = map;
	
	this.mgr = mgr;
	this.minZoom = Clusterer.defaultMinZoom;
	
	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;
	this.more = "";
	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.defaultMinZoom = 3;
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);
				this.mgr.removeMarker(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.1;
		dy *= 0.1;
		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()) && newZoomLevel >= clusterer.minZoom) {
				visibleMarkers.push(marker);
			} else {
				nonvisibleMarkers.push(marker);
			}
		}
	}
	for (i = 0; i < nonvisibleMarkers.length; ++i) {
		marker = nonvisibleMarkers[i];
		if (marker.onMap) {
			clusterer.mgr.removeMarker(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.mgr.removeMarker(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 * Math.PI / 180);
		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);
						clusterer.mgr.removeMarker(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, yTotal = 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,title:"Click for Location List"});
				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.mgr.addMarker(marker,clusterer.minZoom);
			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.mgr.addMarker(cluster.marker,clusterer.minZoom);
			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\"><table style=\"width: 100%\"><tr><td> ...and " + (cluster.markerCount - n) + " more </td><td class=\"clusterer_more\">"+clusterer.more+"</td></tr></table></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);
		this.mgr.removeMarker(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);
	}
};


