﻿var MapViewType = { ROAD: 0, AERIAL: 1, HYBRID: 2, STREET: 3, BIRDSEYE: 4, TERRAIN: 5, BIRDSEYEHYBRID: 6, OBLIQUE: 7, SHADED: 8 },
G_STREET_TYPE = 'Z',
MapModes = { Mode2D: 0, Mode3D: 1 },
LatLong = { Lat: 0, Long: 1 },
MapEngines = { Google: 0, Bing: 1 },
DashBoardSize = { Normal: 0 },
GoogleMapViewTypes = [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.SATELLITE, google.maps.MapTypeId.HYBRID, null, google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN, google.maps.MapTypeId.ROADMAP, null, null]
, BingMapViewTypes = [], BingMapModes = []
//BingMapViewTypes = (!VEMapStyle)?[]:[VEMapStyle.Road,VEMapStyle.Aerial, VEMapStyle.Hybrid,null,VEMapStyle.BirdseyeHybrid, VEMapStyle.Oblique, VEMapStyle.Shaded]
//,BingMapModes = [VEMapMode.Mode2D || null, VEMapMode.Mode3D || null];

function Maps(options) {
    this.mapType = MapEngines.Google;
    this.earth_radius = 3963.191;
    this.map_initialised = false;
    this.sv_initialised = false;
    this.map = null;
    this.streetview = { hasStreetView: true, flashSupport: true,
        proplatlong: null, panoClient: null,
        myPano: null, myPOV: null, yaw: null
    };
    this.mapContainer = null;
    this.svContainer = null;
    this.mapTabs = null;
    this.dash = null;
    this.mapPoint = null;
    this.haltServices = false;
    this.infoWindows = [];

    this.mapOptions = {
        zoom: 5
            , mapId: '#map'
            , streetViewId: '#streetview'
			, center: [54.071032055, -2.78397635452] //uk center :)
			, mapstyle: MapViewType.ROAD
			, EnableBirdseye: false
			, enableDragging: true
			, enableInfoWindow: true
			, enableDoubleClickZoom: true
			, enableScrollWheelZoom: true
			, ShowSwitch: true // boolean to determine if the 2d&3d buttons are shown on the dashboard
			, Fixed: false // boolean value that specifies whether the map center is fixed and unable to be changed by the user
			, MapMode: MapModes.Mode2D
			, ShowDashboard: true
			, ShowFindControl: false
			, ShowMiniMap: false
			, shapeDetailUrl: '/property/gmap_shapeDetails/id/[referenceId]/' //return a json struct with ll and title
			, infoWindowUrl: '/property/gmap_descriptionTabs/ids/[referenceIds]/'	//return a value that can be used as the infoWindow
			, descriptionMaxWidth: 300 //width of infoWindow
			, DashboardSize: DashBoardSize.Normal
        /*
        * resultsView is used by poi to determine if terminal icons should appear or child icons
        * ie. One icon for Terminal containing metro and rail if resultsView = 1
        */
			, agentView: false
			, resultsView: false
			, searchUrl: ''
			, ignore: null
			, circuit: null

    };

    this.mapOptions = $.extend(this.mapOptions, options || {});
    this.additionalTypes = {
        'Attraction': { data: null, icon: 'attractionicon', inRange: false, minZoomLevel: 12, on: false, url: '1/', zoomLevel: 1 },
        'Restaurant': { data: null, icon: 'restauranticon', inRange: false, minZoomLevel: 12, on: false, url: '18/st/23/', zoomLevel: 1 },
        'School': { data: null, icon: 'schoolicon', inRange: false, minZoomLevel: 12, on: false, url: '24/', zoomLevel: 1 },
        'Transport': { data: null, icon: null, inRange: false, minZoomLevel: 12, on: false, url: '31/st/', zoomLevel: 1 }
    };
    this.localPropertiesOn = false;
}

/* --SECTION-- Init functions ----------------------------- */

Maps.prototype.InitMap = function() {
    //KP: temporary. 
    if (this.mapOptions.mapstyle == MapViewType.BIRDSEYEHYBRID) this.mapOptions.mapstyle = MapViewType.ROAD;
    this.mapContainer = $(this.mapOptions.mapId);
    if (typeof this.mapOptions.rectangle !== 'undefined') {
        var centerZoom = this.calculateView(this.mapOptions.rectangle);
        this.mapOptions.center = centerZoom.centerPoint;
        this.mapOptions.zoom = centerZoom.zoom;
    }
    if (this.mapType == MapEngines.Google) {
        this.InitGoogleMap(false);
    }

    this.initNavigation(null, true);
}

Maps.prototype.InitMapWithStreetView = function() { this.InitMap(); this.InitStreetView(); }


Maps.prototype.InitGoogleMap = function(streetViewControl) {
    if (streetViewControl != true) streetViewControl = false;
    if (this.mapContainer.length > 0) {
        if (typeof google.maps.Map != 'undefined') {
            var options = { zoom: this.mapOptions.zoom,
                center: this.getLatLng(this.mapOptions.center),
                mapTypeId: GoogleMapViewTypes[this.mapOptions.mapstyle],
                disableDefaultUI: this.mapOptions.ShowDashboard,
                streetViewControl: streetViewControl,
                addressControl: false,
                draggable: this.mapOptions.enableDragging
            };
            this.map = new google.maps.Map(this.mapContainer[0], options);

            $.each(this.additionalTypes, function(i, v) {
                v.inRange = (v.minZoomLevel < options.zoom);
                if (!v.inRange) {
                    var c = 'plot' + i + 'Control';
                    $('li.' + c).attr('class', c + 'Disabled');
                }
            });
            this.map_initialised = true;
        }

    }

}

Maps.prototype.InitStreetViewLink = function(options) {

    this.referenceId = typeof options.referenceId === 'undefined' ? '' : options.referenceId;
    this.circuit = typeof options.circuit === 'undefined' ? '' : options.circuit;
    this.lat = typeof options.lat === 'undefined' ? 0 : options.lat;
    this.lng = typeof options.lng === 'undefined' ? 0 : options.lng;
    this.panoramaLat = typeof options.panoramaLat === 'undefined' ? 0 : options.panoramaLat;
    this.panoramaLng = typeof options.panoramaLng === 'undefined' ? 0 : options.panoramaLng;
    this.linkContainerId = typeof options.linkContainerId === 'undefined' ? '' : options.linkContainerId;
    this.overlayId = typeof options.overlayId === 'undefined' ? '' : options.overlayId;
    this.panoContainerId = typeof options.panoContainerId === 'undefined' ? '' : options.panoContainerId;
    this.orientationMsgContainerId = typeof options.orientationMsgContainerId === 'undefined' ? '' : options.orientationMsgContainerId;

    this.client = null;
    this.place = null;
    var _self = this;

    this.getNearestPanoramaLatLng = function(callback) {
        // get coordinates of nearest available panorama
        // point as defined by our data for this property
        if (this.place === null) { this.place = new google.maps.LatLng(this.lat, this.lng); this.client = new google.maps.StreetViewService(); }
        this.client.getPanoramaByLocation(this.place, 50, callback);
    }
    this.getPanoramaExists = function() { this.getNearestPanoramaLatLng(this.setLocalInfoImageState); }
    // call arrow object and set image state to reflect availability of Streetview
    this.setLocalInfoImageState = function(point, status) { if ((typeof window.arrow !== 'undefined') && (status === "OK")) { window.arrow.setStreetviewOn(); } }
    this.getPanoramaLink = function() { this.getNearestPanoramaLatLng(this.setPanoramaLink); }

    this.setPanoramaLink = function(point, status) { if (point != null) { var panoramaUrl = '/' + _self.circuit + '/streetview/id/' + _self.referenceId + '/'; var streetviewLinkContainer = $('a[id$=' + _self.linkContainerId + ']', '#StreetviewTab'); if (typeof streetviewLinkContainer !== 'undefined') { jQuery(streetviewLinkContainer).click(function() { var panWin = window.open(panoramaUrl, 'GStreetviewPanorama', 'height=650,width=800,toolbar=0'); panWin.focus(); return false; }); } else { jQuery('#StreetviewTab').hide(); } } }
}


Maps.prototype.InitStreetView = function() {

    if (!this.sv_initialised) {

        if (typeof (this.mapOptions.center[LatLong.Long]) != 'undefined' &&
         typeof (this.mapOptions.center[LatLong.Lat]) != 'undefined') {
            this.streetview.proplatlng = this.getLatLng(this.mapOptions.center);
            var panoOptions = { position: this.streetview.proplatlng,
                features: { userPhotos: false },
                enableFullScreen: false,
                enableCloseButton: false,
                addressControl: false, navigationControl: false,
                navigationControlOptions: { style: google.maps.NavigationControlStyle.DEFAULT, position: google.maps.ControlPosition.LEFT }
            }

            if (this.mapType === MapEngines.Google) {

                if (this.mapContainer.length > 0) { this.svContainer = this.mapContainer; } else {
                    this.svContainer = $(this.mapOptions.streetViewId);
                }
            }

            this.streetview.myPano = new google.maps.StreetViewPanorama(this.svContainer[0], panoOptions);
            var _self = this;
            this.bind(this.streetview.myPano, "error", _self.handleNoFlash);

            this.bind(this.streetview.myPano, "pov_changed", function() { _self.handlePovChanged() })
            this.dash.pano = this.streetview.myPano;
            this.dash.dashboard.append(this.dash.getPanoZoomControls()).addClass("panoActive");

            if (typeof (this.streetview.yaw) != 'undefined') {
                yaw = parseInt(this.streetview.yaw);
                if (isNaN(yaw)) yaw = 0;
            }
            else {
                yaw = this.computeAngle(this.streetview.myPano.getPosition(), this.streetview.proplatlng);
            }

            this.streetview.myPOV = { heading: yaw, pitch: -10, zoom: 1 };
            this.streetview.myPano.setPov(this.streetview.myPOV);

                  this.sv_initialised = true;
        }
    }
    else {
        this.dash.dashboard.addClass("panoActive");
        this.streetview.myPano.setVisible(true);
    }
  
}

Maps.prototype.initNavigation = function(id, showTypeView) {

    if (typeof (showTypeView) == 'undefined') showTypeView = false;
    if (this.mapTabs && this.mapTabs.length > 0) {
        this.BindViewHandlers(this.mapTabs, showTypeView);
    }
    else {

        if (this.mapOptions.ShowDashboard) {
            if (this.mapType == MapEngines.Google) {
                this.dash = new GoogleMapDashboard(this.map, this.mapOptions.EnableBirdseye, this.streetview.myPano);
                this.dash.Init();
                this.mapTabs = this.dash.dashboard.find("#mapTabs");
                this.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.dash.CreateDashboard()[0]);
                this.mapContainer.append(this.dash.dashboard.css({ zIndex: 10, position: 'absolute', top: 0, left: 0 }));
                this.map.controls[google.maps.ControlPosition.LEFT].push(this.dash.InitPanMapControl()[0]);
                this.initNavigation(null, showTypeView);
            }
        }
        else {

            if (id && id.length > 0) {
                this.mapTabs = $(id);
                this.initNavigation(null, showTypeView);
            }
        }
    }
}


