/*
BZ GMaps Lib v.1.0
		
		
*/


//location marker that represents a set of one or more communities at a give long/lat or nearby
function LocationMarker(long, lat, gMarker) {
    this.Long = long;
    this.Lat = lat;
    this.gMarker = gMarker;
    this.commArray = new Array();
}

//adds a community to the location marker's array
LocationMarker.prototype.AddCommunity = function(comm) {
    this.commArray[this.commArray.length] = comm;
}


//returns all communities in the location marker
LocationMarker.prototype.GetCommunities = function() {
    return this.commArray;
}

//class for custom long, lat pair
function CLongLat(long, lat) {
    this.Long = long;
    this.Lat = lat;
}

function Community(lat, long, name, id, priceRange, phone, statusId, desc, city, fax) {
    this.Long = long;
    this.Lat = lat;
    this.Name = name;
    this.Id = id;
    this.PriceRange = priceRange;
    this.Phone = phone;
    this.StatusId = statusId;
    this.Description = desc;
    this.City = city;
    this.Fax = fax;
}


var mapPrintMode = false;
var mapHouseClickEnabled = true;
var mapZoomLevel = "N/A";
var tPrice = 0;
var fPrice = 0;
var single, townhome, condo, available = true;

var gMarkerArray = new Array();

//map initializer for a give map
function InitMap(mapElementId, fromPrice, toPrice, isSingle, isTownhome, isCondo, specsAvailable, isLoaded) {
    if (GBrowserIsCompatible()) {
        fPrice = fromPrice;
        tPrice = toPrice;
        mapZoomLevel = googleMapOveride;

        single = isSingle;
        townhome = isTownhome;
        condo = isCondo;
        available = specsAvailable;

        var mapElement = document.getElementById(mapElementId);
        //setup array of communities
        var commArray = communityData;

        if (!isLoaded) {
            //get map ref

            map = new GMap2(mapElement);
            //add map control
            map.addControl(new GLargeMapControl3D);
        }
        else {
            //Empty markers
            map.clearOverlays();
        }






        if (commArray.length == 0) {
            //no communities to plot, terminate
            return;
        }

        //center map on first sales office first
        map.setCenter(new GLatLng(commArray[0].GeoLatitude, commArray[0].GeoLongitude), 13);

        //setup empty bounds
        var bounds = new GLatLngBounds();
        //expand bounds to include each sales office
        for (var i = 0; i < commArray.length; i++) {
            bounds.extend(new GLatLng(commArray[i].GeoLatitude, commArray[i].GeoLongitude));


        }


        //create community markers
        CreateCommunityMarkers(map, commArray);

        //get zoom level based on occupied area to fit all sales offices
        //zoom level needs to be dynamic
        ////alert(mapZoomLevel);
        if (mapZoomLevel == 'N/A') {
            map.setZoom(map.getBoundsZoomLevel(bounds));
            //alert('current zoom level: ' + map.getBoundsZoomLevel(bounds));
        }
        else {
            map.setZoom(parseInt(mapZoomLevel));
        }
        //alert("10");
        //center map based on occupied area to fit all sales offices
        map.setCenter(bounds.getCenter());
        //alert("11");
    }
}

