// ----------------- BEGIN REQUIRED INTERFACE METHODS --------------------//
var SE_PreLoadTest_BingMap = 1;
SEMap.credentials = "AjoUbW5CQ6JkLvf5gAzmd8SgbBU7ztE2iRjLmqE2XVl0UvGNygo5or30YkMV3y4R";
SEMap.prototype.initMapControl = function () {

    var mapStyle = this.convertMapStyle(this.mapType);
    var centerLocation = null;
    if (this.centerPoint)
        centerLocation = new Microsoft.Maps.Location(this.centerPoint.latitude, this.centerPoint.longitude);
    if (this.markers.length == 1)
        centerLocation = new Microsoft.Maps.Location(this.markers[0].location.latitude, this.markers[0].location.longitude);

    var mapOptions = {
        credentials: SEMap.credentials,
        mapTypeId: mapStyle,
        center: centerLocation,
        zoom: this.zoomLevel,
        enableClickableLogo: false,
        enableSearchLogo: false,
        showDashboard: (this.controlSize != "none"),
        showMapTypeSelector: (this.controlSize == "normal" || this.controlSize == "large"),
        showScalebar: this.showScalebar,
        showMapTypeSelector: !this.hideMapTypeSelector
    }

    this.setMapBounds(mapOptions);

    if (this.birdseyeOrientation != null) {
        // appears to be ignored by the map - ms bug!
        mapOptions.heading = this.birdseyeOrientation;
    }

    this.map = new Microsoft.Maps.Map(this.$mapDiv[0], mapOptions);

    var mapFromInit = this;
    var mapid = this.clientID;
    this.$bingMapDiv = this.$mapDiv.find(".MicrosoftMap");

    $(window).scroll(function () { SEMap_OnScroll(mapid); });

    if (SE_UserAgent('safari'))
        this.$bingMapDiv.find("button").css("-webkit-transform", "");

    this.startViewChangeHandler = Microsoft.Maps.Events.addHandler(this.map, 'viewchangestart',
        function () { mapFromInit.onStartViewChange(); });
    this.endViewChangedHandler = Microsoft.Maps.Events.addThrottledHandler(this.map, 'viewchangeend',
        function () {
            if (thisMap
                && thisMap.map
                && thisMap.map.getZoom() == 20
                && thisMap.map.getImageryId().toLowerCase().indexOf("birdseye") != -1) {
                // sometimes the map will try to zoom all the way on birdseye that doesn't exist
                SEMap.Services.getBirdseyeZoomRange(thisMap.map.getCenter(),
                function (zoomRange) {
                    if (zoomRange == null)
                        thisMap.map.setView({ zoom: 17 });
                })
            }
            mapFromInit.mapTypeChanged(mapFromInit.handleChangeViewCallback()); 
        }, 200);
    this.mapTypeChangedHandler = Microsoft.Maps.Events.addHandler(this.map, 'maptypechanged',
        function () { mapFromInit.mapTypeChanged(mapFromInit.getMapType()); });
    var thisMap = this;
    Microsoft.Maps.Events.addHandler(this.map, 'tiledownloadcomplete', function () {
        if (typeof (__pageTimer) != 'undefined') __pageTimer.addTime("bingmaps7.js tiledownloadcomplete");
        thisMap.tilesLoaded = true;
    });

    this.map.entities.clear();
    this.drawLayer = new Microsoft.Maps.EntityCollection({ zIndex: 10001 });
    this.map.entities.push(this.drawLayer);

    this.pushpinLayer = new Microsoft.Maps.EntityCollection({ zIndex: 10002 });
    this.map.entities.push(this.pushpinLayer);

    
    this.geoRSSLayer = new Microsoft.Maps.EntityCollection();
    this.map.entities.push(this.geoRSSLayer);
}

