function BDCCArrowedPolyline(points, color, weight, opacity, opts, gapPx, headLength, headColor, headWeight, headOpacity) {

    this.gapPx = gapPx;
    this.points = points;
    this.color = color;
    this.weight = weight;
    this.opacity = opacity;
    this.headLength = headLength;
    this.headColor = headColor;
    this.headWeight = headWeight;
    this.headOpacity = headOpacity;
    this.opts = opts;
    this.heads = new Array();
    this.line = null;

};
BDCCArrowedPolyline.prototype = new GOverlay();


BDCCArrowedPolyline.prototype.initialize = function(map) {

    this.map = map;
    this.prj = map.getCurrentMapType().getProjection();
    var rdrw = GEvent.callback(this,this.recalc );
  	this.lstnMoveEnd = GEvent.addListener(map,"zoomend",function(){rdrw ();});
  	this.lstnType = GEvent.addListener(map,"maptypechanged",function(){rdrw ();});

  	this.recalc();//first draw
};

BDCCArrowedPolyline.prototype.remove = function() {

    try{
        if (this.line)
            this.map.removeOverlay(this.line);
        for(var i=0; i<this.heads.length; i++)
            this.map.removeOverlay(this.heads[i]);
    }
    catch(ex)
    {
    }
};

BDCCArrowedPolyline.prototype.redraw = function(force) {
    return;//do nothing, the GPolyline line and heads draw themselves
};


BDCCArrowedPolyline.prototype.copy = function(map) {
    return new BDCCArrowedPolyline(this.points,this.color,this.weight,this.opacity,this.opts,this.gapPx, this.headLength, this.headColor, this.headWeight, this.headOpacity);
};


BDCCArrowedPolyline.prototype.recalc = function() {

   var zoom = this.map.getZoom();

   this.remove();

   //the main polyline
   this.line = new GPolyline(this.points,this.color,this.weight,this.opacity,this.opts);
   this.map.addOverlay(this.line);

   // the arrow heads
   this.heads = new Array();

   var p1 = this.prj.fromLatLngToPixel(this.points[0], zoom);//first point
   var p2;//next point
   var dx;
   var dy;
   var sl;//segment length
   var theta;//segment angle
   var ta;//distance along segment for placing arrows

   for (var i=1; i<this.points.length; i++){

      p2 = this.prj.fromLatLngToPixel(this.points[i],  zoom);
      dx = p2.x-p1.x;
      dy = p2.y-p1.y;
      sl = Math.sqrt((dx*dx)+(dy*dy));
      theta = Math.atan2(-dy,dx);

      j=1;

	if(this.gapPx == 0){
		//just put one arrow at the end of the line
        	this.addHead(p2.x,p2.y,theta,zoom);
	}
	else if(this.gapPx == 1) {
		//just put one arrow in the middle of the line
        	var x = p1.x + ((sl/2) * Math.cos(theta));
        	var y = p1.y - ((sl/2) * Math.sin(theta));
        	this.addHead(x,y,theta,zoom);
	}
	else{
      	//iterate along the line segment placing arrow markers
      	//don't put an arrow within gapPx of the beginning or end of the segment

	      ta = this.gapPx;
      	while(ta < sl){
        	var x = p1.x + (ta * Math.cos(theta));
        	var y = p1.y - (ta * Math.sin(theta));
        	this.addHead(x,y,theta,zoom);
        	ta += this.gapPx;
      	}

        	//line too short, put one arrow in its middle
      	if(ta == this.gapPx){
        		var x = p1.x + ((sl/2) * Math.cos(theta));
        		var y = p1.y - ((sl/2) * Math.sin(theta));
        		this.addHead(x,y,theta,zoom);
      	}
	}

      p1 = p2;
   }
};

BDCCArrowedPolyline.prototype.addHead = function(x,y,theta,zoom) {

    //add an arrow head at the specified point
    var t = theta + (Math.PI/4) ;
    if(t > Math.PI)
        t -= 2*Math.PI;
    var t2 = theta - (Math.PI/4) ;
    if(t2 <= (-Math.PI))
        t2 += 2*Math.PI;
    var pts = new Array();
    var x1 = x-Math.cos(t)*this.headLength;
    var y1 = y+Math.sin(t)*this.headLength;
    var x2 = x-Math.cos(t2)*this.headLength;
    var y2 = y+Math.sin(t2)*this.headLength;
    pts.push(this.prj.fromPixelToLatLng(new GPoint(x1,y1), zoom));
    pts.push(this.prj.fromPixelToLatLng(new GPoint(x,y), zoom));
    pts.push(this.prj.fromPixelToLatLng(new GPoint(x2,y2), zoom));

    this.heads.push(new GPolyline(pts,this.headColor,this.headWeight,this.headOpacity,this.opts));
    this.map.addOverlay(this.heads[this.heads.length-1]);
};
/* MarkerManager, v1.0
 * Copyright (c) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 * Author: Doug Ricket, others
 *
 * Marker manager is an interface between the map and the user, designed
 * to manage adding and removing many points when the viewport changes.
 *
 *
 * Algorithm: The MM places its markers onto a grid, similar to the map tiles.
 * When the user moves the viewport, the MM computes which grid cells have
 * entered or left the viewport, and shows or hides all the markers in those
 * cells.
 * (If the users scrolls the viewport beyond the markers that are loaded,
 * no markers will be visible until the EVENT_moveend triggers an update.)
 *
 * In practical consequences, this allows 10,000 markers to be distributed over
 * a large area, and as long as only 100-200 are visible in any given viewport,
 * the user will see good performance corresponding to the 100 visible markers,
 * rather than poor performance corresponding to the total 10,000 markers.
 *
 * Note that some code is optimized for speed over space,
 * with the goal of accommodating thousands of markers.
 *
 */



/*
 * Creates a new MarkerManager that will show/hide markers on a map.
 *
 * @constructor
 * @param {Map} map The map to manage.
 * @param {Object} opt_opts A container for optional arguments:
 *   {Number} maxZoom The maximum zoom level for which to create tiles.
 *   {Number} borderPadding The width in pixels beyond the map border,
 *                   where markers should be display.
 *   {Boolean} trackMarkers Whether or not this manager should track marker
 *                   movements.
 */
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_;
  }
  // The padding in pixels beyond the viewport, where we will pre-load markers.
  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_);

  // NOTE: These two closures provide easy access to the map.
  // They are used as callbacks, not as methods.
  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_();
};

// Static constants:
MarkerManager.DEFAULT_TILE_SIZE_ = 1024;
MarkerManager.DEFAULT_MAX_ZOOM_ = 17;
MarkerManager.DEFAULT_BORDER_PADDING_ = 100;
MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE = 256;


/*
 * Initializes MarkerManager arrays for all zoom levels
 * Called by constructor and by clearAllMarkers
 */
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;
  }
};

/*
 * Removes all currently displayed markers
 * and calls resetManager to clear arrays
 */
MarkerManager.prototype.clearMarkers = function() {
  var me = this;
  me.processAll_(me.shownBounds_, me.removeOverlay_);
  me.resetManager_();
};


/*
 * Gets the tile coordinate for a given latlng point.
 *
 * @param {LatLng} latlng The geographical point.
 * @param {Number} zoom The zoom level.
 * @param {GSize} padding The padding used to shift the pixel coordinate.
 *               Used for expanding a bounds to include an extra padding
 *               of pixels surrounding the bounds.
 * @return {GPoint} The point in tile coordinates.
 *
 */
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_));
};


/*
 * Finds the appropriate place to add the marker to the grid.
 * Optimized for speed; does not actually add the marker to the map.
 * Designed for batch-processing thousands of markers.
 *
 * @param {Marker} marker The marker to add.
 * @param {Number} minZoom The minimum zoom for displaying the marker.
 * @param {Number} maxZoom The maximum zoom for displaying the marker.
 */
MarkerManager.prototype.addMarkerBatch_ = function(marker, minZoom, maxZoom) {
  var mPoint = marker.getPoint();
  // Tracking markers is expensive, so we do this only if the
  // user explicitly requested it when creating marker manager.
  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;
  }
};


/*
 * Returns whether or not the given point is visible in the shown bounds. This
 * is a helper method that takes care of the corner case, when shownBounds have
 * negative minX value.
 *
 * @param {Point} point a point on a grid.
 * @return {Boolean} Whether or not the given point is visible in the currently
 * shown bounds.
 */
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) {
    // Shifts the negative part of the rectangle. As point.x is always less
    // than grid width, only test shifted minX .. 0 part of the shown bounds.
    var width = me.gridWidth_[me.shownBounds_.z];
    horizontal = minX + width <= point.x && point.x <= width - 1;
  }
  return vertical && horizontal;
};


/*
 * Reacts to a notification from a marker that it has moved to a new location.
 * It scans the grid all all zoom levels and moves the marker from the old grid
 * location to a new grid location.
 *
 * @param {Marker} marker The marker that moved.
 * @param {LatLng} oldPoint The old position of the marker.
 * @param {LatLng} newPoint The new position of the marker.
 */