//creates an array of community location markers based off community long/lat info
function CreateCommunityMarkers(map, commArray) {
    //setup the array to hold all LocationMarker objects
    markerArray = new Array();
    var funcCheck = CheckIfMarkerForLocationExists;
    var funcCreate = CreateMarker;

    //iterate through all communities

    for (var i = 0; i < commArray.length; i++) {
        var showCommunity = false;
        var bossComms = commArray[i].WsmetroBosscommunities;
        for (var j = 0; j < bossComms.length; j++) {
            if (bossComms[j].MinPrice <= tPrice && bossComms[0].MaxPrice >= fPrice || bossComms[j].MinPrice == 0) {
                if (single == true && bossComms[j].ProductTypeName == "Single Family")
                    showCommunity = true;

                if (townhome == true && bossComms[j].ProductTypeName == "Townhome")
                    showCommunity = true;

                if (condo == true && bossComms[j].ProductTypeName == "Condominum")
                    showCommunity = true;
            }

        }
        if (bossComms.length == 0) {
            showCommunity = true;
        }
        if (showCommunity) {
            if (available == true) {
                var wsComms = commArray[i].WsmetroBosscommunities;
                var displayComm = false;
                for (var k = 0; k < wsComms.length; k++) {
                    if (wsComms[k].ActiveInventoryCount > 0) {
                        displayComm = true;
                    }
                }
                if (displayComm == true) {
                    //get current lat and long
                    var currentLat = commArray[i].GeoLatitude;
                    var currentLong = commArray[i].GeoLongitude;


                    //check if a marker already exists for this location
                    var markerIndex = funcCheck(markerArray, currentLat, currentLong);
                    if (markerIndex != -1) {


                        //marker for this location already exists, add the community to the existing marker
                        var locationMarker = markerArray[markerIndex];
                        locationMarker.commArray.push(commArray[i]);

                    }
                    else {
                        //marker does not exist, create a new marker
                        var locationMarker = new LocationMarker(currentLong, currentLat);

                        //add community to the marker
                        locationMarker.commArray.push(commArray[i]);

                        //add marker to marker array
                        markerArray.push(locationMarker);

                    }
                }
            }
            else {
                //get current lat and long
                var currentLat = commArray[i].GeoLatitude;
                var currentLong = commArray[i].GeoLongitude;


                //check if a marker already exists for this location
                var markerIndex = funcCheck(markerArray, currentLat, currentLong);
                if (markerIndex != -1) {


                    //marker for this location already exists, add the community to the existing marker
                    var locationMarker = markerArray[markerIndex];
                    locationMarker.commArray.push(commArray[i]);

                }
                else {
                    //marker does not exist, create a new marker
                    var locationMarker = new LocationMarker(currentLong, currentLat);

                    //add community to the marker
                    locationMarker.commArray.push(commArray[i]);

                    //add marker to marker array
                    markerArray.push(locationMarker);

                }
            }
        }
    }

    //render created markers on map
    for (var i = 0; i < markerArray.length; i++) {

        //create GMarker object
        var gMarker = funcCreate(map, markerArray[i], i);
        //gMarkerArray.push(gMarker);
        //add it to the map
        map.addOverlay(gMarker);
    }


}

function CheckIfMarkerForLocationExists(markerArray, lat, long) {

    //alert("8.2.1");
    var currentLoc;

    for (var j = 0; j < markerArray.length; j++) {


        //check for exact match
        if (markerArray[j].Lat == lat && markerArray[j].Long == long) {
            return j;
        }

        var distance = distVincenty(markerArray[j].Lat, markerArray[j].Long, lat, long);
        //var distance=GetDistanceBetweenLocations(markerArray[j].Long, markerArray[j].Lat, long, lat);


        if (distance < 1000) {
            return j;
        }
    }
    return -1;
}

//creates the GMarker object representing the location for one or more communities
function CreateMarker(map, currentMarker, locMarkerIndex) {
    //construct GLatLng for marker coords
    var markerLatLng = new GLatLng(currentMarker.Lat, currentMarker.Long);

    var infoWindowDOM = CreateLocationMarkerInfoWindowDOM(currentMarker);

    var commListText = CreateLocationMarkerTooltip(currentMarker);

    var multipleCommsAtLocation = false;
    if (currentMarker.commArray.length > 1) {
        multipleCommsAtLocation = true;
    }
    var commStatusId = currentMarker.commArray[0].StatusId;
    var isGreen = false;
    
    for(var i = 0; i < currentMarker.commArray.length; i++){
        if (currentMarker.commArray[i].IsGreen)
        {
            isGreen=true;
        }
    }
    
    

    var commIcon = GetCustomCommunityIcon(multipleCommsAtLocation, locMarkerIndex, commStatusId, isGreen);
    var markerOpts = { icon: commIcon, title: commListText };
    //create actual GMarker
    var gMarker = new GMarker(markerLatLng, markerOpts);
    GEvent.addListener(gMarker, 'click', function() {
        var infoWindowOpts = { maxWidth: 150, noCloseOnClick: false };
        map.openInfoWindowHtml(markerLatLng, infoWindowDOM, infoWindowOpts);

        this.setImage(singleHomeClicked);
        if (isGreen)
            this.setImage(singleHomeClickedGreen);
    });
    GEvent.addListener(gMarker, 'mouseover', function() {

        resetMarkers();
        this.setImage(singleHomeClicked);
        if (isGreen)
            this.setImage(singleHomeClickedGreen);

    });

    //wire up closure to show infowindow upon marker click
    /*
    if (mapHouseClickEnabled) {
    GEvent.addListener(gMarker, "click", function() {

            //Get first community on the list
    var commURL = $("a.bubbleCommCity:first").attr("href");
    window.location = commURL;
    //var infoWindowOpts = { maxWidth: 150, noCloseOnClick: false };
    //map.openInfoWindowHtml(markerLatLng, infoWindowDOM, infoWindowOpts);
    }
    );
    }
    */
    currentMarker.gMarker = gMarker;
    return gMarker;

}