SEMap.prototype.setMapBounds = function (mapOptions) {
    // size the map in the following priority
    //  - if bounds is defined - use the bounds 
    //  - if more than one markers, fit markers
    //  - if one marker - center on marker default zoom
    //  - otherwise use the provided center point and zoom
    
    if (this.markers.length > 1 || this.bounds != null) {
        if (this.mapBoundaryPoints.length > 0)
            mapOptions.bounds = this.locationRectFromSEBounds(this.getBoundsFromPoints(this.mapBoundaryPoints, 0));
        else if (this.bounds != null)
            mapOptions.bounds = this.locationRectFromSEBounds(this.bounds);
        else
            mapOptions.bounds = this.locationRectFromSEBounds(this.getMarkerBounds(.1));
        var isValidBounds = false;
        if (mapOptions.bounds) {
            var nw = mapOptions.bounds.getNorthwest();
            var se = mapOptions.bounds.getSoutheast();
            if (nw && se) {
                isValidBounds = Math.abs(nw.latitude - se.latitude) > .00001
                    && Math.abs(nw.longitude - se.longitude) > .00001;
            }
        }
        if (!isValidBounds) {
            mapOptions.zoom = 14;
            mapOptions.center = mapOptions.bounds.center;
            mapOptions.bounds = null;
        }
        else {
            mapOptions.zoom = null;
            mapOptions.center = null;
        }
    }
}

SEMap.prototype.drawLoadBoundaryPoints = function () {
    if (this.mapBoundaryPoints.length > 0) {
        this.drawEnable();
        for (i = 0; i < this.mapBoundaryPoints.length; i++) {
            var sePoint = this.mapBoundaryPoints[i];
            this._drawPoints[i] = new Microsoft.Maps.Location(sePoint.latitude, sePoint.longitude);
        }
        this._drawGeomType = SEMap.Draw.polygon;
        this.drawLoadShape();
        if (this.$drawClearButton)
            this.$drawClearButton.show();
        this.hasUnsavedBoundaries = false;
    }
}

SEMap.prototype.convertMapStyle = function (mapType) {
    var mapStyle = Microsoft.Maps.MapTypeId.auto;
    if (mapType == SEMap.MapType.aerial)
    { mapStyle = Microsoft.Maps.MapTypeId.aerial; }
    else if (mapType == SEMap.MapType.birdseye)
    { mapStyle = Microsoft.Maps.MapTypeId.birdseye; }
    else if (mapType == SEMap.MapType.road || mapType == "street")
    { mapStyle = Microsoft.Maps.MapTypeId.road; }

    return mapStyle;
}

SEMap.prototype.setMapType = function (mapType) {
    this.map.setOptions({ mapTypeId: this.convertMapStyle(mapType) });
    this.mapType = mapType;
}

SEMap.prototype.getMapType = function () {
    var mapType = SEMap.MapType.auto;
    switch (this.map.getMapTypeId()) {
        case Microsoft.Maps.MapTypeId.auto:
            mapType = SEMap.MapType.auto;
            break;
        case Microsoft.Maps.MapTypeId.birdseye:
            mapType = SEMap.MapType.birdseye;
            break;
        case Microsoft.Maps.MapTypeId.road:
            mapType = SEMap.MapType.road;
            break;
    }
    return mapType;
}

SEMap.prototype.getBounds = function () {
    if (this.map) {
        var bounds = this.map.getBounds();
        var nw = bounds.getNorthwest();
        var se = bounds.getSoutheast();

        if (nw.latitude)
            return new SEBounds(new SEPoint(nw.latitude, nw.longitude),
		            new SEPoint(se.latitude, se.longitude));
        else
            return new SEBounds(new SEPoint(), new SEPoint());
    }
    else
        return new SEBounds(new SEPoint(), new SEPoint());
}

SEMap.prototype.getCenter = function () {
    var ctr = this.map.getCenter();
    return new SEPoint(ctr.latitude, ctr.longitude);
}

SEMap.prototype.getZoomLevel = function () {
    return this.map.getZoom();
}

