/*
 * extinfowindow.js
 */
function ExtInfoWindow(marker, windowId, html, opt_opts) {
  this.html_ = html;
  this.marker_ = marker;
  this.infoWindowId_ = windowId;

  this.options_ = opt_opts == null ? {} : opt_opts;
  this.ajaxUrl_ = this.options_.ajaxUrl == null ? null : this.options_.ajaxUrl;
  this.callback_ = this.options_.ajaxCallback == null ? null : this.options_.ajaxCallback;

  this.borderSize_ = this.options_.beakOffset == null ? 0 : this.options_.beakOffset;
  this.paddingX_ = this.options_.paddingX == null ? 0 + this.borderSize_ : this.options_.paddingX + this.borderSize_;
  this.paddingY_ = this.options_.paddingY == null ? 0 + this.borderSize_ : this.options_.paddingY + this.borderSize_;

  this.map_ = null;

  this.container_ = document.createElement('div');
  this.container_.style.position = 'relative';
  this.container_.style.display = 'none';

  this.contentDiv_ = document.createElement('div');
  this.contentDiv_.id = this.infoWindowId_ + '_contents';
  this.contentDiv_.innerHTML = this.html_;
  this.contentDiv_.style.display = 'block';
  this.contentDiv_.style.visibility = 'hidden';

  this.wrapperDiv_ = document.createElement('div');
};