Maps.prototype.handlePovChanged = function() {
  
    var yaw = this.streetview.myPano.getPov().heading;
    this.mapTabs.siblings('#messagebox').html('You are facing ' + getOrientationOnYaw(yaw));

    // private function
    function getOrientationOnYaw(yaw) {
        if (yaw >= 337.50 || yaw <= 22.49) { return 'north'; }
        else if (yaw >= 22.50 && yaw <= 67.49) { return 'north east'; }
        else if (yaw >= 67.50 && yaw <= 112.49) { return 'east'; }
        else if (yaw >= 112.50 && yaw <= 157.49) { return 'south east'; }
        else if (yaw >= 157.50 && yaw <= 202.49) { return 'south'; }
        else if (yaw >= 202.50 && yaw <= 247.49) { return 'south west'; }
        else if (yaw >= 247.50 && yaw <= 292.49) { return 'west'; }
        else if (yaw >= 292.49 && yaw <= 337.49) { return 'north west'; }
    }
}

Maps.prototype.BindViewHandlers = function(obj, showTypeView) { 
        var _self = this;
        obj.bind("click", function(e) {
            var target = e.target, li = $(target).closest("li, div"), mapType = MapViewType.ROAD;
            if (target.tagName == "A" || target.tagName == "SPAN" || target.tagName == "INPUT") {
                switch (li[0].id) {
                    case "roadbutton": mapType = MapViewType.ROAD;
                        break;
                    case "aerialbutton": mapType = MapViewType.AERIAL;
                        if ($('#roadtoggle').find(":checkbox")[0].checked) mapType = MapViewType.HYBRID;
                        break;
                    case "roadtoggle":
                        if (target.checked) {
                            mapType = MapViewType.HYBRID;
                            li = $('#aerialbutton');
                        }
                        else {
                            mapType = MapViewType.AERIAL;
                            li = $('#aerialbutton');
                        }
                        break;
                    case "birdseyebutton": mapType = MapViewType.BIRD; break;
                    case "terrainbutton": mapType = MapViewType.TERRAIN; break;
                    case "streetviewbutton": mapType = MapViewType.STREET;
                        break;
                    // default: mapType = MapViewType.ROAD; break;   
                }

                _self.setMapType(mapType, showTypeView, li);
            }
        });
}

/* --SECTION-- Helpers ------------------------------------ */

Maps.prototype.PanoramaAvailable = function(ll, callback) {
    //This is google only, so really - it should't rely on the this keyword 
    // get coordinates of nearest available panorama
    // point as defined by our data for this property

    var client = new google.maps.StreetViewService(), place = (ll instanceof google.maps.LatLng) ? ll : new google.maps.LatLng(ll[LatLong.Lat], ll[LatLong.Long]);
    client.getPanoramaByLocation(place, 50, callback);
}


Maps.prototype.distance = function(lat1, lon1, lat2, lon2) {
    var earthRadius = 6371007; //meters
    var factor = Math.PI / 180;
    var dLat = (lat2 - lat1) * factor;
    var dLon = (lon2 - lon1) * factor;
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * factor) * Math.cos(lat2 * factor) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = earthRadius * c;

    return d;
};

Maps.prototype.calculateView = function(rectangle) {

    //default m/pixel
    var defaultScales = new Array(78271.52, 39135.76, 19567.88, 9783.94, 4891.97, 2445.98, 1222.99, 611.5, 305.75, 152.87, 76.44, 38.22, 19.11, 9.55, 4.78, 2.39, 1.19, 0.6, 0.3);

    //our bounding rectangle components
    var maxLat = rectangle.sw[LatLong.Lat];
    var minLat = rectangle.ne[LatLong.Lat];
    var maxLong = rectangle.ne[LatLong.Long];
    var minLong = rectangle.sw[LatLong.Long];

    //calculate center coordinates
    var centerLat = (maxLat + minLat) / 2, centerLong = (maxLong + minLong) / 2;
    //want to calculate the distance in m along the centers latitude between the two longitudes
    var meanDistanceX = this.distance(centerLat, minLong, centerLat, maxLong);
    //want to calculate the distance in m along the centers longitude between the two latitudes
    var meanDistanceY = this.distance(maxLat, centerLong, minLat, centerLong);

    //dimensions of the map - need to remove px or percentage and convert to int

    var mapWidth = parseFloat(this.mapContainer.width(), 10), mapHeight = parseFloat(this.mapContainer.height(), 10);

    var resolutionX = meanDistanceX / mapWidth, resolutionY = meanDistanceY / mapHeight;
    var resolution = (resolutionX > resolutionY) ? resolutionX : resolutionY;
    var m = 156543.04 * Math.cos(centerLat * Math.PI / 180);
    var logX = Math.log(m / resolution);
    var logY = Math.log(2);
    var zoom = Math.max(1, Math.min(Math.floor(logX / logY), 19));

    return { centerPoint: [centerLat, centerLong], zoom: zoom };
}

Maps.prototype.computeAngle = function(T, O) {
    var R = Math.PI / 180, J = 180 / Math.PI, K = O.lat() - T.lat(), M = O.lng() - T.lng(), yaw = Math.atan2(M * Math.cos(O.lat() * R), K) * J;
    return this.wrapAngle(yaw);
};

Maps.prototype.wrapAngle = function(B) {
    if (B >= 360) B -= 360;
    else if (B < 0) B += 360;
    return B;
};
Maps.prototype.handleNoFlash = function(errorCode) {
    if (errorCode == 603) {

        this.flash_support = false;
        this.hasStreetView = false;
        if (typeof HandleNoStreetView == 'function') {
            HandleNoStreetView.call(this);
        }
        this.setMapType(MapViewType.ROAD, true);
        return;
    }
};

Maps.prototype.setGoogleMapType = function(map_type) {
    if (this.map == null) return;
    if (GoogleMapViewTypes[map_type] != null) { this.map.setMapTypeId(GoogleMapViewTypes[map_type]); }
}

Maps.prototype.setMapType = function(map_type, showTypeView, target) {

    if (this.map == null) return;
    if (target && target.hasClass("notavailable")) return;
    this.mapOptions.mapstyle = map_type;
    if (typeof (showTypeView) == 'undefined') showTypeView = false;
    if (typeof (map_type) == "undefined" || map_type === '') { var map_type = this.mapOptions.mapstyle; }

    if (this.mapType == MapEngines.Google) { this.setGoogleMapType(map_type); }

    if (this.mapTabs && this.mapTabs.length > 0) {
        if (this.mapContainer.data("selectedTab")) {
            this.mapContainer.data("selectedTab").removeClass("selected");
        }
        else {
            this.mapContainer.find('.selected').removeClass("selected");
        }
        if (target) {
            this.mapContainer.data("selectedTab", target.addClass("selected"));
        }
    }
    (map_type == MapViewType.ROAD || map_type == MapViewType.STREET) ? $('#roadtoggle').hide() : $('#roadtoggle').show();
    if (showTypeView) {
        if (map_type == MapViewType.STREET) { this.clearInfoWindows(); this.InitStreetView(); }
        else {
            if (this.sv_initialised === true) {
                this.streetview.myPano.setVisible(false);
                this.dash.dashboard.removeClass("panoActive");
                this.dash.dashboard.find("#messagebox").empty();
            }
        }
    }
};

Maps.prototype.toggleStreetView = function() {
    var toggle = this.streetview.myPano.getVisible();
    if (toggle == false) {
        this.streetview.myPano.setVisible(true);
    } else {
        this.streetview.myPano.setVisible(false);
    }
}

Maps.prototype.GetMapView = function() {

    if (this.mapType == MapEngines.Google) {
        var b = this.map.getBounds();
        if (b) {
            var ne = b.getNorthEast(), sw = b.getSouthWest();
            return { sw: [sw.lat(), sw.lng()], ne: [ne.lat(), ne.lng()] }
        }
        else {
            //map isn't initialised yet, what to do? at the moment, cheat and use mapoptions
            return this.mapOptions.rectangle;
        }
    }
}
Maps.prototype.groupPropertyData = function(properties) {
    var letterCode = 65;
    var recordcount = properties.recordcount;
    var data = {};
    for (var i = 0; i < recordcount; i++) {
        var referenceId = properties.data.referenceid[i], d = properties.data.description[i],
        t = properties.data.title[i], lat = properties.data.latitude[i], lng = properties.data.longitude[i], 
        _d = '<div><div>' + d + '</div></div>';
       
        if (lat != '' && lng != '') {
            var c = String.fromCharCode(letterCode++), ll = [lat, lng];
            data = this.appendDataRow(data, ll, t, c, _d);
        }
    }
    return data;
}
Maps.prototype.groupLocalPropertyData = function(properties, ignore) {
    var recordcount = properties.recordcount;
    var data = {};
    for (var i = 0, pd; i < recordcount; i++) {
        pd = properties.data;
        var lat = pd.latitude[i], lng = pd.longitude[i];
        if (pd.referenceid[i] != ignore && lat != '' && lng != '') {
            var ll = [lat, lng],
            title = properties.data.title[i], //should just be the address...(??)
             bedrooms = parseInt(pd.bedrooms[i], 10),
             character = (isNaN(bedrooms) || bedrooms === 0) ? '-' : (bedrooms > 9) ? '+' : bedrooms;

            data = this.appendDataRow(data, ll, title, character, pd.description[i]);
        }
    }
    return data;
}

Maps.prototype.groupAgentData = function(agents) {
    var letterCode = 65;
    var recordcount = agents.recordcount;
    var data = {};
    for (var i = 0; i < recordcount; i++) {
        //var referenceId = agents.data.referenceid[i];
        var agentcode = agents.data.agentcode[i];
        var description = agents.data.description[i];
        var title = agents.data.title[i];
        var url = agents.data.url[i];
        var lat = agents.data.latitude[i];
        var lng = agents.data.longitude[i];

        var _description = '';
        _description += '<div>';
        _description += '<div>' + description + '</div>';
        _description += '</div>';

        if (lat != '' && lng != '') {
            //var character = i+1;
            var character = '';
            var ll = [lat, lng];
            data = this.appendAgentDataRow(data, ll, title, character, _description, url);
        }
    }
    return data;
}
Maps.prototype.appendDataRow = function(data, ll, title, character, description) {
    var llString = ll.toString(); //something like (54.00000,00.000000)

    llString = 'll_' + llString.replace(/[\(,\.\s\)-]/g, '_');
    if (typeof data[llString] === 'undefined') {
        data[llString] = {
            character: character,
            description: [],
            labels: {},
            ll: ll,
            title: title
        };
    } else {
        data[llString].character = '+'; // multiple properties
    }
    data[llString].description.push(description);
    var descriptions = data[llString].description;
    if (descriptions.length > 1) {
        data[llString].title = descriptions.length + ' Properties';
    }
    data.size = typeof data.size === 'undefined' ? 0 : data.size;
    data.size++;
    return data;
}