SEMap.prototype.getMapType = function () {
    // map the server side MapType enum
    switch (this.map.getMapTypeId()) {
        case Microsoft.Maps.MapTypeId.road:
            return "Road";
        case Microsoft.Maps.MapTypeId.auto:
            return "Default";
        case Microsoft.Maps.MapTypeId.aerial:
            return "Aerial";
        case Microsoft.Maps.MapTypeId.birdseye:
            return "Birdseye";
    }
    return "auto";
}

SEMap.prototype.setCenterZoom = function (centerPoint, zoomLevel) {
    var oldZoom = this.zoomLevel;
    this.centerPoint = centerPoint;
    this.zoomLevel = zoomLevel;

    if (SEMap.IsValidLatLong(centerPoint.latitude, centerPoint.longitude)) {
        this.map.setView({
            center: new Microsoft.Maps.Location(this.centerPoint.latitude, this.centerPoint.longitude),
            zoom: zoomLevel
        });

        if (oldZoom == zoomLevel)
            this.handleChangeViewCallback(); 
    }
}

SEMap.prototype.locationRectFromSEBounds = function (bounds) {
    return Microsoft.Maps.LocationRect.fromCorners(
        new Microsoft.Maps.Location(bounds.northWest.latitude, bounds.northWest.longitude),
        new Microsoft.Maps.Location(bounds.southEast.latitude, bounds.southEast.longitude));
}

SEMap.prototype.setBestMapView = function (bounds) {

    var viewRect = this.locationRectFromSEBounds(bounds);

    this.map.setView({ bounds: viewRect });
    this.zoomLevel = this.getZoomLevel();
    this.centerPoint = new SEPoint((bounds.northWest.latitude + bounds.southEast.latitude) / 2, 
        (bounds.northWest.longitude + bounds.southEast.longitude) / 2);
}

SEMap.prototype.panMap = function (direction, xDelta, yDelta) {
    
    var thisMap = this;
    
    var widthPx = this.map.getWidth();
    var heightPx = this.map.getHeight();
    var bounds = this.map.getBounds();
    var widthLong = bounds.getEast() - bounds.getWest();
    var heightLat = bounds.getNorth() - bounds.getSouth();
    var ctr = this.map.getCenter();
    
    if (isNaN(xDelta)) xDelta = 0;
    if (isNaN(yDelta)) yDelta = 0;
    ctr.latitude += yDelta / heightPx * heightLat;
    ctr.longitude += xDelta / widthPx * widthLong;
    this.map.setView({ center: ctr });   
}

SEMap.prototype.getXY = function (myPosition) {

    var pixelReference = Microsoft.Maps.PixelReference.viewport;
    return this.map.tryLocationToPixel(myPosition, pixelReference);
}

SEMap.prototype.loadMapDimensions = function()
{
    this.mapDimensions = {};
    this.mapDimensions.width = this.map.getWidth();
    this.mapDimensions.height = this.map.getHeight();
    this.mapDimensions.pageTop = this.map.getPageY();
    this.mapDimensions.pageLeft = this.map.getPageX();
}

SEMap.prototype.setMapTypeSelectorVisibility = function(isVisible)
{
    this.$mapDiv.find(".NavBar_typeButtonContainer").toggle(isVisible);
}

// -----------------------------------------
// ---------- begin pushpins  --------------

SEMarker.prototype.addPushpin = function () {

    var pinLocation = new Microsoft.Maps.Location(this.location.latitude, this.location.longitude);
    var selectedIcon = this.selectedIcon;
    var pin = new Microsoft.Maps.Pushpin(pinLocation, {
        icon: this.icon.src,
        height: this.icon.height,
        width: this.icon.width,
        typeName: "mapPushpin",
        zIndex: SEMap.BaseZIndex
    });
    try {
        this.map.pushpinLayer.push(pin);
    } catch (e) {
        // there is a error in vapicore.js that is throw when the pin is added - but it still seems to add the pin so ignore it
    }

    pin.marker = this;
    return pin;
}

