<!--

/*******************************************************************************
 ****  BEGIN MARKER MANAGER INCLUDE  *******************************************
 ******************************************************************************/

/* 
 * 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;
};


/*******************************************************************************
 ****  END MARKER MANAGER INCLUDE  *********************************************
 ******************************************************************************/



/*******************************************************************************
 ****  BEGIN LABELED MARKER INCLUDE  *******************************************
 ******************************************************************************/

function LabeledMarker(a,b){this.latlng_=a;this.opts_=b;this.labelText_=b.labelText||"";this.labelClass_=b.labelClass||"LabeledMarker_markerLabel";this.labelOffset_=b.labelOffset||new GSize(0,0);this.clickable_=b.clickable||true;this.title_=b.title||"";this.labelVisibility_=true;if(b.draggable){b.draggable=false}GMarker.apply(this,arguments)}
LabeledMarker.prototype=new GMarker(new GLatLng(0,0));
LabeledMarker.prototype.initialize=function(c){GMarker.prototype.initialize.apply(this,arguments);this.map_=c;this.div_=document.createElement("div");this.div_.className=this.labelClass_;this.div_.innerHTML=this.labelText_;this.div_.style.position="absolute";this.div_.style.cursor="pointer";this.div_.title=this.title_;c.getPane(G_MAP_MARKER_PANE).appendChild(this.div_);if(this.clickable_){function newEventPassthru(a,b){return function(){GEvent.trigger(a,b)}}var d=['click','dblclick','mousedown','mouseup','mouseover','mouseout'];for(var i=0;i<d.length;i++){var e=d[i];GEvent.addDomListener(this.div_,e,newEventPassthru(this,e))}}}
LabeledMarker.prototype.redraw=function(a){GMarker.prototype.redraw.apply(this,arguments);var p=this.map_.fromLatLngToDivPixel(this.latlng_);var z=GOverlay.getZIndex(this.latlng_.lat());this.div_.style.left=(p.x+this.labelOffset_.width)+"px";this.div_.style.top=(p.y+this.labelOffset_.height)+"px";this.div_.style.zIndex=z}
LabeledMarker.prototype.remove=function(){GEvent.clearInstanceListeners(this.div_);if(this.div_.outerHTML){this.div_.outerHTML=""}if(this.div_.parentNode){this.div_.parentNode.removeChild(this.div_);}this.div_=null;GMarker.prototype.remove.apply(this,arguments)}
LabeledMarker.prototype.copy=function(){return new LabeledMarker(this.latlng_,this.opt_opts_)}
LabeledMarker.prototype.show=function(){GMarker.prototype.show.apply(this,arguments);if(this.labelVisibility_){this.showLabel()}else{this.hideLabel()}}
LabeledMarker.prototype.hide=function(){GMarker.prototype.hide.apply(this,arguments);this.hideLabel()}
LabeledMarker.prototype.setLabelVisibility=function(a){this.labelVisibility_=a;if(!this.isHidden()){if(this.labelVisibility_){this.showLabel()}else{this.hideLabel()}}}
LabeledMarker.prototype.getLabelVisibility=function(){return this.labelVisibility_}
LabeledMarker.prototype.hideLabel=function(){this.div_.style.visibility='hidden'}
LabeledMarker.prototype.showLabel=function(){this.div_.style.visibility='visible'}

/*******************************************************************************
 ****  END LABELED MARKER INCLUDE  *********************************************
 ******************************************************************************/



/*******************************************************************************
 ****  BEGIN MY MAP TYPE CONTROL INCLUDE  **************************************
 ******************************************************************************/

function MyMapTypeControl() {
}