Maps.prototype.appendAgentDataRow = function(data, ll, title, character, description, url) {
    var llString = ll.toString(); //something like (54.00000,00.000000)
    llString = 'll_' + llString.replace(/[\(,\.\s\)-]/g, '_');
    var _title = '<a href="' + url + '">' + title + '</a>';
    var _description = description;
    if (typeof data[llString] === 'undefined') {
        data[llString] = {
            character: character,
            description: [],
            labels: {},
            ll: ll,
            title: _title,
            url: url
        }
    } else {
        data[llString].character = '+'; // multiple agents
        _description = '<br />' + _title + '<br />' + _description; // multiple agents, so set the branch title into the description
    }
    data[llString].description.push(_description);
    var descriptions = data[llString].description;
    if (descriptions.length == 2) {
        //now multiple agents so fix the title for first agent in the array
        var firstAgentDescription = data[llString].description[0];
        var firstAgentTitle = data[llString].title;
        var firstAgentNewDescription = '<br />' + firstAgentTitle + '<br />' + firstAgentDescription;
        data[llString].description[0] = firstAgentNewDescription;
    }
    if (descriptions.length > 1) {
        data[llString].title = descriptions.length + ' Branches';
    }
    data.size = typeof data.size == 'undefined' ? 0 : data.size;
    data.size++;
    return data;
}

Maps.prototype.validCoords = function(lat, lng, rect) {
    //VE, is it needed for Google
    var _ll = [lat, lng];
    if ((lat === 0) || (lng === 0)) { return false; }
    rect = rect || this.GetMapView();
    if (this.mapType == MapEngines.Google) {
        if ((lat < rect.ne[LatLong.Lat]) && (lat > rect.sw[LatLong.Lat]) &&
        (lng < rect.ne[LatLong.Long]) && (lng > rect.sw[LatLong.Long])) { return [lat, lng]; }
        else { return false; }
    }
    else {
        var _ll = (lat instanceof VELatLong) ? lat : (typeof lat == 'number' && typeof lng == 'number') ? new VELatLong(lat, lng) : null;
        if ((_ll.Latitude < rect.ne[LatLong.Lat]) && (_ll.Latitude > rect.sw[LatLong.Lat]) &&
			(_ll.Longitude > rect.ne[LatLong.Long]) && (_ll.Longitude < rect.sw[LatLong.Long])) {
            return _ll;
        }
    }
    return false;
};

Maps.prototype.DeleteShapeLayer = function(layer) {
    if (this.mapType === MapEngines.Google) {
        if (layer.length > 0) {
            $.each(layer, function(i, v) { v.setMap(null); });
            layer.length = 0;
        }
    }
    else {
        this.map.DeleteShapeLayer(layer);
    }
}


Maps.prototype.getLatLngArray = function(object) {
    if (this.mapType === MapEngines.Google) { return [object.lat(), object.lng()]; }
    else { return [object.Latitude, object.Longitude]; }
    return [0, 0];
}
Maps.prototype.getLatLng = function(ll) {
    if (this.mapType === MapEngines.Google) {
        return new google.maps.LatLng(ll[LatLong.Lat], ll[LatLong.Long]);
    }
    else {
        return new VELatLong(ll[LatLong.Lat], ll[LatLong.Long]);
    }
    return null;
}
Maps.prototype.getBounds = function(sw, ne) {
    if (this.mapType === MapEngines.Google) {

        return new google.maps.LatLngBounds(this.getLatLng(sw), this.getLatLng(ne));
    }
    else {
        return new VERectangle(this.getLatLng(ne), this.getLatLng(sw));
    }
    return null;
}
Maps.prototype.GetZoom = function() {
    var zoomLevel = this.mapOptions.zoom;
    if (this.mapType === MapEngines.Google) {
        zoomLevel = this.map.getZoom();
    }
    else {
        zoomLevel = this.map.GetZoomLevel();
    }
    return zoomLevel
}

Maps.prototype.bind = function(object, event, callback, isDomListener) {

    if (isDomListener !== true) { isDomListener = false; }
    var listener = null;
    if (this.mapType === MapEngines.Google) {
        listener = (isDomListener) ? google.maps.event.addDomListener(object, event, callback) : google.maps.event.addListener(object, event, callback)
    } else {
        switch (event) { case "dragend": event = "onendpan"; break; case "zoom_changed": event = "onendzoom"; break; default: event = "on" + event; break; }
        this.AttachEvent(event, callback)
    }
    return listener;
};

Maps.prototype.unbind = function(object, event, callback) {

    if (this.mapType === MapEngines.Google) {
        if (typeof object !== 'undefined' && object !== null) {
            google.maps.event.removeListener(object)
        }
    } else { switch (event) { case "dragend": event = "onendpan"; break; case "zoom_changed": event = "onendzoom"; break; } this.map.DetachEvent(event, callback) };
};


Maps.prototype.Clear = function() {
    if (this.mapContainer.data("defaultLabels")) {
        this.DeleteShapeLayer(this.mapContainer.data("defaultLabels"));
        this.mapContainer.data("defaultLabels", null)
    }
}

Maps.prototype.PixelToLatLong = function(xy, zoom) {
    if (this.mapType === MapEngines.Google) {
        return this.getLatLngArray(this.map.getProjection().fromPointToLatLng(this.getPixel(xy)));
    }
    else {
        return this.getLatLngArray(this.map.PixelToLatLong(this.getPixel(xy), zoom));
    }
}
Maps.prototype.getPixel = function(xy) {
    var pix = null;
    if (this.mapType === MapEngines.Google) {
        pix = new google.maps.Point(xy.x, xy.y);
    }
    else {
        pix = new VEPixel();
        pix.x = xy.x;
        pix.y = xy.y;
    }
    return pix;
}


/* --SECTION-- Plotters ----------------------------------- */

Maps.prototype.plotDefaultShape = function(ll, title, character, description, layer, url) {
    var shape = this.getDefaultShape(ll, title, character, description);
    if (typeof url !== 'undefined') {

        var listeners = [];
        if (this.mapType === MapEngines.Google) { listeners = shape.get('listeners'); }
        listeners.push(this.bind(shape.label, "click", function(e) { _self.clusterClick(url) }, true));

    }
    return shape;
};