SEMarker.prototype.addHighlightActions = function () {
    if (this.map.enableMouseOverHighlight) {
        var thisPin = this.pin;
        var thisMarker = this;
        this.mouseOverHighlightHandler = Microsoft.Maps.Events.addHandler(thisPin, 'mouseover', function () {
            thisMarker.map.highlightPushpin(thisMarker.markerIndex, true);
        });       
    }
}

SEMarker.prototype.setZIndex = function (zIndex) {    
    if (this.pin.getZIndex() != zIndex)
        this.pin.setOptions({ zIndex: zIndex });
}

SEMarker.prototype.highlightIcon = function () {
    this.setZIndex(SEMap.TooltipZIndex);
    this.pin.setOptions({ icon: this.selectedIcon.src });
}

SEMarker.prototype.unHighlightIcon = function () {
    
    this.setZIndex(SEMap.BaseZIndex);
    var icon = this.pin.getIcon();
    if (icon != this.icon.src)
        this.pin.setOptions({ icon: this.icon.src });   
}

SEMap.prototype.clearMapMarkers = function () {
    if (this.pushpinLayer) {
        for (var iPin = 0; iPin < this.pushpinLayer.length; iPin++) {
            var pin = this.pushpinLayer[iPin];
            if (pin.mouseOvemouseOverHighlightHandlerrHandler) Microsoft.Maps.Events.removeHandler(pin.mouseOverHighlightHandler);
            if (pin.mouseOutUnHighlightHandler) Microsoft.Maps.Events.removeHandler(pin.mouseOutUnHighlightHandler);
        }
        this.pushpinLayer.clear();
    }
}

// -----------------------------------------
// ---------- end pushpins  ----------------

// -----------------------------------------
// ---------- begin drawing ----------------

SEMap.prototype.drawEnable = function () {
    this._drawClear();
    this.drawLayer = new Microsoft.Maps.EntityCollection();
    this.map.entities.push(this.drawLayer);
    this.drawPolygonOptions = { fillColor: new Microsoft.Maps.Color(100, 128, 128, 128),
        strokeColor: new Microsoft.Maps.Color(254, 0, 0, 0),
        strokeThickness: 1
    };
    this.drawPolylineOptions = { strokeColor: new Microsoft.Maps.Color(254, 0, 0, 0),
        strokeThickness: 1
    };
}

SEMap.prototype.drawStart = function (geomType) {
    
    this.drawEnable();
    var thisMap = this;

    this.drawMapClickHandler = Microsoft.Maps.Events.addHandler(this.map, 'click',
        function (e) { thisMap.drawPolyMouseClick(e); });

    this.drawIsDrawing = true;
    this.drawSetCursor("crosshair")

}

SEMap.prototype.drawSetCursor = function (cursorType) {
    if (this.drawIsDrawing) {
        var thisMap = this;
        clearTimeout(this.drawCursorTimeout);
        this.$bingMapDiv.css("cursor", 'crosshair');
        // since some other things will keep trying to change it back
        this.drawCursorTimeout = setTimeout(function () { thisMap.drawSetCursor(cursorType); }, 200);
    }
}

SEMap.prototype._drawDone = function (isClear) {
    this.drawSetCursor("");
    try {
    
        Microsoft.Maps.Events.removeHandler(this.drawMapClickHandler);
        Microsoft.Maps.Events.removeHandler(this.drawPolyMouseMoveHandler);
        if (this._tempShape)
            this.drawLayer.remove(this._tempShape);
    }
    catch (err) { }

    if (this._drawPoints.length < 3) {
        this._drawPoints = new Array();
        this.mapBoundaryPoints = new Array();
    }
    else {
        if (!isClear) {
            this.drawLoadShape();
            this.drawResizeToFitSelection(this._drawPoints, .1);
        }
    }
    this._tempPoints = null;
    this._tempDistance = 0;    
}