//creates HTML for the community or cluster pop-up infowindow as a DOM tree
function CreateLocationMarkerInfoWindowDOM(currentMarker) {
    //get marker communities
    var markerCommArray = currentMarker.commArray;

    //setup flag indicating if marker encapsulates multiple communities
    var multipleCommsAtLocation = false;

    //set the flag correctly and init tooltip to indicate multiple properties at location
    if (markerCommArray.length > 1) {
        multipleCommsAtLocation = true;
    }

    var divWrapper = document.createElement("DIV");

    if (multipleCommsAtLocation) {
        YAHOO.util.Dom.addClass(divWrapper, "GMap_CommInfoListWrapper");
    }
    else {
        YAHOO.util.Dom.addClass(divWrapper, "GMap_CommInfoWrapper");
    }

    if (multipleCommsAtLocation) {

        //Communities label
        var divCommunities = document.createElement("DIV");
        YAHOO.util.Dom.addClass(divCommunities, "bubbleMultipleComms");

        var txtComms = document.createTextNode(bubbleTextCommunitiesTitle + ":");
        divCommunities.appendChild(txtComms);
        divWrapper.appendChild(divCommunities);

        //setup UL-based list for all communities in the cluster
        var ulCommList = document.createElement("UL");
        YAHOO.util.Dom.addClass(ulCommList, "bubbleList")
        divWrapper.appendChild(ulCommList);

        //iterate through all communities associated with the marker and add them to the DOM
        for (var i = 0; i < markerCommArray.length; i++) {

            //get reference to current community
            var comm = markerCommArray[i];
            var link = comm.ShortcutUrl;
            var driving = comm.ShortcutUrl + "?initTab=DD";
            if (link == "") {
                link = manualLinkPrefix.replace("/" + shortcutPrePath + "/", "/") + "Community.aspx?CommunityID=" + comm.WebsiteCommunityKey;
                driving = link + "&initTab=DD";
            }
            else {
                link = manualLinkPrefix + link;
                driving = link + "?initTab=DD";
            }

            var minPrice = new Array();
            for (var j = 0; j < comm.WsmetroBosscommunities.length; j++) {
                minPrice.push(comm.WsmetroBosscommunities[j].MinPrice);
            }
            var commMinPrice = Math.min.apply(Math, minPrice);
            if (comm.WsmetroBosscommunities.length == 0)
                commMinPrice = 0;
            //create community LI item
            var liItem = document.createElement("LI");
            ulCommList.appendChild(liItem);

            var liLink = document.createElement("A");
            liLink.setAttribute("href", link);

            YAHOO.util.Dom.addClass(liLink, "GMap_CommInfoMultiLink");
            liItem.appendChild(liLink);

            //set community name as link text

            var txCommName = document.createTextNode(comm.Name);
            
           liLink.appendChild(txCommName);
           if (comm.IsGreen) {
               var spanGreenHome = document.createElement("SPAN");
               YAHOO.util.Dom.addClass(spanGreenHome, "greenHome");
               YAHOO.util.Dom.addClass(spanGreenHome, "gHSmall");
               var txGreenHome = document.createTextNode(" "+gHT);
               spanGreenHome.appendChild(txGreenHome);
               liItem.appendChild(spanGreenHome);
           }
            //create a <BR/>
            var brEl = document.createElement("BR");
            liItem.appendChild(brEl);
            
            //create span element to contain the community info 1
            //and add price range info to it if the price range exists
            if (commMinPrice != "") {
                var spanCommInfo1 = document.createElement("SPAN");
                YAHOO.util.Dom.addClass(spanCommInfo1, "GMap_MultiLink_CommInfoSpan");
                liItem.appendChild(spanCommInfo1);

                //create text element with "From $XXX" and phone
                var txCommInfo1 = document.createTextNode(bubbleTextPriceFromMulti + " " + convetToPricedFromText(commMinPrice));
                spanCommInfo1.appendChild(txCommInfo1);
            }

            //create span element to contain the community info 1
            //and add price range info to it if the phone exists
            if (comm.Phone != undefined) {

                var spanCommInfo2 = document.createElement("SPAN");
                YAHOO.util.Dom.addClass(spanCommInfo2, "GMap_MultiLink_CommInfoSpan");
                liItem.appendChild(spanCommInfo2);

                //only put a break if there is a price range specified and phone specified
                if (commMinPrice != "" && commMinPrice != "0") {
                    var brSpacer = document.createElement("BR");
                    spanCommInfo2.appendChild(brSpacer);
                }


                var txCommInfo2 = document.createTextNode("Phone: " + comm.Phone);
                spanCommInfo2.appendChild(txCommInfo2);
            }
        }

    }
    else {
        var comm = markerCommArray[0];
        var link = comm.ShortcutUrl;
        var driving = comm.ShortcutUrl + "?initTab=DD";
        if (link == "") {
            link = manualLinkPrefix.replace("/" + shortcutPrePath + "/", "/") + "Community.aspx?CommunityID=" + comm.WebsiteCommunityKey;
            driving = link + "&initTab=DD";
        }
        else {
            link = manualLinkPrefix + link;
            driving = link + "?initTab=DD";
        }
        var commMinPrice = 0;
        var minPrice = new Array();
        for (var i = 0; i < comm.WsmetroBosscommunities.length; i++) {
            if (comm.WsmetroBosscommunities[i].MinPrice != 0) {
                minPrice.push(comm.WsmetroBosscommunities[i].MinPrice);
            }
        }
        commMinPrice = Math.min.apply(Math, minPrice);
        if (comm.WsmetroBosscommunities.length == 0)
            commMinPrice = 0;
        var spanCommName = document.createElement("DIV");
        YAHOO.util.Dom.addClass(spanCommName, "bubbleCommName");
        divWrapper.appendChild(spanCommName);

        var txCommName = document.createTextNode(comm.Name);
        spanCommName.appendChild(txCommName);
        //create Green Text if available
        
        if (comm.IsGreen) {
            var divGreenComm = document.createElement("DIV");
            YAHOO.util.Dom.addClass(divGreenComm, "greenHome");
            var txGreen = document.createTextNode(eSmartGreenHomesText);
            divGreenComm.appendChild(txGreen);

            divWrapper.appendChild(divGreenComm);
        }

        //var brSpacer=document.createElement("BR");
        //divWrapper.appendChild(brSpacer);

        //var divSpacer = document.createElement("DIV");
        //YAHOO.util.Dom.addClass(divSpacer, "GMap_CommInfoWrapperSingleSpacer");
        //divWrapper.appendChild(divSpacer);

        //create span for city
        if (comm.City != undefined) {
            var spanCommCity = document.createElement("DIV");
            YAHOO.util.Dom.addClass(spanCommCity, "bubbleCommCity");
            var txCommCity = document.createTextNode(comm.City);
            spanCommCity.appendChild(txCommCity);

            divWrapper.appendChild(spanCommCity);

        }


        //var brSpacer2=document.createElement("BR");
        //divWrapper.appendChild(brSpacer2);
        //var brSpacer21=document.createElement("BR");
        //divWrapper.appendChild(brSpacer21);

        //var divSpacer1 = document.createElement("DIV");
        //YAHOO.util.Dom.addClass(divSpacer1, "GMap_CommInfoWrapperDoubleSpacer");
        //divWrapper.appendChild(divSpacer1);

        //create span element to contain the community info 1
        //and add price range info to it if the phone exists
        if (comm.Phone != undefined) {
            var spanCommInfo2 = document.createElement("SPAN");
            YAHOO.util.Dom.addClass(spanCommInfo2, "GMap_CommInfoSpan");
            divWrapper.appendChild(spanCommInfo2);

            var txCommInfo2 = document.createTextNode("Sales office: " + comm.Phone);
            spanCommInfo2.appendChild(txCommInfo2);
        }

        //create span element to contain the community info 1
        //and add price range info to it if the price range exists
        //comm.PriceRange = "450"; //test for price range
        if (commMinPrice != "") {
            //
            //var brSpacer4=document.createElement("BR");
            //divWrapper.appendChild(brSpacer4);
            //var divSpacer3 = document.createElement("DIV");
            //YAHOO.util.Dom.addClass(divSpacer3, "GMap_CommInfoWrapperSingleSpacer");
            //divWrapper.appendChild(divSpacer3);

            var spanCommInfo1 = document.createElement("DIV");
            YAHOO.util.Dom.addClass(spanCommInfo1, "bubbleCommPriceText");
            divWrapper.appendChild(spanCommInfo1);

            //create text element with "From $XXX" and phone
            var txCommInfo1 = document.createTextNode(bubbleTextPriceFrom);
            spanCommInfo1.appendChild(txCommInfo1);

            var spanCommInfo2 = document.createElement("DIV");
            YAHOO.util.Dom.addClass(spanCommInfo2, "bubbleCommCity");
            divWrapper.appendChild(spanCommInfo2);

            var txCommInfo2 = document.createTextNode(convetToPricedFromText(commMinPrice));
            spanCommInfo2.appendChild(txCommInfo2);

        }



        //var brSpacer3=document.createElement("BR");
        //divWrapper.appendChild(brSpacer3);
        //var brSpacer31=document.createElement("BR");
        //divWrapper.appendChild(brSpacer31);
        var divSpacer4 = document.createElement("DIV");

        YAHOO.util.Dom.addClass(divSpacer4, "bubbleCommPriceText");


        var lnkCommPage = document.createElement("A");

        lnkCommPage.setAttribute("href", link);
        YAHOO.util.Dom.addClass(lnkCommPage, "bubbleCommCity");
        var txCommNameInLink = document.createTextNode(bubbleTextGoToThisCommunity);
        lnkCommPage.appendChild(txCommNameInLink);
        divSpacer4.appendChild(lnkCommPage)
        divWrapper.appendChild(divSpacer4);

        var divSpacer5 = document.createElement("DIV");
        divSpacer5.style.textAlign = "center";
        YAHOO.util.Dom.addClass(divSpacer5, "bubbleCommCity");

        var lnkCommPage2 = document.createElement("A");

        lnkCommPage2.setAttribute("href", driving);

        YAHOO.util.Dom.addClass(lnkCommPage2, "bubbleCommCity");

        var txCommNameInLink2 = document.createTextNode(bubbleTextGetDrivingDirections);
        lnkCommPage2.appendChild(txCommNameInLink2);
        divSpacer5.appendChild(lnkCommPage2);
        divWrapper.appendChild(divSpacer5);
    }

    return divWrapper;
}