//use the GOverlay class
ExtInfoWindow.prototype = new GOverlay();
ExtInfoWindow.prototype.initialize = function(map) {
  this.map_ = map;

  this.defaultStyles = {
    containerWidth: this.map_.getSize().width / 2,
    borderSize: 1
  };

  this.wrapperParts = {
    tl:{t:0, l:0, w:0, h:0, domElement: null},
    t:{t:0, l:0, w:0, h:0, domElement: null},
    tr:{t:0, l:0, w:0, h:0, domElement: null},
    l:{t:0, l:0, w:0, h:0, domElement: null},
    r:{t:0, l:0, w:0, h:0, domElement: null},
    bl:{t:0, l:0, w:0, h:0, domElement: null},
    b:{t:0, l:0, w:0, h:0, domElement: null},
    br:{t:0, l:0, w:0, h:0, domElement: null},
    beak:{t:0, l:0, w:0, h:0, domElement: null},
    close:{t:0, l:0, w:0, h:0, domElement: null}
  };

  for (var i in this.wrapperParts ) {
    var tempElement = document.createElement('div');
    tempElement.id = this.infoWindowId_ + '_' + i;
    tempElement.style.visibility = 'hidden';
    document.body.appendChild(tempElement);
    tempElement = document.getElementById(this.infoWindowId_ + '_' + i);
    var tempWrapperPart = eval('this.wrapperParts.' + i);    
    tempWrapperPart.w = parseInt(this.getStyle_(tempElement, 'width'));
    tempWrapperPart.h = parseInt(this.getStyle_(tempElement, 'height'));
    document.body.removeChild(tempElement);
  }
  for (var i in this.wrapperParts) {
    if (i == 'close' ) {
      //first append the content so the close button is layered above it
      this.wrapperDiv_.appendChild(this.contentDiv_);
    }
    var wrapperPartsDiv = null;
    if (this.wrapperParts[i].domElement == null) {
      wrapperPartsDiv = document.createElement('div');
      this.wrapperDiv_.appendChild(wrapperPartsDiv);
    } else {
      wrapperPartsDiv = this.wrapperParts[i].domElement;
    }
    wrapperPartsDiv.id = this.infoWindowId_ + '_' + i;
    wrapperPartsDiv.style.position = 'absolute';
    wrapperPartsDiv.style.width = this.wrapperParts[i].w + 'px';
    wrapperPartsDiv.style.height = this.wrapperParts[i].h + 'px';
    wrapperPartsDiv.style.top = this.wrapperParts[i].t + 'px';
    wrapperPartsDiv.style.left = this.wrapperParts[i].l + 'px';
    this.wrapperParts[i].domElement = wrapperPartsDiv;
  }
  
  this.map_.getPane(G_MAP_FLOAT_PANE).appendChild(this.container_);
  this.container_.id = this.infoWindowId_;
  var containerWidth  = this.getStyle_(document.getElementById(this.infoWindowId_), 'width');
  this.container_.style.width = (containerWidth == null ? this.defaultStyles.containerWidth : containerWidth);

  this.map_.getContainer().appendChild(this.contentDiv_);
  this.contentWidth = this.getDimensions_(this.container_).width;
  this.contentDiv_.style.width = this.contentWidth + 'px';
  this.contentDiv_.style.position = 'absolute';

  this.container_.appendChild(this.wrapperDiv_);

  GEvent.bindDom(this.container_, 'mousedown', this,this.onClick_);
  GEvent.bindDom(this.container_, 'dblclick', this,this.onClick_);
  GEvent.bindDom(this.container_, 'DOMMouseScroll', this, this.onClick_);
  

  GEvent.trigger(this.map_, 'extinfowindowopen');
  if (this.ajaxUrl_ != null ) {
    this.ajaxRequest_(this.ajaxUrl_);
  }
};
ExtInfoWindow.prototype.onClick_ = function(e) {
  if(navigator.userAgent.toLowerCase().indexOf('msie') != -1 && document.all) {
    window.event.cancelBubble = true;
    window.event.returnValue = false;
  } else {
    //e.preventDefault();
    e.stopPropagation();
  }
};
ExtInfoWindow.prototype.remove = function() {
  if (this.map_.getExtInfoWindow() != null) {
    GEvent.trigger(this.map_, 'extinfowindowbeforeclose');
    
    GEvent.clearInstanceListeners(this.container_);
    if (this.container_.outerHTML) {
      this.container_.outerHTML = ''; //prevent pseudo-leak in IE
    }
    if (this.container_.parentNode) {
      this.container_.parentNode.removeChild(this.container_);
    }
    this.container_ = null;
    GEvent.trigger(this.map_, 'extinfowindowclose');
    this.map_.setExtInfoWindow_(null);
  }
};
ExtInfoWindow.prototype.copy = function() {
  return new ExtInfoWindow(this.marker_, this.infoWindowId_, this.html_, this.options_);
};
ExtInfoWindow.prototype.redraw = function(force) {
  if (!force || this.container_ == null) return;

  //set the content section's height, needed so  browser font resizing does not affect the window's dimensions
  var contentHeight = this.contentDiv_.offsetHeight;
  this.contentDiv_.style.height = contentHeight + 'px';

  //reposition contents depending on wrapper parts.
  //this is necessary for content that is pulled in via ajax
  this.contentDiv_.style.left = this.wrapperParts.l.w + 'px';
  this.contentDiv_.style.top = this.wrapperParts.tl.h + 'px';
  this.contentDiv_.style.visibility = 'visible';

  //Finish configuring wrapper parts that were not set in initialization
  this.wrapperParts.tl.t = 0;
  this.wrapperParts.tl.l = 0;
  this.wrapperParts.t.l = this.wrapperParts.tl.w;
  this.wrapperParts.t.w = (this.wrapperParts.l.w + this.contentWidth + this.wrapperParts.r.w) - this.wrapperParts.tl.w - this.wrapperParts.tr.w;
  this.wrapperParts.t.h = this.wrapperParts.tl.h;
  this.wrapperParts.tr.l = this.wrapperParts.t.w + this.wrapperParts.tl.w;
  this.wrapperParts.l.t = this.wrapperParts.tl.h;
  this.wrapperParts.l.h = contentHeight;
  this.wrapperParts.r.l = this.contentWidth + this.wrapperParts.l.w;
  this.wrapperParts.r.t = this.wrapperParts.tr.h;
  this.wrapperParts.r.h = contentHeight;
  this.wrapperParts.bl.t = contentHeight + this.wrapperParts.tl.h;
  this.wrapperParts.b.l = this.wrapperParts.bl.w;
  this.wrapperParts.b.t = contentHeight + this.wrapperParts.tl.h;
  this.wrapperParts.b.w = (this.wrapperParts.l.w + this.contentWidth + this.wrapperParts.r.w) - this.wrapperParts.bl.w - this.wrapperParts.br.w;
  this.wrapperParts.b.h = this.wrapperParts.bl.h;
  this.wrapperParts.br.l = this.wrapperParts.b.w + this.wrapperParts.bl.w;
  this.wrapperParts.br.t = contentHeight + this.wrapperParts.tr.h;
  this.wrapperParts.close.l = this.wrapperParts.tr.l +this.wrapperParts.tr.w - this.wrapperParts.close.w - this.borderSize_;
  this.wrapperParts.close.t = this.borderSize_;
  this.wrapperParts.beak.l = this.borderSize_ + (this.contentWidth / 2) - (this.wrapperParts.beak.w / 2);
  this.wrapperParts.beak.t = this.wrapperParts.bl.t + this.wrapperParts.bl.h - this.borderSize_;

  for (var i in this.wrapperParts) {
    if (i == 'close' ) {
      //first append the content so the close button is layered above it
      this.wrapperDiv_.insertBefore(this.contentDiv_, this.wrapperParts[i].domElement);
    }
    var wrapperPartsDiv = null;
    if (this.wrapperParts[i].domElement == null) {
      wrapperPartsDiv = document.createElement('div');
      this.wrapperDiv_.appendChild(wrapperPartsDiv);
    } else {
      wrapperPartsDiv = this.wrapperParts[i].domElement;
    }
    wrapperPartsDiv.id = this.infoWindowId_ + '_' + i;
    wrapperPartsDiv.style.position='absolute';
    wrapperPartsDiv.style.width = this.wrapperParts[i].w + 'px';
    wrapperPartsDiv.style.height = this.wrapperParts[i].h + 'px';
    wrapperPartsDiv.style.top = this.wrapperParts[i].t + 'px';
    wrapperPartsDiv.style.left = this.wrapperParts[i].l + 'px';
    this.wrapperParts[i].domElement = wrapperPartsDiv;
  }
  var currentMarker = this.marker_;
  var thisMap = this.map_;
  GEvent.addDomListener(this.wrapperParts.close.domElement, 'click', 
    function() {
      thisMap.closeExtInfoWindow();
    }
  );
  var pixelLocation = this.map_.fromLatLngToDivPixel(this.marker_.getPoint());
  this.container_.style.position = 'absolute';
  var markerIcon = this.marker_.getIcon();
  this.container_.style.left = (pixelLocation.x 
    - (this.contentWidth / 2) 
    - markerIcon.iconAnchor.x 
    + markerIcon.infoWindowAnchor.x
  ) + 'px';

  this.container_.style.top = (pixelLocation.y
    - this.wrapperParts.bl.h
    - contentHeight
    - this.wrapperParts.tl.h
    - this.wrapperParts.beak.h
    - markerIcon.iconAnchor.y
    + markerIcon.infoWindowAnchor.y
    + this.borderSize_
  ) + 'px';

  this.container_.style.display = 'block';

  if(this.map_.getExtInfoWindow() != null) {
    this.repositionMap_();
  }
};
ExtInfoWindow.prototype.resize = function(){
  
  //Create temporary DOM node for new contents to get new height
  //This is done because if you manipulate this.contentDiv_ directly it causes visual errors in IE6
  var tempElement = this.contentDiv_.cloneNode(true);
  tempElement.id = this.infoWindowId_ + '_tempContents';
  tempElement.style.visibility = 'hidden';	
  tempElement.style.height = 'auto';
  document.body.appendChild(tempElement);
  tempElement = document.getElementById(this.infoWindowId_ + '_tempContents');
  var contentHeight = tempElement.offsetHeight;
  document.body.removeChild(tempElement);

  //Set the new height to eliminate visual defects that can be caused by font resizing in browser
  this.contentDiv_.style.height = contentHeight + 'px';

  var contentWidth = this.contentDiv_.offsetWidth;
  var pixelLocation = this.map_.fromLatLngToDivPixel(this.marker_.getPoint());

  var oldWindowHeight = this.wrapperParts.t.domElement.offsetHeight + this.wrapperParts.l.domElement.offsetHeight + this.wrapperParts.b.domElement.offsetHeight;	
  var oldWindowPosTop = this.wrapperParts.t.domElement.offsetTop;

  //resize info window to look correct for new height
  this.wrapperParts.l.domElement.style.height = contentHeight + 'px';
  this.wrapperParts.r.domElement.style.height = contentHeight + 'px';
  var newPosTop = this.wrapperParts.b.domElement.offsetTop - contentHeight;
  this.wrapperParts.l.domElement.style.top = newPosTop + 'px';
  this.wrapperParts.r.domElement.style.top = newPosTop + 'px';
  this.contentDiv_.style.top = newPosTop + 'px';
  windowTHeight = parseInt(this.wrapperParts.t.domElement.style.height);
  newPosTop -= windowTHeight;
  this.wrapperParts.close.domElement.style.top = newPosTop + this.borderSize_ + 'px';
  this.wrapperParts.tl.domElement.style.top = newPosTop + 'px';
  this.wrapperParts.t.domElement.style.top = newPosTop + 'px';
  this.wrapperParts.tr.domElement.style.top = newPosTop + 'px';

  this.repositionMap_();
};
ExtInfoWindow.prototype.repositionMap_ = function(){
  //pan if necessary so it shows on the screen
  var mapNE = this.map_.fromLatLngToDivPixel(
    this.map_.getBounds().getNorthEast()
  );
  var mapSW = this.map_.fromLatLngToDivPixel(
    this.map_.getBounds().getSouthWest()
  );
  var markerPosition = this.map_.fromLatLngToDivPixel(
    this.marker_.getPoint()
  );

  var panX = 0;
  var panY = 0;
  var paddingX = this.paddingX_;
  var paddingY = this.paddingY_;
  var infoWindowAnchor = this.marker_.getIcon().infoWindowAnchor;
  var iconAnchor = this.marker_.getIcon().iconAnchor;

  //test top of screen	
  var windowT = this.wrapperParts.t.domElement;
  var windowL = this.wrapperParts.l.domElement;
  var windowB = this.wrapperParts.b.domElement;
  var windowR = this.wrapperParts.r.domElement;
  var windowBeak = this.wrapperParts.beak.domElement;

  var offsetTop = markerPosition.y - ( -infoWindowAnchor.y + iconAnchor.y +  this.getDimensions_(windowBeak).height + this.getDimensions_(windowB).height + this.getDimensions_(windowL).height + this.getDimensions_(windowT).height + this.paddingY_);
  if (offsetTop < mapNE.y) {
    panY = mapNE.y - offsetTop;
  } else {
    //test bottom of screen
    var offsetBottom = markerPosition.y + this.paddingY_;
    if (offsetBottom >= mapSW.y) {
      panY = -(offsetBottom - mapSW.y);
    }
  }

  //test right of screen
  var offsetRight = Math.round(markerPosition.x + this.getDimensions_(this.container_).width/2 + this.getDimensions_(windowR).width + this.paddingX_ + infoWindowAnchor.x - iconAnchor.x);
  if (offsetRight > mapNE.x) {
    panX = -( offsetRight - mapNE.x);
  } else {
    //test left of screen
    var offsetLeft = - (Math.round( (this.getDimensions_(this.container_).width/2 - this.marker_.getIcon().iconSize.width/2) + this.getDimensions_(windowL).width + this.borderSize_ + this.paddingX_) - markerPosition.x - infoWindowAnchor.x + iconAnchor.x);
    if( offsetLeft < mapSW.x) {
      panX = mapSW.x - offsetLeft;
    }
  }

  if (panX != 0 || panY != 0 && this.map_.getExtInfoWindow() != null ) {
    this.map_.panBy(new GSize(panX,panY));
  }
};
ExtInfoWindow.prototype.ajaxRequest_ = function(url){
  var thisMap = this.map_;
  var thisCallback = this.callback_;
  GDownloadUrl(url, function(response, status){
    var infoWindow = document.getElementById(thisMap.getExtInfoWindow().infoWindowId_ + '_contents');
    if (response == null || status == -1 ) {
      infoWindow.innerHTML = '<span class="error">ERROR: The Ajax request failed to get HTML content from "' + url + '"</span>';
    } else {
      infoWindow.innerHTML = response;
    }
    if (thisCallback != null ) {
      thisCallback();
    }
    thisMap.getExtInfoWindow().resize();
    GEvent.trigger(thisMap, 'extinfowindowupdate');
  });
};
ExtInfoWindow.prototype.getDimensions_ = function(element) {
  var display = this.getStyle_(element, 'display');
  if (display != 'none' && display != null) { // Safari bug
    return {width: element.offsetWidth, height: element.offsetHeight};
  }

  // All *Width and *Height properties give 0 on elements with display none,
  // so enable the element temporarily
  var els = element.style;
  var originalVisibility = els.visibility;
  var originalPosition = els.position;
  var originalDisplay = els.display;
  els.visibility = 'hidden';
  els.position = 'absolute';
  els.display = 'block';
  var originalWidth = element.clientWidth;
  var originalHeight = element.clientHeight;
  els.display = originalDisplay;
  els.position = originalPosition;
  els.visibility = originalVisibility;
  return {width: originalWidth, height: originalHeight};
};
ExtInfoWindow.prototype.getStyle_ = function(element, style) {
  var found = false;
  style = this.camelize_(style);
  var value = element.style[style];
  if (!value) {
    if (document.defaultView && document.defaultView.getComputedStyle) {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    } else if (element.currentStyle) {
      value = element.currentStyle[style];
    }
  }
  if((value == 'auto') && (style == 'width' || style == 'height') && (this.getStyle_(element, 'display') != 'none')) {
    if( style == 'width' ) {
      value = element.offsetWidth;
    }else {
      value = element.offsetHeight;
    }
  }
  return (value == 'auto') ? null : value;
};
ExtInfoWindow.prototype.camelize_ = function(element) {
  var parts = element.split('-'), len = parts.length;
  if (len == 1) return parts[0];
  var camelized = element.charAt(0) == '-'
    ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
    : parts[0];

  for (var i = 1; i < len; i++) {
    camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
  }
  return camelized;
};