Maps.prototype.plotInlineAgents = function() {

    if (this.mapType == MapEngines.Google) {

        var $$ = $('a', 'div.agentLister');
        var obj = { "columnlist": "agentcode,description,latitude,longitude,title,url", "data": {}, "recordcount": $$.length };
        $.each(obj.columnlist.split(','), function(i, v) { obj.data[v] = []; });
        $$.each(function(i) {
            var keyobj = {}, rel = this.rel.substr(this.rel.indexOf(':') + 1).replace(/[\{\}]/g, '').split(','), $$$ = $(this);
            $.each(rel, function(i, v) {
                var keyvalue = v.split(':');
                if (keyvalue[0] == 'code') { keyobj[keyvalue[0]] = keyvalue[1].replace(/'/g, ''); }
                else { keyobj[keyvalue[0]] = parseFloat(keyvalue[1]); }
            });
            if ((keyobj.lat !== 0) && (keyobj.lng !== 0)) {
                obj.data.agentcode.push(keyobj.code);
                obj.data.latitude.push(keyobj.lat);
                obj.data.longitude.push(keyobj.lng);
                obj.data.description.push($$$.parent().next().text());
                obj.data.title.push($$$.text());
                obj.data.url.push(this.href);
            }
        });
        this.plotAgentsSub(obj);
    }
}

Maps.prototype.plotAgentsSub = function(agents) {
    //group all the data by lat lng
    var data = this.groupAgentData(agents);
    //loop properties
    var shapeArray = [];
    for (var llKey in data) {
        if (llKey.substring(0, 3) == 'll_') {
            var agent = data[llKey];
            var title = agent.title;
            var ll = agent.ll;
            var character = agent.character;
            var description = agent.description;
            var url = agent.url;
            var shape = this.getAgentShape(ll, title, character, description, url);
            shapeArray.push(shape);
        }
    }
    if (shapeArray.length > 0) {
        if (this.mapType === MapEngines.Bing) {
            var layer = new VEShapeLayer();
            plMap.AddShapeLayer(layer);
            layer.AddShape(shapeArray);
        }
    }
};
Maps.prototype.plotAgents = function(url) {
    //do some ajax
    var callback = function(transport) {
        var agents = $.parseJSON(transport.d);
        if (agents === null) { return; }
        this.plotAgentsSub(agents);
    };
    //do some prototype.js ajax stuff here, where the callback method will be callback above.
    $.ajax({ type: 'POST', url: '/svc/mapresultsservice.asmx/GetAgentBranchResults', contentType: 'application/json; charset=utf-8',
        dataType: 'json', data: "{'searchRequest':'" + url + "'}",
        success: function(transport) { callback(transport); }
    });
};

Maps.prototype.togglePlotAdditionals = function(control) {

    var targetName = control.target.tagName.toLowerCase();
    if (('span|input').indexOf(targetName) === -1) { return; }
    var _self = window.map;
    control = $(control.target).parent(), className = control.attr('class');
    if (className === 'plotLocalPropertiesControl') {
        if (control.find(":checkbox")[0].checked) {

            _self.togglePlotLocalProperties(true);
        }
        else {

            _self.togglePlotLocalProperties(false);
        }
    }
    else {
        var type = /^plot(.*?)Control(Disabled|On)?$/.exec(className)[1];
        if ((type === '') || (!window.map.additionalTypes[type].inRange && !window.map.additionalTypes[type].on)) { return; }
        control.data('on', typeof control.data('on') === 'undefined' ? true : control.data('on'));

        if (control.data('on')) {
            var attach = true;
            $.each(window.map.additionalTypes, function(key, value) {
                if (value.on === true) { attach = false; return false; }
            });
            _self.additionalTypes[type].on = true;
            control.attr('class', 'plot' + type + 'ControlOn');
            _self.plotAdditionalType(type);
            
            if (attach === true) {
                _self.mapContainer.data("dragendPOI", _self.bind(_self.map, "dragend", function() { var e = { eventName: 'onendpan' }; _self.plotAdditionals.call(_self, e) }));
                _self.mapContainer.data("zoom_changedPOI", _self.bind(_self.map, "zoom_changed", function() { var e = { eventName: 'onendzoom' }; _self.plotAdditionals.call(_self, e) }));
            }

        }
        else {
            window.map.additionalTypes[type].on = false;
            control.attr('class', 'plot' + type + 'Control' + (window.map.additionalTypes[type].inRange ? '' : 'Disabled'));
            var layerId = type.toLowerCase() + 'ShapeLayer';
            var layer = (_self.mapType == MapEngines.Google) ? _self.mapContainer.data(layerId) : _self.map[layerId];

            if (typeof layer !== 'undefined') {
                _self.DeleteShapeLayer(layer);
                if (window.map.mapType == MapEngines.Google) {
                    //TODO: remove shapes
                }
                else {

                    delete window.map[layerId];
                }
            }
            var detach = true; // detach events from map if there aren't any set to redraw
            $.each(window.map.additionalTypes, function(key, value) {
                if (value.on === true) { detach = false; return false; }
            });
            if (detach) {
                if (_self.mapContainer.data('dragendPOI') && _self.mapContainer.data('dragendPOI') != null) { _self.unbind(_self.mapContainer.data("dragendPOI"), "dragend", _self.plotAdditionals); }
                if (_self.mapContainer.data('zoom_changedPOI') && _self.mapContainer.data('zoom_changedPOI') != null) { _self.unbind(_self.mapContainer.data("zoom_changedPOI"), "zoom_changed", _self.plotAdditionals); }
                _self.mapContainer.data('dragendPOI', null);
                _self.mapContainer.data('zoom_changedPOI', null);
            }
        }
        control.data('on', !control.data('on'));
    }

}

Maps.prototype.plotSearchResults = function(idList) {
    //do some ajax

    var callback = function(transport) {
        if (transport.d === null) { return; }

        //group all the data by lat lng
        var data = this.groupPropertyData(transport.d);

        //loop properties
        var shapeArray = [], llArray = [];

        for (var llKey in data) {
            if (llKey.substring(0, 3) == 'll_') {

                var property = data[llKey], title = property.title, ll = property.ll,
                character = property.character, description = property.description,
                shape = this.getDefaultShape(ll, title, character, description);
                shapeArray.push(shape);
                llArray.push(ll);

            }
        }
        if (llArray.length > 0) {
            // if (shapeArray.length == 1) { plMap.SetZoomLevel(14); }
            var ne = llArray[0],
                sw = llArray[0];

            this.unbind(this.mapContainer.data("dragend"), "dragend", function() { SearchMapControlManager.prototype.toggleMapSearchControls({ eventName: 'dragend' }) });
            this.unbind(this.mapContainer.data("zoom_changed"), "zoom_changed", function() { SearchMapControlManager.prototype.toggleMapSearchControls({ eventName: 'zoom_changed' }) });
            this.mapContainer.data("dragend", null); this.mapContainer.data("zoom_changed", null)
            if (this.mapType === MapEngines.Bing) {
                var layer = new VEShapeLayer();
                this.map.AddShapeLayer(layer);
                layer.AddShape(shapeArray);
            }

            $.each(llArray, function(i, v) {
                ne[LatLong.Lat] = Math.max(v[LatLong.Lat], ne[LatLong.Lat]);
                ne[LatLong.Long] = Math.min(v[LatLong.Long], ne[LatLong.Long]);
                sw[LatLong.Lat] = Math.min(v[LatLong.Lat], sw[LatLong.Lat]);
                sw[LatLong.Long] = Math.max(v[LatLong.Long], sw[LatLong.Long]);

            });

            if (shapeArray.length === 1) {
                this.SetCenterAndZoom(ne, 14);
            }
            else {
                this.SetView({ sw: sw, ne: ne });
            }
             }
    };
    var _self = this;
    //do some prototype.js ajax stuff here, where the callback method will be callback above.
    $.ajax({ type: 'POST', url: '/svc/mapresultsservice.asmx/GetPropertyResults', contentType: 'application/json; charset=utf-8',
        dataType: 'json', data: '{"propertyIdList":"' + idList + '"}',
        success: function(transport) { callback.call(_self, transport); }
    });
}
Maps.prototype.SetCenterAndZoom = function(point, zoom) {
    if (this.mapType === MapEngines.Google) {
        this.map.setCenter(this.getLatLng(point)); this.map.setZoom(zoom);
    }
    else {
        this.map.SetCenterAndZoom(this.getLatLng(point), zoom);
    }
}
Maps.prototype.SetView = function(rectangle) {
    if (this.mapType === MapEngines.Google) { this.map.panToBounds(this.getBounds(rectangle.sw, rectangle.ne)) }
    else { this.map.SetMapView(this.getBounds(rectangle.sw, rectangle.ne)); }
}

Maps.prototype.plotAdditionals = function(e) {

    var currZoomLevel = this.GetZoom(), plType, clear = (e.eventName === 'onendpan');

    for (var type in this.additionalTypes) {
        plType = this.additionalTypes[type];
        if (plType.on === true) {

            if (!clear) { // are we in zoom range?
                clear = !plType.inRange;
            }
            if (clear) {
                this.additionalTypes[type].data = null;
                if (e.eventName === 'onendzoom') { clear = false; } // did we zoom out?
            }
            this.plotAdditionalType(type);

        }
    }

}
Maps.prototype.plotAdditionalType = function(type) {

    if (type === '') { return; }
    if (this.haltServices) { this.haltServices = false; return; }
    var minZoomLevel = this.additionalTypes[type].minZoomLevel, currZoomLevel = this.GetZoom();
    var _self = this;
    var callback = function(transport, save) {
        if (save) {
            _self.additionalTypes[type].data = transport;
            _self.additionalTypes[type].zoomLevel = currZoomLevel;
        }
        var pois = transport.d;

        if ((pois === null) || ((typeof pois.recordcount !== 'undefined') && (pois.recordcount == 0))) {
            return;
        }
        var shapeArray = [];
        var iconType = _self.additionalTypes[type].icon;
        var icon = _self.buildPoiIcon(iconType);

        for (var p = 0, pl = pois.recordcount; p < pl; p++) {
            var ll = _self.validCoords(pois.data.latitude[p], pois.data.longitude[p], rectangle);
            if (ll != false) {
                var description = [pois.data.description[p]], title = pois.data.title[p];
                if (type == 'Transport') { // remake the icon for the transport subtype
                    iconType = pois.data.subtype[p].toLowerCase().replace(/\s/g, '_') + 'icon';
                    icon = _self.buildPoiIcon(iconType);
                }

                var shape = _self.getShape(ll, title, icon, description);
                if (type == 'Transport') { shape.subtypeid = pois.data.subtypeid[p]; }
                shapeArray.push(shape);
            }
        }

        var layerId = type.toLowerCase() + 'ShapeLayer';
        if (this.mapType == MapEngines.Bing) {

            if (shapeArray.length > 0) {
                if (typeof this.map[layerId] !== 'undefined') {
                    _self.map.DeleteShapeLayer(plMap[layerId]); delete plMap[layerId];
                }
                _self.map[layerId] = new VEShapeLayer();
                _self.map.AddShapeLayer(this.map[layerId]);

                if (type == 'Transport') {
                    for (var s = 0, sl = shapeArray.length; s < sl; ++s) {
                        shape = shapeArray[s];
                        switch (shape.subtypeid) {
                            case 35:
                            case 36:
                            case 37: shape.SetMaxZoomLevel(14); shapeArray[s] = shape; break;
                        }
                    }
                }
                _self.map[layerId].AddShape(shapeArray);
            }
        }
        else {
            if (_self.mapContainer.data(layerId) && _self.mapContainer.data(layerId).length > 0) {
                _self.DeleteShapeLayer(_self.mapContainer.data(layerId));
                _self.mapContainer.data(layerId, null)
            }

            _self.mapContainer.data(layerId, shapeArray)

        }
    };

    var rvURL = '', subTypes = '32,33,34,35,36,37', rectangle = null, style = MapViewType.ROAD;


    if (this.mapType === MapEngines.Google) {

        rectangle = this.GetMapView();
    }
    else {
        //TODO: correct for VE (make generic)
        //check for birdseye
        rectangle = (style == BingMapViewTypes[MapViewType.BIRDSEYE] || style == BingMapViewTypes[MapViewType.BIRDSEYEHYBRID]) ?
			this.map.GetBirdseyeScene().GetBoundingRectangle() : this.map.GetMapView();
    }
    var nw = rectangle.ne, se = rectangle.sw;


    // if getMapView failed to get the map boundaries then use PixelToLatLong
    if ((typeof se[LatLong.Lat] === 'undefined') || (se[LatLong.Lat] === null) || (typeof nw[LatLong.Lat] === 'undefined') || (nw[LatLong.Lat] === null) ||
			(typeof se[LatLong.Long] === 'undefined') || (se[LatLong.Long] === null) || (typeof nw[LatLong.Long] === 'undefined') || (nw[LatLong.Long] === null)) {
        var mapContainer = this.mapContainer;
        if (this.mapType === MapEngines.Bing) {
            var point = this.map.PixelToLatLong(new VEPixel(0, 0), this.map.GetZoomLevel());
            nw = [point.Latitude, point.Longitude]
            point = this.map.PixelToLatLong(new VEPixel(mapContainer.width(), mapContainer.height()), this.map.GetZoomLevel());
            se = [point.Latitude, point.Longitude]
            rectangle = { sw: se, ne: nw };
        }
        else {
            var point;
            //TODO: do a google equivalent.
        }
    }

    var max_lat = nw[LatLong.Lat], max_lng = se[LatLong.Long];
    var min_lat = se[LatLong.Lat], min_lng = nw[LatLong.Long];

    var url = '/' + this.mapOptions.circuit + '/poisearch/nt/' + min_lat + '/xt/' + max_lat + '/ng/' + min_lng + '/xg/' + max_lng + '/t/' + this.additionalTypes[type].url;
    if (type == 'Transport') {
        if (typeof this.mapOptions.resultsView !== 'undefined' && this.mapOptions.resultsView === true) {
            rvURL = 'rv/1/';
            subTypes = '32,33,34,35,36,37';
        }
        url += subTypes + '/' + rvURL;
    }

    if (this.additionalTypes[type].data !== null) { // use existing data instead of another AJAX call
        callback(this.additionalTypes[type].data, false); return;
    }

    if (currZoomLevel > minZoomLevel) {
        $.ajax({ type: 'POST', url: '/svc/mapresultsservice.asmx/Get' + type + 'Results', contentType: 'application/json; charset=utf-8',
             dataType: 'json', data: '{"neLatitude":' + se[LatLong.Lat] + ', "neLongitude":' + se[LatLong.Long] + ',"swLatitude":' + nw[LatLong.Lat] + ', "swLongitude":' + nw[LatLong.Long] + '}',
            success: function(transport) { callback(transport, true); }
        });
    }
}

Maps.prototype.plotPrimaryShape = function(ll, title, character, layer) {
    var icon = '/content/images/maps/primary.gif';
    this.plotShape(ll, "", icon, null); 
    //info window removed as part of plnet-707
}

Maps.prototype.plotBrowseClusterShape = function(ll, title, place, description, url, layer, iconType) {
    //TODO: bing?
    if (typeof ll.lat !== "undefined") { ll = [ll.lat, ll.lng]; }
    var shape = this.getBrowseClusterShape(ll, title, place, description, url, iconType);
    return shape;
};
Maps.prototype.plotPrimaryClusterShape = function(ll, title, place, description, layer) {
    if (typeof ll.lat !== "undefined") { ll = [ll.lat, ll.lng]; }
    var shape = this.getPrimaryClusterShape(ll, title, place, description);
    return shape;
};

/* --SUBSECTION-- Build Icons ----------------------------- */

Maps.prototype.buildPoiIcon = function(type) {
    if (this.mapType === MapEngines.Google) {
        var icon;

        switch (type) {
            case null:
            case 'metroicon':
            case 'metro_and_railicon':
            case 'railicon':
                icon = 'rail';
                break;
            case 'ferriesicon':
                icon = 'boat';
                break;
            case 'airportsicon':
                icon = 'airport';
            case 'busicon':
                icon = 'bus';
            case 'schoolicon': icon = 'school1'; break;
            case 'restauranticon': icon = 'food'; break;
            case 'attractionicon': icon = 'attractions'; break;
            default:
                icon = 'rail.png';
                break;
        }
        return '/content/images/maps/' + icon + '.png';
    } else {
        return '<div class="' + type + '">&nbsp;<' + '/div>';
    }

}

Maps.prototype.buildBrowseClusterIcon = function(place, iconType) {
    return ['<span class="browse',
			(iconType == 'background' ? 'related' : ''),
			'clustericon">',
			(place || ''),
			'</span>']
		.join('');
}

Maps.prototype.buildPrimaryClusterIcon = function(place) {
    return '<span class="primaryclustericon">' + (place || '') + '</span>';
}

Maps.prototype.buildDefaultIcon = function(character) { return '<div class="defaulticon">' + (character || '') + '</div>'; }

/* --SUBSECTION-- Shape Getters --------------------------- */

Maps.prototype.getDefaultShape = function(ll, title, character, description) {
    var icon = this.buildDefaultIcon(character);

    var shape = this.getLabel(ll, title, icon, description);
    return shape;
}
Maps.prototype.getAgentShape = function(ll, title, character, description, url) {
    var icon = this.buildDefaultIcon(character);
    var shape = this.getLabel(ll, title, icon, description);
    if (this.mapType === MapEngines.Google) { listeners = shape.get('listeners'); }
    listeners.push(this.bind(shape.label, "click", function(e) { _self.clusterClick(url) }, true));
    listeners.push(this.bind(shape.label, "doubleclick", function(e) { _self.clusterClick(url) }, true));
    return shape;
};

Maps.prototype.getBrowseClusterShape = function(ll, title, place, description, url, iconType) {
    var icon = this.buildBrowseClusterIcon(place, iconType);

    var _description = [];
    if (description && description.length > 0) _description = ['<div class="browseclusterdescription">' + (description || '') + '</div>'];
    var shape = this.getLabel(ll, place, icon, _description), _self = this, listeners = [];
    if (this.mapType === MapEngines.Google) { listeners = shape.get('listeners'); }
    listeners.push(this.bind(shape.label, "click", function(e) { _self.clusterClick(url) }, true));
    listeners.push(this.bind(shape.label, "doubleclick", function(e) { _self.clusterClick(url) }, true));

    return shape;

};

Maps.prototype.getPrimaryClusterShape = function(ll, title, place, description, url) {
    var icon = this.buildPrimaryClusterIcon(place);
    var _description = ['<span class="browseclusterdescription">' + (description || '') + '</span>'];
    var shape = this.getLabel(ll, place, icon, _description), _self = this, listeners = [];
    if (this.mapType === MapEngines.Google) { listeners = shape.get('listeners'); }
    listeners.push(this.bind(shape.label, "click", function(e) { _self.clusterClick(url) }, true));
    listeners.push(this.bind(shape.label, "doubleclick", function(e) { _self.clusterClick(url) }, true));
    return shape;
};

Maps.prototype.clusterClick = function(url) {
    if (typeof url != 'undefined') { location.href = url; }
}

Maps.prototype.getLabel = function(ll, title, icon, description) {

    var shape = new GoogleLabel({ map: this.map, text: icon, title: title, position: this.getLatLng(ll) }); //this.getShape(ll, title, icon, _description);

    var _description = '', offset = 35;

    if (description && description.length > 0) { // add the description
        if (typeof description === 'string') { description = [description]; }
        _description = ['<div class="',
				'infowindow' + (description.length > 1 ? ' multi_infowindow' : ''),
				'">',
				description.join(''),
				'</div>'];
        _description = _description.join('');
    }


    if (_description.length > 0) {
        var listeners = shape.get('listeners'), _self = this;
        listeners.push(this.bind(shape.label, "mouseover", function() {

            _self.addInfoWindow({ latlng: shape.get('position'), map: shape.get('map'), body: _description,
                title: title, offsetVertical: shape.label.offsetHeight, offsetHorizontal: $(shape.label).width(), disableAutoPan: true
            }, true);

        }, true));
    }

    return shape;
}

Maps.prototype.clearInfoWindows = function() {

    if (this.mapType == MapEngines.Google) {
        if (this.infoWindows.length > 0) {
            $.each(this.infoWindows, function(i, v) {
                v.setMap(null);
            });
            this.infoWindows = [];
        }
        this.unbind(this.mapContainer.data("center_changedInfoWindow"));
        this.mapContainer.data("center_changedInfoWindow", null)
    }
}

Maps.prototype.addInfoWindow = function(labelOpts, clear) {
    if (this.mapType == MapEngines.Google) {
        if (clear) {
            if (this.infoWindows.length > 0) {
                this.clearInfoWindows();

            }

        }
        var info = new InfoBox(labelOpts)
        this.infoWindows.push(info);
        if (this.infoWindows.length == 1) {
            var _self = this;
            this.mapContainer.data("center_changedInfoWindow", this.bind(this.map, "center_changed", function() { _self.clearInfoWindows(); }, false));
        }
    }
}


Maps.prototype.getShape = function(ll, title, icon, description) {
    var shape;

    if (this.mapType == MapEngines.Google) {
        var point = this.getLatLng(ll);
        shape = new google.maps.Marker({
            position: point,
            map: this.map
             , icon: icon
        }
        );
        var _description = '';
        if (description) { // add the description
            if (typeof description === 'string') { description = [description]; }
            _description = ['<div class="',
				'infowindow' + (description.length > 1 ? ' multi_infowindow' : ''),
				'">',
				description.join(''),
				'</div>'];
            _description = _description.join('');
        }
            if ((title && title.length > 0) || _description.length > 0) {
            var listeners = [];
            var _self = this;
            listeners.push(this.bind(shape, "mouseover", function() {

            _self.addInfoWindow({ latlng: shape.getPosition(), map: this.map, title: title, body: _description, disableAutoPan: true, offsetHorizontal: 14, offsetHorizontalRight: 14 }, true)

            }));

        }
    }
    else {
        shape = new VEShape(VEShapeType.Pushpin, ll);
        shape.SetCustomIcon(icon); // Set the icon
        shape.SetTitle(title); // Set the title
        if (description !== null) { // add the description
            if (typeof description === 'string') { description = [description]; }
            var _description = ['<div class="',
				'infowindow' + (description.length > 1 ? ' multi_infowindow' : ''),
				'">',
				description.join(''),
				'</div>'];
            shape.SetDescription(_description.join(''));
        }
    }
    return shape;
}


Maps.prototype.plotShape = function(ll, title, icon, description) {
    var shape = null;
    if (this.mapType == MapEngines.Google) {
        shape = this.getShape(ll, title, icon, description);
    }
    else {
        //        shape = new VEShape(VEShapeType.Pushpin, ll);
        //        shape.SetCustomIcon(icon); // Set the icon
        //        shape.SetTitle(title); // Set the title
        //        if (description !== null) { // add the description
        //            if (typeof description === 'string') { description = [description]; }
        //            var _description = ['<div class="',
        //				'infowindow' + (description.length > 1 ? ' multi_infowindow' : ''),
        //				'">',
        //				description.join(''),
        //				'</div>'];
        //            shape.SetDescription(_description.join(''));
        //        }
    }
    return shape;
};

/* --SECTION-- SEARCH MAP MANAGER ------------------------- */

SearchMapControlManager = function(map, form, buttonTop, buttonBottom, searchText, inputReset) {
    _self = this;
    this.map = map;
    this.form = form;
    this.buttonTop = buttonTop;
    this.buttonBottom = buttonBottom;
    this.inputReset = inputReset;
    this.panned = false;
    this.searchText = searchText || 'searching...';
    if (typeof buttonTop !== 'undefined') { buttonTop.bind('click', _self.search); }
    if (typeof buttonBottom !== 'undefined') { buttonBottom.bind('click', _self.search); }
    this.listeners = [this.map.bind(this.map.map, "dragend", function() { _self.toggleMapSearchControls({ eventName: 'dragend' }) }), this.map.bind(this.map.map, "zoom_changed", function() { _self.toggleMapSearchControls({ eventName: 'zoom_changed' }) })]


};


SearchMapControlManager.prototype.hideMapSearchControls = function(control) {

    $.each(control ? [control] : [this.buttonTop, this.buttonBottom], function(i, v) {
        this[0].className = 'mapSearchControlDisabled'; this[0].isActive = false;
    });
};
SearchMapControlManager.prototype.showMapSearchControls = function() {

    //var _self = this;
    $.each([this.buttonTop, this.buttonBottom], function(i, v) {
        if (typeof this !== 'undefined') {

            if (!_self.panned) {

                var zoomLevel = _self.map.GetZoom(),
			    initialZoomLevel = (_self.map.mapType == MapEngines.Google) ? _self.map.mapOptions.zoom : _self.map.initialZoomLevel;

                if ((zoomLevel == initialZoomLevel) && this.hideMapSearchControls) {
                    this.hideMapSearchControls(this); return;
                }
            }
            this[0].className = 'mapSearchControl'; this[0].isActive = true;
            if ($.browser.msie6) { this.width(this.parent().width()); }
        }
    });
};

SearchMapControlManager.prototype.toggleMapSearchControls = function(e) {

    if (e.eventName === 'dragend') { this.panned = true; }
    var currZoomLevel = this.map.GetZoom(), map = this.map;

    $('ul.plotControlsList li').each(function(i, v) {
        var type = /^plot(.*?)Control(Disabled|On)?$/.exec(this.className)[1];
        map.additionalTypes[type].inRange = (map.additionalTypes[type].minZoomLevel < currZoomLevel);
        if (!map.additionalTypes[type].inRange) {
            this.className = 'plot' + type + 'Control' + (map.additionalTypes[type].on ? 'On' : 'Disabled');
        }
        else {
            this.className = 'plot' + type + 'Control' + (map.additionalTypes[type].on ? 'On' : '');
        }
    });

    if (currZoomLevel > 10) {

        if (this.showMapSearchControls) { this.showMapSearchControls(); }
    }
    else {
        if (this.hideMapSearchControls) { this.hideMapSearchControls(); }
    }

};


SearchMapControlManager.prototype.search = function(e) {
    var button = e.target;
    button.blur();
    if (arguments.callee.init || !button.isActive) { return false; }
    arguments.callee.init = true;
    _self.buttonTop.val(_self.searchText);
    _self.buttonBottom.val(_self.searchText);
    _self.setCoords();
    $('#' + _self.inputReset).val('14');
};


SearchMapControlManager.prototype.setCoords = function() {

    var mapElem = _self.map.mapContainer, rect = _self.map.GetMapView();
    var nw = rect.ne,
	se = (_self.map.mapType == MapEngines.Google) ? rect.sw : _self.map.PixelToLatLong(new VEPixel(mapElem.width() - 4, mapElem.height() - 4));
    //note BING Map version will not work! (but I don't care about that for now)

    $('input:hidden[id$=ne]').val(nw[LatLong.Lat] + ', ' + nw[LatLong.Long]);
    $('input:hidden[id$=sw]').val(se[LatLong.Lat] + ', ' + se[LatLong.Long]);
};

SearchMapControlManager.prototype.toggleHelpTip = function(button, action, helpDivId) {
    // help tip availabe if button is not active
    if (!button.isActive) {
        var helpDiv = $('#' + helpDivId);
        if (action == 'show') {
            helpDiv.show();
        }
        else { helpDiv.hide(); }
    }
}

/* DASHBOARD EVENT HANDLERS ------------------*/

GoogleMapDashboard = function(map, showStreetView, pano) {
    this.showStreetView = showStreetView;
    this.map = map;
    this.pano = pano || this.map.getStreetView();
    this.isStreetViewDash = false;
    this.dashboard = null;
    this.panoZoom = 1;
}
GoogleMapDashboard.prototype.InitStreetViewDash = function(pano) {
    this.isStreetViewDash = true;
    this.pano = pano || this.map.getStreetView();
    this.dashboard = this.CreateDashboard();
    this.dashboard.append(this.getPanoZoomControls())
    this.dashboard.append(MapDashboard.createDiv('mapTabs').append(this.getStreetViewButton(this.isStreetViewDash, true))
                                                                      .append(this.getAerialViewButton())
                                                                     .append(this.getRoadViewButton(!this.isStreetViewDash))
                                                                     .append(this.getMapRoadsToggle()));

    return this.dashboard;
}

GoogleMapDashboard.prototype.Init = function() {
    this.dashboard = this.CreateDashboard();
    this.dashboard.append(this.getZoomControls())
                            .append(MapDashboard.createDiv('mapTabs').append(this.getStreetViewButton(this.isStreetViewDash))
                                                                      .append(this.getAerialViewButton())
                                                                     .append(this.getRoadViewButton(!this.isStreetViewDash))
                                                                     .append(this.getMapRoadsToggle())

                             )
                             .append(MapDashboard.createDiv('messagebox'))
    return this.dashboard;
}


GoogleMapDashboard.prototype.InitPanMapControl = function() {

    var parent = MapDashboard.createDiv('mapPanControl');
    var control = MapDashboard.createDiv('mapPan');

    var dash = this;

    var getScrollXY = function() {
        var scrOfX = 0, scrOfY = 0;
        if (typeof (window.pageYOffset) == 'number') {
            //Netscape compliant
            scrOfY = window.pageYOffset;
            scrOfX = window.pageXOffset;
        } else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
            //DOM compliant
            scrOfY = document.body.scrollTop;
            scrOfX = document.body.scrollLeft;
        } else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
            //IE6 standards compliant mode
            scrOfY = document.documentElement.scrollTop;
            scrOfX = document.documentElement.scrollLeft;
        }
        return [scrOfX, scrOfY];
    }
    control.bind('click', function(ev) {
        if (!ev) var ev = window.event;

        var target = $(ev.target);
        var offset = target.offset();
        var width = target.width();
        var height = target.height();

        var offsets = getScrollXY();

        //get the x and y vs the middle of the control.
        var x = ev.clientX - (offset.left - offsets[0]) - width / 2;
        var y = ev.clientY - (offset.top - offsets[1]) - height / 2;

        //now compare them to get a direction to pan in 
        var absX = Math.sqrt(Math.pow(x, 2));
        var absY = Math.sqrt(Math.pow(y, 2));
        var max = absX > absY ? absX : absY;

        var ratioX = x / max; //this is the relative click weight
        var ratioY = y / max; //this is the relative click weight

        var relativeX = (absX / (width / 2)) * 100;
        var relativeY = (absY / (height / 2)) * 100;

        var panX = Math.round(ratioX * relativeX);
        var panY = Math.round(ratioY * relativeY);

        dash.map.panBy(panX, panY);
    }
	                );
    parent.append(control);

    return parent;
}