// To "subclass" the GControl, we set the prototype object to
// an instance of the GControl object
MyMapTypeControl.prototype = new GControl();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.
MyMapTypeControl.prototype.initialize = function(map) {
  var container = document.createElement("div");

  var tmapDiv = document.createElement("div");
  this.setButtonStyle_(tmapDiv);
  container.appendChild(tmapDiv);
  tmapDiv.appendChild(document.createTextNode("Karte"));
  GEvent.addDomListener(tmapDiv, "click", function() {
    map.setMapType(G_NORMAL_MAP);
    this.siblings().each(function(e){e.setStyle({color:'#999'})});
    this.setStyle({color:'#555'});
  });
  
  var tsatDiv = document.createElement("div");
  this.setButtonStyle_(tsatDiv);
  container.appendChild(tsatDiv);
  tsatDiv.appendChild(document.createTextNode("Satellit"));
  GEvent.addDomListener(tsatDiv, "click", function() {
    map.setMapType(G_SATELLITE_MAP);
    this.siblings().each(function(e){e.setStyle({color:'#999'})});
    this.setStyle({color:'#555'});
  });
  
  var thybDiv = document.createElement("div");
  this.setButtonStyle_(thybDiv);
  container.appendChild(thybDiv);
  thybDiv.appendChild(document.createTextNode("Hybrid"));
  GEvent.addDomListener(thybDiv, "click", function() {
    map.setMapType(G_HYBRID_MAP);
    this.siblings().each(function(e){e.setStyle({color:'#999'})});
    this.setStyle({color:'#555'});
  });
  
  var tterDiv = document.createElement("div");
  this.setButtonStyle_(tterDiv);
  container.appendChild(tterDiv);
  tterDiv.appendChild(document.createTextNode("Gelände"));
  GEvent.addDomListener(tterDiv, "click", function() {
    map.setMapType(G_PHYSICAL_MAP);
    this.siblings().each(function(e){e.setStyle({color:'#999'})});
    this.setStyle({color:'#555'});
  });
  tterDiv.setStyle({color:'#555'});

  map.getContainer().appendChild(container);
  return container;
}

MyMapTypeControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(0,0));
}

MyMapTypeControl.prototype.setButtonStyle_ = function(button) {
  $(button).setStyle({ color: "#999",
                    backgroundColor: "#fff",
                    fontFamily: "Verdana",
                    fontWeight: "bold",
                    fontSize: "10px",
                    padding: "1px",
                    paddingBottom: "2px",
                    textAlign: "center",
                    width: "5em",
                    cssFloat: "left",
                    cursor: "pointer"
  });
}

/*******************************************************************************
 ****  END MY MAP TYPE CONTROL INCLUDE  ****************************************
 ******************************************************************************/



var map;
var mgr;
var tooltip;
var markers = [];

var active_station;
if (typeof active_station_param != 'undefined' && active_station_param) {
	active_station = active_station_param;
}
var active_station_latlng;
var viewangle;

var line1;
var line2;

var panoramas = [ 'panoName=/images/panoramas/scottland_elie2',
                  'panoName=/images/panoramas/scottland_elie',
				  'panoName=/images/panoramas/scottland_nortberwick' ]

function getViewAngleCoords(center, fov, angle) {
    var cx = center.lat();
    var cy = center.lng();
    
    var phi1 = degrad*((-1)*angle + 90 - fov/2);
    var phi2 = degrad*((-1)*angle + 90 + fov/2);
    
    return [ center, new GLatLng(cx + Math.sin(phi1)*0.01*Math.cos(degrad*cx), cy + Math.cos(phi1)*0.01), new GLatLng(cx + Math.sin(phi2)*0.01*Math.cos(degrad*cx), cy + Math.cos(phi2)*0.01), center ];
}

function updateViewAngle() {
    if (typeof viewangle != 'undefined') map.removeOverlay(viewangle);
    if (typeof line1 != 'undefined') map.removeOverlay(line1);
    if (typeof line2 != 'undefined') map.removeOverlay(line2);
    var coords = getViewAngleCoords(active_station_latlng, window.document.pano.myidPanoGet('pano.hfov'), window.document.pano.myidPanoGet('pano.pan'));
    viewangle = new GPolygon(coords, '#0000FF', 0, 0, '#0000FF', 0.3);
    line1 = new GPolyline([coords[0], coords[1]], '#0000FF', 3, 0.5);
    line2 = new GPolyline([coords[0], coords[2]], '#0000FF', 3, 0.5);
    map.addOverlay(viewangle);
    map.addOverlay(line1);
    map.addOverlay(line2);
}

function coordCorrection(latlng) {
    old=map.fromLatLngToDivPixel(latlng);
    return map.fromDivPixelToLatLng(new GPoint(old.x, old.y));
}

var myHotspotIcon = new GIcon();
        myHotspotIcon.image = '/images/icons/wm_marker.png';
        myHotspotIcon.iconSize = new GSize(68, 68);
        myHotspotIcon.iconAnchor = new GPoint(34, 34);
        myHotspotIcon.infoWindowAnchor = new GPoint(40, 4);
		
var mySelectedHotspotIcon = new GIcon();
        mySelectedHotspotIcon.image = '/images/icons/wm_marker.png';
        mySelectedHotspotIcon.iconSize = new GSize(68, 68);
        mySelectedHotspotIcon.iconAnchor = new GPoint(34, 34);