SEMap.prototype.drawPolyMouseClick = function (e) {

    var thisMap = this;
    this._drawGeomType = this.$drawHelp.find("#rdoDrawRectangle").is(":checked")
        ? SEMap.Draw.rectangle : SEMap.Draw.polygon;
    this.$drawHelp.hide();
    this._drawCurrentLatLong = this.getMouseLatLong(e);
    if (this._drawPoints.length == 0 && this._drawGeomType != "point") {
        this.drawPolyMouseMoveHandler = Microsoft.Maps.Events.addHandler(thisMap.map, 'mousemove',
            function (e) { thisMap.drawPolyMouseMove(e) });
        this.drawAddPolygonStartPushpin();
        this.drawSetCursor("crosshair");
    }

    if (this._drawGeomType == SEMap.Draw.rectangle && (this._tempPoints && this._tempPoints.length == 2)) {
        this._drawPoints.length = 5
        this._drawPoints[0] = this._tempPoints[0];
        this._drawPoints[1] = new Microsoft.Maps.Location(this._tempPoints[0].latitude, this._tempPoints[1].longitude);
        this._drawPoints[2] = this._tempPoints[1];
        this._drawPoints[3] = new Microsoft.Maps.Location(this._tempPoints[1].latitude, this._tempPoints[0].longitude);
        this._drawPoints[4] = this._tempPoints[0];
        this.drawDone();
    }
    else if (this._drawGeomType == SEMap.Draw.rectangle
        || this.drawPolygonStartPin.getIcon().indexOf("polygon_end") == -1) {
        this._drawPoints.push(this._drawCurrentLatLong);
    }
    this.drawSetCursor(SEMap.Draw.crosshair);
}

SEMap.prototype.drawAddPolygonStartPushpin = function () {
    if (this._drawGeomType == SEMap.Draw.polygon) {
        this.drawPolygonStartPin = new Microsoft.Maps.Pushpin(this._drawCurrentLatLong, {
            icon: SEMap.Draw.polygonStartIcon.src,
            height: SEMap.Draw.polygonStartIcon.height,
            width: SEMap.Draw.polygonStartIcon.width
        });
        var anchor = this.drawPolygonStartPin.getAnchor();
        anchor.y -= this.drawPolygonStartPin.getHeight() / 2;
        this.drawPolygonStartPin.setOptions({ anchor: anchor });
        this.drawPolygonStartPin.map = this;
        this.drawLayer.push(this.drawPolygonStartPin);
        var thisMap = this;
        this.drawPolygonStartPinClickHandler = Microsoft.Maps.Events.addHandler(this.drawPolygonStartPin, 'click',
                    function () { thisMap.donePolygon(); });
        this.drawPolygonStartPinMouseOverHandler = Microsoft.Maps.Events.addHandler(this.drawPolygonStartPin, 'mouseover',
                    function () { thisMap.drawPolygonStartPin.setOptions({ icon: SEMap.Draw.polygonStartIconOver.src }); });
        this.drawPolygonStartPinMouseOutHandler = Microsoft.Maps.Events.addHandler(this.drawPolygonStartPin, 'mouseout',
                    function () { thisMap.drawPolygonStartPin.setOptions({ icon: SEMap.Draw.polygonStartIcon.src }); });
    }
}


SEMap.prototype.donePolygon = function () {
    if (this._drawPoints.length > 2) {
        this._drawPoints[this._drawPoints.length] = this._drawPoints[0];
        this.drawDone();
        Microsoft.Maps.Events.removeHandler(this.drawPolygonStartPinClickHandler);
        Microsoft.Maps.Events.removeHandler(this.drawPolygonStartPinMouseOverHandler);
        Microsoft.Maps.Events.removeHandler(this.drawPolygonStartPinMouseOutHandler);
        this.drawLayer.remove(this.drawPolygonStartPin);
        this.drawPolygonStartPin = null;
        this.drawSetCursor("");
        this.hasUnsavedMapBoundaries = true;
    }
    else {
        this.drawClear();
    }
}

