function MarkerManager(map,opt_opts){
   var me=this;
   me.map_=map;
   me.mapZoom_=map.getZoom();
   me.projection_=map.getCurrentMapType().getProjection();
   opt_opts=opt_opts||{};
   me.tileSize_=MarkerManager.DEFAULT_TILE_SIZE_;
   var maxZoom=MarkerManager.DEFAULT_MAX_ZOOM_;
   if(opt_opts.maxZoom!=undefined){
      maxZoom=opt_opts.maxZoom
   }
   me.maxZoom_=maxZoom;
   me.trackMarkers_=opt_opts.trackMarkers;
   var padding;
   if(typeof opt_opts.borderPadding=="number"){
      padding=opt_opts.borderPadding
   }else{
      padding=MarkerManager.DEFAULT_BORDER_PADDING_
   }
   me.swPadding_=new GSize(-padding,padding);
   me.nePadding_=new GSize(padding,-padding);
   me.borderPadding_=padding;
   me.gridWidth_=[];
   me.grid_=[];
   me.grid_[maxZoom]=[];
   me.numMarkers_=[];
   me.numMarkers_[maxZoom]=0;
   GEvent.bind(map,"moveend",me,me.onMapMoveEnd_);
   me.removeOverlay_=function(marker){
      map.removeOverlay(marker);
      me.shownMarkers_--
   };
   me.addOverlay_=function(marker){
      map.addOverlay(marker);
      me.shownMarkers_++
   };
   me.resetManager_();
   me.shownMarkers_=0;
   me.shownBounds_=me.getMapGridBounds_()
};
MarkerManager.DEFAULT_TILE_SIZE_=1024;
MarkerManager.DEFAULT_MAX_ZOOM_=17;
MarkerManager.DEFAULT_BORDER_PADDING_=100;
MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE=256;
MarkerManager.prototype.resetManager_=function(){
   var me=this;
   var mapWidth=MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE;
   for(var zoom=0;zoom<=me.maxZoom_;++zoom){
      me.grid_[zoom]=[];
      me.numMarkers_[zoom]=0;
      me.gridWidth_[zoom]=Math.ceil(mapWidth/me.tileSize_);
      mapWidth<<=1
   }
};
MarkerManager.prototype.clearMarkers=function(){
   var me=this;
   me.processAll_(me.shownBounds_,me.removeOverlay_);
   me.resetManager_()
};
MarkerManager.prototype.getTilePoint_=function(latlng,zoom,padding){
   var pixelPoint=this.projection_.fromLatLngToPixel(latlng,zoom);
   return new GPoint(Math.floor((pixelPoint.x+padding.width)/this.tileSize_),Math.floor((pixelPoint.y+padding.height)/this.tileSize_))
};
MarkerManager.prototype.addMarkerBatch_=function(marker,minZoom,maxZoom){
   var mPoint=marker.getPoint();
   if(this.trackMarkers_){
      GEvent.bind(marker,"changed",this,this.onMarkerMoved_)
   }
   var gridPoint=this.getTilePoint_(mPoint,maxZoom,GSize.ZERO);
   for(var zoom=maxZoom;zoom>=minZoom;zoom--){
      var cell=this.getGridCellCreate_(gridPoint.x,gridPoint.y,zoom);
   cell.push(marker);
   gridPoint.x=gridPoint.x>>1;
   gridPoint.y=gridPoint.y>>1
   }
};
MarkerManager.prototype.isGridPointVisible_=function(point){
   var me=this;
   var vertical=me.shownBounds_.minY<=point.y&&point.y<=me.shownBounds_.maxY;
   var minX=me.shownBounds_.minX;
   var horizontal=minX<=point.x&&point.x<=me.shownBounds_.maxX;
   if(!horizontal&&minX<0){
      var width=me.gridWidth_[me.shownBounds_.z];
      horizontal=minX+width<=point.x&&point.x<=width-1
   }
   return vertical&&horizontal
};
MarkerManager.prototype.onMarkerMoved_=function(marker,oldPoint,newPoint){
   var me=this;
   var zoom=me.maxZoom_;
   var changed=false;
   var oldGrid=me.getTilePoint_(oldPoint,zoom,GSize.ZERO);
   var newGrid=me.getTilePoint_(newPoint,zoom,GSize.ZERO);
   while(zoom>=0&&(oldGrid.x!=newGrid.x||oldGrid.y!=newGrid.y)){
      var cell=me.getGridCellNoCreate_(oldGrid.x,oldGrid.y,zoom);
      if(cell){
         if(me.removeFromArray(cell,marker)){
            me.getGridCellCreate_(newGrid.x,newGrid.y,zoom).push(marker)
         }
      }
      if(zoom==me.mapZoom_){
         if(me.isGridPointVisible_(oldGrid)){
            if(!me.isGridPointVisible_(newGrid)){
               me.removeOverlay_(marker);
               changed=true
            }
         }else{
            if(me.isGridPointVisible_(newGrid)){
               me.addOverlay_(marker);
               changed=true
            }
         }
      }
      oldGrid.x=oldGrid.x>>1;
      oldGrid.y=oldGrid.y>>1;
      newGrid.x=newGrid.x>>1;
      newGrid.y=newGrid.y>>1;
      --zoom
   }
   if(changed){
      me.notifyListeners_()
   }
};
MarkerManager.prototype.removeMarker=function(marker){
   var me=this;
   var zoom=me.maxZoom_;
   var changed=false;
   var point=marker.getPoint();
   var grid=me.getTilePoint_(point,zoom,GSize.ZERO);
   while(zoom>=0){
      var cell=me.getGridCellNoCreate_(grid.x,grid.y,zoom);
      if(cell){
         me.removeFromArray(cell,marker)
      }
      if(zoom==me.mapZoom_){
         if(me.isGridPointVisible_(grid)){
            me.removeOverlay_(marker);
            changed=true
         }
      }
      grid.x=grid.x>>1;
      grid.y=grid.y>>1;
      --zoom
   }
   if(changed){
      me.notifyListeners_()
   }
};
MarkerManager.prototype.addMarkers=function(markers,minZoom,opt_maxZoom){
   var maxZoom=this.getOptMaxZoom_(opt_maxZoom);
   for(var i=markers.length-1;i>=0;i--){
      this.addMarkerBatch_(markers[i],minZoom,maxZoom)
   }
   this.numMarkers_[minZoom]+=markers.length
};
MarkerManager.prototype.getOptMaxZoom_=function(opt_maxZoom){
   return opt_maxZoom!=undefined?opt_maxZoom:this.maxZoom_
};
MarkerManager.prototype.getMarkerCount=function(zoom){
   var total=0;
   for(var z=0;z<=zoom;z++){
      total+=this.numMarkers_[z]
   }
   return total
};
MarkerManager.prototype.addMarker=function(marker,minZoom,opt_maxZoom){
   var me=this;
   var maxZoom=this.getOptMaxZoom_(opt_maxZoom);
   me.addMarkerBatch_(marker,minZoom,maxZoom);
   var gridPoint=me.getTilePoint_(marker.getPoint(),me.mapZoom_,GSize.ZERO);
   if(me.isGridPointVisible_(gridPoint)&&minZoom<=me.shownBounds_.z&&me.shownBounds_.z<=maxZoom){
      me.addOverlay_(marker);
      me.notifyListeners_()
   }
   this.numMarkers_[minZoom]++
};
GBounds.prototype.containsPoint=function(point){
   var outer=this;
   return(outer.minX<=point.x&&outer.maxX>=point.x&&outer.minY<=point.y&&outer.maxY>=point.y)
};
MarkerManager.prototype.getGridCellCreate_=function(x,y,z){
   var grid=this.grid_[z];
   if(x<0){
      x+=this.gridWidth_[z]
   }
   var gridCol=grid[x];
   if(!gridCol){
      gridCol=grid[x]=[];
      return gridCol[y]=[]
   }
   var gridCell=gridCol[y];
   if(!gridCell){
      return gridCol[y]=[]
   }
   return gridCell
};
MarkerManager.prototype.getGridCellNoCreate_=function(x,y,z){
   var grid=this.grid_[z];
   if(x<0){
      x+=this.gridWidth_[z]
   }
   var gridCol=grid[x];
   return gridCol?gridCol[y]:undefined
};
MarkerManager.prototype.getGridBounds_=function(bounds,zoom,swPadding,nePadding){
   zoom=Math.min(zoom,this.maxZoom_);
   var bl=bounds.getSouthWest();
   var tr=bounds.getNorthEast();
   var sw=this.getTilePoint_(bl,zoom,swPadding);
   var ne=this.getTilePoint_(tr,zoom,nePadding);
   var gw=this.gridWidth_[zoom];
   if(tr.lng()<bl.lng()||ne.x<sw.x){
      sw.x-=gw
   }
   if(ne.x-sw.x+1>=gw){
      sw.x=0;ne.x=gw-1
   }
   var gridBounds=new GBounds([sw,ne]);
   gridBounds.z=zoom;
   return gridBounds
};
MarkerManager.prototype.getMapGridBounds_=function(){
   var me=this;
   return me.getGridBounds_(me.map_.getBounds(),me.mapZoom_,me.swPadding_,me.nePadding_)
};
MarkerManager.prototype.onMapMoveEnd_=function(){
   var me=this;
   me.objectSetTimeout_(this,this.updateMarkers_,0)
};
MarkerManager.prototype.objectSetTimeout_=function(object,command,milliseconds){
   return window.setTimeout(function(){
      command.call(object)
   },milliseconds)
};
MarkerManager.prototype.refresh=function(){
   var me=this;if(me.shownMarkers_>0){
      me.processAll_(me.shownBounds_,me.removeOverlay_)
   }me.processAll_(me.shownBounds_,me.addOverlay_);
   me.notifyListeners_()
};
MarkerManager.prototype.updateMarkers_=function(){
   var me=this;
   me.mapZoom_=this.map_.getZoom();
   var newBounds=me.getMapGridBounds_();
   if(newBounds.equals(me.shownBounds_)&&newBounds.z==me.shownBounds_.z){
      return
   }
   if(newBounds.z!=me.shownBounds_.z){
      me.processAll_(me.shownBounds_,me.removeOverlay_);
      me.processAll_(newBounds,me.addOverlay_)
   }else{
      me.rectangleDiff_(me.shownBounds_,newBounds,me.removeCellMarkers_);
      me.rectangleDiff_(newBounds,me.shownBounds_,me.addCellMarkers_)
   }
   me.shownBounds_=newBounds;me.notifyListeners_()
};
MarkerManager.prototype.notifyListeners_=function(){
   GEvent.trigger(this,"changed",this.shownBounds_,this.shownMarkers_)
};
MarkerManager.prototype.processAll_=function(bounds,callback){
   for(var x=bounds.minX;x<=bounds.maxX;x++){
      for(var y=bounds.minY;y<=bounds.maxY;y++){
         this.processCellMarkers_(x,y,bounds.z,callback)
      }
   }
};
MarkerManager.prototype.processCellMarkers_=function(x,y,z,callback){
   var cell=this.getGridCellNoCreate_(x,y,z);
   if(cell){
      for(var i=cell.length-1;i>=0;i--){
         callback(cell[i])
      }
   }
};
MarkerManager.prototype.removeCellMarkers_=function(x,y,z){
   this.processCellMarkers_(x,y,z,this.removeOverlay_)
};
MarkerManager.prototype.addCellMarkers_=function(x,y,z){
   this.processCellMarkers_(x,y,z,this.addOverlay_)
};
MarkerManager.prototype.rectangleDiff_=function(bounds1,bounds2,callback){
   var me=this;
   me.rectangleDiffCoords(bounds1,bounds2,function(x,y){
      callback.apply(me,[x,y,bounds1.z])
   })
};
MarkerManager.prototype.rectangleDiffCoords=function(bounds1,bounds2,callback){
   var minX1=bounds1.minX;
   var minY1=bounds1.minY;
   var maxX1=bounds1.maxX;
   var maxY1=bounds1.maxY;
   var minX2=bounds2.minX;
   var minY2=bounds2.minY;
   var maxX2=bounds2.maxX;
   var maxY2=bounds2.maxY;
   for(var x=minX1;x<=maxX1;x++){
      for(var y=minY1;y<=maxY1&&y<minY2;y++){
         callback(x,y)
      }
      for(var y=Math.max(maxY2+1,minY1);y<=maxY1;y++){
         callback(x,y)
      }
   }
   for(var y=Math.max(minY1,minY2);y<=Math.min(maxY1,maxY2);y++){
      for(var x=Math.min(maxX1+1,minX2)-1;x>=minX1;x--){
         callback(x,y)
      }
      for(var x=Math.max(minX1,maxX2+1);x<=maxX1;x++){
         callback(x,y)
      }
   }
};
MarkerManager.prototype.removeFromArray=function(array,value,opt_notype){
   var shift=0;
   for(var i=0;i<array.length;++i){
      if (array[i]===value||(opt_notype&&array[i]==value)){
         array.splice(i--,1);
         shift++;
      }
   }
   return shift;
}; 