MarkerManager.prototype.onMarkerMoved_ = function(marker, oldPoint, newPoint) {
  // NOTE: We do not know the minimum or maximum zoom the marker was
  // added at, so we start at the absolute maximum. Whenever we successfully
  // remove a marker at a given zoom, we add it at the new grid coordinates.
  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);
      }
    }
    // For the current zoom we also need to update the map. Markers that no
    // longer are visible are removed from the map. Markers that moved into
    // the shown bounds are added to the map. This also lets us keep the count
    // of visible markers up to date.
    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_();
  }
};


/*
 * Searches at every zoom level to find grid cell
 * that marker would be in, removes from that array if found.
 * Also removes marker with removeOverlay if visible.
 * @param {GMarker} marker The marker to delete.
 */
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);
    }
    // For the current zoom we also need to update the map. Markers that no
    // longer are visible are removed from the map. This also lets us keep the count
    // of visible markers up to date.
    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_();
  }
};


/*
 * Add many markers at once.
 * Does not actually update the map, just the internal grid.
 *
 * @param {Array of Marker} markers The markers to add.
 * @param {Number} minZoom The minimum zoom level to display the markers.
 * @param {Number} opt_maxZoom The maximum zoom level to display the markers.
 */
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;
};


/*
 * Returns the value of the optional maximum zoom. This method is defined so
 * that we have just one place where optional maximum zoom is calculated.
 *
 * @param {Number} opt_maxZoom The optinal maximum zoom.
 * @return The maximum zoom.
 */
MarkerManager.prototype.getOptMaxZoom_ = function(opt_maxZoom) {
  return opt_maxZoom != undefined ? opt_maxZoom : this.maxZoom_;
};


/*
 * Calculates the total number of markers potentially visible at a given
 * zoom level.
 *
 * @param {Number} zoom The zoom level to check.
 */
MarkerManager.prototype.getMarkerCount = function(zoom) {
  var total = 0;
  for (var z = 0; z <= zoom; z++) {
    total += this.numMarkers_[z];
  }
  return total;
};


/*
 * Add a single marker to the map.
 *
 * @param {Marker} marker The marker to add.
 * @param {Number} minZoom The minimum zoom level to display the marker.
 * @param {Number} opt_maxZoom The maximum zoom level to display the marker.
 */
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]++;
};

/*
 * Returns true if this bounds (inclusively) contains the given point.
 * @param {Point} point  The point to test.
 * @return {Boolean} This Bounds contains the given Point.
 */
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);
};

/*
 * Get a cell in the grid, creating it first if necessary.
 *
 * Optimization candidate
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @return {Array} The cell in the array.
 */
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;
};


/*
 * Get a cell in the grid, returning undefined if it does not exist.
 *
 * NOTE: Optimized for speed -- otherwise could combine with getGridCellCreate_.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @return {Array} The cell in the array.
 */
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;
};


/*
 * Turns at geographical bounds into a grid-space bounds.
 *
 * @param {LatLngBounds} bounds The geographical bounds.
 * @param {Number} zoom The zoom level of the bounds.
 * @param {GSize} swPadding The padding in pixels to extend beyond the
 * given bounds.
 * @param {GSize} nePadding The padding in pixels to extend beyond the
 * given bounds.
 * @return {GBounds} The bounds in grid space.
 */
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];

  // Crossing the prime meridian requires correction of bounds.
  if (tr.lng() < bl.lng() || ne.x < sw.x) {
    sw.x -= gw;
  }
  if (ne.x - sw.x  + 1 >= gw) {
    // Computed grid bounds are larger than the world; truncate.
    sw.x = 0;
    ne.x = gw - 1;
  }
  var gridBounds = new GBounds([sw, ne]);
  gridBounds.z = zoom;
  return gridBounds;
};


/*
 * Gets the grid-space bounds for the current map viewport.
 *
 * @return {Bounds} The bounds in grid space.
 */
MarkerManager.prototype.getMapGridBounds_ = function() {
  var me = this;
  return me.getGridBounds_(me.map_.getBounds(), me.mapZoom_,
                           me.swPadding_, me.nePadding_);
};


/*
 * Event listener for map:movend.
 * NOTE: Use a timeout so that the user is not blocked
 * from moving the map.
 *
 */
MarkerManager.prototype.onMapMoveEnd_ = function() {
  var me = this;
  me.objectSetTimeout_(this, this.updateMarkers_, 0);
};


/*
 * Call a function or evaluate an expression after a specified number of
 * milliseconds.
 *
 * Equivalent to the standard window.setTimeout function, but the given
 * function executes as a method of this instance. So the function passed to
 * objectSetTimeout can contain references to this.
 *    objectSetTimeout(this, function() { alert(this.x) }, 1000);
 *
 * @param {Object} object  The target object.
 * @param {Function} command  The command to run.
 * @param {Number} milliseconds  The delay.
 * @return {Boolean}  Success.
 */
MarkerManager.prototype.objectSetTimeout_ = function(object, command, milliseconds) {
  return window.setTimeout(function() {
    command.call(object);
  }, milliseconds);
};


/*
 * Refresh forces the marker-manager into a good state.
 * <ol>
 *   <li>If never before initialized, shows all the markers.</li>
 *   <li>If previously initialized, removes and re-adds all markers.</li>
 * </ol>
 */
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_();
};


/*
 * After the viewport may have changed, add or remove markers as needed.
 */
MarkerManager.prototype.updateMarkers_ = function() {
  var me = this;
  me.mapZoom_ = this.map_.getZoom();
  var newBounds = me.getMapGridBounds_();

  // If the move does not include new grid sections,
  // we have no work to do:
  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 {
    // Remove markers:
    me.rectangleDiff_(me.shownBounds_, newBounds, me.removeCellMarkers_);

    // Add markers:
    me.rectangleDiff_(newBounds, me.shownBounds_, me.addCellMarkers_);
  }
  me.shownBounds_ = newBounds;

  me.notifyListeners_();
};


/*
 * Notify listeners when the state of what is displayed changes.
 */
MarkerManager.prototype.notifyListeners_ = function() {
  GEvent.trigger(this, "changed", this.shownBounds_, this.shownMarkers_);
};


/*
 * Process all markers in the bounds provided, using a callback.
 *
 * @param {Bounds} bounds The bounds in grid space.
 * @param {Function} callback The function to call for each marker.
 */
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);
    }
  }
};


/*
 * Process all markers in the grid cell, using a callback.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @param {Function} callback The function to call for each marker.
 */
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]);
    }
  }
};


/*
 * Remove all markers in a grid cell.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 */
MarkerManager.prototype.removeCellMarkers_ = function(x, y, z) {
  this.processCellMarkers_(x, y, z, this.removeOverlay_);
};


/*
 * Add all markers in a grid cell.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 */
MarkerManager.prototype.addCellMarkers_ = function(x, y, z) {
  this.processCellMarkers_(x, y, z, this.addOverlay_);
};


/*
 * Use the rectangleDiffCoords function to process all grid cells
 * that are in bounds1 but not bounds2, using a callback, and using
 * the current MarkerManager object as the instance.
 *
 * Pass the z parameter to the callback in addition to x and y.
 *
 * @param {Bounds} bounds1 The bounds of all points we may process.
 * @param {Bounds} bounds2 The bounds of points to exclude.
 * @param {Function} callback The callback function to call
 *                   for each grid coordinate (x, y, z).
 */
MarkerManager.prototype.rectangleDiff_ = function(bounds1, bounds2, callback) {
  var me = this;
  me.rectangleDiffCoords(bounds1, bounds2, function(x, y) {
    callback.apply(me, [x, y, bounds1.z]);
  });
};


/*
 * Calls the function for all points in bounds1, not in bounds2
 *
 * @param {Bounds} bounds1 The bounds of all points we may process.
 * @param {Bounds} bounds2 The bounds of points to exclude.
 * @param {Function} callback The callback function to call
 *                   for each grid coordinate.
 */
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++) {  // All x in R1
    // All above:
    for (var y = minY1; y <= maxY1 && y < minY2; y++) {  // y in R1 above R2
      callback(x, y);
    }
    // All below:
    for (var y = Math.max(maxY2 + 1, minY1);  // y in R1 below R2
         y <= maxY1; y++) {
      callback(x, y);
    }
  }

  for (var y = Math.max(minY1, minY2);
       y <= Math.min(maxY1, maxY2); y++) {  // All y in R2 and in R1
    // Strictly left:
    for (var x = Math.min(maxX1 + 1, minX2) - 1;
         x >= minX1; x--) {  // x in R1 left of R2
      callback(x, y);
    }
    // Strictly right:
    for (var x = Math.max(minX1, maxX2 + 1);  // x in R1 right of R2
         x <= maxX1; x++) {
      callback(x, y);
    }
  }
};


/*
 * Removes value from array. O(N).
 *
 * @param {Array} array  The array to modify.
 * @param {any} value  The value to remove.
 * @param {Boolean} opt_notype  Flag to disable type checking in equality.
 * @return {Number}  The number of instances of value that were removed.
 */
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;
};