SEMap.prototype.getMouseLatLong = function (e) {
    var myBingMap;
    if (e.target.tryPixelToLocation)
        myBingMap = e.target;
    else if (e.target.map)
        myBingMap = e.target.map.map;
    else if (e.target.marker)
        myBingMap = e.target.marker.map.map;

    var yOffset = $(myBingMap.getRootElement()).offset().top - myBingMap.getPageY();
    var xOffset = $(myBingMap.getRootElement()).offset().left - myBingMap.getPageX();
    if (SE_MSIEVersion() != 0 && SE_MSIEVersion() < 8) {
        // add the border
        yOffset += 2;
        xOffset += 2;
    }
     var point = new Microsoft.Maps.Point(e.getX() - xOffset, e.getY() - yOffset);
   
    return myBingMap.tryPixelToLocation(point, Microsoft.Maps.PixelReference.viewport);
}

SEMap.prototype.drawPolyMouseMove = function (e) {

    this._drawCurrentLatLong = this.getMouseLatLong(e);    
    this._tempPoints = this._drawPoints.slice(0, this._drawPoints.length);
    if (this._drawGeomType == SEMap.Draw.rectangle && this._tempPoints.length == 2) {
        this._tempPoints[1] = this._drawCurrentLatLong;
    }
    else {
        this._tempPoints.push(this._drawCurrentLatLong);
    }

    try {
        if (this._tempShape != null)
            this.drawLayer.remove(this._tempShape);
    }
    catch (err) {
        return;
    }
    
    if (this._tempPoints.length == 2) {
        if (this._drawGeomType == SEMap.Draw.polygon) {
             this._tempShape = this.drawAddShape(SEMap.Draw.polyline, this._tempPoints);
            this.drawLayer.push(this._tempShape);
        }
        else if (this._drawGeomType == SEMap.Draw.rectangle) {
            rectPoints = new Array();
            rectPoints[0] = this._tempPoints[0];
            rectPoints[1] = new Microsoft.Maps.Location(this._tempPoints[0].latitude, this._tempPoints[1].longitude);
            rectPoints[2] = this._tempPoints[1];
            rectPoints[3] = new Microsoft.Maps.Location(this._tempPoints[1].latitude, this._tempPoints[0].longitude);
            rectPoints[4] = this._tempPoints[0];
            this._tempShape = this.drawAddShape(SEMap.Draw.polygon, rectPoints);
            this.drawLayer.push(this._tempShape);
        }
    }

    if (this._tempPoints.length > 2) {
        this._tempShape = this.drawAddShape(this._drawGeomType, this._tempPoints);
        this.drawLayer.push(this._tempShape);
    }
}

SEMap.prototype.drawAddShape = function (geomType, locations) {

    var shape = {};
    if (geomType == SEMap.Draw.polygon)
        shape = new Microsoft.Maps.Polygon(locations, this.drawPolygonOptions);
    else if (geomType == SEMap.Draw.polyline)
        shape = new Microsoft.Maps.Polyline(locations, this.drawPolylineOptions);
    shape.map = this;
    return shape;
}

SEMap.prototype.drawLoadShape = function() {
    var currentShape = null;
    switch (this._drawGeomType) {
        case SEMap.Draw.polygon:
        case SEMap.Draw.rectangle:
            if (this._drawPoints.length > 2)
                currentShape = this.drawAddShape(SEMap.Draw.polygon, this._drawPoints);

            break;
        case SEMap.Draw.polyline:
            currentShape = this.drawAddShape(SEMap.Draw.polyline, this._drawPoints);
            break;
    }

    if (currentShape) {
        this.drawLayer.push(currentShape);
    }
}

SEMap.prototype.drawGetPoints = function() {
    var sePoints = new Array();
    if (this._drawPoints) {
        for (var i = 0; i < this._drawPoints.length; i++) {
            sePoints[sePoints.length] = new SEPoint(this._drawPoints[i].latitude, this._drawPoints[i].longitude);
        }
    }
    return sePoints;
}