GMap.prototype.ExtInfoWindowInstance_ = null;
GMap.prototype.ClickListener_ = null;
GMap.prototype.InfoWindowListener_ = null;

GMarker.prototype.openExtInfoWindow = function(map, cssId, html, opt_opts) {
  if (map == null) {
    throw 'Error in GMarker.openExtInfoWindow: map cannot be null';
    return false;
  }
  if (cssId == null || cssId == '') {
    throw 'Error in GMarker.openExtInfoWindow: must specify a cssId';
    return false;
  }
  
  map.closeInfoWindow();
  if (map.getExtInfoWindow() != null) {
    map.closeExtInfoWindow();
  }
  if (map.getExtInfoWindow() == null) {
    map.setExtInfoWindow_( new ExtInfoWindow(
      this,
      cssId,
      html,
      opt_opts
    ) );
    if (map.ClickListener_ == null) {
      //listen for map click, close ExtInfoWindow if open
      map.ClickListener_ = GEvent.addListener(map, 'click',
      function(event) {
          if( !event && map.getExtInfoWindow() != null ){
            map.closeExtInfoWindow();
          }
        }
      );
    }
    if (map.InfoWindowListener_ == null) {
      //listen for default info window open, close ExtInfoWindow if open
      map.InfoWindowListener_ = GEvent.addListener(map, 'infowindowopen', 
      function(event) {
          if (map.getExtInfoWindow() != null) {
            map.closeExtInfoWindow();
          }
        }
      );
    }
    map.addOverlay(map.getExtInfoWindow());
  }
};
GMarker.prototype.closeExtInfoWindow = function(map) {
  if( map.getExtInfoWindow() != null ){
    map.closeExtInfoWindow();
  }
};
GMap2.prototype.getExtInfoWindow = function(){
  return this.ExtInfoWindowInstance_;
};
GMap2.prototype.setExtInfoWindow_ = function( extInfoWindow ){
  this.ExtInfoWindowInstance_ = extInfoWindow;
}
GMap2.prototype.closeExtInfoWindow = function(){
  if( this.getExtInfoWindow() != null ){
    this.ExtInfoWindowInstance_.remove();
  }
};