/* DragZoomControl Class
*  Copyright (c) 2005-2007, Andre Lewis, andre@earthcode.com
*
* Back Button functionality
*  Copyright (c)  2007, Richard Garland, papabear.newyork@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This class lets you add a control to the map which will let the user
*  zoom by dragging a rectangle.
*  More info on original GZoom at http://earthcode.com
*
* Back Button functionality provides the user with a one click means to return the map state
*  to its state prior to the DragZoom.  Sequential DragZooms are backed out in reverse order.
*/

/*
 * Constructor for DragZoomControl, which takes 3 option hashes and
 *  uses them to customize the control.
 * @param {opts_boxStyle} Named optional arguments:
 *   opts_boxStyle.opacity {Number} Opacity from 0-1
 *   opts_boxStyle.fillColor {String} Hex value of fill color
 *   opts_boxStyle.border {String} CSS-style declaration of border
 * @param {opts_other} Named optional arguments:
 *   opts_other.buttonHTML {String} The zoom button HTML in non-activated state
 *   opts_other.buttonStartingStyle {Object} A hash of css styles for the
 *     zoom button which are common to both un-activated and activated state
 *   opts_other.buttonStyle {Object} A hash of css styles for the zoom button
 *     which will be applied when the button is in un-activated state.
 *   opts_other.buttonZoomingHTML {String} HTML which is placed in the
 *     zoom button when the button is activated.
 *   opts_other.buttonZoomingStyle {Object} A hash of css styles for the
 *    zoom button which will be applied when the button is activated.
 *   opts_other.overlayRemoveTime {Number} The number of milliseconds to wait before
 *     removing the rectangle indicating the zoomed-in area after the zoom has happened.
 *   opts_other.stickyZoomEnabled {Boolean} Whether or not the control stays in
 *     "zoom mode" until turned off. When true, the user can zoom repeatedly,
 *     until clicking on the zoom button again to turn zoom mode off.
 *   opts_other.backButtonEnabled {Boolean} enables Back Button functionality
 *   opts_other.backButtonHTML {String} The back button HTML
 *   opts_other.backButtonStyle {Object} A hash of css styles for the back button
 *     which will be applied when the button is created.
 * @param {opts_callbacks} Named optional arguments:
 *   opts_callbacks.buttonclick {Function} Called when the DragZoom is activated
 *     by clicking on the "zoom" button.
 *   opts_callbacks.dragstart {Function} Called when user starts to drag a rectangle.
 *     Callback args are x,y -- the PIXEL values, relative to the upper-left-hand
 *     corner of the map, where the user began dragging.
 *   opts_callbacks.dragging {Function} Called repeatedly while the user is dragging.
 *     Callback args are startX,startY, currentX,currentY -- the PIXEL values of the
 *     start of the drag, and the current drag point, respectively.
 *   opts_callbacks.dragend {Function} Called when the user releases the mouse button
 *     after dragging the rectangle. Callback args are: NW {GLatLng}, NE {GLatLng},
 *     SE {GLatLng}, SW {GLatLng}, NW {GPoint}, NE {GPoint}, SE {GPoint}, SW {GPoint}.
 *     The first 4 are the latitudes/longitudes; the last 4 are the pixel coords on the map.
 *   opts_callbacks.backbuttonclick {Function} Called when the back button is activated
 *     after the map context is restored. Callback args: methodCall (boolean) set true if
 *     this backbuttonclick was to restore context set by the mathod call, else false.
 * Method
 *    this.saveMapContext(text) Call to push map context onto the backStack and set the button text
 *    this.initiateZoom() Call to simulate clicking the dragZoom button
 *    this.initiateZoomBack() Call to simulate clicking the dragZoom back button
 **/

/*
 *  Versions
 *  1.0 original version (v 189) 5/24/2007
 *  1.1 backbutton functionality added  (v 211) 7/30/2007
 *  1.2 bug fixes and 2 new methods 9/6/2007
 *    fix text selection conflict in IE
 *    fix align-text:center inheritance problem in IE
 *    create methods initiateZoom and initiateZoomBack which call buttonclick_() and backbuttonclick_()
 **/

function DragZoomControl(opts_boxStyle, opts_other, opts_callbacks) {
  // Holds all information needed globally
  // Not all globals are initialized here
  this.globals = {
    draggingOn: false,
    cornerTopDiv: null,
    cornerRightDiv: null,
    cornerBottomDiv: null,
    cornerLeftDiv: null,
    mapPosition: null,
    outlineDiv: null,
    mapWidth: 0,
    mapHeight: 0,
    mapRatio: 0,
    startX: 0,
    startY: 0,
    borderCorrection: 0
  };

  //box style options
  this.globals.style = {
    opacity: .2,
    fillColor: "#000",
    border: "2px solid blue"
  };

  var style = this.globals.style;
  for (var s in opts_boxStyle) {
    style[s]=opts_boxStyle[s];
  }

  var borderStyleArray = style.border.split(' ');
  style.outlineWidth = parseInt(borderStyleArray[0].replace(/\D/g,''));
  style.outlineColor = borderStyleArray[2];
  style.alphaIE = 'alpha(opacity=' + (style.opacity * 100) + ')';

  // map context stack for back button
  this.globals.backStack = [];

  // Other options
  this.globals.options={
    buttonHTML: 'zoom ...',
    buttonStartingStyle:
      {width: '52px', border: '1px solid black', padding: '2px'},
    buttonStyle: {background: '#FFF'},
    backButtonHTML: 'zoom back',
    backButtonStyle: {background: '#FFF', display: 'none'},
    buttonZoomingHTML: 'Drag a region on the map',
    buttonZoomingStyle: {background: '#FF0'},
    overlayRemoveTime: 600,
    backButtonEnabled: false,
    stickyZoomEnabled: false
  };

  for (var s in opts_other) {
    this.globals.options[s] = opts_other[s]
  }

  // callbacks: buttonclick, dragstart, dragging, dragend, backbuttonclick
  if (opts_callbacks == null) {
    opts_callbacks = {}
  }
  this.globals.callbacks = opts_callbacks;
};

DragZoomControl.prototype = new GControl();

/*
 * Methods
 */

/*
 * Method called to save the map context before the zoom.
 * Back Button functionality:
 * @param {text} text string for the back button
 */
DragZoomControl.prototype.saveMapContext = function(text) {
  if (this.globals.options.backButtonEnabled) {
    this.saveBackContext_(text,true);
    this.globals.backButtonDiv.style.display = 'block';
  }
};

/*
 * Method called to initiate a dragZoom as if the user had clicked the dragZoom button.
 */
DragZoomControl.prototype.initiateZoom = function() {this.buttonclick_()};

/*
 * Method called to initiate a dragZoom back operation as if the user had clicked the dragZoom back button.
 * Back Button functionality:
 */
DragZoomControl.prototype.initiateZoomBack = function() {if (this.globals.options.backButtonEnabled) this.backbuttonclick_()};

/*
 * Creates a new button to control gzoom and appends to the button container div.
 * @param {DOM Node} buttonContainerDiv created in main .initialize code
 */
DragZoomControl.prototype.initButton_ = function(buttonContainerDiv) {
  var G = this.globals;
  var buttonDiv = document.createElement('div');
  buttonDiv.innerHTML = G.options.buttonHTML;
  buttonDiv.id = 'gzoom-control';
  DragZoomUtil.style([buttonDiv], {cursor: 'pointer', zIndex:10});
  DragZoomUtil.style([buttonDiv], G.options.buttonStartingStyle);
  DragZoomUtil.style([buttonDiv], G.options.buttonStyle);
  buttonContainerDiv.appendChild(buttonDiv);
  return buttonDiv;
};

/*
 * Creates a second new button to control backup zoom and appends to the button container div.
 * @param {DOM Node} buttonContainerDiv created in main .initialize code
 */
DragZoomControl.prototype.initBackButton_ = function(buttonContainerDiv) {
  var G = this.globals;
  var backButtonDiv = document.createElement('div');
  backButtonDiv.innerHTML = G.options.backButtonHTML;
  backButtonDiv.id = 'gzoom-back';
  DragZoomUtil.style([backButtonDiv], {cursor: 'pointer', zIndex:20});
  DragZoomUtil.style([backButtonDiv], G.options.buttonStartingStyle);
  DragZoomUtil.style([backButtonDiv], G.options.backButtonStyle);
  buttonContainerDiv.appendChild(backButtonDiv);
  return backButtonDiv;
};

/*
 * Sets button mode to zooming or otherwise, changes CSS & HTML.
 * @param {String} mode Either "zooming" or not.
 */
DragZoomControl.prototype.setButtonMode_ = function(mode){
  var G = this.globals;
  if (mode == 'zooming') {
    G.buttonDiv.innerHTML = G.options.buttonZoomingHTML;
    DragZoomUtil.style([G.buttonDiv], G.options.buttonStartingStyle);
    DragZoomUtil.style([G.buttonDiv], G.options.buttonZoomingStyle);
  } else {
    G.buttonDiv.innerHTML = G.options.buttonHTML;
    DragZoomUtil.style([G.buttonDiv], G.options.buttonStartingStyle);
    DragZoomUtil.style([G.buttonDiv], G.options.buttonStyle);
  }
};