GoogleMapDashboard.prototype.updatePanel = function() {
    this.setZoomBar(this.map.getZoom());
    this.setPanoZoomBar(this.pano.getPov().zoom);
}

GoogleMapDashboard.prototype.CreateDashboard = function() {
    var dashboard = MapDashboard.createDiv('pldashboard');
    return dashboard;
}


GoogleMapDashboard.prototype.getAerialViewButton = function() {
    var aerialButton = MapDashboard.createDivWithLink('aerialbutton', '', null, "Switch to 'Aerial' view"), dash = this;
    return aerialButton
}

GoogleMapDashboard.prototype.getStreetViewButton = function(isSelected, panoAvailable) {

    if (this.showStreetView) {

        var aerialButton = MapDashboard.createDivWithLink('streetviewbutton', (isSelected) ? 'selected' : '', null, "Switch to 'street view'"), dash = this;
        if (typeof panoAvailable === 'undefined') {
            Maps.prototype.PanoramaAvailable(this.map.getCenter(), function(panoData, status) { if (status !== 'OK') { $(aerialButton).addClass('notavailable'); } });
        }
        return aerialButton;
    }
}
GoogleMapDashboard.prototype.getRoadViewButton = function(isSelected) {
    var roadButton = MapDashboard.createDivWithLink('roadbutton',(isSelected) ? 'selected':'', null, "Switch to 'Road' view"), dash = this;
    return roadButton;
}
GoogleMapDashboard.prototype.getMapRoadsCheckbox = function() {
    //create a checkbox
    var dash = this, mapRoadCheckbox = $('<input type="checkbox" id="displayRoad" name="displayRoad"  />');
    return mapRoadCheckbox;
}
GoogleMapDashboard.prototype.getMapRoadsToggle = function() {
    var dash = this, mapRoadsToggle = MapDashboard.createDiv('roadtoggle'), mapRoadCheckLabel = $("<label></label>"), mapRoadCheckbox = this.getMapRoadsCheckbox(true);
    mapRoadCheckLabel.html('Roads')
                       .attr("for", "displayRoad");
    mapRoadsToggle.hide().append(mapRoadCheckLabel.append(mapRoadCheckbox));
    return mapRoadsToggle;
}