/*
 * clusterflag.js
 */
function Clusterflag(coords, text) {
	this.coords_ = coords;
	this.text_ = text;
}
Clusterflag.prototype = new GOverlay();
Clusterflag.prototype.initialize = function(map) {
	var div = document.createElement("div");
	div.style.cssText = 'top:' + this.coords_.y + 'px;' + 
											'left:' + this.coords_.x + 'px;' + 
											'border:1px solid #7AC042;' + 
											'background:#ffffff;' + 
											'width:230px;' + 
											'padding:7px;' + 
											'display:none;' + 
											'z-index:999999;' + 
											'position:absolute;';
	div.innerHTML = '<span style="color:#108AB5;font-size:11px;font-family:Arial,sans-serif;font-weight:normal;">' + 
									'<strong><u>' + this.text_ + '</u> Properties in this area.</strong> &nbsp;' + 
									'<span style="color:#999999;">Click to zoom</span>' + 
									'</span>';
  map.getPane(G_MAP_MAP_PANE).appendChild(div);
  this.map_ = map;
  this.div_ = div;
}
Clusterflag.prototype.remove = function() {
  this.div_.parentNode.removeChild(this.div_);
}
Clusterflag.prototype.copy = function() {
  return new Clusterflag(this.coords_, this.text_);
}
Clusterflag.prototype.reposition = function( coords ) {
	this.coords_ = coords;
	this.div_.style.left = this.coords_.x + 'px';
	this.div_.style.top = this.coords_.y + 'px';
}
Clusterflag.prototype.hide = function () {
  this.div_.style.display = "none";
};
Clusterflag.prototype.show = function () {
  this.div_.style.display = "";
};
Clusterflag.prototype.redraw = function(force) {
	if (!force) return;
}