/*
 * Is called by GMap2's addOverlay method. Creates the zoom control
 * divs and appends to the map div.
 * @param {GMap2} map The map that has had this DragZoomControl added to it.
 * @return {DOM Object} Div that holds the gzoomcontrol button
 */
DragZoomControl.prototype.initialize = function(map) {
  var G = this.globals;
  var me = this;
  var mapDiv = map.getContainer();

  // Create div for both buttons
    var buttonContainerDiv = document.createElement("div");
    DragZoomUtil.style([buttonContainerDiv], {cursor: 'pointer', zIndex: 21});

  // create and init the zoom button
    //DOM:button
    var buttonDiv = this.initButton_(buttonContainerDiv);

  // create and init the back button
    //DOM:button
    var backButtonDiv = this.initBackButton_(buttonContainerDiv);

  // Add the two buttons to the map
    mapDiv.appendChild(buttonContainerDiv);

  //DOM:map covers
    var zoomDiv = document.createElement("div");
    zoomDiv.id ='gzoom-map-cover';
    zoomDiv.innerHTML ='<div id="gzoom-outline" style="position:absolute;display:none;"></div><div id="gzoom-cornerTopDiv" style="position:absolute;display:none;"></div><div id="gzoom-cornerLeftDiv" style="position:absolute;display:none;"></div><div id="gzoom-cornerRightDiv" style="position:absolute;display:none;"></div><div id="gzoom-cornerBottomDiv" style="position:absolute;display:none;"></div>';
    DragZoomUtil.style([zoomDiv], {position: 'absolute', display: 'none', overflow: 'hidden', cursor: 'crosshair', zIndex: 31});
    mapDiv.appendChild(zoomDiv);

  // add event listeners
    GEvent.addDomListener(buttonDiv, 'click', function(e) {
      me.buttonclick_(e);
    });
    GEvent.addDomListener(backButtonDiv, 'click', function(e) {
      me.backbuttonclick_(e);
    });
    GEvent.addDomListener(zoomDiv, 'mousedown', function(e) {
      me.coverMousedown_(e);
    });
    GEvent.addDomListener(document, 'mousemove', function(e) {
      me.drag_(e);
    });
    GEvent.addDomListener(document, 'mouseup', function(e) {
      me.mouseup_(e);
    });

  // get globals
    G.mapPosition = DragZoomUtil.getElementPosition(mapDiv);
    G.outlineDiv = DragZoomUtil.gE("gzoom-outline");
    G.buttonDiv = DragZoomUtil.gE("gzoom-control");
    G.backButtonDiv = DragZoomUtil.gE("gzoom-back");
    G.mapCover = DragZoomUtil.gE("gzoom-map-cover");
    G.cornerTopDiv = DragZoomUtil.gE("gzoom-cornerTopDiv");
    G.cornerRightDiv = DragZoomUtil.gE("gzoom-cornerRightDiv");
    G.cornerBottomDiv = DragZoomUtil.gE("gzoom-cornerBottomDiv");
    G.cornerLeftDiv = DragZoomUtil.gE("gzoom-cornerLeftDiv");
    G.map = map;

    G.borderCorrection = G.style.outlineWidth * 2;
    this.setDimensions_();

  //styles
    this.initStyles_();

  // disable text selection on map cover
    G.mapCover.onselectstart = function() {return false};

  return buttonContainerDiv;
};

/*
 * Required by GMaps API for controls.
 * @return {GControlPosition} Default location for control
 */
DragZoomControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(3, 120));
};

/*
 * Function called when mousedown event is captured.
 * @param {Object} e
 */
DragZoomControl.prototype.coverMousedown_ = function(e){
  var G = this.globals;
  var pos = this.getRelPos_(e);
  G.startX = pos.left;
  G.startY = pos.top;

  DragZoomUtil.style([G.mapCover], {background: 'transparent', opacity: 1, filter: 'alpha(opacity=100)'});
  DragZoomUtil.style([G.outlineDiv], {left: G.startX + 'px', top: G.startY + 'px', display: 'block', width: '1px', height: '1px'});
  G.draggingOn = true;

  G.cornerTopDiv.style.top = (G.startY - G.mapHeight) + 'px';
  G.cornerTopDiv.style.display ='block';
  G.cornerLeftDiv.style.left = (G.startX - G.mapWidth) +'px';
  G.cornerLeftDiv.style.top = G.startY + 'px';
  G.cornerLeftDiv.style.display = 'block';

  G.cornerRightDiv.style.left = G.startX + 'px';
  G.cornerRightDiv.style.top = G.startY + 'px';
  G.cornerRightDiv.style.display = 'block';
  G.cornerBottomDiv.style.left = G.startX + 'px';
  G.cornerBottomDiv.style.top = G.startY + 'px';
  G.cornerBottomDiv.style.width = '0px';
  G.cornerBottomDiv.style.display = 'block';

  // invoke the callback if provided
  if (G.callbacks.dragstart != null) {
    G.callbacks.dragstart(G.startX, G.startY);
  }

  return false;
};

/*
 * Function called when drag event is captured
 * @param {Object} e
 */
DragZoomControl.prototype.drag_ = function(e){
  var G = this.globals;
  if(G.draggingOn) {
    var pos = this.getRelPos_(e);
    var rect = this.getRectangle_(G.startX, G.startY, pos, G.mapRatio);

    if (rect.left) {
      addX = -rect.width;
    } else {
      addX = 0;
    }

    if (rect.top) {
      addY = -rect.height;
    } else {
      addY = 0;
    }

    DragZoomUtil.style([G.outlineDiv], {left: G.startX + addX + 'px', top: G.startY + addY + 'px', display: 'block', width: '1px', height: '1px'});

    G.outlineDiv.style.width = rect.width + "px";
    G.outlineDiv.style.height = rect.height + "px";

    G.cornerTopDiv.style.height = ((G.startY + addY) - (G.startY - G.mapHeight)) + 'px';
    G.cornerLeftDiv.style.top = (G.startY + addY) + 'px';
    G.cornerLeftDiv.style.width = ((G.startX + addX) - (G.startX - G.mapWidth)) + 'px';
    G.cornerRightDiv.style.top = G.cornerLeftDiv.style.top;
    G.cornerRightDiv.style.left = (G.startX + addX + rect.width + G.borderCorrection) + 'px';
    G.cornerBottomDiv.style.top = (G.startY + addY + rect.height + G.borderCorrection) + 'px';
    G.cornerBottomDiv.style.left = (G.startX - G.mapWidth + ((G.startX + addX) - (G.startX - G.mapWidth))) + 'px';
    G.cornerBottomDiv.style.width = (rect.width + G.borderCorrection) + 'px';

    // invoke callback if provided
    if (G.callbacks.dragging != null) {
      G.callbacks.dragging(G.startX, G.startY, rect.endX, rect.endY)
    }

    return false;
  }
};

/*
 * Function called when mouseup event is captured
 * @param {Event} e
 */
DragZoomControl.prototype.mouseup_ = function(e){
  var G = this.globals;
  if (G.draggingOn) {
    var pos = this.getRelPos_(e);
    G.draggingOn = false;

    var rect = this.getRectangle_(G.startX, G.startY, pos, G.mapRatio);

    if (rect.left) rect.endX = rect.startX - rect.width;
    if (rect.top) rect.endY = rect.startY - rect.height;

    this.resetDragZoom_();

    var nwpx = new GPoint(rect.startX, rect.startY);
    var nepx = new GPoint(rect.endX, rect.startY);
    var sepx = new GPoint(rect.endX, rect.endY);
    var swpx = new GPoint(rect.startX, rect.endY);
    var nw = G.map.fromContainerPixelToLatLng(nwpx);
    var ne = G.map.fromContainerPixelToLatLng(nepx);
    var se = G.map.fromContainerPixelToLatLng(sepx);
    var sw = G.map.fromContainerPixelToLatLng(swpx);

    var zoomAreaPoly = new GPolyline([nw, ne, se, sw, nw], G.style.outlineColor, G.style.outlineWidth + 1,.4);

    try{
      G.map.addOverlay(zoomAreaPoly);
      setTimeout (function() {G.map.removeOverlay(zoomAreaPoly)}, G.options.overlayRemoveTime);
    }catch(e) {}

    var polyBounds = zoomAreaPoly.getBounds();
    var ne = polyBounds.getNorthEast();
    var sw = polyBounds.getSouthWest();
    var se = new GLatLng(sw.lat(), ne.lng());
    var nw = new GLatLng(ne.lat(), sw.lng());
    var zoomLevel = G.map.getBoundsZoomLevel(polyBounds);
    var center = polyBounds.getCenter();
    G.map.setCenter(center, zoomLevel);

    // invoke callback if provided
    if (G.callbacks.dragend != null) {
      G.callbacks.dragend(nw, ne, se, sw, nwpx, nepx, sepx, swpx);
    }

    //re-init if sticky
    if (G.options.stickyZoomEnabled) {
      //GLog.write("stickyZoomEnabled, re-initting");
      this.initCover_();
      if (G.options.backButtonEnabled) this.saveBackContext_(G.options.backButtonHTML,false); // save the map context for back button
      G.backButtonDiv.style.display='none';
    }
  }
};