GoogleMapDashboard.prototype.CheckBirdseyeButton = function() { }
GoogleMapDashboard.prototype.setPanoZoomBar = function(level) {
for (var i = 4; i >= 0; i--) {
    var obj = document.getElementById('zoom_' + i)
    if (obj) {
        if (i > level) {
            obj.className = 'mapZoomBars';
        } else {
            obj.className = 'mapZoomBars_selected';
        }
    }
}
 }
GoogleMapDashboard.prototype.setZoomBar = function(level) {

    for (var i = 19; i >= 1; i--) {
        var obj = document.getElementById('zoom_' + i)
        if (obj) {
            if (i > level) {
                obj.className = 'mapZoomBars';
            } else {
                obj.className = 'mapZoomBars_selected';
            }
        }
    }
}
GoogleMapDashboard.prototype.getPanoZoomControls = function() {
    var mapControls = MapDashboard.createDiv('panoZoomControls', 'mapzoomcontrols');
    var dash = this;
    mapControls.append(this.getZoomControl('in'));
    mapControls.append(this.getZoomLevelsControl('pano'));
    mapControls.append(this.getZoomControl('out'));
    google.maps.event.addListener(this.pano, "pov_changed", function(e) { if (dash.panoZoom != Math.floor(dash.pano.getPov().zoom)) { dash.panoZoom = dash.pano.getPov().zoom;  dash.setPanoZoomBar(dash.panoZoom);  } });
    return mapControls;
}

GoogleMapDashboard.prototype.getZoomControls = function() {
    var mapControls = MapDashboard.createDiv('mapZoomControls', 'mapzoomcontrols');
    mapControls.append(this.getZoomControl('in'));
    mapControls.append(this.getZoomLevelsControl('map'));
    mapControls.append(this.getZoomControl('out'));
    var dash = this;
    google.maps.event.addListener(this.map, "zoom_changed", function() { dash.CheckBirdseyeButton(); dash.setZoomBar(dash.map.getZoom()); });
    return mapControls;
}
GoogleMapDashboard.prototype.getZoomControl = function(inout) {
    var dash = this;
    var mapZoom = MapDashboard.createDivWithLink('mapzoom' + inout, null, null, 'Zoom ' + inout + ' one level');
    mapZoom.bind('click', function(e) { dash.zoomMap(e) });
    return mapZoom;
}
GoogleMapDashboard.prototype.zoomMap = function(e) {
    var obj = $(e.target).closest('div'), objParent = obj.parent('div'), zoom = 11;

    if (objParent[0].id === "mapZoomControls") {
        zoom = this.map.getZoom()
    }
    else if (objParent[0].id === "panoZoomControls") {
        zoom = this.pano.getPov().zoom;
    }

    if (obj[0].id == 'mapzoomin') {
        zoom++;
    } else {
        zoom--;
    }

    if (objParent[0].id === "mapZoomControls") {
        this.map.setZoom(zoom);
    }
    else if (objParent[0].id === "panoZoomControls") {
        if (zoom > 4) zoom = 4;
        if (zoom < 0) zoom = 0;
        var pov = this.pano.getPov();
        pov.zoom = zoom;
        this.pano.setPov(pov);
    }
}


GoogleMapDashboard.prototype.getZoomLevelsControl = function(ZoomType) {
    var zoomLevels = MapDashboard.createDiv('zoomLevels'), dash = this;

    if (ZoomType === "pano") {
        var zoom = this.panoZoom;
        zoomLevels.append(this.getZoomBar(4, zoom));
        zoomLevels.append(this.getZoomBar(3, zoom));
        zoomLevels.append(this.getZoomBar(2, zoom));
        zoomLevels.append(this.getZoomBar(1, zoom));
        zoomLevels.append(this.getZoomBar(0, zoom));
        zoomLevels.bind('click', function(e) {
            if (e.target.tagName === "A") {

                var div = $(e.target).closest("div"); if (div.length > 0) {
                    var level = parseInt(div.get(0).id.replace('zoom_', ''));
                    var pov = dash.pano.getPov();
                    if (isNaN(level)) { level = 1; }
                    if (pov.zoom != level) {
                        pov.zoom = level;
                        dash.pano.setPov(pov);
                        dash.setPanoZoomBar(level);
                    }
                }
            } return false;
        });
    }
    else {
        zoomLevels.append(this.getZoomBar(19));
        zoomLevels.append(this.getZoomBar(18));
        zoomLevels.append(this.getZoomBar(17));
        zoomLevels.append(this.getZoomBar(16));
        zoomLevels.append(this.getZoomBar(15));
        zoomLevels.append(this.getZoomBar(13));
        zoomLevels.append(this.getZoomBar(11));
        zoomLevels.bind('click', function(e) { if (e.target.tagName === "A") { var div = $(e.target).closest("div"); if (div.length > 0) { var level = parseInt(div.get(0).id.replace('zoom_', '')); if (isNaN(level)) { level = 11; } dash.map.setZoom(level); } } return false; })

    }


    return zoomLevels;
}



