/**
 * @filename MapsUtil.js
 *
 * @description Class to get form fields for activities
 *
 * @author Steve Mitchell
 * @email steve.mitchell@garmin.com
 *
 * Copyright(c) 2010, Garmin International
 */

var MapsUtilConstants = {
	mapMarkerPaths: {
		START: '/api/activity/component/images/map-marker-start.png',
		END: '/api/activity/component/images/map-marker-end.png',
		LAP_MARKER: '/api/activity/component/details/style/images/lap-marker-',
        SHADOW_ICON: '/api/activity/component/images/shadow50.png'
    },
    LAYER_POLYLINES: "polylines",
    LAYER_MARKERS: "markers",
    LAYER_LAPS: "laps",
    MEASURE_STATUTE: 'statute',
    RADIUS_KILOMETERS: 6371,
    RADIUS_MILES: 3959,
    MAP_CONTROL_ZOOM_IN: 'map_control_zoom_in',
    MAP_CONTROL_ZOOM_OUT: 'map_control_zoom_out',
    MAP_CONTROL_ROAD: 'map_control_road',
    MAP_CONTROL_SATELLITE: 'map_control_satellite',
    MAP_CONTROL_HYBRID: 'map_control_hybrid',
    MAP_STYLE_ROAD: VEMapStyle.Road,
    MAP_STYLE_SATELLITE: VEMapStyle.Aerial,
    MAP_STYLE_HYBRID: VEMapStyle.Hybrid
};

// ---- extend Number object with methods for converting degrees/radians

/** Converts numeric degrees to radians */
if (typeof(Number.prototype.toRad) === "undefined") {
  Number.prototype.toRad = function() {
    return this * Math.PI / 180;
  }
}

/** Converts radians to numeric (signed) degrees */
if (typeof(Number.prototype.toDeg) === "undefined") {
  Number.prototype.toDeg = function() {
    return this * 180 / Math.PI;
  }
}

/**
 * Formats the significant digits of a number, using only fixed-point notation (no exponential)
 *
 * @param   {Number} precision: Number of significant digits to appear in the returned string
 * @returns {String} A string representation of number which contains precision significant digits
 */
if (typeof(Number.prototype.toPrecisionFixed) === "undefined") {
  Number.prototype.toPrecisionFixed = function(precision) {
    if (isNaN(this)) return 'NaN';
    var numb = this < 0 ? -this : this;  // can't take log of -ve number...
    var sign = this < 0 ? '-' : '';

    if (numb == 0) { n = '0.'; while (precision--) n += '0'; return n };  // can't take log of zero

    var scale = Math.ceil(Math.log(numb)*Math.LOG10E);  // no of digits before decimal
    var n = String(Math.round(numb * Math.pow(10, precision-scale)));
    if (scale > 0) {  // add trailing zeros & insert decimal as required
      l = scale - n.length;
      while (l-- > 0) n = n + '0';
      if (scale < n.length) n = n.slice(0,scale) + '.' + n.slice(scale);
    } else {          // prefix decimal and leading zeros if required
      while (scale++ < 0) n = '0' + n;
      n = '0.' + n;
    }
    return sign + n;
  }
}

/** Trims whitespace from string (q.v. blog.stevenlevithan.com/archives/faster-trim-javascript) */
if (typeof(String.prototype.trim) === "undefined") {
  String.prototype.trim = function() {
    return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  }
}

if(Garmin == null) var Garmin = {};
if(Garmin.map == null) Garmin.map = {};

function MarkerClickHandler(param) {
   this.activityId = param;
}

MarkerClickHandler.prototype.fire = function() {
    parent.window.location = '/player/' + this.activityId;
}