SEMap.prototype.drawGetPointsString = function () {
    var myPoints = this.drawGetPoints();
    var ret = "";
    for (i = 0; i < myPoints.length; i++) {
        ret += (ret != "" ? "|" : "") + myPoints[i].latitude + "," + myPoints[i].longitude;
    }
    return ret;
}

SEMap.prototype._drawClear = function () {
    if (this.drawLayer)
        this.map.entities.remove(this.drawLayer);
    this._drawGeomType = "";
    this._drawPoints = new Array();
    this._drawCurrentLatLong;
    this._tempShape = null;
    this._tempPoints = null;
    this._tempDistance = 0;
    this.drawSetCursor("");
}

// -----------------------------------------
// ---------- end drawing ------------------


// -----------------------------------------
// ------------- GEORSS Services ----------
SEMap.prototype.loadGeoRSS = function (url, strokeColor, fillColor) {

    this.geoRSSLayer.clear();
    
    var polylineOptions = strokeColor
        ? strokeColor
        : { fillColor: new Microsoft.Maps.Color(156, 0, 0, 255) };

    var polygonOptions = {
        fillColor: (fillColor ? fillColor : new Microsoft.Maps.Color(156, 0, 0, 255)),
        strokeColor: polylineOptions
    };

    this.geoRSSController = new SEMap.GeoRSSController(null, polylineOptions, polygonOptions, this);
    this.geoRSSController.LoadGeoRSS(url);
}

// -----------------------------------------
// ---------- End GEORSS ------------------


// -----------------------------------------
// ------------- RESTful Services ----------
SEMap.Services = {};

SEMap.Services.getImageMetadata = function (options, callback) {

    var url = "http://dev.virtualearth.net/REST/V1/Imagery/Metadata/";
    var urlImageType = options.imageType ? options.imageType : SEMap.MapType.birdseye;
    var urlLocations = options.location.latitude + "," + options.location.longitude;
    var zoom = options.zoom ? options.zoom : 17;
    url += urlImageType + "/" + urlLocations 

    $.ajax(url, {
        dataType: 'jsonp',
        jsonp: 'jsonp',
        data: { z1: zoom, key: SEMap.credentials, o: 'json' },
        success: function (jsonData, textStatus, jqXHR) { callback(jsonData) },
        error: function (jqXHR, textStatus, errorThrown) { }//don't show an error if we can't talk to Microsoft 
    });

}

SEMap.Services._extractBirdseyeZoomRange = function (imageMetaData) {
    if (imageMetaData.resourceSets
        && imageMetaData.resourceSets.length > 0
        && imageMetaData.resourceSets[0].resources.length > 0) {        
        return imageMetaData.resourceSets[0].resources[0];
    }
    else
        return null;
}

SEMap.Services.getBirdseyeZoomRange = function(location, callback)
{
SEMap.Services.getImageMetadata({ location: location },
    function (imageMetadata) {
        callback(SEMap.Services._extractBirdseyeZoomRange(imageMetadata));
    })
}

SEMap.RouteLineThickness = 8;
SEMap.RouteLineColor = null
SEMap.prototype.getRoute = function (startPoint, endPoint, callback, padding) {
    if (!SEMap.RouteLineColor)
        SEMap.RouteLineColor = new Microsoft.Maps.Color(130, 0, 183, 255);

    var urlRoute = "http://dev.virtualearth.net/REST/v1/Routes?"
        + "wp.0=" + startPoint
        + "&wp.1=" + endPoint
        + "&routePathOutput=Points"
        + "&du=mi";
    var thisMap = this;
    $.ajax(urlRoute, {
        dataType: 'jsonp',
        jsonp: 'jsonp',
        data: { key: SEMap.credentials, o: 'json' },
        success: function (jsonData, textStatus, jqXHR) { thisMap.routeCallback(jsonData, callback, padding) },
        error: function (jqXHR, textStatus, errorThrown) { } //don't show an error if we can't talk to Microsoft 
    });
    this.clearRoute();
}