/*
 * markerclusterer.js
 */
function MarkerClusterer(map, opt_markers, opt_opts) {
  // private members
  var clusters_ = [];
  var map_ = map;
  var maxZoom_ = null;
  var me_ = this;
  var gridSize_ = 60;
	var sizes = [53, 56, 66, 66, 66];  // used to set width/height on cluster markers
  var styles_ = [];
  var leftMarkers_ = [];
  var mcfn_ = null;
	
	// create default styles
  var i = 0;
  for (i = 1; i <= 5; ++i) {
    styles_.push({
      'url': "http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/images/m" + i + ".png",
      'height': sizes[i - 1],
      'width': sizes[i - 1]
    });
  }
  if (typeof opt_opts === "object" && opt_opts !== null) {
    if (typeof opt_opts.gridSize === "number" && opt_opts.gridSize > 0) {
      gridSize_ = opt_opts.gridSize;
    }
    if (typeof opt_opts.maxZoom === "number") {
      maxZoom_ = opt_opts.maxZoom;
    }
    if (typeof opt_opts.styles === "object" && opt_opts.styles !== null && opt_opts.styles.length !== 0) {
      styles_ = opt_opts.styles;
    }
  }
  function addLeftMarkers_() {
    if (leftMarkers_.length === 0) {
      return;
    }
    var leftMarkers = [];
    for (i = 0; i < leftMarkers_.length; ++i) {
      me_.addMarker(leftMarkers_[i], true, null, null, true);
    }
    leftMarkers_ = leftMarkers;
  }
  this.getStyles_ = function () {
    return styles_;
  };
  this.clearMarkers = function () {
    for (var i = 0; i < clusters_.length; ++i) {
      if (typeof clusters_[i] !== "undefined" && clusters_[i] !== null) {
        clusters_[i].clearMarkers();
      }
    }
    clusters_ = [];
    leftMarkers_ = [];
    GEvent.removeListener(mcfn_);
  };
  function isMarkerInViewport_(marker) {
    return map_.getBounds().containsLatLng(marker.getLatLng());
  }
  function reAddMarkers_(markers) {
    var len = markers.length;
    var clusters = [];
    for (var i = len - 1; i >= 0; --i) {
      me_.addMarker(markers[i].marker, true, markers[i].isAdded, clusters, true);
    }
    addLeftMarkers_();
  }
  this.addMarker = function (marker, opt_isNodraw, opt_isAdded, opt_clusters, opt_isNoCheck) {
    if (opt_isNoCheck !== true) {
      if (!isMarkerInViewport_(marker)) {
        leftMarkers_.push(marker);
        return;
      }
    }

    var isAdded = opt_isAdded;
    var clusters = opt_clusters;
    var pos = map_.fromLatLngToDivPixel(marker.getLatLng());

    if (typeof isAdded !== "boolean") {
      isAdded = false;
    }
    if (typeof clusters !== "object" || clusters === null) {
      clusters = clusters_;
    }

    var length = clusters.length;
    var cluster = null;
    for (var i = length - 1; i >= 0; i--) {
      cluster = clusters[i];
      var center = cluster.getCenter();
      if (center === null) {
        continue;
      }
      center = map_.fromLatLngToDivPixel(center);

      // Found a cluster which contains the marker.
      if (pos.x >= center.x - gridSize_ && pos.x <= center.x + gridSize_ &&
          pos.y >= center.y - gridSize_ && pos.y <= center.y + gridSize_) {
        cluster.addMarker({
          'isAdded': isAdded,
          'marker': marker
        });
        if (!opt_isNodraw) {
          cluster.redraw_();
        }
        return;
      }
    }

    // No cluster contain the marker, create a new cluster.
    cluster = new Cluster(this, map);
    cluster.addMarker({
      'isAdded': isAdded,
      'marker': marker
    });
    if (!opt_isNodraw) {
      cluster.redraw_();
    }

    // Add this cluster both in clusters provided and clusters_
    clusters.push(cluster);
    if (clusters !== clusters_) {
      clusters_.push(cluster);
    }
  };
  this.removeMarker = function (marker) {
    for (var i = 0; i < clusters_.length; ++i) {
      if (clusters_[i].remove(marker)) {
        clusters_[i].redraw_();
        return;
      }
    }
  };
  this.redraw_ = function () {
    var clusters = this.getClustersInViewport_();
    for (var i = 0; i < clusters.length; ++i) {
      clusters[i].redraw_(true);
    }
  };
  this.getClustersInViewport_ = function () {
    var clusters = [];
    var curBounds = map_.getBounds();
    for (var i = 0; i < clusters_.length; i ++) {
      if (clusters_[i].isInBounds(curBounds)) {
        clusters.push(clusters_[i]);
      }
    }
    return clusters;
  };
  this.getMaxZoom_ = function () {
    return maxZoom_;
  };
  this.getMap_ = function () {
    return map_;
  };
  this.getGridSize_ = function () {
    return gridSize_;
  };
  this.getTotalMarkers = function () {
    var result = 0;
    for (var i = 0; i < clusters_.length; ++i) {
      result += clusters_[i].getTotalMarkers();
    }
    return result;
  };
  this.getTotalClusters = function () {
    return clusters_.length;
  };
  this.resetViewport = function () {
    var clusters = this.getClustersInViewport_();
    var tmpMarkers = [];
    var removed = 0;

    for (var i = 0; i < clusters.length; ++i) {
      var cluster = clusters[i];
      var oldZoom = cluster.getCurrentZoom();
      if (oldZoom === null) {
        continue;
      }
      var curZoom = map_.getZoom();
      if (curZoom !== oldZoom) {

        // If the cluster zoom level changed then destroy the cluster
        // and collect its markers.
        var mks = cluster.getMarkers();
        for (var j = 0; j < mks.length; ++j) {
          var newMarker = {
            'isAdded': false,
            'marker': mks[j].marker
          };
          tmpMarkers.push(newMarker);
        }
        cluster.clearMarkers();
        removed++;
        for (j = 0; j < clusters_.length; ++j) {
          if (cluster === clusters_[j]) {
            clusters_.splice(j, 1);
          }
        }
      }
    }
    reAddMarkers_(tmpMarkers);
    this.redraw_();
  };
  this.addMarkers = function (markers) {
    for (var i = 0; i < markers.length; ++i) {
      this.addMarker(markers[i], true);
    }
    this.redraw_();
  };
  if (typeof opt_markers === "object" && opt_markers !== null) {
    this.addMarkers(opt_markers);
  }
  mcfn_ = GEvent.addListener(map_, "moveend", function () {
    me_.resetViewport();
  });
}
function Cluster(markerClusterer) {
  var center_ = null;
  var markers_ = [];
  var markerClusterer_ = markerClusterer;
  var map_ = markerClusterer.getMap_();
  var clusterMarker_ = null;
  var zoom_ = map_.getZoom();
  this.getMarkers = function () {
    return markers_;
  };
  this.isInBounds = function (bounds) {
    if (center_ === null) {
      return false;
    }

    if (!bounds) {
      bounds = map_.getBounds();
    }
    var sw = map_.fromLatLngToDivPixel(bounds.getSouthWest());
    var ne = map_.fromLatLngToDivPixel(bounds.getNorthEast());

    var centerxy = map_.fromLatLngToDivPixel(center_);
    var inViewport = true;
    var gridSize = markerClusterer.getGridSize_();
    if (zoom_ !== map_.getZoom()) {
      var dl = map_.getZoom() - zoom_;
      gridSize = Math.pow(2, dl) * gridSize;
    }
    if (ne.x !== sw.x && (centerxy.x + gridSize < sw.x || centerxy.x - gridSize > ne.x)) {
      inViewport = false;
    }
    if (inViewport && (centerxy.y + gridSize < ne.y || centerxy.y - gridSize > sw.y)) {
      inViewport = false;
    }
    return inViewport;
  };
  this.getCenter = function () {
    return center_;
  };
  this.addMarker = function (marker) {
    if (center_ === null) {
      center_ = marker.marker.getLatLng();
    }
    markers_.push(marker);
  };
  this.removeMarker = function (marker) {
    for (var i = 0; i < markers_.length; ++i) {
      if (marker === markers_[i].marker) {
        if (markers_[i].isAdded) {
          map_.removeOverlay(markers_[i].marker);
        }
        markers_.splice(i, 1);
        return true;
      }
    }
    return false;
  };
  this.getCurrentZoom = function () {
    return zoom_;
  };
  this.redraw_ = function (isForce) {
    if (!isForce && !this.isInBounds()) {
      return;
    }

    // Set cluster zoom level.
    zoom_ = map_.getZoom();
    var i = 0;
    var mz = markerClusterer.getMaxZoom_();
    if (mz === null) {
      mz = map_.getCurrentMapType().getMaximumResolution();
    }
    if (zoom_ >= mz || this.getTotalMarkers() === 1) {

      // If current zoom level is beyond the max zoom level or the cluster
      // have only one marker, the marker(s) in cluster will be showed on map.
      for (i = 0; i < markers_.length; ++i) {
        if (markers_[i].isAdded) {
          if (markers_[i].marker.isHidden()) {
            markers_[i].marker.show();
          }
        } else {					
          map_.addOverlay( markers_[i].marker );
					// MJK, 9-1-09
					// if there's a callback set, call it
					if (markers_[i].marker.callback != '') {						
						markers_[i].marker.callback( markers_[i].marker );
					}
          markers_[i].isAdded = true;					
        }
      }
      if (clusterMarker_ !== null) {
        clusterMarker_.hide();
      }
    } else {
      // Else add a cluster marker on map to show the number of markers in
      // this cluster.
      for (i = 0; i < markers_.length; ++i) {
        if (markers_[i].isAdded && (!markers_[i].marker.isHidden())) {
          markers_[i].marker.hide();
        }
      }
      if (clusterMarker_ === null) {
        clusterMarker_ = new ClusterMarker_(center_, this.getTotalMarkers(), markerClusterer_.getStyles_(), markerClusterer_.getGridSize_());
        map_.addOverlay(clusterMarker_);
      } else {
        if (clusterMarker_.isHidden()) {
          clusterMarker_.show();
        }
        clusterMarker_.redraw(true);
      }
    }
  };
  this.clearMarkers = function () {
    if (clusterMarker_ !== null) {
      map_.removeOverlay(clusterMarker_);
    }
    for (var i = 0; i < markers_.length; ++i) {
      if (markers_[i].isAdded) {
        map_.removeOverlay(markers_[i].marker);
      }
    }
    markers_ = [];
  };
  this.getTotalMarkers = function () {
    return markers_.length;
  };
}
function ClusterMarker_(latlng, count, styles, padding) {
  var index = 0;
  var dv = count;
  while (dv !== 0) {
    dv = parseInt(dv / 10, 10);
    index ++;
  }
  if (styles.length < index) {
    index = styles.length;
  }
  this.url_ = styles[index - 1].url;
  this.height_ = styles[index - 1].height;
  this.width_ = styles[index - 1].width;
  this.textColor_ = styles[index - 1].opt_textColor;
  this.anchor_ = styles[index - 1].opt_anchor;
  this.latlng_ = latlng;
  this.index_ = index;
  this.styles_ = styles;
  this.text_ = count;
  this.padding_ = padding;
}