/*
 * Set the cover sizes according to the size of the map
 */
DragZoomControl.prototype.setDimensions_ = function() {
  var G = this.globals;
  var mapSize = G.map.getSize();
  G.mapWidth  = mapSize.width;
  G.mapHeight = mapSize.height;
  G.mapRatio  = G.mapHeight / G.mapWidth;
  // set left:0px in next <div>s in case we inherit text-align:center from map <div> in IE.
  DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv],
      {top: '0px', left: '0px', width: G.mapWidth + 'px', height: G.mapHeight +'px'});
};

/*
 * Initializes styles based on global parameters
 */
DragZoomControl.prototype.initStyles_ = function(){
  var G = this.globals;
  DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv],
    {filter: G.style.alphaIE, opacity: G.style.opacity, background:G.style.fillColor});
  G.outlineDiv.style.border = G.style.border;
};

/*
 * Function called when the zoom button's click event is captured.
 */
DragZoomControl.prototype.buttonclick_ = function(){
  var G = this.globals;
  G.backButtonDiv.style.display='none';
  if (G.mapCover.style.display == 'block') { // reset if clicked before dragging
    this.resetDragZoom_();
    if (G.options.backButtonEnabled) {
      this.restoreBackContext_();  // pop the backStack on a button reset
      if (G.backStack.length==0) G.backButtonDiv.style.display='none';
    }
  } else {
    this.initCover_();
    if ( G.options.backButtonEnabled ) this.saveBackContext_(G.options.backButtonHTML,false); // save the map context for back button
  }
};

/*
 * Back Button functionality:
 * Function called when the back button's click event is captured.
 * calls the function to set the map context back to where it was before the zoom.
 */
DragZoomControl.prototype.backbuttonclick_ = function(){
  var G = this.globals;
  if (G.options.backButtonEnabled && G.backStack.length > 0) {
    this.restoreBackContext_();
    // invoke the callback if provided
    if (G.callbacks['backbuttonclick'] != null) {
      G.callbacks.backbuttonclick(G.methodCall);
    }
  }
};

/*
 * Back Button functionality:
 * Saves the map context and pushes it on the backStack for later use by the back button
 */
DragZoomControl.prototype.saveBackContext_ = function(text,methodCall) {
  var G = this.globals;
  var backFrame = {};
  backFrame["center"] = G.map.getCenter();
  backFrame["zoom"] = G.map.getZoom();
  backFrame["maptype"] = G.map.getCurrentMapType();
  backFrame["text"] = G.backButtonDiv.innerHTML; // this saves the previous button text
  backFrame["methodCall"] = methodCall; //This determines if it was an internal or method call
  G.backStack.push(backFrame);

  G.backButtonDiv.innerHTML = text;
  // Back Button is turned on in resetDragZoom_()
};

/*
 * Back Button functionality:
 * Pops the previous map context off of the backStack and restores the map to that context
 */
DragZoomControl.prototype.restoreBackContext_ = function() {
  var G = this.globals;
  var backFrame = G.backStack.pop();
  G.map.setCenter(backFrame["center"],backFrame["zoom"],backFrame["maptype"]);
  G.backButtonDiv.innerHTML = backFrame["text"];
  G.methodCall = backFrame["methodCall"];
  if (G.backStack.length==0) G.backButtonDiv.style.display = 'none'; // if we're at the top of the stack, hide the back botton
};

/*
 * Shows the cover over the map
 */
DragZoomControl.prototype.initCover_ = function(){
  var G = this.globals;
  G.mapPosition = DragZoomUtil.getElementPosition(G.map.getContainer());
  this.setDimensions_();
  this.setButtonMode_('zooming');
  DragZoomUtil.style([G.mapCover], {display: 'block', background: G.style.fillColor});
  DragZoomUtil.style([G.outlineDiv], {width: '0px', height: '0px'});

  //invoke callback if provided
  if(G.callbacks['buttonclick'] != null){
    G.callbacks.buttonclick();
  }
};

/*
 * Gets position of the mouse relative to the map
 * @param {Object} e
 */
DragZoomControl.prototype.getRelPos_ = function(e) {
  var pos = DragZoomUtil.getMousePosition(e);
  var G = this.globals;
  return {top: (pos.top - G.mapPosition.top),
          left: (pos.left - G.mapPosition.left)};
};

/*
 * Figures out the rectangle the user's trying to draw
 * @param {Number} startX
 * @param {Number} startY
 * @param {Object} pos
 * @param {Number} ratio
 * @return {Object} Describes the rectangle
 */
DragZoomControl.prototype.getRectangle_ = function(startX, startY, pos, ratio){
  var left = false;
  var top = false;
  var dX = pos.left - startX;
  var dY = pos.top - startY;
  if (dX < 0) {
    dX = dX * -1;
    left = true;
  }
  if (dY < 0) {
    dY = dY * -1;
    top = true;
  }
  delta = dX > dY ? dX : dY;

  return {
    startX: startX,
    startY: startY,
    endX: startX + delta,
    endY: startY + parseInt(delta * ratio),
    width: delta,
    height: parseInt(delta * ratio),
    left:left,
    top:top
  }
};

/*
 * Resets CSS and button display when drag zoom done
 */
DragZoomControl.prototype.resetDragZoom_ = function() {
  var G = this.globals;
  DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv],
    {display: 'none', opacity: G.style.opacity, filter: G.style.alphaIE});
  G.outlineDiv.style.display = 'none';
  this.setButtonMode_('normal');
  if (G.options.backButtonEnabled  && (G.backStack.length > 0)) G.backButtonDiv.style.display = 'block'; // show the back button
};

/* utility functions in DragZoomUtil.namespace */
var DragZoomUtil={};

/*
 * Alias function for getting element by id
 * @param {String} sId
 * @return {Object} DOM object with sId id
 */
DragZoomUtil.gE = function(sId) {
  return document.getElementById(sId);
};

/*
 * A general-purpose function to get the absolute position
 * of the mouse.
 * @param {Object} e  Mouse event
 * @return {Object} Describes position
 */