/*start zoom controls*/
GoogleMapDashboard.prototype.getZoomBar = function(level,panoZoom) {
    var zoom = panoZoom || this.map.getZoom(), classString = (level <= zoom) ? 'mapZoomBars_selected' : 'mapZoomBars',
    bar = MapDashboard.createDivWithLink('zoom_' + level, classString);
    return bar;
}




/*start DOM helpers*/
var MapDashboard = {
    _self: this,
    createDiv: function(id, className) {
        var div = document.createElement('div');
        div.id = id;
        
        if(className && className.length > 0)div.className = className;
        return $(div);
    },
    createDivWithLink: function(id, className, href, title) {
        var div = MapDashboard.createDiv(id);
        if (className !== null && typeof className !== 'undefined') {
            div.addClass(className);
        }

        var anchor = document.createElement('a');
        anchor.href = (href === null || typeof href === 'undefined') ? 'javascript:void(0);' : href;

        if (title !== null && typeof title !== 'undefined') {
            anchor.title = title;
        }

        div.append(anchor);
        return div;
    }
}
/*end DOM helpers*/

/* --SECTION-- MVC objects -------------------------------- */

/* An InfoBox is like an info window, but it displays
* under the marker, opens quicker, and has flexible styling.
* @param {GLatLng} latlng Point to place bar at
* @param {Map} map The map on which to display this InfoBox.
* @param {Object} opts Passes configuration options - content,
*   offsetVertical, offsetHorizontal, className, height, width
*/
function InfoBox(opts) {
    //google.maps.OverlayView.call(this);
    this.setValues(opts);

    this.latlng_ = opts.latlng;
    this.map_ = opts.map;

    this.offsetVertical_ = opts.offsetVertical + 20 || 20; // -195;

    this.offsetHorizontal_ = opts.offsetHorizontal || 5;
    this.offsetHorizontalRight_ = opts.offsetHorizontalRight || 0;
    this.height_ = 0;
    this.width_; //= opts.width || 0;
    this.title_ = opts.title;
    this.body_ = opts.body;
    this.disableAutoPan_ = opts.disableAutoPan || false;
    this.listeners = this.listeners_ = [];
    this.container = null;
    var me = this;
    this.boundsChangedListener_ =
    google.maps.event.addListener(this.map_, "bounds_changed", function() {
        return me.panMap.apply(me);
    });

    // Once the properties of this OverlayView are initialized, set its map so
    // that we can display it.  This will trigger calls to panes_changed and
    // draw.

    this.setMap(this.map_);
    
}

/* InfoBox extends GOverlay class from the Google Maps API*/
InfoBox.prototype = new google.maps.OverlayView;

InfoBox.prototype.onRemove = function() {

    if (this.div_) {

        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;

    }
    for (var i = 0, I = this.listeners_.length; i < I; i++) {
        google.maps.event.removeListener(this.listeners_[i]);
    }
}


/* Redraw the Bar based on the current projection and zoom level*/
InfoBox.prototype.draw = function() {

    // Creates the element if it doesn't exist already.
    // this.createElement();
    if (!this.div_) return;

    // Calculate the DIV coordinates of two opposite corners of our bounds to
    // get the size and position of our Bar

    var pixPosition = this.getProjection().fromLatLngToContainerPixel(this.latlng_),
    getScroll = function() {
        var xScroll, yScroll;
        if (window.pageYOffset) {
            yScroll = window.pageYOffset;
            xScroll = window.pageXOffset;
        } else if (document.documentElement && document.documentElement.scrollTop) {	 // Explorer 6 Strict
            yScroll = document.documentElement.scrollTop;
            xScroll = document.documentElement.scrollLeft;
        } else if (document.body) {// all other Explorers
            yScroll = document.body.scrollTop;
            xScroll = document.body.scrollLeft;
        }
        return new Array(xScroll, yScroll)

    };

    if (!pixPosition) return;

    var $div = $(this.div_), divoffset = $div.offset(), windowWidth = $(window).width(),
    mapDiv = $(this.map_.getDiv()), mapDivOffset = mapDiv.offset();

    this.height_ = $div.height();
    this.width_ = $div.width();


    var totalX = mapDivOffset.left + this.width_ + pixPosition.x + this.offsetHorizontal_,
    totalY = mapDivOffset.top + pixPosition.y - this.height_ + this.offsetVertical_,
    windowScrollPos = getScroll(), diffX = windowWidth - totalX, diffY = windowScrollPos[1] - totalY;

    var leftValue = (pixPosition.x + this.offsetHorizontal_), topValue = pixPosition.y - this.height_ + this.offsetVertical_, beak = $div.find('.ero-beak');
    this.div_.style.position = 'absolute';
    this.div_.style.visibility = 'visible';

    // Now position our DIV based on the DIV coordinates of our bounds
    if (diffY > 0) {
        var bottom = parseInt(beak.css('bottom').replace('px', ''));
        this.div_.style.top = topValue + diffY + 'px';
        beak[0].style.bottom = 18 + diffY + "px";
    }
    else {
        this.div_.style.top = topValue + 'px';
    }

    if (diffX < 0) {
        this.div_.className = this.div_.className.replace("ero-leftBeak", "ero-rightBeak");
        var rightPadding = parseInt($div.css('padding-right').replace('px', ''));
        if (isNaN(rightPadding)) rightPadding = 0;
        leftValue = pixPosition.x - this.width_ - rightPadding - this.offsetHorizontalRight_;

    }
    else {
        this.div_.className = this.div_.className.replace("ero-rightBeak", "ero-leftBeak");
    }
    this.div_.style.left = leftValue + "px";
    //Should be 0 but 8 was added as an adjustment due to icon size. (i.e. the latlng x,y position could be outside the visible map bounds but the icon is still inside and visible to the user)   
    if (pixPosition.x < -8 || pixPosition.y < -8 || pixPosition.y > mapDiv.height() + 8 || pixPosition.x > mapDiv.width()) { this.div_.style.display = 'none'; } else { this.div_.style.display = 'block'; }
};

/* Creates the DIV representing this InfoBox in the floatPane.  If the panes
* object, retrieved by calling getPanes, is null, remove the element from the
* DOM.  If the div exists, but its parent is not the floatPane, move the div
* to the new pane.
* Called from within draw.  Alternatively, this can be called specifically on
* a panes_changed event.
*/
InfoBox.prototype.removeWindow = function(ib) {

    return function() { ib.setMap(null); }
}

InfoBox.prototype.GetBodyPanes = function() {
    var Pane
    // if (!this.map.draggable) {
    var mapDiv = this.map.getDiv();
    Pane = $('#IB_' + mapDiv.id);
    if (Pane.length == 0) {
        Pane = document.createElement("div");
        var offset = $(mapDiv).offset();
        Pane.setAttribute("id", 'IB_' + mapDiv.id);
        $(Pane).css({ position: 'absolute', top: offset.top + 'px', left: offset.left + 'px' }).appendTo('body');

    } else {
        Pane = Pane[0];
    }
    // }
    // else {
    //  Pane = this.getPanes().floatPane;
    // }
    return Pane;
}

InfoBox.prototype.onAdd = function() {

    var panes = this.GetBodyPanes();
    var div = this.div_;
    if (!div) {

        // This does not handle changing panes.  You can set the map to be null and
        // then reset the map to move the div.

        div = this.div_ = document.createElement("div");
        div.className = "ero-leftBeak ero";
        div.setAttribute("style", "zoom:1;");

        var beakDiv = document.createElement("div"), contentDiv = document.createElement("div"), topDiv = document.createElement("div");
        beakDiv.className = "ero-beak";


        contentDiv.className = "ero-shadow";

        contentDiv.innerHTML = '<div class="ero-body"><div class="ero-actionsBackground"><div class="ero-previewArea"><div class="firstChild"><p></p>' + ((this.title_.length > 0) ? '<div class="VE_Pushpin_Popup_Title"><b>' + this.title_ + '</b></div>' : '') + ((this.body_ != '') ? ('<div class="VE_Pushpin_Popup_Body">' + this.body_ + '</div>') : '') + '</div></div><div class="ero-paddingHack"></div></div></div>';
        topDiv.className = "ero-close";
        this.listeners_.push(google.maps.event.addDomListener(topDiv, 'click', InfoBox.prototype.removeWindow(this)));

        div.appendChild(topDiv);
        div.appendChild(contentDiv);
        div.appendChild(beakDiv);
        div.style.visibility = 'hidden';
        panes.appendChild(div);
        this.container = div;
        this.panMap();
       

    } else if (div.parentNode != panes.floatPane && div.parentNode.id.indexOf("IB_") == -1) {

        // The panes have changed.  Move the div.
        //KP div.parentNode.removeChild(div);
        //KP panes.floatPane.appendChild(div);

    } else {
        // The panes have not changed, so no need to create or move the div.


    }
}

/* Pan the map to fit the InfoBox.*/
InfoBox.prototype.panMap = function() {


    if (!this.disableAutoPan_) {
        // if we go beyond map, pan map
        var map = this.map_, bounds = map.getBounds();
        if (!bounds) return;
        if (!this.div_) return;
        var $div = $(this.div_);
        this.height_ = $div.height();
        this.width_ = $div.width();
        var position = this.latlng_, iwWidth = this.width_, iwHeight = this.height_,
    iwOffsetX = this.offsetHorizontal_, iwOffsetY = this.offsetVertical_,
    padX = 23, padY = 4;


        // The degrees per pixel
        var mapDiv = map.getDiv(), mapWidth = mapDiv.offsetWidth, mapHeight = mapDiv.offsetHeight,
                     boundsSpan = bounds.toSpan(), longSpan = boundsSpan.lng(), latSpan = boundsSpan.lat(),
                     degPixelX = longSpan / mapWidth, degPixelY = latSpan / mapHeight;


        // The bounds of the map
        var mapWestLng = bounds.getSouthWest().lng(), mapEastLng = bounds.getNorthEast().lng(),
                         mapNorthLat = bounds.getNorthEast().lat(), mapSouthLat = bounds.getSouthWest().lat();

        // The bounds of the infowindow
        var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX,
                        iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX,
                        iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY,
                        iwSouthLat = position.lat() - (iwOffsetY + iwHeight + padY) * degPixelY;

        // calculate center shift
        var shiftLng = (iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0) +
                                    (iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0),
                        shiftLat = (iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0) +
                                    (iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0);
        // The center of the map
        var center = map.getCenter();

        // The new map center
        var centerX = center.lng() - shiftLng, centerY = center.lat() - shiftLat;

        // center the map to the new shifted center
        this.haltServices = true;
     
        map.setCenter(new google.maps.LatLng(centerY, centerX));

        // Remove the listener after panning is complete.
        google.maps.event.removeListener(this.boundsChangedListener_);
        this.boundsChangedListener_ = null;
    }
};

function GoogleLabel(options) {

    this.setValues(options);

    // Label specific
    var span = this.span_ = document.createElement('a')
    var div = this.div_ = document.createElement('div');

    this.label = div;
    this.listeners = this.listeners_ = [];
    div.appendChild(span);
    div.style.cssText = 'position: absolute; display: none;';
};