ClusterMarker_.prototype = new GOverlay();
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_ + ") no-repeat 0 0;";
  }
	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';

  div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
      pos.x + "px;color:" + txtColor +  ";position:absolute;font-size:11px;" +
      'font-family:Arial,sans-serif;font-weight:bold';
  div.innerHTML = this.text_;
  map.getPane(G_MAP_MAP_PANE).appendChild(div);
  var padding = this.padding_;
	var tooltip = new Clusterflag( {x:(pos.x + this.width_), y:pos.y}, this.text_ );
	tooltip.initialize( map );
	GEvent.addDomListener(div, "mouseover", function () {		
		tooltip.show();
	});
	GEvent.addDomListener(div, "mouseout", function () {		
		tooltip.hide();
	});
  GEvent.addDomListener(div, "click", function () {
		// kill tooltip
		tooltip.hide();
		// zoom
		var pos = map.fromLatLngToDivPixel(latlng);
    var sw = new GPoint(pos.x - padding, pos.y + padding);
    sw = map.fromDivPixelToLatLng(sw);
    var ne = new GPoint(pos.x + padding, pos.y - padding);
    ne = map.fromDivPixelToLatLng(ne);
    var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
    map.setCenter(latlng, zoom);
  });
	
	this.div_ = div;
	this.tooltip_ = tooltip;
};
ClusterMarker_.prototype.remove = function () {
  this.div_.parentNode.removeChild(this.div_);
	this.tooltip_.remove();
};
ClusterMarker_.prototype.copy = function () {
  return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.styles_, this.padding_);
};
ClusterMarker_.prototype.redraw = function (force) {
  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";
	
	this.tooltip_.reposition( {x:(pos.x + this.width_), y:pos.y} );
};
ClusterMarker_.prototype.hide = function () {
  this.div_.style.display = "none";
	this.tooltip_.hide();
};
ClusterMarker_.prototype.show = function () {
  this.div_.style.display = "";
	this.tooltip_.show();	
};
ClusterMarker_.prototype.isHidden = function () {
  return this.div_.style.display === "none";
};