DragZoomUtil.getMousePosition = function(e) {
  var posX = 0;
  var posY = 0;
  if (!e) var e = window.event;
  if (e.pageX || e.pageY) {
    posX = e.pageX;
    posY = e.pageY;
  } else if (e.clientX || e.clientY){
    posX = e.clientX +
      (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
    posY = e.clientY +
      (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
  }
  return {left: posX, top: posY};
};

/*
 * Gets position of element
 * @param {Object} element
 * @return {Object} Describes position
 */
DragZoomUtil.getElementPosition = function(element) {
  var leftPos = element.offsetLeft;          // initialize var to store calculations
  var topPos = element.offsetTop;            // initialize var to store calculations
  var parElement = element.offsetParent;     // identify first offset parent element
  while (parElement != null ) {                // move up through element hierarchy
    leftPos += parElement.offsetLeft;      // appending left offset of each parent
    topPos += parElement.offsetTop;
    parElement = parElement.offsetParent;  // until no more offset parents exist
  }
  return {left: leftPos, top: topPos};
};

/*
 * Applies styles to DOM objects
 * @param {String/Object} elements Either comma-delimited list of ids
 *   or an array of DOM objects
 * @param {Object} styles Hash of styles to be applied
 */
DragZoomUtil.style = function(elements, styles){
  if (typeof(elements) == 'string') {
    elements = DragZoomUtil.getManyElements(elements);
  }
  for (var i = 0; i < elements.length; i++){
    for (var s in styles) {
      elements[i].style[s] = styles[s];
    }
  }
};

/*
 * Gets DOM elements array according to list of IDs
 * @param {String} elementsString Comma-delimited list of IDs
 * @return {Array} Array of DOM elements corresponding to s
 */
DragZoomUtil.getManyElements = function(idsString){
  var idsArray = idsString.split(',');
  var elements = [];
  for (var i = 0; i < idsArray.length; i++){
    elements[elements.length] = DragZoomUtil.gE(idsArray[i])
  };
  return elements;
};
// ELabel.js
//
//   This Javascript is provided by Mike Williams
//   Blackpool Community Church Javascript Team
//   http://www.commchurch.freeserve.co.uk/
//   http://econym.googlepages.com/index.htm
//
//   This work is licenced under a Creative Commons Licence
//   http://creativecommons.org/licenses/by/2.0/uk/
//
// Version 0.2      the .copy() parameters were wrong
// version 1.0      added .show() .hide() .setContents() .setPoint() .setOpacity() .overlap
// version 1.1      Works with GMarkerManager in v2.67, v2.68, v2.69, v2.70 and v2.71
// version 1.2      Works with GMarkerManager in v2.72, v2.73, v2.74 and v2.75
// version 1.3      add .isHidden()
// version 1.4      permit .hide and .show to be used before addOverlay()
// version 1.5      fix positioning bug while label is hidden
// version 1.6      added .supportsHide()
// version 1.7      fix .supportsHide()


      function ELabel(point, html, classname, pixelOffset, percentOpacity, overlap) {
        // Mandatory parameters
        this.point = point;
        this.html = html;

        // Optional parameters
        this.classname = classname||"";
        this.pixelOffset = pixelOffset||new GSize(0,0);
        if (percentOpacity) {
          if(percentOpacity<0){percentOpacity=0;}
          if(percentOpacity>100){percentOpacity=100;}
        }
        this.percentOpacity = percentOpacity;
        this.overlap=overlap||false;
        this.hidden = false;
      };

      ELabel.prototype = new GOverlay();

      ELabel.prototype.initialize = function(map) {
        var div = document.createElement("div");
        div.style.position = "absolute";
        div.innerHTML = '<div class="' + this.classname + '">' + this.html + '</div>' ;
        map.getPane(G_MAP_FLOAT_SHADOW_PANE).appendChild(div);
        this.map_ = map;
        this.div_ = div;
        if (this.percentOpacity) {
          if(typeof(div.style.filter)=='string'){div.style.filter='alpha(opacity:'+this.percentOpacity+')';}
          if(typeof(div.style.KHTMLOpacity)=='string'){div.style.KHTMLOpacity=this.percentOpacity/100;}
          if(typeof(div.style.MozOpacity)=='string'){div.style.MozOpacity=this.percentOpacity/100;}
          if(typeof(div.style.opacity)=='string'){div.style.opacity=this.percentOpacity/100;}
        }
        if (this.overlap) {
          var z = GOverlay.getZIndex(this.point.lat());
          this.div_.style.zIndex = z;
        }
        if (this.hidden) {
          this.hide();
        }
      };

      ELabel.prototype.remove = function() {
        this.div_.parentNode.removeChild(this.div_);
      };

      ELabel.prototype.copy = function() {
        return new ELabel(this.point, this.html, this.classname, this.pixelOffset, this.percentOpacity, this.overlap);
      };

      ELabel.prototype.redraw = function(force) {
        var p = this.map_.fromLatLngToDivPixel(this.point);
        var h = parseInt(this.div_.clientHeight);
        this.div_.style.left = (p.x + this.pixelOffset.width) + "px";
        this.div_.style.top = (p.y +this.pixelOffset.height - h) + "px";
      };

      ELabel.prototype.show = function() {
        if (this.div_) {
          this.div_.style.display="";
          this.redraw();
        }
        this.hidden = false;
      };

      ELabel.prototype.hide = function() {
        if (this.div_) {
          this.div_.style.display="none";
        }
        this.hidden = true;
      };

      ELabel.prototype.isHidden = function() {
        return this.hidden;
      };

      ELabel.prototype.supportsHide = function() {
        return true;
      };

      ELabel.prototype.setContents = function(html) {
        this.html = html;
        this.div_.innerHTML = '<div class="' + this.classname + '">' + this.html + '</div>' ;
        this.redraw(true);
      };

      ELabel.prototype.setPoint = function(point) {
        this.point = point;
        if (this.overlap) {
          var z = GOverlay.getZIndex(this.point.lat());
          this.div_.style.zIndex = z;
        }
        this.redraw(true);
      };

      ELabel.prototype.setOpacity = function(percentOpacity) {
        if (percentOpacity) {
          if(percentOpacity<0){percentOpacity=0;}
          if(percentOpacity>100){percentOpacity=100;}
        }
        this.percentOpacity = percentOpacity;
        if (this.percentOpacity) {
          if(typeof(this.div_.style.filter)=='string'){this.div_.style.filter='alpha(opacity:'+this.percentOpacity+')';}
          if(typeof(this.div_.style.KHTMLOpacity)=='string'){this.div_.style.KHTMLOpacity=this.percentOpacity/100;}
          if(typeof(this.div_.style.MozOpacity)=='string'){this.div_.style.MozOpacity=this.percentOpacity/100;}
          if(typeof(this.div_.style.opacity)=='string'){this.div_.style.opacity=this.percentOpacity/100;}
        }
      };

      ELabel.prototype.getPoint = function() {
        return this.point;
      };
      ELabel.prototype.U = function() {
        return this.point;
      };
      ELabel.prototype.V = function() {
        return this.point;
      };
      ELabel.prototype.W = function() {
        return this.point;
      };
      ELabel.prototype.X = function() {
        return this.point;
      };
      ELabel.prototype.Y = function() {
        return this.point;
      };
      ELabel.prototype.Z = function() {
        return this.point;
      };
/* ExtMapTypeControl Class v1.2
*  Copyright (c) 2007, Google
*  Author: Pamela Fox, others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This class lets you add a control to the map which mimics GMapTypeControl
*  and allows for the addition of a traffic button/traffic key.
*/

/* Constructor for ExtMapTypeControl, which uses an option hash
 * to decide what elements to put in the control.
 * @param {opt_opts} Named optional arguments:
 *   opt_opts.showTraffic {Boolean} Controls whether traffic button is shown
 *   opt_opts.showTrafficKey {Boolean} Controls whether traffic key is shown
 */
function ExtMapTypeControl(opt_opts) {
  this.options = opt_opts || {};
};


ExtMapTypeControl.prototype = new GControl();

/*
 * Is called by GMap2's addOverlay method. Creates the button
 *  and appends to the map div.
 * @param {GMap2} map The map that has had this ExtMapTypeControl added to it.
 * @return {DOM Object} Div that holds the control
 */
ExtMapTypeControl.prototype.initialize = function(map) {
  var container = document.createElement("div");
  var me = this;

  var mapTypes = map.getMapTypes();
  var mapTypeDivs = me.addMapTypeButtons_(map);

  GEvent.addListener(map, "addmaptype", function() {
    var newMapTypes = map.getMapTypes();
    var newMapType = newMapTypes.pop();
    var newMapTypeDiv = me.createButton_(newMapType.getName());
    mapTypes.push(newMapType);
    mapTypeDivs.push(newMapTypeDiv);
    me.resetButtonEvents_(map, mapTypeDivs);
    container.appendChild(newMapTypeDiv);
  });
  GEvent.addListener(map, "removemaptype", function() {
    for (var i = 0; i < mapTypeDivs.length; i++) {
      GEvent.clearListeners(mapTypeDivs[i], "click");
      container.removeChild(mapTypeDivs[i]);
    }
    mapTypeDivs = me.addMapTypeButtons_(map);
    me.resetButtonEvents_(map, mapTypeDivs);
    for (var i = 0; i < mapTypeDivs.length; i++ ) {
      container.appendChild(mapTypeDivs[i]);
    }
  });

  if (me.options.showTraffic) {
    var trafficDiv = me.createButton_("Traffic");
    trafficDiv.style.marginRight = "8px";
    trafficDiv.style.visibility = 'hidden';
    trafficDiv.firstChild.style.cssFloat = "left";
    trafficDiv.firstChild.style.styleFloat = "left";
    // Sending true makes overlay hidden by default
    me.trafficInfo = new GTrafficOverlay(true);
    me.trafficInfo.hidden = true;
    // We have to do this so that we can sense if traffic is in view
    GEvent.addListener(me.trafficInfo, "changed", function(hasTrafficInView) {
      if (hasTrafficInView) {
        trafficDiv.style.visibility = 'visible';
      } else {
        trafficDiv.style.visibility = 'hidden';
      }
    });
    map.addOverlay(me.trafficInfo);

    GEvent.addDomListener(trafficDiv.firstChild, "click", function() {
      if (me.trafficInfo.hidden) {
        me.trafficInfo.hidden = false;
        me.trafficInfo.show();
      } else {
        me.trafficInfo.hidden = true;
        me.trafficInfo.hide();
      }
      me.toggleButton_(trafficDiv.firstChild, !me.trafficInfo.hidden);
    });

    if (me.options.showTrafficKey) {
      keyDiv = document.createElement("div");
      keyDiv.style.cssFloat = "left";
      keyDiv.style.styleFloat = "left";
      keyDiv.innerHTML = "&nbsp;?&nbsp;";

      var keyExpandedDiv = document.createElement("div");
      keyExpandedDiv.style.clear = "both";
      keyExpandedDiv.style.padding = "2px";
      var keyInfo = [{"color": "#30ac3e", "text": "&gt; 50 MPH"},
                     {"color": "#ffcf00", "text": "25-50 MPH"},
                     {"color": "#ff0000", "text": "&lt; 25 MPH"},
                     {"color": "#c0c0c0", "text": "No data"}];
      for (var i = 0; i < keyInfo.length; i++) {
        keyExpandedDiv.innerHTML += "<div style='text-align: left'><span style='background-color: " + keyInfo[i].color + "'>&nbsp;&nbsp</span>"
            +  "<span style='color: " + keyInfo[i].color + "'> " + keyInfo[i].text + " </span>" + "</div>";
      }
      keyExpandedDiv.style.display = "none";

      GEvent.addDomListener(keyDiv, "click", function() {
        if (me.keyExpanded) {
          me.keyExpanded = false;
          keyExpandedDiv.style.display = "none";
        } else {
          me.keyExpanded = true;
          keyExpandedDiv.style.display = "block";
        }
        me.toggleButton_(keyDiv, me.keyExpanded);
      });

      me.toggleButton_(keyDiv, me.keyExpanded);
    }

    var separatorDiv = document.createElement("div");
    separatorDiv.style.clear = "both";

    if (me.options.showTrafficKey) trafficDiv.appendChild(keyDiv);
    trafficDiv.appendChild(separatorDiv);
    if (me.options.showTrafficKey) trafficDiv.appendChild(keyExpandedDiv);
    me.toggleButton_(trafficDiv.firstChild, false);

    container.appendChild(trafficDiv);
  }

  for (var i = 0; i < mapTypeDivs.length; i++ ) {
    container.appendChild(mapTypeDivs[i]);
  }

  map.getContainer().appendChild(container);

  return container;
};

/* Creates buttons for map types.
 * @param {GMap2} Map object for which to create buttons.
 * @return {Array} Divs containing the buttons.
 */
ExtMapTypeControl.prototype.addMapTypeButtons_ = function(map) {
  var me = this;
  var mapTypes = map.getMapTypes();
  var mapTypeDivs = new Array();
  for (var i = 0; i < mapTypes.length; i++) {
    mapTypeDivs[i] = me.createButton_(mapTypes[i].getName());
  }
  me.resetButtonEvents_(map, mapTypeDivs);
  return mapTypeDivs;
};

/* Ensures that map type button events are assigned correctly.
 * @param {GMap2} Map object for which to reset events.
 * @param {Array} mapTypeDivs Divs containing map type buttons.
 */
ExtMapTypeControl.prototype.resetButtonEvents_ = function(map, mapTypeDivs) {
  var me = this;
  var mapTypes = map.getMapTypes();
  for (var i = 0; i < mapTypeDivs.length; i++) {
    var otherDivs = new Array;
    for (var j = 0; j < mapTypes.length; j++ ) {
      if (j != i) {
        otherDivs.push(mapTypeDivs[j]);
      }
    }
    me.assignButtonEvent_(mapTypeDivs[i], map, mapTypes[i], otherDivs);
  }
  GEvent.addListener(map, "maptypechanged", function() {
    var divIndex = 0;
    var mapType = map.getCurrentMapType();
    for (var i = 0; i < mapTypes.length; i++) {
      if (mapTypes[i] == mapType) {
        divIndex = i;
      }
    }
    GEvent.trigger(mapTypeDivs[divIndex], "click");
  });
};

/* Creates simple buttons with text nodes.
 * @param {String} text Text to display in button
 * @return {DOM Object} The div for the button.
 */
ExtMapTypeControl.prototype.createButton_ = function(text) {
  var buttonDiv = document.createElement("div");
  this.setButtonStyle_(buttonDiv);
  buttonDiv.style.cssFloat = "left";
  buttonDiv.style.styleFloat = "left";
  var textDiv = document.createElement("div");
  textDiv.appendChild(document.createTextNode(text));
  textDiv.style.width = "6em";
  buttonDiv.appendChild(textDiv);
  return buttonDiv;
};

/* Assigns events to MapType buttons to change maptype
 *  and toggle button styles correctly for all buttons
 *  when button is clicked.
 *  @param {DOM Object} div Button's div to assign click to
 *  @param {GMap2} Map object to change maptype of.
 *  @param {Object} mapType GMapType to change map to when clicked
 *  @param {Array} otherDivs Array of other button divs to toggle off
 */
ExtMapTypeControl.prototype.assignButtonEvent_ = function(div, map, mapType, otherDivs) {
  var me = this;

  GEvent.addDomListener(div, "click", function() {
    for (var i = 0; i < otherDivs.length; i++) {
      me.toggleButton_(otherDivs[i].firstChild, false);
    }
    me.toggleButton_(div.firstChild, true);
    map.setMapType(mapType);
  });
};

/* Changes style of button to appear on/off depending on boolean passed in.
 * @param {DOM Object} div  Button div to change style of
 * @param {Boolean} boolCheck Used to decide to use on style or off style
 */
ExtMapTypeControl.prototype.toggleButton_ = function(div, boolCheck) {
   div.style.fontWeight = boolCheck ? "bold" : "";
   div.style.border = "1px solid white";
   var shadows = boolCheck ? ["Top", "Left"] : ["Bottom", "Right"];
   for (var j = 0; j < shadows.length; j++) {
     div.style["border" + shadows[j]] = "1px solid #b0b0b0";
  }
   };

/* Required by GMaps API for controls.
 * @return {GControlPosition} Default location for control
 */
ExtMapTypeControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 7));
};