//creates a string representing the location marker tooltip
function CreateLocationMarkerTooltip(currentMarker) {

    //setup vars to hold HTML for infowindow and text for tooltip for all
    //communities associated with the marker

    var commListText = "";

    //get marker communities
    var markerCommArray = currentMarker.commArray;

    //setup flag indicating if marker encapsulates multiple communities
    var multipleCommsAtLocation = false;

    //set the flag correctly and init tooltip to indicate multiple properties at location
    if (markerCommArray.length > 1) {
        multipleCommsAtLocation = true;

        commListText += multipleCommsMessage + "\n";
    }


    //iterate through all communities associated with the marker and add them to the HMTL and tooltip
    for (var i = 0; i < markerCommArray.length; i++) {
        if (i > 0) {

            commListText += "\n";
        }



        //if multiple communities at location, prefix the listing in the tooltip with dashed bullet
        if (multipleCommsAtLocation) {
            commListText += "- ";

        }


        commListText += markerCommArray[i].Name;


    }


    return commListText;

}

function GetCustomCommunityIcon(multipleCommsAtLocation, locMarkerIndex, commStatusId,isGreen) {


    var imgUrlPrefix = "";


    if (document.location.href.toLowerCase().indexOf("/build/") != -1) {
        imgUrlPrefix = "../";
    }

    var commIcon = new GIcon();
    if (mapPrintMode) {
        commIcon=GIcon.G_DEFAULT_ICON;
        //commIcon.image = imgUrlPrefix + singleHomeImage;

        //commIcon.iconSize = new GSize(18, 18);

        //commIcon.iconAnchor = new GPoint(6, 6);
        //commIcon.infoWindowAnchor = new GPoint(5, 1);
    }
    else {
        if (multipleCommsAtLocation) {
            commIcon.image = imgUrlPrefix + singleHomeImage;
            if (isGreen)
                commIcon.image = imgUrlPrefix + singleHomeImageGreen;      
            commIcon.iconSize = new GSize(30, 51);
            //commIcon.shadowSize = new GSize(59, 32);
            commIcon.iconAnchor = new GPoint(15, 51);
            commIcon.infoWindowAnchor = new GPoint(15, 51);
        }
        else {
            //commIcon = GIcon.G_DEFAULT_ICON;
            //normal icon
            commIcon.image = imgUrlPrefix + singleHomeImage;
            if (isGreen)
                commIcon.image = imgUrlPrefix + singleHomeImageGreen;       
            commIcon.iconSize = new GSize(30, 51);
            //commIcon.shadowSize = new GSize(59, 32);
            commIcon.iconAnchor = new GPoint(15, 51);
            commIcon.infoWindowAnchor = new GPoint(15, 51);
        }
    }

    return commIcon;
}