SEMap.prototype.clearRoute = function () {
    if (this.routeshape)
        this.map.entities.remove(this.routeshape);

}

SEMap.prototype.routeCallback = function (result, callback, padding) {
    if (result &&
        result.resourceSets &&
        result.resourceSets.length > 0 &&
        result.resourceSets[0].resources &&
        result.resourceSets[0].resources.length > 0) {
        this.drawRoute(result.resourceSets[0].resources[0], padding);
        callback(this.getItinerary(result.resourceSets[0].resources[0]));
    }
    else if (result && result.errorDetails) {
        var sError = "We were unable to determine the location of " + (result.errorDetails.length -1) + " waypoint" + (result.errorDetails.length > 2 ? "s" : "") + ":<ul>";
        for (var i = 1; i < result.errorDetails.length; i++)
            sError += "<li>" + result.errorDetails[i] + "</li>";
        sError += "</ul>";
        callback(null, sError);
    }
}

SEMap.prototype.drawRoute = function (resource, padding) {
    // Set the map view
    var bbox = resource.bbox;
    var bounds = new SEBounds(new SEPoint(bbox[2], bbox[1]), new SEPoint(bbox[0], bbox[3]));

    this.addBoundsPadding(padding ? padding : .1, bounds.northWest, bounds.southEast);
    this.setBestMapView(bounds);

    // Draw the route
    var routeline = resource.routePath.line;
    var routepoints = new Array();
    for (var i = 0; i < routeline.coordinates.length; i++) {
        routepoints[i] = new Microsoft.Maps.Location(routeline.coordinates[i][0], routeline.coordinates[i][1]);
    }

    // Draw the route on the map    
    this.routeshape = new Microsoft.Maps.EntityCollection();
    this.pushpinLayer.clear();
    this.routeshape.push(new Microsoft.Maps.Pushpin(routepoints[routepoints.length -1], {
        icon: "/common/semapping/images/route_end.gif",
        width: 24, height: 24
    }));
    
    this.routeshape.push(new Microsoft.Maps.Pushpin(routepoints[0], {
        icon: "/common/semapping/images/route_start.gif",
        width: 24, height: 24
    }));
    this.routeshape.push(new Microsoft.Maps.Polyline(routepoints, {
        strokeColor: SEMap.RouteLineColor,
        strokeThickness: SEMap.RouteLineThickness
    }));

    this.map.entities.push(this.routeshape);
}

    SEMap.prototype.getItinerary = function(route) {
    // Unroll route
    
    if (route) {
        
        var myRoute = {};
        myRoute.travelDistance = route.travelDistance.toFixed(1) + " mi";
        var hours = Math.floor((route.travelDuration % 86400) / 3600);
        var minutes = Math.floor(((route.travelDuration % 86400) % 3600) / 60);
        myRoute.travelDuration =  (hours > 0 ? hours + " hour" + (hours > 1 ? "s " : " ") : "") 
            + minutes + " minutes ";
        myRoute.legs = [];
        var legs = route.routeLegs;
        var leg = null;
        
        for (var i = 0; i < legs.length; i++) {
            leg = legs[i]; 
            for (var j = 0; j < leg.itineraryItems.length; j++) {
                var turn = leg.itineraryItems[j];  
                var hints = "";
                myRoute.legs.push(turn.instruction.text + " (" + turn.travelDistance.toFixed(1) + " mi)");
            } 
        }
    }
    else
        myRoute.legs.push("Sorry, we couldn't find directions for the addresses you entered.");

    return myRoute;
 }

SEMap.prototype.getDirections = function (from, to, callback) {
    this.getRoute(from, to, callback)
}
// ------------- End RESTful Services ------
// -----------------------------------------