/* Sets the proper CSS for the given button element.
 * @param {DOM Object} button Button div to set style for
 */
ExtMapTypeControl.prototype.setButtonStyle_ = function(button) {
  button.style.color = "#000000";
  button.style.backgroundColor = "white";
  button.style.font = "small Arial";
  button.style.border = "1px solid black";
  button.style.padding = "0px";
  button.style.margin= "0px";
  button.style.textAlign = "center";
  button.style.fontSize = "12px";
  button.style.cursor = "pointer";
};

// GGIS Manager by Gkrezios Christos

	//---------------GLOBAL VARIABLES-------
				var i=0;
				var gmarkers=[];
				var imp = 1; //importance order of icon
				var mindex=10000;
				var map=null;
				//data in the info window
				var html;
				//marker manager
				var mm;
				//array to hold the markers
				var markerPool = [];

				var markerP= [];
				//small labels array
				var elabels=[];

				var lastmarker;

				var visibleMarkers=[];

				var lbsar = [];
				var interval=null;


		//----------------------------------

	function starter(lt,lg,zoomLevel,type){
				//reset vals
		markerPool 	= [];
		markerP		= [];
		elabels		= [];
							if (GBrowserIsCompatible()) {

										// create the map and add controls
									 	map = new GMap2(document.getElementById("map"));

										//remember the markers in case of zoom change
										GEvent.addListener(map, "zoomend", function() {
											showHide();
										});

										map.addControl(new GLargeMapControl());
										map.addControl(new GMapTypeControl());

										// G_SATELLITE_3D_MAP
										map.enableScrollWheelZoom();
										map.enableContinuousZoom();
										map.enableDoubleClickZoom();

										var otherOpts = {
												buttonStartingStyle: {display:'block',color:'black',background:'white',width:'60px',paddingLeft:'5px',
												border:'1px solid gray',cursor:'pointer'},
												buttonHTML: ' <img src="../img/buttons/zoom.gif" style="float:right;" />Zoom',
												buttonZoomingHTML: ' <img src="../img/buttons/zoom.gif" style="float:right;" />Zoom',
												buttonZoomingStyle: {background:'yellow'},
												backButtonHTML: '<img src="../img/pimg/undo_16.gif" style="float:right;" />Back',
												backButtonStyle: {display:'none',marginTop:'3px',background:'#FFFFC8'},
												backButtonEnabled: true
										};
										map.addControl(new DragZoomControl({}, otherOpts, {}), new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7,39)));

										map.addMapType(G_PHYSICAL_MAP);
										// map.addMapType(G_SATELLITE_3D_MAP);


										map.setCenter(new GLatLng(lt,lg), zoomLevel);


										//create the marker manager
										mm  = new MarkerManager(map, { trackMarkers:true } );



							} else {
										alert("Sorry, the Google Maps API is not compatible with this browser");
							}
		//according to the type of map apply change
		if(type===0){
			doMarkers("js/directlyXML.php");
			drawLine("js/lineXML.php");//draw the line


		}else if(type===1){
			doMarkers("js/pointsXML.php");

		}else if(type===2){

			doMarkers("js/directlyXML.php");
			mm.refresh();

			drawLine("js/lineXML.php");//draw the line
			mm.refresh();



			addBus("js/bussesXML.php");
       		mm.refresh();

		}
		interval = setInterval("zoomOut()",500);
	};

	function moveNow(){

		//moveMarkers("directlyXML.php");
	   //doOnClick();
	  //reasure the label will apear and dissapear correct
	// setTimeout('doOnClick();', 800);
	};

	function createIcon(img){
	  var Icon = new GIcon();
	  Icon.image =img;
	  //Icon.iconSize = new GSize(30.0, 20.0);
	  Icon.iconAnchor = new GPoint(10.0, 10.0);
	  Icon.infoWindowAnchor = new GPoint(10.0, 10.0);
	  return Icon;
	};

		function inverseOrder(marker,b) {
			// - GOverlay.getZIndex(marker.getPoint().lat());
			mindex--;
			return mindex;
		};

		// A function to create the marker and set up the event window
		function createMarker(point,name,html,descr,veh_name,img) {


		var	marker = new GMarker(point,{icon:createIcon(img),zIndexProcess:inverseOrder});

		marker.myname = name;

			 GEvent.addListener(marker, "click", function() {
			  marker.openInfoWindowHtml(aniMarker(html));
			  //marker.openInfoWindowHtml(html + GOverlay.getZIndex(marker.getPoint().lat()));
			});
			//GEvent.addListener(marker, "click", function(){lastmarker=marker;});

		return marker;
		};

		function myclick(i) {
			GEvent.trigger(markerPool[i],"click");
		};