function addHotspot(latlng, number, tooltip) {
	
    markericon = (number == 28) ? mySelectedHotspotIcon : myHotspotIcon;
	markers[number] = new LabeledMarker(latlng, { icon: markericon, clickable: true, labelOffset: new GSize(-34, -6) });
    markers[number].tooltip = tooltip;
    markers[number].station = number;

    GEvent.addListener(markers[number], "click", function() {
		if (active_station) markers[active_station].setImage('/images/icons/wm_marker.png');
		this.setImage('/images/marker/selected.png');
        map.setZoom(9);
		map.panTo(coordCorrection(this.getLatLng()));
        active_station = this.station;
		active_station_latlng = this.getLatLng();
    });

    GEvent.addListener(markers[number], "mouseover", function() {
        this.setImage('/images/icons/wm_marker.png');
        showMapTooltip(this);
    });

    GEvent.addListener(markers[number], "mouseout", function() {
        if (active_station) {
			if (this.station == active_station) {
				this.setImage('/images/icons/wm_marker.png'); // selected
			} else {
				this.setImage('/images/icons/wm_marker.png'); // normal 
			}
		}
		else {
			this.setImage('/images/icons/wm_marker.png'); // normal
		}
        $('mapTooltip').hide();
    });

    mgr.addMarker(markers[number], 1);
}

function showMapTooltip(marker) {
    $('mapTooltip').update(marker.tooltip);
	var point=map.getCurrentMapType().getProjection().fromLatLngToPixel(map.fromDivPixelToLatLng(new GPoint(0,0),true),map.getZoom());
	var offset=map.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),map.getZoom());
	var anchor=marker.getIcon().iconAnchor;
	var width=marker.getIcon().iconSize.width;
	var height=tooltip.clientHeight;
	var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(offset.x - point.x - anchor.x + width, offset.y - point.y -anchor.y -height)); 
	pos.apply(tooltip);
	new Effect.Appear(tooltip, {duration:0.3,from:0,to:1});
}

function updateContent() {
    $('content').setStyle(
        { paddingTop: 600 + 'px' }
    );
}

function load() {
	
	wm_client = new CookieJar({ expires:3600,  path: '/'});    
	mysettings = wm_client.get('mysettings');

	if (mysettings.height > 200) {
   		$('canvas').setStyle({height: mysettings.height+'px'});
   		$('content').setStyle({paddingTop: mysettings.height+50+'px'});
	} else {
		mysettings.height = 200;
	}
	
    if (GBrowserIsCompatible()) {   
        // Initialize map
		map = new GMap2($('canvas'));
        map.addMapType(G_PHYSICAL_MAP);
        map.enableContinuousZoom()
        map.setCenter(new GLatLng(54.5,20), 4);
        map.addControl(new MyMapTypeControl());
        map.addControl(new GSmallMapControl());
        map.setMapType(G_PHYSICAL_MAP);
		
        // Create and initialize tooltip
        tooltip = document.createElement('div');
        tooltip.id='mapTooltip';
        $(tooltip).addClassName('tooltip');
        map.getPane(G_MAP_FLOAT_PANE).appendChild(tooltip);
        tooltip.style.display='none';
        
        mgr = new MarkerManager(map);
		
		var points = [];

		points.push(new GLatLng(56.064004,-3.220732));				
		points.push(new GLatLng(50.771397, 6.102368));
		points.push(new GLatLng(51.84535, 12.244552));

		var route = new GPolyline(points, '#ffffff', 4, 1);

		map.addOverlay(route);

		addHotspot(new GLatLng(50.771397, 6.102368), 1, '<strong>Aachen</strong><br /> Luisenstrasse<br />Germany');
		addHotspot(new GLatLng(56.064004,-3.220732), 2, '<strong>Burntisland</strong><br />Kinghorn Road<br />Scottland');
		addHotspot(new GLatLng(51.84535, 12.244552), 3, '<strong>Dessau</strong><br />Friedrich Schneider Strasse 11<br />Germany');
        
        new Resizable('canvas', {handle:'statusbar',constraint:'vertical',min:'200',max:'800',onResize:function(resizable, event) { 
        	var height = parseInt($('canvas').getStyle('height'));
       		$('content').setStyle({paddingTop: height+50+'px'});
       		
       		settings = {height: height};
			wm_client.put('mysettings', settings);
			
        	map.checkResize();} 
        });


    } else {
		window.location.href = urls.nocompat;
	}
}
    
Event.observe(window, 'load', load);
Event.observe(window, 'unload', GUnload);

//-->