/*
* Calculate geodesic distance (in m) between two points specified by latitude/longitude (in numeric degrees)
* using Vincenty inverse formula for ellipsoids
*/
function distVincenty(lat1, lon1, lat2, lon2) {

    var a = 6378137, b = 6356752.3142, f = 1 / 298.257223563;  // WGS-84 ellipsiod
    var L = (lon2 - lon1).toRad();

    var U1 = Math.atan((1 - f) * Math.tan(lat1 * Math.PI / 180));

    var U2 = Math.atan((1 - f) * Math.tan(lat2 * Math.PI / 180));

    var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);

    var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);


    var lambda = L, lambdaP = 2 * Math.PI;
    var iterLimit = 20;
    while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0) {

        var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
        var sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) +
      (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
        if (sinSigma == 0) return 0;  // co-incident points
        var cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
        var sigma = Math.atan2(sinSigma, cosSigma);
        var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
        var cosSqAlpha = 1 - sinAlpha * sinAlpha;
        var cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
        if (isNaN(cos2SigmaM)) cos2SigmaM = 0;  // equatorial line: cosSqAlpha=0 (§6)
        var C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
        lambdaP = lambda;
        lambda = L + (1 - C) * f * sinAlpha *
      (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
    }
    if (iterLimit == 0) return NaN  // formula failed to converge

    var uSq = cosSqAlpha * (a * a - b * b) / (b * b);
    var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
    var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
    var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) -
    B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
    var s = b * A * (sigma - deltaSigma);

    s = s.toFixed(3); // round to 1mm precision
    return s;
}