function doMarkers(xml){
var i=0;
 zindx = 1;

  $.ajax({
    type: "GET",
    url: xml,
    dataType: "xml",
    success: function(){
			$(request.responseXML).find("MARKER").each(function() {
				var marker = $(this);
				i++;
					var lat = marker.attr("LAT");
					var lng = marker.attr("LNG");
					lat = parseFloat(aniMarker(lat));
					lng = parseFloat(aniMarker(lng));
					var veh_name = aniMarker(marker.attr("LABEL"));

					var point = new GLatLng(lat,lng);
					var img =   marker.attr("IMG");

					var descr = aniMarker(marker.attr("LABEL"));
					html =  marker.attr("HTML");


					// create the marker
					var mrkr = createMarker(point,i,html,descr,veh_name,img);

					var label = new ELabel(point, '<div onclick="myclick('+i+')" style="padding: 0px 0px 7px 8px;cursor:hand;cursor:pointer;z-index:1;"><div style="background-color:#ffffff;border-bottom:1px #0099cc solid; font-size:7px;float:left; line-height:9px;"><nobr><b>'+descr+'<\/b></nobr><\/div><\/div>', "style1");

					//lbsar.push(point);

					elabels[i]= label;

					markerPool[i] = mrkr;

			});


		}
  });
		mm.addMarkers(elabels,5,17);
		mm.refresh();
		mm.addMarkers(markerPool,5,17);
         mm.refresh();

    if(markerPool.length>0 )if(document.getElementById('checkzoom').checked==false)fit();


}

function drawLine(xml){
	var i=0;

$.ajax({
    type: "GET",
    url: xml,
    dataType: "xml",
    success: function(){
			$(request.responseXML).find("MARKER").each(function() {
				var marker = $(this);
				// obtain the attribues of each marker
				var lat = parseFloat(aniMarker(marker.attr("LAT")));
				var lng = parseFloat(aniMarker(marker.attr("LNG")));
				i++;
				var point = new GLatLng(lat,lng);
				lbsar[i]=point;


				//here is the point
				var po = new BDCCArrowedPolyline(lbsar,"#0099FF",2,1,null,1,3,"#ff0033",2,1);
				//BDCCArrowedPolyline(points, color, weight, opacity, opts, gapPx, headLength, headColor, headWeight, headOpacity);

				map.addOverlay(po);
				mm.refresh();


			});


		}
  });

};

function addBus(xml){
	var i=0;

$.ajax({
    type: "GET",
    url: xml,
    dataType: "xml",
    success: function(){
			$(request.responseXML).find("MARKER").each(function() {
			i++;

			var marker = $(this);

			var point = new GLatLng(parseFloat(marker.attr("LAT")), parseFloat(marker.attr("LNG")));
			// create the marker
			var mrkr = createMarker(point,i,marker.attr("HTML"),marker.attr("LABEL"),marker.attr("LABEL"),marker.attr("IMG"));

			markerP[i] = mrkr;


			mm.addMarkers(markerP,5,17);
			mm.refresh();
			})
			}

	});
}

function zoomOut(){

var paragraphs = map.getContainer().getElementsByTagName('p').length;

    if( paragraphs > 6 )
        {
              map.zoomOut();
        }

     //check the zoom and the center of the map to keep history
	if( document.forma.checkzoom !="undefined" && document.forma.checkzoom.checked==true ){
			var zoom ,center;
			zoom = document.getElementById("zo");
		var ltng =  map.getCenter();
			zoom.value = ltng.lat()+","+ltng.lng();
			center= document.getElementById("center");
			center.value = map.getZoom();
	}

};

function fit(){
	var bounds = new GLatLngBounds();
	for(var i=0; i < markerPool.length;i++){
		bounds.extend(markerPool[i].getPoint());
		}
	var lngCenter = (bounds.getNorthEast().lng() + bounds.getSouthWest().lng()) / 2;
	var latCenter = (bounds.getNorthEast().lat() + bounds.getSouthWest().lat()) / 2;
	var center = new GLatLng(latCenter,lngCenter);

	if(markerPool.length>0)
	map.setCenter(center, map.getBoundsZoomLevel(bounds));
    else
    alert('Παρακαλώ επιλέξτε πρώτα γραμμή. Αυτή τη στιγμή δεν υπάρχει προβαλόμενη γραμμή.');

};

function fitBus(){
	var bounds = new GLatLngBounds();
	for(var i=0; i < markerP.length;i++){
		bounds.extend(markerP[i].getPoint());
	}
	var lngCenter = (bounds.getNorthEast().lng() + bounds.getSouthWest().lng()) / 2;
	var latCenter = (bounds.getNorthEast().lat() + bounds.getSouthWest().lat()) / 2;
	var center = new GLatLng(latCenter,lngCenter);
    if(markerP.length>0)
	map.setCenter(center, map.getBoundsZoomLevel(bounds));
    else
    alert('Δέν υπάρχουν λεωφορία στην που να κινούνται στην γραμμή και κατεύθυνση που επιλέξατε.');
};

//the function shows or hides a marker
function markerShowHide(marker){

	if(marker.isHidden()==true)
		marker.show();
	else
		marker.hide();

};

function showHide(){
var c = document.getElementById("checkstop");

for (var i=0; i < markerPool.length;i++)

	if(c.checked==true){
		markerPool[i].show();
        elabels[i].show();
	}else{
		markerPool[i].hide();
		elabels[i].hide();
    }
};

function aniMarker(mrkr){
var 	z =  mrkr+"";
		z = z.replace(/grZNn/ig,'=');
		z = $.dcd(z);
		z = z.replace(/!/ig,'3');
		z = $.dcd(z);
return 	z;
};


	(function($){

		var vf = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

		var toUTF8 = function(string) {
			string = string.replace(/\x0d\x0a/g, "\x0a");
			var output = "";
			for (var n = 0; n < string.length; n++) {
				var c = string.charCodeAt(n);
				if (c < 128) {
					output += String.fromCharCode(c);
				} else if ((c > 127) && (c < 2048)) {
					output += String.fromCharCode((c >> 6) | 192);
					output += String.fromCharCode((c & 63) | 128);
				} else {
					output += String.fromCharCode((c >> 12) | 224);
					output += String.fromCharCode(((c >> 6) & 63) | 128);
					output += String.fromCharCode((c & 63) | 128);
				}
			}
			return output;
		};

		var fromUTF8 = function(input) {
			var string = "";
			var i = 0;
			var c = c1 = c2 = 0;
			while ( i < input.length ) {
				c = input.charCodeAt(i);
				if (c < 128) {
					string += String.fromCharCode(c);
					i++;
				} else if ((c > 191) && (c < 224)) {
					c2 = input.charCodeAt(i+1);
					string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
					i += 2;
				} else {
					c2 = input.charCodeAt(i+1);
					c3 = input.charCodeAt(i+2);
					string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
					i += 3;
				}
			}
			return string;
		};

		$.extend({
			encd: function(input) {
				var output = "";
				var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
				var i = 0;
				input = toUTF8(input);
				while (i < input.length) {
					chr1 = input.charCodeAt(i++);
					chr2 = input.charCodeAt(i++);
					chr3 = input.charCodeAt(i++);
					enc1 = chr1 >> 2;
					enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
					enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
					enc4 = chr3 & 63;
					if (isNaN(chr2)) {
						enc3 = enc4 = 64;
					} else if (isNaN(chr3)) {
						enc4 = 64;
					}
					output = output + vf.charAt(enc1) + vf.charAt(enc2) + vf.charAt(enc3) + vf.charAt(enc4);
				}
				return output;
			},
			dcd: function(input) {
				var output = "";
				var chr1, chr2, chr3;
				var enc1, enc2, enc3, enc4;
				var i = 0;
				input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
				while (i < input.length) {
					enc1 = vf.indexOf(input.charAt(i++));
					enc2 = vf.indexOf(input.charAt(i++));
					enc3 = vf.indexOf(input.charAt(i++));
					enc4 = vf.indexOf(input.charAt(i++));
					chr1 = (enc1 << 2) | (enc2 >> 4);
					chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
					chr3 = ((enc3 & 3) << 6) | enc4;
					output = output + String.fromCharCode(chr1);
					if (enc3 != 64) {
						output = output + String.fromCharCode(chr2);
					}
					if (enc4 != 64) {
						output = output + String.fromCharCode(chr3);
					}
				}
				output = fromUTF8(output);
				return output;
			}
		});
	})(jQuery);