GoogleLabel.prototype = new google.maps.OverlayView;

GoogleLabel.prototype.onAdd = function() {

    var pane = this.getPanes().overlayLayer;
    pane.appendChild(this.div_);
    var _self = this;
    this.listeners_.push(google.maps.event.addListener(this, 'position_changed', function() { _self.draw() }))
    this.listeners_.push(google.maps.event.addListener(this, 'text_changed', function() { _self.draw; }));

}
GoogleLabel.prototype.onRemove = function() {
    this.div_.parentNode.removeChild(this.div_);
    for (var i = 0, I = this.listeners_.length; i < I; i++) {
        google.maps.event.removeListener(this.listeners_[i]);
    }
}

GoogleLabel.prototype.draw = function() {

    var projection = this.getProjection(), position = projection.fromLatLngToDivPixel(this.get('position')), div = this.div_;
    div.style.left = position.x + 'px';
    div.style.top = position.y + 'px';
    div.style.display = 'block';

    div.setAttribute("title", this.get('title').toString());
    this.span_.innerHTML = this.get('text').toString();

}
/* PL UI Extensions */

// arrow to local info. page
function localInfoArrow(imageId, containerId) {
    this.birdsEyeOn = false;
    this.streetviewOn = false;
    this.container = jQuery('#' + containerId);
    this.arrowImage = jQuery('a[id$=' + imageId + ']', this.container);
    this.imageUrl = 'none';

    this.setStreetviewOn = function() {
        this.streetviewOn = true;
        this.updateDisplay();
    };

    this.setBirdsEyeOn = function() {
        this.birdsEyeOn = true;
        this.updateDisplay();
    };

    this.setImage = function(imageUrl) {
        this.imageUrl = imageUrl;
        this.displayImage();
    };

    this.displayImage = function() {
        if (typeof this.arrowImage !== 'undefined') {
            this.arrowImage.css({ 'background-image': this.imageUrl });
        }
    };

    // toggle the image display state with the availability of Streetview and/or MS Bird's Eye
    this.updateDisplay = function() {
        if (this.container.is('#BirdViewTab')) {
            var sel = this.birdsEyeOn ? '_b' : '';
            if (this.streetviewOn) { sel += '_S'; }
            if (sel.length) {
                this.setImage('url(/content/images/maps/arrow-art' + sel + '_final.gif)');
            }
        }
        else {
            this.container = $('#StreetviewTab');
            if (typeof this.container !== 'undefined') {
                if (this.streetviewOn) { this.container.show(); }
                else { this.container.hide(); }
            }
        }
    };
}



window.PL.controls.browsemap.plot = function(mapPoints) {
    if (!mapPoints) { return; }
    switch (window.PL.controls.browsemap.type) {
        case 'Area':

            var shapeArray = [];
            if (window.map.mapType === MapEngines.Google) {
                $.each(mapPoints, function(i, v) {
                    if (v.type === 'area') {

                        if (v.current && !window.map.mapOptions.agentView) {
                            shapeArray.push(window.map.plotPrimaryClusterShape(v.coords, v.name, v.name, v.description, window.map.clusterShapeLayer));
                        }
                        else {
                            shapeArray.push(window.map.plotBrowseClusterShape(v.coords, v.name, v.name, v.description, v.url, window.map.clusterShapLayer, v.neighbour ? 'background' : 'forground'));
                        }
                    }
                    else {
                        shapeArray.push(window.map.plotDefaultShape(v.coords, v.name, '', v.description, undefined, v.url));
                    }
                });
            }
            else {
                window.map.clusterShapeLayer = new VEShapeLayer();
                window.map.AddShapeLayer(window.map.clusterShapeLayer);
                $.each(mapPoints, function(i, v) {
                    var coords = (typeof v.coords.lat === 'undefined') ? v.coords : new VELatLong(v.coords.lat, v.coords.lng);
                    if (v.type === 'area') {
                        if (v.current && !window.map.options.agentView) {
                            shapeArray.push(window.map.plotPrimaryClusterShape(coords, v.name, v.name, v.description, window.map.clusterShapeLayer));
                        }
                        else {
                            shapeArray.push(
                        window.map.plotBrowseClusterShape(coords, v.name, v.name, v.description, v.url, window.map.clusterShapeLayer, v.neighbour ? 'background' : 'foreground'));
                        }
                    }
                    else {
                        shapeArray.push(window.map.plotDefaultShape(coords, v.name, '', v.description, undefined, v.url));
                    }
                });
            }
            window.map.mapContainer.data("defaultLabels", shapeArray);
            break;
        case 'Property':

            $.each(mapPoints, function(i, v) {
                if (v.type == 'localinfo') {

                    window.map.plotPrimaryShape(v.coords, v.description, '');
                }
                else {

                    window.map.plotDefaultShape(v.coords, v.name, '', v.description, undefined, v.url);
                }
            });
            break;
        default: throw new Error('No type set for the map'); break;
    }
}
window.PL.controls.browsemap.redraw = function(bbox, points) {

    if (!bbox || !points) { return; }


    var rect = (typeof bbox.ne === 'undefined') ? bbox : { sw: [bbox.sw.lat, bbox.sw.lng], ne: [bbox.ne.lat, bbox.ne.lng] };
    var bounds = window.map.GetMapView();


    if (rect.ne != bounds.ne && rect.sw != bounds.sw) {

        var view = window.map.calculateView(rect);
        window.map.SetCenterAndZoom(view.centerPoint, view.zoom);

    }
    window.map.Clear();
    window.PL.controls.browsemap.plot(points);
}


Maps.prototype.togglePlotLocalProperties = function(on) {
    if (on) {
        this.localPropertiesOn = true;
        this.plotLocalProperties(null);
        //toggle it on;
        var _self = this;
        this.mapContainer.data("dragendLocalProps", this.bind(this.map, "dragend", function() { _self.plotLocalProperties() }));
        this.mapContainer.data("zoom_changedLocalProps", this.bind(this.map, "zoom_changed", function() { _self.plotLocalProperties() }));

    } else {
        this.localPropertiesOn = false;
        if (this.mapContainer.data("localPropertiesShapeLayer")) {
            this.DeleteShapeLayer(this.mapContainer.data("localPropertiesShapeLayer"));
            this.mapContainer.data("localPropertiesShapeLayer", null);
        }
        //toggle it off;
        if (this.mapContainer.data("dragendLocalProps")) { this.unbind(this.mapContainer.data("dragendLocalProps"), "dragend", function() { _self.plotLocalProperties() }); this.mapContainer.data("dragendLocalProps", null) }
        if (this.mapContainer.data("zoom_changedLocalProps")) this.unbind(this.mapContainer.data("zoom_changedLocalProps"), "zoom_changed", function() { _self.plotLocalProperties() }, this.mapContainer.data("zoom_changedLocalProps", null));
    }
}
Maps.prototype.plotLocalProperties = function(callback) {

    var rectangle;
    var style = this.mapOptions.mapstyle;
    //check for birdseye
    if (style == MapViewType.BIRDSEYE || style == MapViewType.BIRDSEYEHYBRID) {
        //This needs further coding to convert VERectangle to {sw:[],ne:[]}
        if (this.map.GetBirdseyeScene) {
            rectangle = this.map.GetBirdseyeScene().GetBoundingRectangle();
        }
    }
    else {
        rectangle = this.GetMapView();
    }

    var nw = rectangle.ne, se = rectangle.sw;

    // if getMapView failed to get the map boundaries then use PixelToLatLong
    if ((typeof se[LatLong.Lat] == 'undefined') ||
    (se[LatLong.Lat] == null) ||
    (typeof nw[LatLong.Lat] == 'undefined') ||
    (nw[LatLong.Lat] == null) ||
    (typeof se[LatLong.Long] == 'undefined') ||
    (se[LatLong.Long] == null) || (typeof nw[LatLong.Long] == 'undefined') || (nw[LatLong.Long] == null)) {

        nw = this.PixelToLatLong({ x: 0, y: 0 }, this.GetZoom());
        var ux = this.mapContainer.get(0).offsetWidth, uy = this.mapContainer.get(0).offsetHeight;
        se = this.PixelToLatLong({ x: ux, y: uy }, this.GetZoom());
    }

    var min_lat = se[LatLong.Lat];
    var max_lat = nw[LatLong.Lat];
    var min_lng = se[LatLong.Long];
    var max_lng = nw[LatLong.Long];

    var url = this.mapOptions.searchUrl;

    var regexp = new RegExp("/p/[^/]*/");
    url = url.replace(regexp, '/');
    regexp = new RegExp("/nt/[^/]*/");
    url = url.replace(regexp, '/');
    regexp = new RegExp("/xt/[^/]*/");
    url = url.replace(regexp, '/');
    regexp = new RegExp("/ng/[^/]*/");
    url = url.replace(regexp, '/');
    regexp = new RegExp("/xg/[^/]*/");
    url = url.replace(regexp, '/');

    url += 'outputFormat/json/nt/' + min_lat + '/xt/' + max_lat + '/ng/' + min_lng + '/xg/' + max_lng;

    ignore = this.mapOptions.ignore;

    //do some ajax
    callback = callback || function(transport) {
        //hide the existing house markers
        //Todo: call to hide layer.
        if (this.mapContainer.data("localPropertiesShapeLayer")) {
            this.DeleteShapeLayer(this.mapContainer.data("localPropertiesShapeLayer"));
            this.mapContainer.data("localPropertiesShapeLayer", null)
        }

        if (this.localPropertiesOn) {


            if (this.mapType === MapEngines.Bing) {
                this.mapContainer.data("localPropertiesShapeLayer", new VEShapeLayer());
                this.map.AddShapeLayer(this.mapContainer.data("localPropertiesShapeLayer"));
            }

            var properties = $.parseJSON(transport.d);
            if (properties === null) { return; }
            //group all the data by lat lng
            var data = this.groupLocalPropertyData(properties, ignore);
            //plot the grouped data
            var shapeArray = [];
            for (var llKey in data) {
                if (llKey.substring(0, 3) == 'll_') {
                    var property = eval('data.' + llKey);
                    var title = property.title;
                    var ll = property.ll;
                    var character = property.character;
                    var description = property.description;
                    var shape = this.getDefaultShape(ll, title, character, description);
                    shapeArray.push(shape);
                }
            }
            if (shapeArray.length > 0) {
                if (this.mapType === MapEngines.Google) { this.mapContainer.data("localPropertiesShapeLayer", shapeArray); } else {
                    this.mapContainer.data("localPropertiesShapeLayer").AddShape(shapeArray);
                }
            }
        }
    };
    //do some prototype.js ajax stuff here, where the callback method will be callback above.
    //ORIGINAL:		new Ajax.Request(url,{onComplete:function(transport){callback(transport);callback();}});
    alert("get local properties not implemented on PL.Net");
    return;
    $.ajax({ type: 'POST', url: url, contentType: 'application/json; charset=utf-8',
        dataType: 'json', success: function(transport) { callback(transport); }
    });
}


