// Craig Hananh 60702810 // craig_hannah@gse.harvard.edu// 2/12/2012 CSCI-76 HTML5 Staff's Choice, Mobile Local// Maximum number of location entries to trackvar MAX_ENTRIES = 10;// Number of decimals to display for lat/longvar GEO_DISP_PRECISION = 3;// Options for getting our geographic locationvar GEO_OPTS = {    enableHighAccuracy : true,    timeout : 60000,    maximumAge : 60000};var WELCOME_MESSAGE = "Welcome to MobilLocal! <br/>" +             "&nbsp;Get the news and weather for here or there!";// Tracked location entries, persisted from/to from web storagevar myLocations = [];// Variables used for global message handlingvar errorMsgs = "";var msgs = "";//*****************************************************************************/** * Initial setup (called on page load) * @param None * @return Null  */function init() {    // Ensure that the browser has the necessary functionality    if(!supports_web_storage()) {        errorMsgs += "<li>A browser capable of web storage is required</li>";    }    if(!supports_geo_location()) {        errorMsgs += "<li>A browser capable of geo location is required</li>";    }    if(errorMsgs !== "") {        document.getElementById("messages").innerHTML = "<h4>Errors were found</h4>" + "Sadly, the browser you are using does not have the capabilities needed to run this web application." + "Specifically, the errors are:<ul>" + errorMsgs + "</ul>";        document.getElementById("appdetail").innerHTML = "";        return;    }    // Load data in from web storage    var myLocationStrings = localStorage.locations;    if(myLocationStrings !== null) {        myLocations = JSON.parse(myLocationStrings);    }	msg = WELCOME_MESSAGE;	    // Display the currently defined locations    displayLocs();}//*****************************************************************************//* Define the 3 screen interaction entry points/** * Process a location value entered * @param None * @return False (to prevent form submission)  */function processLoc() {    if(document.forms.location.cityStateZip.value === "") {        document.getElementById("messages").innerHTML = "<header>Messages</header><h4>" + "You must provide either a Zip Code or a city and state." + "</h4>";        return false;    }    var locToUse = {};    locToUse.cityStateZip = document.forms.location.cityStateZip.value;    resetScreen();    storeLoc(locToUse);    expandAndProcessLocation(locToUse);    return false;}/** * Handle a request to use our current geographic location * @param None * @return False (to prevent form submission)  */function getGeo() {    resetScreen();    if(navigator.geolocation) {        navigator.geolocation.getCurrentPosition(geo_handler, geo_error_handler, GEO_OPTS);    } else {        errorMsgs="Geolocation not supported by your browser!";        displayMessages();    }    return false;}/** * Handle a request to reset everything back to the initial state * @param None * @return False (to prevent form submission)  */function resetHist() {    resetScreen();    myLocations = [];    // Get rid of the locations    localStorage.removeItem("locations");    displayLocs();}//*****************************************************************************/** * Define the normal geolocation callback handler * @param Our position's latitute/longitude * @return Null */ function geo_handler(pos) {    var locToUse = {};    locToUse.lat = pos.coords.latitude;    locToUse.lon = pos.coords.longitude;    storeLoc(locToUse);    expandAndProcessLocation(locToUse);}/** * Define the error geolocation callback handler * @param Our position's latitute/longitude * @return Null */function geo_error_handler(err) {    switch(err.code) {        case err.PERMISSION_DENIED:            errorMsgs="This page is not allowed to view your position. Message: " + err.message;            break;        case err.POSITION_UNAVAILABLE:            errorMsgs = "Your position is not available. Message: " + err.message;            break;        case err.TIMEOUT:            errorMsgs = "Timeout when determining your location.";            break;        default:            errorMsgs = "Unknown error occurred! Message: " + err.message;    }	displayMessages();}//*****************************************************************************/** * Get the screen ready to process a new request * @param None * @return Null */function resetScreen() {    document.getElementById("news").innerHTML = "";    document.getElementById("weather").innerHTML = "";    document.forms.location.cityStateZip.value = "";}/** * A convenience function to facilitate our history links * @param The index into our location array of the entry to process * @return Null */function getOldRequest(ind) {    expandAndProcessLocation(myLocations[ind]);}/** * Save a location into our global array & persist to web storage * @param The location value to save * @return the index (relative to 0) of the entry just saved */function storeLoc(loc) {    // Don't re-add this if it already exists    for(var i = myLocations.length - 1; i >= 0; i--) {        if(varEqual(myLocations[i].lat, loc.lat) && varEqual(myLocations[i].lon, loc.lon)) {            return i;        }        if(varEqual(myLocations[i].cityStateZip, loc.cityStateZip) && varEqual(myLocations[i].cityStateZip, loc.cityStateZip)) {            return i;        }    }    // Add the new entry    if(myLocations.length >= MAX_ENTRIES) {// Limit # of entries        myLocations.splice(0, 1);    }    var locAddedInd = myLocations.push(loc);    //Add it    localStorage.locations = JSON.stringify(myLocations);    //Resave    return locAddedInd - 1;    //Return relative to 0}//*****************************************************************************/** * Format all the current locations into a text block & show on screen * @param None * @return Null */function displayLocs() {    //Put out any msgs we might have    displayMessages();        var locStrings = "";    var locString;    var header = "<header>Previous locations used:</header> ";    for( iterLoc = 0; iterLoc < myLocations.length; iterLoc++) {        locString = "";        if(myLocations[iterLoc].cityStateZip !== undefined) {            locString += "Zip/city/state: " + myLocations[iterLoc].cityStateZip;            if(myLocations[iterLoc].drvd_lat !== undefined) {                locString += " (Derived latitude: " + roundDecimal(myLocations[iterLoc].drvd_lat, 3) + ", longitude: " + roundDecimal(myLocations[iterLoc].drvd_lon, 3) + ")";            }        } else if(myLocations[iterLoc].lat !== undefined && myLocations[iterLoc].lon !== undefined) {            locString += "Lat: " + myLocations[iterLoc].lat + " Lon: " + myLocations[iterLoc].lon;            if(myLocations[iterLoc].drvd_csz !== undefined) {                locString += " (Derived address: " + myLocations[iterLoc].drvd_csz + ")";            }        }        if(locString.length > 0) {            locStrings += "<li><a onclick='resetScreen(), getOldRequest(" + iterLoc + ");'>" + locString + "</a></li>";        }    }    if(locStrings.length === 0) {        document.getElementById("usedLocs").innerHTML = header + "<ul><li>None defined</li></ul>";    } else {        document.getElementById("usedLocs").innerHTML = header + "<ul>" + locStrings + "</ul>";    }}/** * Display any error or information messages we might have to display * @param None * @return Null */function displayMessages() {    document.getElementById("messages").innerHTML = "";    if((errorMsgs !== undefined && errorMsgs.length > 0) || (msgs !== undefined && msgs.length > 0)) {        dispMsg = "<header>Messages</header>";        if(errorMsgs !== undefined && errorMsgs.length > 0) {            dispMsg += "<h4>" + errorMsgs + "</h4>";            errorMsgs = "";        }        if(msgs !== undefined && msgs.length > 0) {            dispMsg += "<p>" + msgs + "</p>";            msgs = "";        }        document.getElementById("messages").innerHTML = dispMsg;    }}//*****************************************************************************/** * Get the news for the given location * @param None * @return Null */function processNews(location) {    var url = "";    var url1 = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20rss%20where%20url%3D'http%3A%2F%2Fnews.google.com%2Fnews%3Fgeo%3D"    var url2 = "%26num%3D10%26output%3Drss%20'&format=json&diagnostics=true"    if(location != null && location.length > 0) {        url = url1 + encodeURIComponent(location.replace(/ /g, "+")) + url2;        console.log(location.replace(/ /g, "+"));        $.getJSON(url, function(newsResp) {            var stories = "";            if(newsResp.query.results == null) {                stories = "<li>None found</li>"            } else {                for(story in newsResp.query.results.item) {                    stories += "<li><a href=\"" + newsResp.query.results.item[story].link + "\" target=\"_blank\"> " + newsResp.query.results.item[story].title + "</a></li>";                }            }            if(stories.length == 0) {                document.getElementById("news").innerHTML = "None";            } else {                document.getElementById("news").innerHTML = "<header>News for " + location + "</header><ul>" + stories + "</ul>";            }        });    }}/** * Get the weather for the given location * (Uses http://www.worldweatheronline.com/feed-generater.aspx api 8dc5cb0002084635111502) * @param A comma separated string of latitude and longitude * @param The address to display as a heading for the weather data * @return Null */function processWeather(latLongString, addressToDisplay) {    var url = "";    var url1 = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20json%20where%20url%3D'http%3A%2F%2Ffree.worldweatheronline.com%2Ffeed%2Fweather.ashx%3Fq%3D";    var url2 = "%26format%3Djson%26num_of_days%3D4%26key%3D8dc5cb0002084635111502'&format=json&diagnostics=true";    if(latLongString != null && latLongString.length > 0) {        url = url1 + encodeURIComponent(latLongString.replace(/ /g, "+")) + url2;        $.getJSON(url, function(weatherResp) {            var currentConditions = "";            var weatherMessage = "";            var weatherLocation = "Weather for " + addressToDisplay;            var weatherForecasts = "";            if(weatherResp.query.results == null) {                weatherMessage = "<li>No results found</li>"            } else if(weatherResp.query.results.data.hasOwnProperty("error")) {                weatherMessage = "<li>Error - " + weatherResp.query.results.data.error.msg + "</li>";            } else if(weatherResp.query.results.data.hasOwnProperty("current_condition")) {                currentConditions = "<li>Current conditions: <ul>" + "<li>Description: " + weatherResp.query.results.data.current_condition.weatherDesc.value + "</li>" + "<li>Temperature: " + weatherResp.query.results.data.current_condition.temp_F + "F, " + weatherResp.query.results.data.current_condition.temp_C + "C</li>" + "<li>Wind speed: " + weatherResp.query.results.data.current_condition.windspeedMiles + " mph, Direction: " + weatherResp.query.results.data.current_condition.winddir16Point + "</li>" + "</ul></li>";            }            if(weatherResp.query.results.data.hasOwnProperty("request")) {                weatherLocation += " (" + weatherResp.query.results.data.request.query + ")";            }            if(weatherResp.query.results.data.hasOwnProperty("weather")) {                var weatherArray = [];                if(jQuery.isArray(weatherResp.query.results.data.weather)) {                    weatherArray = weatherResp.query.results.data.weather;                } else {                    weatherArray[0] = weatherResp.query.results.data.weather                }                for(forecastDay in weatherArray) {                    weatherForecasts += "<li>Forecast for " + weatherArray[forecastDay].date + "<ul>";                    weatherForecasts += "<li>Description: " + weatherArray[forecastDay].weatherDesc.value + "</li>";                    weatherForecasts += "<li>High Temperature: " + weatherArray[forecastDay].tempMaxF + " F, " + weatherArray[forecastDay].tempMaxC + " C </li>";                    weatherForecasts += "<li>Low Temperature: " + weatherArray[forecastDay].tempMinF + " F, " + weatherArray[forecastDay].tempMinC + " C </li>";                    weatherForecasts += "<li>Wind speed: " + weatherArray[forecastDay].windspeedMiles + " mph, Direction: " + weatherArray[forecastDay].winddir16Point + "</li>";                    weatherForecasts += "</ul>";                }                weatherForecasts += "</ul>";            }            var weatherReport = "<header>" + weatherLocation + "</header><ul>" + weatherMessage + currentConditions + weatherForecasts + "</ul>";            document.getElementById("weather").innerHTML = weatherReport;        });    }}//*****************************************************************************/** * Forward or reverse geocode the given location * @param location object that contains latitude and longitude, or a city and state or a zip * @param The address to display as a heading for the weather data * @return Null */function expandAndProcessLocation(requestedLoc) {    var geocoder = new google.maps.Geocoder();    var geoParm = [];    if(requestedLoc.lat !== undefined && requestedLoc.lon !== undefined) {        geoParm.latLng = new google.maps.LatLng(requestedLoc.lat, requestedLoc.lon);        geocoder.geocode(geoParm, callBackLL);    } else if(requestedLoc.cityStateZip !== undefined) {        geoParm.address = requestedLoc.cityStateZip;        geocoder.geocode(geoParm, callBackCSZ);    }/** * The callback function for the reverse geocode request * @param The geocode response object * @param the geocode request result status * @return Null */    function callBackLL(response, status) {        console.log(response);        if(status == google.maps.GeocoderStatus.OK) {            if(response[1]) {                loc = -1//augmentMyLocLL(requestedLoc.lat, requestedLoc.lon, response[1].formatted_address);                for(var i = myLocations.length - 1; i >= 0; i--) {                    if(myLocations[i].lat !== undefined && myLocations[i].lat == requestedLoc.lat && myLocations[i].lon !== undefined && myLocations[i].lon == requestedLoc.lon) {                        myLocations[i].drvd_csz = response[1].formatted_address;                        loc = i;                        break;                    }                }                if(loc < 0) {                    console.log("callBackCSZ error, unmatched CSZ: " + requestedLoc.cityStateZip);                } else {                    displayLocs();                    processNews(myLocations[loc].drvd_csz);                    processWeather(requestedLoc.lat + "," + requestedLoc.lon, myLocations[loc].drvd_csz);                }            } else {           				errorMsgs += "Callback for lat/long returned no results<br/>";				displayMessages();				}				} else {					errorMsgs = "Geocoder failed due to: " + status;					displayMessages();				}    }/** * The callback function for the geocode request * @param The geocode response object * @param the geocode request result status * @return Null */    function callBackCSZ(response, status) {        console.log(response);        if(status == google.maps.GeocoderStatus.OK) {            if(response[0]) {                loc = -1;                for(var i = myLocations.length - 1; i >= 0; i--) {                    if(myLocations[i].cityStateZip !== undefined && myLocations[i].cityStateZip == requestedLoc.cityStateZip) {                        myLocations[i].drvd_lat = response[0].geometry.location.lat();                        myLocations[i].drvd_lon = response[0].geometry.location.lng();                        myLocations[i].drvd_csv = response[0].formatted_address;                        loc = i;                        break;                    }                }                if(loc < 0) {                    console.log("callBackCSZ error, unmatched CSZ: " + requestedLoc.cityStateZip);                } else {                    displayLocs();                    processNews(requestedLoc.cityStateZip);                    processWeather(myLocations[loc].drvd_lat + "," + myLocations[loc].drvd_lat, myLocations[loc].drvd_csv);                }            } else {                errorMsgs += "Callback for city/state/zip returned no results<br/>";                displayMessages();            }        } else {            errorMsgs = "Geocoder failed due to: " + status;            displayMessages();        }    }}//*****************************************************************************/** * Utility function to check if web storage is supported * @param None * @return A boolean indicating if web storage is supported */function supports_web_storage() {    try {        return 'localStorage' in window && window.localStorage !== null;    } catch (e) {        return false;    }}/** * Utility function to check if geo location is supported * @param None * @return A boolean indicating if geo location is supported */function supports_geo_location() {    return !!navigator.geolocation;}/** * Utility function to round to a given number of digits * @param The number to round * @param The number of decimals the result should have * @return The rounded number */function roundDecimal(number, digits) {    adjuster = Math.pow(10, digits);    return Math.round(number * adjuster) / adjuster;}/** * Utility function to compare 2 variables * (Attempting to make this case indifferent (using toUpperCase)  * caused some very flakey results ) * @param The number to round * @param The number of decimals the result should have * @return False if they're unequal or either are undefined, otherwise true */function varEqual(var1, var2) {    if(var1 == undefined || var2 == undefined) {        return false;    }    return (var1 == var2);}//*****************************************************************************