Garmin.map.MapsUtil = {
    /** You can pass in the config parameters like so:
     * Garmin.map.MapsUtil.initializeMap('mapId', {smallMap:true});
     * @param config.showMoreButton {Boolean} true if you want the more button, duh.
     * @param config.largeMap {Boolean} true if you want the large map, false for small.
     */
	initializeMap: function(elementId, config) {
        $(elementId).show();
        map = new VEMap(elementId);
        if(config.key) {
            map.SetCredentials(config.key);
        }
//        if (config.largeMap) {
//            map.SetDashboardSize(VEDashboardSize.Normal);
//        } else if (config.smallMap) {
//            map.SetDashboardSize(VEDashboardSize.Small);
//        } else {
//            map.SetDashboardSize(VEDashboardSize.Tiny);
//        }
        map.HideDashboard();
        var mapOptions = new VEMapOptions();
        mapOptions.EnableBirdseye = false;
        mapOptions.EnableDashboardLabels = false;
        mapOptions.EnableSearchLogo = false;
        mapOptions.EnableClickableLogo = false;
        mapOptions.UseEnhancedRoadStyle = true;
        var latLong = new VELatLong(38.8813889, -94.8188889);
        if (typeof(config.latitude) != 'undefined' && typeof(config.longitude) != 'undefined') {
            latLong = new VELatLong(config.latitude, config.longitude);
        }
        var zoom = 1;
        if (typeof(config.zoom) != 'undefined') {
            zoom = config.zoom;
        }
        map.LoadMap(
           latLong,
           zoom,
           VEMapStyle.Road,
           false,
           VEMapMode.Mode2D,
           false,
           1,
           mapOptions
        );
        if (config.distanceUnit && config.distanceUnit != null) {
            var parts = config.distanceUnit.split("_");
            if( parts[0].toLowerCase() == MapsUtilConstants.MEASURE_STATUTE ) {
                map.SetScaleBarDistanceUnit(VEDistanceUnit.Miles);
            } else {
                map.SetScaleBarDistanceUnit(VEDistanceUnit.Kilometers);
            }
        }
        var obliqueNotification = $("MSVE_obliqueNotification");
        if (obliqueNotification && obliqueNotification.style) {
           obliqueNotification.style.visibility = "hidden";
           obliqueNotification.style.display = "";
        }
        map.AttachEvent("onclick",function(event) {
            var veShape = map.GetShapeByID(event.elementID);
            if (veShape && veShape.click) {
                veShape.click.fire();
            }
        });
        map.AttachEvent("onendzoom",function(event) {
            if (map.changeZoom && map.changeZoom.fire) {
                map.changeZoom.fire();
            }
        }.bind(map));
        map.AttachEvent("onendpan",function(event) {
            if (map.endPan && map.endPan.fire) {
                map.endPan.fire();
            }
        }.bind(map));
    	return map;
	},

    zoomInHandler: function(event) {
       map.ZoomIn();
    },

    zoomOutHandler: function(event) {
       map.ZoomOut();
    },

    roadViewHandler: function(event) {
        this.changeMapStyle(MapsUtilConstants.MAP_STYLE_ROAD);
    },

    arielViewHandler: function(event) {
        this.changeMapStyle(MapsUtilConstants.MAP_STYLE_SATELLITE);
    },

    hybridViewHandler: function(event) {
       this.changeMapStyle(MapsUtilConstants.MAP_STYLE_HYBRID);
    },

     changeMapStyle: function(style) {
        var current = "current";
        var roadButton = $(MapsUtilConstants.MAP_CONTROL_ROAD);
        var satelliteButton = $(MapsUtilConstants.MAP_CONTROL_SATELLITE);
        var hybridButton = $(MapsUtilConstants.MAP_CONTROL_HYBRID);
        switch(style) {
            case MapsUtilConstants.MAP_STYLE_ROAD: {
                map.SetMapStyle(MapsUtilConstants.MAP_STYLE_ROAD);
                roadButton.addClassName(current);
                satelliteButton.removeClassName(current);
                hybridButton.removeClassName(current);
                break;
            }
            case MapsUtilConstants.MAP_STYLE_SATELLITE: {
                map.SetMapStyle(MapsUtilConstants.MAP_STYLE_SATELLITE);
                satelliteButton.addClassName(current);
                roadButton.removeClassName(current);
                hybridButton.removeClassName(current);
                break;
            }
            case MapsUtilConstants.MAP_STYLE_HYBRID: {
                map.SetMapStyle(MapsUtilConstants.MAP_STYLE_HYBRID);
                hybridButton.addClassName(current);
                roadButton.removeClassName(current);
                satelliteButton.removeClassName(current);
                break;
            }
        }
    },


    centerOnPolylineStart: function(map) {
        var layer = this.getLayer(map, MapsUtilConstants.LAYER_POLYLINES);
        if (layer) {
            for (i = 0; i < layer.GetShapeCount(); i++ )   {
                var shape = layer.GetShapeByIndex(i);
                if (shape.GetType() == VEShapeType.Polyline) {
                    var points = shape.GetPoints();
                    if (points && points.length > 0) {
                        map.SetCenter(points[0]);
                    }
                    break;
                }
            }
        }
    },

    /**
     * From http://www.movable-type.co.uk/scripts/latlong.html.
     *  Returns the distance from this point to the supplied point, in km
     * (using Haversine formula)
     *
     * from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
     *       Sky and Telescope, vol 68, no 2, 1984
     * @param latitude1 - latitude of starting point.
     * @param longitude1 - longitude of starting point.
     * @param latitude2 - latitude of ending point.
     * @param longitude2 - longitude of ending point.
     * @param  isMetric - True for kilometers, otherwise, miles
     * @return Distance between points.
     */
    calculateDistanceBetweenPoints: function(latitude1, longitude1, latitude2, longitude2, isMetric) {
      if (typeof(latitude1) == 'undefined') {
            throw "Latitude 1 is required";
        }
        if (typeof(longitude1) == 'undefined') {
            throw "Longitude 1 is required";
        }
        if (typeof(latitude2) == 'undefined') {
            throw "Latitude 2 is required";
        }
        if (typeof(longitude2) == 'undefined') {
            throw "Longitude 2 is required";
        }
        var radius = MapsUtilConstants.RADIUS_MILES
        if (isMetric) {
            radius =  MapsUtilConstants.RADIUS_KILOMETERS;
        }
        var dLat = (latitude2 - latitude1).toRad();
        var dLon = (longitude2 - longitude1).toRad();
        var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(latitude1.toRad()) * Math.cos(latitude2.toRad()) *
            Math.sin(dLon/2) * Math.sin(dLon/2);
        var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        return radius * c;
    },

    /**
     * Calculates the bearing of a line.
     * See http://www.movable-type.co.uk/scripts/latlong.html
     * @param latitude1 - latitude of starting point.
     * @param longitude1 - longitude of starting point.
     * @param latitude2 - latitude of ending point.
     * @param longitude2 - longitude of ending point.
     */
    calculateBearingOfPoints: function(latitude1, longitude1, latitude2, longitude2) {
            if (typeof(latitude1) == 'undefined') {
                throw "Latitude 1 is required";
            }
            if (typeof(longitude1) == 'undefined') {
                throw "Longitude 1 is required";
            }
            if (typeof(latitude2) == 'undefined') {
                throw "Latitude 2 is required";
            }
            if (typeof(longitude2) == 'undefined') {
                throw "Longitude 2 is required";
            }
            var lat1 = latitude1.toRad();
            var lat2 = latitude2.toRad();
            var dLon = (longitude2-longitude1).toRad();
            var y = Math.sin(dLon) * Math.cos(lat2);
            var x = Math.cos(lat1)*Math.sin(lat2) -
                  Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
            return Math.atan2(y, x).toDeg();
    },

    /**
     * Calculates a destination point a certain distance from a starting point
     * along a given bearing.
     * See http://www.movable-type.co.uk/scripts/latlong.html
     *  @param latitude - latitude of starting point in degrees.
     * @param longitude - longitude of starting point in degrees.
     * @param dist - The distance to the destination
     * @param bearing - The bearing of the destination point from the starting point in degrees.
     * @param isMetric
     * @return Array containing lat and lon of destination point.
     */
    calculateDestinationPoint: function(latitude, longitude, dist, bearing, isMetric){
        if (typeof(latitude) == 'undefined') {
            throw "Latitude 1 is required";
        }
        if (typeof(longitude) == 'undefined') {
            throw "Longitude 1 is required";
        }
        if (typeof(dist) == 'undefined') {
            throw "Distance is required";
        }
        if (typeof(bearing) == 'undefined') {
            throw "Bearing is required";
        }
        var R = MapsUtilConstants.RADIUS_MILES; // miles
        if (isMetric) {
            R = MapsUtilConstants.RADIUS_KILOMETERS; // km
        }
        var brng = bearing.toRad();
        var lat1 = latitude.toRad();
        var lon1 = longitude.toRad();

        var angularDistance = dist/R;
		var lat2 = Math.asin( Math.sin(lat1)*Math.cos(angularDistance) +
							  Math.cos(lat1)*Math.sin(angularDistance)*Math.cos(brng) );

		var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(angularDistance)*Math.cos(lat1),
									 Math.cos(angularDistance)-Math.sin(lat1)*Math.sin(lat2));

		lon2 = (lon2+3*Math.PI)%(2*Math.PI) - Math.PI; // normalise to -180...+180
		return [lat2.toDeg(), lon2.toDeg()];
    },

    /**
     * Calculates the midpoint of a line.
     * See http://www.movable-type.co.uk/scripts/latlong.html
     * @param latitude1 - latitude of starting point.
     * @param longitude1 - longitude of starting point.
     * @param latitude2 - latitude of ending point.
     * @param longitude2 - longitude of ending point.
     * @return Array containing lat and lon of line midpoint.
     */
    calculateLineMidpoint: function(latitude1, longitude1, latitude2, longitude2) {
        if (typeof(latitude1) == 'undefined') {
            throw "Latitude 1 is required";
        }
        if (typeof(longitude1) == 'undefined') {
            throw "Longitude 1 is required";
        }
        if (typeof(latitude2) == 'undefined') {
            throw "Latitude 2 is required";
        }
        if (typeof(longitude2) == 'undefined') {
            throw "Longitude 2 is required";
        }
        var lat1 = latitude1.toRad();
        var lon1 = longitude1.toRad();
        var lat2 = latitude2.toRad();
        var dLon = (longitude2 - longitude1).toRad();
       var Bx = Math.cos(lat2) * Math.cos(dLon);
        var By = Math.cos(lat2) * Math.sin(dLon);
        var lat3 = Math.atan2(Math.sin(lat1)+Math.sin(lat2),
                              Math.sqrt( (Math.cos(lat1)+Bx)*(Math.cos(lat1)+Bx) + By*By) );
        var lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx);
        return [lat3.toDeg(), lon3.toDeg()];
    },

    centerOnPolylinePoints: function(map) {
        var layer =  this.getLayer(map, MapsUtilConstants.LAYER_POLYLINES);
        if (layer) {
            for (i = 0; i < layer.GetShapeCount(); i++ )   {
                var shape = layer.GetShapeByIndex(i);
                if (shape.GetType() == VEShapeType.Polyline) {
                    var points = shape.GetPoints();
                    if (points && points.length > 0) {
                        map.SetMapView(points);
                    }
                    break;
                }
            }
        }
    },

    /**
     * This function returns a map layer. If none
     * exists then a new layer is created and added to the map.
     * @param map - The map to which the layer will be added
     * @param layerName - The title of the layer to look for.
     */
    getLayer: function(map, layerName) {
        var layer = null;
        for(var i = 0; i < map.GetShapeLayerCount(); i++) {
            var nextLayer = map.GetShapeLayerByIndex(i);
            if (nextLayer.GetTitle() == layerName) {
               layer = nextLayer;
               break;
            }
        }
        if (layer == null) {
            layer = new VEShapeLayer();
            layer.SetTitle(layerName);
            map.AddShapeLayer(layer);
        }
        return layer;
    },

    /**
     * This function is from Google's polyline utility.
     * @param encodedSamples - The encodedSamples attribute from GPolyline
     */
    decodeLine: function (encodedSamples) {

      var index = 0;
      var decodedSamples = [];
      var lat = 0;
      var lng = 0;
      if (encodedSamples && encodedSamples != undefined) {
          var len = encodedSamples.length;
          while (index < len) {
            var charVal;
            var offset = 0;
            var value = 0;
            do {
              charVal = encodedSamples.charCodeAt(index++) - 63;
              value |= (charVal & 0x1f) << offset;
              offset += 5;
            } while (charVal >= 0x20);
            var decodedLat = ((value & 1) ? ~(value >> 1) : (value >> 1));
            lat += decodedLat;

            offset = 0;
            value = 0;
            do {
              charVal = encodedSamples.charCodeAt(index++) - 63;
              value |= (charVal & 0x1f) << offset;
              offset += 5;
            } while (charVal >= 0x20);
            var decodedLng = ((value & 1) ? ~(value >> 1) : (value >> 1));
            lng += decodedLng;

            decodedSamples.push([lat * 1e-5, lng * 1e-5]);
          }
      }
      return decodedSamples;
    },

	/*
    getEarthInstanceCB: function(pluginInstance) {
      Garmin.map.MapsUtil.ge = pluginInstance;
    },
	*/

    addEncodedPolylineToMap: function(map, polylineJson, details) {
        var layer = this.getLayer(map, MapsUtilConstants.LAYER_POLYLINES);
        if(layer.IsVisible()) {
           layer.Hide();
        }
        if (layer.GetShapeCount() > 0) {
            layer.DeleteAllShapes();
        }
        var p = polylineJson.gPolyline;
        if (p) {
            var activityId = p.activityId;
            if(p.numberOfPoints > 0) {
                var bottomLeft = new VELatLong(p.minLat, p.minLon);
                var topRight = new VELatLong( p.maxLat, p.maxLon);
                var bottomRight = new VELatLong(bottomLeft.Latitude, topRight.Longitude);
                var topLeft = new VELatLong(topRight.Latitude, bottomLeft.Longitude);
                var rec = new VELatLongRectangle(topLeft, bottomRight, topRight, bottomLeft);
                map.SetMapView(rec);
               var points = [];
               var array = this.decodeLine(p.encodedSamples);
               for (i = 0; i < array.length; i++) {
                   var latlng = array[i];
                   if (latlng && latlng.length > 1) {
                       points.push(new VELatLong(latlng[0], latlng[1]));
                   }
               }
                this.polyline = new VEShape(VEShapeType.Polyline, points);
                var vecolor = new VEColor(255, 0, 0, 0.8);
                this.polyline.SetLineColor(vecolor);
                this.polyline.SetLineWidth(2);
                this.polyline.HideIcon();//hide the icon VE automatically displays
                layer.AddShape(this.polyline);
                layer.Show();
                layer = this.getLayer(map, MapsUtilConstants.LAYER_MARKERS);
                if(layer.IsVisible())
                {
                   layer.Hide();
                }
                if (layer.GetShapeCount() > 0) {
                    layer.DeleteAllShapes();
                }
                this.endMarker = this.getEndMarker(new VELatLong(p.endLat, p.endLon));
                layer.AddShape(this.endMarker);
                this.startMarker = this.getStartMarker(new VELatLong(p.startLat, p.startLon), activityId);
                layer.AddShape(this.startMarker);
                layer.Show();
            }else if(details != null){
                var emptyMap = $('noMapDataContainer');
                $('noMapDataContainer').show();
                $('activityMapContainer').hide();
            }
        }
    },

    populateLapMarkerLayer: function(map, lapMarkerJson){
        if (lapMarkerJson && lapMarkerJson != undefined) {
            var layer = this.getLayer(map, MapsUtilConstants.LAYER_LAPS);
            if(layer.IsVisible()) {
               layer.Hide();
            }
            if (layer.GetShapeCount() > 0) {
                layer.DeleteAllShapes();
            }
            for (var i = 0; i < lapMarkerJson.length; i++){
                for (var lapData in lapMarkerJson[i]){
                    for (var lapMarker in lapMarkerJson[i]){
                        var lapData = lapMarkerJson[i][lapMarker];
                        if (lapData && lapData != undefined) {
                            var marker = this.getMarker(new VELatLong(lapData.lat, lapData.long),{
                                    icon: MapsUtilConstants.mapMarkerPaths.LAP_MARKER+lapMarker+".png",
                                    iconSize: [18, 28],
                                    iconShadow: MapsUtilConstants.mapMarkerPaths.SHADOW_ICON,
                                    iconShadowSize: [30, 27],
                                    iconAnchor: [-2, 22],
                                    hover: true
                            });
                            layer.AddShape(marker);
                        }
                    }
                }
            }
        }
    },

    getMarker: function(latLong, config) {
        var marker = new VEShape(VEShapeType.Pushpin, latLong);
        marker.SetTitle(config.infoBubble);
        if(typeof(config.icon) != 'undefined') {
            var customIcon = new VECustomIconSpecification();
            var markerOffsetx = 0;
            var markerOffsety = 0;
            var shadowOffsetx = 0;
            var shadowOffsety = 0;
            if (typeof(config.iconAnchor) != 'undefined') {
                markerOffsetx = -config.iconAnchor[0];
                markerOffsety = -config.iconAnchor[1];
            } else if (typeof(config.iconSize) != 'undefined'){
                markerOffsetx = -config.iconSize[0]/2;
                markerOffsety = -config.iconSize[1]/2;
            }
            if (typeof(config.iconShadow) != 'undefined') {
                if (config.iconAnchor) {
                    shadowOffsetx = -config.iconAnchor[0];
                    shadowOffsety = -config.iconAnchor[1];
                } else if (config.iconShadowSize){
                    shadowOffsetx = -config.iconShadowSize[0]/2;
                    shadowOffsety = -config.iconShadowSize[1]/2;
                }
            }
            var cursorType = "auto";
            if (typeof(config.cursor) != 'undefined') {
                cursorType =  config.cursor;
            }
            var customHTML = [];
            customHTML.push("<div style='cursor:" + cursorType + "' ");
            if (typeof(config.iconClasses) != 'undefined') {
               customHTML.push("class='" + config.iconClasses + "' ");
            }
            if (typeof(config.title) != 'undefined') {
               customHTML.push("title='" + config.title + "' ");
            }

            customHTML.push(">");
            if (typeof(config.iconShadow) != 'undefined') {
                customHTML.push(" <div style='position:absolute;left:" + shadowOffsetx + "px;top:" +  shadowOffsety + "px;'>");
                customHTML.push("<img src='");
                customHTML.push(config.iconShadow);
                customHTML.push("' border='0' />");
                customHTML.push("</div>");
            }
            customHTML.push(" <div style='position:absolute;left:" + markerOffsetx + "px;top:" +  markerOffsety + "px;'>");
            customHTML.push("<img src='");
            customHTML.push(config.icon);
            customHTML.push("' id='");
            customHTML.push(config.marker);
            customHTML.push("' ");
            if (typeof(config.opacity) != 'undefined') {
                customHTML.push("' style='");
                customHTML.push("opacity:");
                customHTML.push(config.opacity);
                customHTML.push("' ");
            }
            customHTML.push(" border='0' />");
            customHTML.push("</div>");
            customHTML.push("</div>");
            customIcon.CustomHTML = customHTML.join("");
            marker.SetCustomIcon(customIcon);
        }
		return marker;
    },

    hideLapMarkersOnMap: function(map){
        var layer = this.getLayer(map, MapsUtilConstants.LAYER_LAPS);
        if(layer.IsVisible()) {
           layer.Hide();
        }
    },

    showLapMarkersOnMap: function(map, lapMarkerJson){
        var layer = this.getLayer(map, MapsUtilConstants.LAYER_LAPS);
        if(!layer.IsVisible()) {
           layer.Show();
        }
    },

    // NOTE: Had to tweak iconAnchor values for Bing. Was [9, 34] for Google.
    getStartMarker: function(vertex, activityId) {
    	var marker = this.getMarker(vertex,{
			icon: MapsUtilConstants.mapMarkerPaths.START,
			title: bundle_resource.player,
            iconShadow: MapsUtilConstants.mapMarkerPaths.SHADOW_ICON,
		    iconSize: [20, 34],
            iconShadowSize: [37, 34],
		    iconAnchor: [-3, 21],
		    infoWindowAnchor: [9, 2],
		    infoShadowAnchor: [18, 25],
            cursor: 'pointer'
		});
        marker.click = new MarkerClickHandler(activityId);
        return marker;
    },

    // NOTE: Had to tweak iconAnchor values for Bing. Was [9, 34] for Google.
   getEndMarker: function(vertex) {
	    var marker = this.getMarker(vertex, {
			icon: MapsUtilConstants.mapMarkerPaths.END,
			iconSize: [20, 34],
            iconShadow: MapsUtilConstants.mapMarkerPaths.SHADOW_ICON,
            iconShadowSize: [37, 34],
		    iconAnchor: [-3, 21],
		    infoWindowAnchor: [9, 2],
		    infoShadowAnchor: [18, 25],
			hover: true
		});
    	return marker;

    },

    /**
     * Calculates time as the quotient of distance/speed.
     * If either are zero then zero is returned.
     * @param distance - The distance traveled
     * @param speed - The speed in units per hour
     * @return distance/speed
     */
    calculateTime: function(distance, speed) {
       if (distance == 0 || speed == 0) {
           return 0;
       }
       return distance/speed;
    },

    /**
     * Calculates speed as the quotient of distance/time.
     * If either are zero then zero is returned.
     * @param distance - The distance traveled
     * @param time - The time to travel the given distance
     * @return distance/time
     */
    calculateSpeed: function(distance, time) {
       if (distance == 0 || time == 0) {
           return 0;
       }
       return distance/time;
    },

    /**
     * Adjusts the size of the map after a window resize.
     * @param width
     * @param height
     */
    resizeMap: function(width, height) {
        map.Resize(width, height);
    }
}