// extend String object with method for parsing degrees or lat/long values to numeric degrees
//
// this is very flexible on formats, allowing signed decimal degrees, or deg-min-sec suffixed by
// compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W) or fixed-width
// format without separators (eg 0033709W). Seconds and minutes may be omitted. (Minimal validation
// is done).

String.prototype.parseDeg = function() {
    if (!isNaN(this)) return Number(this);                 // signed decimal degrees without NSEW

    var degLL = this.replace(/^-/, '').replace(/[NSEW]/i, '');  // strip off any sign or compass dir'n
    var dms = degLL.split(/[^0-9.]+/);                     // split out separate d/m/s
    for (var i in dms) if (dms[i] == '') dms.splice(i, 1);    // remove empty elements (see note below)
    switch (dms.length) {                                  // convert to decimal degrees...
        case 3:                                              // interpret 3-part result as d/m/s
            var deg = dms[0] / 1 + dms[1] / 60 + dms[2] / 3600; break;
        case 2:                                              // interpret 2-part result as d/m
            var deg = dms[0] / 1 + dms[1] / 60; break;
        case 1:                                              // decimal or non-separated dddmmss
            if (/[NS]/i.test(this)) degLL = '0' + degLL;       // - normalise N/S to 3-digit degrees
            var deg = dms[0].slice(0, 3) / 1 + dms[0].slice(3, 5) / 60 + dms[0].slice(5) / 3600; break;
        default: return NaN;
    }
    if (/^-/.test(this) || /[WS]/i.test(this)) deg = -deg; // take '-', west and south as -ve
    return deg;
}
// note: whitespace at start/end will split() into empty elements (except in IE)

// extend Number object with methods for converting degrees/radians

Number.prototype.toRad = function() {  // convert degrees to radians
    return this * Math.PI / 180;
}
