1 2 3// the semi-colon before function invocation is a safety net against concatenated 4// scripts and/or other plugins which may not be closed properly. 5; 6(function ($, window, document, undefined) { 7 8 var tableDescFr = new Array(); 9 tableDescFr["tornado"] = "tornade"; 10 tableDescFr["tropical storm"] = "tempête tropicale"; 11 tableDescFr["hurricane"] = "ouragan"; 12 tableDescFr["severe thunderstorms"] = "orages violents"; 13 tableDescFr["thunderstorms"] = "orages"; 14 tableDescFr["mixed rain and snow"] = "pluie et neige mélangées"; 15 tableDescFr["mixed rain and sleet"] = "pluie mélangée et grésil"; 16 tableDescFr["mixed snow and sleet"] = "neige mélangée et grésil"; 17 tableDescFr["freezing drizzle"] = "bruine verglaçante"; 18 tableDescFr["drizzle"] = "bruine"; 19 tableDescFr["freezing rain"] = "pluie verglaçante"; 20 tableDescFr["showers"] = "averses"; 21 tableDescFr["showers"] = "averses"; 22 tableDescFr["snow flurries"] = "rafales de neige"; 23 tableDescFr["light snow showers"] = "neige légère"; 24 tableDescFr["blowing snow"] = "souffle de neige"; 25 tableDescFr["snow"] = "neige"; 26 tableDescFr["hail"] = "grêle"; 27 tableDescFr["sleet"] = "grésil"; 28 tableDescFr["dust"] = "poussière"; 29 tableDescFr["foggy"] = "brumeux"; 30 tableDescFr["haze"] = "haze"; 31 tableDescFr["smoky"] = "fumé"; 32 tableDescFr["blustery"] = "gémissement"; 33 tableDescFr["windy"] = "vent"; 34 tableDescFr["cold"] = "froid"; 35 tableDescFr["cloudy"] = "nuageux"; 36 tableDescFr["mostly cloudy (night)"] = "principalement nuageux (nuit)"; 37 tableDescFr["mostly cloudy (day)"] = "principalement nuageux (jour)"; 38 tableDescFr["partly cloudy (night)"] = "en partie nuageux (nuit)"; 39 tableDescFr["partly cloudy (day)"] = "partiellement nuageux (jour)"; 40 tableDescFr["clear (night)"] = "clair (nuit)"; 41 tableDescFr["sunny"] = "ensoleillé"; 42 tableDescFr["fair (night)"] = "juste (nuit)"; 43 tableDescFr["fair (day)"] = "juste (jour)"; 44 tableDescFr["mixed rain and hail"] = "pluie mélangée et grêle"; 45 tableDescFr["hot"] = "chaud"; 46 tableDescFr["isolated thunderstorms"] = "orages isolés"; 47 tableDescFr["scattered thunderstorms"] = "orages épars"; 48 tableDescFr["scattered thunderstorms"] = "orages épars"; 49 tableDescFr["scattered showers"] = "averses éparses"; 50 tableDescFr["heavy snow"] = "neige lourde"; 51 tableDescFr["scattered snow showers"] = "neige éparses"; 52 tableDescFr["heavy snow"] = "neige lourde";
53 tableDescFr["partly cloudy"] = "partiellement nuageux"; 54 tableDescFr["thundershowers"] = "tonnerre"; 55 tableDescFr["snow showers"] = "averses de neige"; 56 tableDescFr["isolated hundershowers"] = "tonnerres isolées"; 57 tableDescFr["partly cloud"] = "partiellement nuageux"; 58 tableDescFr["showers"] = "averses"; 59 tableDescFr["am showers"] = "averses matinale"; 60 tableDescFr["pm showers"] = "après-midi d'averses"; 61 tableDescFr["pm thunderstorms"] = "après-midi orageux"; 62 tableDescFr["scattered thunderstorms"] = "orages dispersés"; 63 tableDescFr["light rain with thunder"] = "pluie fine avec tonnerre"; 64 tableDescFr["thunderstorms"] = "orages"; 65 tableDescFr["heavy rain"] = "forte pluie"; 66 tableDescFr["mostly sunny"] = "assez ensoleillé"; 67 tableDescFr["light rain"] = "pluie fine"; 68 tableDescFr["fog"] = "brouillard"; 69 tableDescFr["fair"] = "beau"; 70 tableDescFr["sunny"] = "ensoleillé"; 71 tableDescFr["am rain"] = "pluie matinale"; 72 tableDescFr["pm rain"] = "après-midi de pluie";
73 tableDescFr["mostly cloudy"] = "assez nuageux"; 74 tableDescFr["isolated thunderstorms"] = "orages isolés"; 75 tableDescFr["thundershowers"] = "orage avec averses"; 76 tableDescFr["heavy thunderstorms"] = "gros orages"; 77 tableDescFr["clear"] = "clair"; 78 tableDescFr["rain"] = "pluie"; 79 tableDescFr["cloudy"] = "nuageux"; 80 tableDescFr["breezy"] = "vents frais"; 81 82 83 84 var pluginName = "flatWeatherPlugin"; 85 86 // Create the defaults once 87 var defaults = { 88 location: "Waterloo, ON", //city, region 89 zip: "", //city, region 90 country: "Canada", //country 91 title:'', 92 displayCityNameOnly: false, 93 api: "openweathermap", //api: yahoo or openweathermap 94 forecast: 5, //number of days to forecast, max 5 95 apikey: "", //optional api key for openweathermap 96 view: "full", //options: simple, today, partial, forecast, full 97 render: true, //render: false if you to make your own markup, true plugin generates markup 98 loadingAnimation: true, //show loading animation 99 lang: 'fr' 100 //units : "metric" or "imperial" default: "auto" 101 }; 102 103 var apiurls = { 104 "openweathermap": ["https://api.openweathermap.org/data/2.5/weather", "https://api.openweathermap.org/data/2.5/forecast/daily"], 105 "yahoo": ["https://query.yahooapis.com/v1/public/yql"] 106 }; 107 108 // Plugin Constructor 109 function Plugin(element, options) { 110 111 this.element = element; 112 113 // jQuery has an extend method which merges the contents of two or 114 // more objects, storing the result in the first object. The first object 115 // is generally empty as we don't want to alter the default options for 116 // future instances of the plugin 117 this.settings = $.extend({}, defaults, options); 118 119 //set units if otherwise not set 120 if (!this.settings.units || this.settings.units == "auto") { 121 //basically just support for auto units of USA 122 this.settings.units = (["united states", "usa", "united states of america", "us"].indexOf(this.settings.country.toLowerCase()) == -1) ? "metric" : "imperial"; 123 } 124 125 //bound forecast to max of 5 days, api won't return more then that 126 this.settings.forecast = Math.min(this.settings.forecast, 5); 127 128 //store plugin name for local reference 129 this._name = pluginName; 130 131 this.once = false; 132 133 //call initilizaiton 134 this.init(); 135 } 136 ; 137 138 // Avoid Plugin.prototype conflicts 139 $.extend(Plugin.prototype, { 140 init: function () { 141 //if you want the pluging to render markup init will do all the work 142 //otherwise you are on your own 143 if (this.settings.render) { 144 145 //if first run show loading icon (if enabled) 146 if (this.settings.loadingAnimation && !this.once) { 147 //add a loading spinner, animated with css 148 this.loading = $("<div/>", {"id": "flatWeatherLoading", "class": "wi loading"}); 149 this.loading.appendTo(this.element); 150 } 151 152 this.fetchWeather().then(this.render, this.error); 153 154 } 155 this.once = true; //init has happened, can be used to prevent some init tasks happening again 156 }, 157 fetchWeather: function () { 158 //Fetches the weather from the API with an ajax request 159 //Returns a promise of (weather object, this) 160 161 //scope of this for nested functions 162 var that = this; 163 164 //create promise 165 var promise = new $.Deferred(); 166 167 168 //data params to send along with each ajax request 169 //array because some apis may require multiple requests 170 //params[0] is sent to apiurls[api][0] and so on 171 var params = []; 172 173 //build location query string 174 var location = this.settings.location ;//+ " " + this.settings.country; 175 176 //build the paramaters required for specified api 177 if (this.settings.api == "openweathermap") { 178 //openweathermap requires two requests: one for today, another for the forecast. 179 180 //see openweathermap api for details on params passed to api 181 182 //the first request grabs the daily forecast 183 var parameters = {}; 184 if(location){ 185 parameters.q = location; 186 } 187 if(this.settings.zip){ 188 parameters.zip = this.settings.zip; 189 } 190 parameters.lang = this.settings.lang; 191 parameters.units = this.settings.units; 192 if (this.settings.apikey) 193 parameters.appid = this.settings.apikey; 194 195 params.push(parameters); //params for first request url 196 197 //same as the first with added cnt forecast paramater in days 198 //the second request grabs the forecast for the number of days requested 199 parameters.cnt = this.settings.forecast + 1; //plus one to include today 200 params.push(parameters); //params for second request url 201 202 } else if (this.settings.api == "yahoo") { 203 //yahoo weather uses c and f for metric/imperial unit identifiers,
204 //convert our stored text string to match what they expect 205 var u = (this.settings.units == "metric") ? "c" : "f"; 206 207 //see yahoo yql weather api for details on params passed to api 208 var parameters = {}; 209 parameters.q = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text='" + location + "') AND u='" + u + "'"; 210 //parameters.env = "store://datatables.org/alltableswithkeys"; //some sort of api version info... because yahoo. 211 parameters.format = "json"; 212 params.push(parameters); 213 214 } 215 216 217 //for each request send the associated paramaters, then when all are done render all data 218 var requests = []; //requests sent 219 //for each url in apiurls for the api set, send the associated params to it in an ajax request 220 for (var i = 0; i < apiurls[this.settings.api].length; i++) { 221 //jquery ajax request promise 222 requests.push($.get(apiurls[this.settings.api][i], params[i])); 223 224 } 225 226 //when all request promises are done 227 $.when.apply(this, requests) 228 .done(function () { 229 230 //grab the result from the promise as passed by arguments 231 //and convert it to an actual array with slice 232 var args = Array.prototype.slice.call(arguments); 233 234 //remove a layer of nesting for easier use 235 //the [0] element is the result, the rest of the array is 236 //info about the ajax request and promise that we can toss 237 if (requests.length > 1) { 238 //if multiple requests, each promise result of the ajax request is part of an array 239 args = args.map(function (val) { 240 return val[0] 241 }); 242 } else { 243 args = args[0]; 244 } 245 246 247 //check for results that returned http 200 but had errors from api 248 if (that.settings.api == "openweathermap" && !(args[0].cod == "200" && args[1].cod == "200")) { 249 console.log("Error interacting with the openweathermap api see error object below for details:"); 250 console.log(args); 251 promise.reject(args, that); 252 } else if (that.settings.api == "yahoo" && (args.query.count == 0 || args.query.results.channel.description == "Yahoo! Weather Error")) { 253 //yahoo weather really should return a better error checking method. 254 console.log("Error interacting with the yahoo api see error object below for details:"); 255 console.log(args); 256 console.log(requests); 257 promise.reject(args, that); 258 } else { 259 260 //now take that fancy api data and map it to a common format with datamapper function 261 var weather = datamapper(args, that.settings); 262 263 that._weather = weather; //store it on the instance 264 265 $.data(that.element, "weather", weather); //and store it on the DOM for general use 266 267 promise.resolve(weather, that); 268 269 } 270 271 272 }) 273 .fail(function (error) { 274 //TODO draw fails. 275 console.log("fail"); 276 promise.reject(error, that); 277 }); 278 279 return promise; 280 281 }, 282 error: function (error, context) { 283 284 if (!context) { 285 //if called directly and not via plugin we need to set context to this vs passed when a promise 286 context = this; 287 } 288 289 if (context.settings.loadingAnimation && context.settings.render) { 290 context.loading.remove(); //remove loading spinner 291 } 292 293 if (context.settings.api == "openweathermap") { 294 if (error[0].cod != "200") { 295 error = error[0].cod + " " + error[0].message + ". See console log for details."; 296 } else { 297 error = error[1] + " See console log for details."; 298 } 299 } else if (context.settings.api == "yahoo") { 300 301 if (error.query.results) { 302 error = "Error: " + error.query.results.channel.item.title + ". See console log for details."; 303 } else { 304 error = "Error: no results. See console log for details."; 305 } 306 } 307 308 var div = $("<div/>", {"class": "flatWeatherPlugin " + context.settings.view}); 309 $("<h2/>").text("Error").appendTo(div); 310 $("<p/>").text(error).appendTo(div); 311 $(context.element).html(div); //recall that this.element is set in plugin constructor 312 return $(context.element); 313 }, 314 //Generates the DOM elements 315 render: function (weather, context) { 316 317 if (!context) { 318 //if called directly and not via plugin we need to set context to this vs passed when a promise 319 context = this; 320 weather = this._weather; 321 } 322 323 //string showing degree symbol + F or C 324 var degrees = context.settings.units == "metric" ? "°C" : "°F"; 325 326 if (context.settings.loadingAnimation && context.settings.render) { 327 context.loading.remove(); //remove loading spinner 328 } 329 330 //Now that we have everything lets make a dom fragment of our data. 331 //Then append that fragment once to the dom once its all made. 332 //There is a bunch of if switches for various view options but this 333 //is mostly self-explainatory dom generating code from the weather object 334 var div = $("<div/>", {"class": "flatWeatherPlugin " + context.settings.view}); 335 336 if (context.settings.displayCityNameOnly) { 337 //$("<h2/>").text(weather.city).appendTo(div); 338 $("<h2/>").text(context.settings.title).appendTo(div); 339 } else { 340 $("<h2/>").text(weather.location).appendTo(div); 341 } 342 343 344 if (context.settings.view != "forecast") { 345 var today = $("<div/>", {"class": "wiToday"}); 346 var iconGroup = $("<div/>", {"class": "wiIconGroup"}); 347 $("<div/>", {"class": "wi " + "wi" + weather.today.code}).appendTo(iconGroup); 348 if (context.settings.lang == 'fr' && tableDescFr[weather.today.desc.toLowerCase()]) { 349 $("<p/>", {"class": "wiText"}).text(tableDescFr[weather.today.desc.toLowerCase()]).appendTo(iconGroup); 350 } else { 351 $("<p/>", {"class": "wiText"}).text(weather.today.desc).appendTo(iconGroup); 352 } 353 354 iconGroup.appendTo(today); 355 $("<p/>", {"class": "wiTemperature"}).html(weather.today.temp.now + "<sup>" + degrees + "</sup>").appendTo(today); 356 today.appendTo(div); 357 } 358 359 if (context.settings.view != "simple") { 360 var detail = $("<div/>", {"class": "wiDetail"}); 361 362 if (context.settings.view == "partial") { 363 $("<p/>", {"class": "wiDay"}).text(weather.today.day).appendTo(today); 364 } 365 366 if (context.settings.view != "partial") { 367 if (context.settings.view != "today") { 368 $("<p/>", {"class": "wiDay"}).text(weather.today.day).appendTo(detail); 369 } 370 var astro = $("<ul/>", {"class": "astronomy"}).appendTo(detail); 371 $("<li/>", {"class": "wi sunrise"}).text(weather.today.sunrise).appendTo(astro); 372 $("<li/>", {"class": "wi sunset"}).text(weather.today.sunset).appendTo(astro); 373 var temp = $("<ul/>", {"class": "temp"}).appendTo(detail); 374 $("<li/>").html("Max : " + weather.today.temp.max + "<sup>" + degrees + "</sup>").appendTo(temp); 375 $("<li/>").html("Min : " + weather.today.temp.min + "<sup>" + degrees + "</sup>").appendTo(temp); 376 var atmo = $("<ul/>", {"class": "atmosphere"}).appendTo(detail); 377 $("<li/>", {"class": "wi humidity"}).text(weather.today.humidity).appendTo(atmo); 378 $("<li/>", {"class": "wi pressure"}).text(weather.today.pressure).appendTo(atmo); 379 $("<li/>", {"class": "wi wind"}).text(formatWind(weather.today.wind.speed, weather.today.wind.deg, context.settings.units)).appendTo(atmo); 380 detail.appendTo(today); 381 } 382 383 384 if (context.settings.view != "today" || context.settings.view == "forecast") { 385 var forecast = $("<ul/>", {"class": "wiForecasts"}); 386 var startingIndex = (context.settings.view == "forecast") ? 0 : 1; 387 //index should include today for forecast view exclude for other views 388 for (var i = startingIndex; i < weather.forecast.length; i++) { 389 var day = $("<li/>", {"class": "wiDay"}).html("<span>" + weather.forecast[i].day + "</span>").appendTo(forecast); 390 var sub = $("<ul/>", {"class": "wiForecast"}).appendTo(day); 391 $("<li/>", {"class": "wi " + "wi" + weather.forecast[i].code}).appendTo(sub); 392 $("<li/>", {"class": "wiMax"}).html(weather.forecast[i].temp.max + "<sup>" + degrees + "</sup>").appendTo(sub); 393 $("<li/>", {"class": "wiMin"}).html(weather.forecast[i].temp.min + "<sup>" + degrees + "</sup>").appendTo(sub); 394 } 395 forecast.appendTo(div); 396 } 397 } 398 399 400 //now append our dom fragment to the target element 401 $(context.element).html(div); //recall that this.element is set in plugin constructor 402 403 return $(context.element); 404 405 } 406 407 });
408 409 410 //jQuery Constructor 411 // A lightweight plugin wrapper on the jquery fn constructor, 412 // preventing against multiple instantiations on the same element 413 $.fn[pluginName] = function (options, args) { 414 if ($.isFunction(Plugin.prototype[options])) { 415 //enable function access via .flatWeatherPlugin('function', 'args') 416 //grab the plugin instance from the dom reference and call function with any args 417 //return the results of the 418 return this.data("plugin_" + pluginName)[options](args); 419 } 420 //return this for jquery chainability 421 return this.each(function () { 422 //check if plugin has been attached to the dom 423 if (!$.data(this, "plugin_" + pluginName)) { 424 var plugin = new Plugin(this, options); //call constructor 425 return $.data(this, "plugin_" + pluginName, plugin); //attach plugin instance to the dom data 426 } 427 }); 428 }; 429 430 431 /* 432 //datamapper converts raw aka dirty un-standardize data from either api 433 //into a unified format for easier use as follows: 434 { 435 location : String, //as returned back from api 436 today : { 437 temp : { 438 //temperatures are in units requested from api 439 now : Number, ex. 18 440 min : Number, ex. 24 441 max : Number ex. 12 442 }, 443 desc : String, ex. "Partly Cloudy" 444 code : Number, ex. "801" see css or weather codes for meaning 445 wind : { 446 speed : 4, //either km/h or mph 447 deg : Number, //direction in degrees from North 448 }, 449 pressure : Number, //barometric pressure 450 humidity : Number, //% humidity 451 sunrise : Time, 452 sunset : Time, 453 day : String, 454 455 }, 456 forecast : [{Day: String, code:Number, desc: String, temp : {min:number, max:number}}] 457 } 458 //note: input data is in an array of the returned api result request(s) in the same order as setup in the apiurls 459 //All data manipulation and cleaning up happens below 460 //making this was tedious. 461 */ 462 function datamapper(input, settings) { 463 464 var out = {}; //map input to out 465 466 if (settings.api == "openweathermap") { 467 468 //data[0] is current weather, data[1] is forecast 469 if (input[0].name != "") { 470 out.location = input[0].name + ", " + input[0].sys.country; 471 out.city = input[0].name; 472 } else if (input[1].city.name != "") { //sometimes the api doesn't return a location. weird, try the name from second request 473 out.location = input[1].city.name + ", " + input[1].city.country; 474 out.city = input[1].city.name; 475 } else { //still no location? fall back to settings 476 out.location = settings.location + ", " + settings.country; 477 out.city = settings.location; 478 } 479 480 out.today = {}; 481 out.today.temp = {}; 482 out.today.temp.now = Math.round(input[0].main.temp); 483 out.today.temp.min = Math.round(input[0].main.temp_min); 484 out.today.temp.max = Math.round(input[0].main.temp_max); 485 486 out.today.desc = input[0].weather[0].description.capitalize(); 487 out.today.code = input[0].weather[0].id; 488 //no weather id code remapping needed, we will use this as our default weather code system 489 //and convert all other codes to the openweathermap weather code format 490 491 out.today.wind = input[0].wind; 492 out.today.humidity = input[0].main.humidity; 493 out.today.pressure = input[0].main.pressure; 494 out.today.sunrise = epochToHours(input[0].sys.sunrise); 495 out.today.sunset = epochToHours(input[0].sys.sunset); 496 497 out.today.day = getDayString(new Date()); 498 499 out.forecast = []; 500 for (var i = 0; i < settings.forecast; i++) { 501 var forecast = {}; 502 forecast.day = getDayString(new Date(input[1].list[i].dt * 1000)); //api time is in unix epoch 503 forecast.code = input[1].list[i].weather[0].id;
504 forecast.desc = input[1].list[i].weather[0].description.capitalize(); 505 forecast.temp = {max: Math.round(input[1].list[i].temp.max), min: Math.round(input[1].list[i].temp.min)} 506 out.forecast.push(forecast); 507 } 508 509 } else if (settings.api == "yahoo") { 510 511 //key = yahoo code, value = standard code (based on openweathermap codes) 512 var codes = { 513 0: "900", //tornado 514 1: "901", //tropical storm 515 2: "902", //hurricane 516 3: "212", //severe thunderstorms 517 4: "200", //thunderstorms 518 5: "616", //mixed rain and snow 519 6: "612", //mixed rain and sleet 520 7: "611", //mixed snow and sleet 521 8: "511", //freezing drizzle 522 9: "301", //drizzle 523 10: "511", //freezing rain 524 11: "521", //showers 525 12: "521", //showers 526 13: "600", //snow flurries 527 14: "615", //light snow showers 528 15: "601", //blowing snow 529 16: "601", //snow 530 17: "906", //hail 531 18: "611", //sleet 532 19: "761", //dust 533 20: "741", //foggy 534 21: "721", //haze 535 22: "711", //smoky 536 23: "956", //blustery 537 24: "954", //windy 538 25: "903", //cold 539 26: "802", //cloudy 540 27: "802", //mostly cloudy (night) 541 28: "802", //mostly cloudy (day) 542 29: "802", //partly cloudy (night) 543 30: "802", //partly cloudy (day) 544 31: "800", //clear (night) 545 32: "800", //sunny 546 33: "951", //fair (night) 547 34: "951", //fair (day) 548 35: "906", //mixed rain and hail 549 36: "904", //hot 550 37: "210", //isolated thunderstorms 551 38: "210", //scattered thunderstorms 552 39: "210", //scattered thunderstorms 553 40: "521", //scattered showers 554 41: "602", //heavy snow 555 42: "621", //scattered snow showers 556 43: "602", //heavy snow 557 44: "802", //partly cloudy 558 45: "201", //thundershowers 559 46: "621", //snow showers 560 47: "210", //isolated thundershowers 561 3200: "951", //not available... alright... lets make that sunny. 562 } 563 564 input = input.query.results.channel; //get rid of a bunch of silly yahoo nested objects; 565 566 out.location = input.location.city + ", " + input.location.country; 567 out.city = input.location.city; 568 569 out.today = {}; 570 out.today.temp = {}; 571 out.today.temp.now = Math.round(input.item.condition.temp); 572 out.today.temp.min = Math.round(input.item.forecast[0].low); 573 out.today.temp.max = Math.round(input.item.forecast[0].high); 574 575 out.today.desc = input.item.condition.text.capitalize(); 576 out.today.code = codes[input.item.condition.code]; //map weather code 577 578 out.today.wind = {}; 579 out.today.wind.speed = input.wind.speed; 580 out.today.wind.deg = input.wind.direction; 581 out.today.humidity = input.atmosphere.humidity; 582 out.today.pressure = input.atmosphere.pressure; 583 out.today.sunrise = input.astronomy.sunrise.toUpperCase(); 584 out.today.sunset = input.astronomy.sunset.toUpperCase(); 585 586 out.today.day = getDayString(new Date()); 587 588 out.forecast = []; 589 //grab only the number of forecast days desired from settings 590 for (var i = 0; i < settings.forecast; i++) { 591 var forecast = {}; 592 forecast.day = getDayString(new Date(input.item.forecast[i].date)); 593 forecast.code = codes[input.item.forecast[i].code]; //map weather code
594 forecast.desc = input.item.forecast[i].text.capitalize(); 595 forecast.temp = {max: Math.round(input.item.forecast[i].high), min: Math.round(input.item.forecast[i].low)} 596 out.forecast.push(forecast); 597 } 598 } 599 600 return out; 601 602 } 603 ; 604 605 //Helpers 606 String.prototype.capitalize = function () { 607 return this.charAt(0).toUpperCase() + this.slice(1); 608 }; 609 610 //take a date object and return a day string 611 function getDayString(date) { 612 return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()]; 613 } 614 ; 615 616 //converts and epoch time in seconds to hours in the day 617 function epochToHours(date) { 618 date = new Date(date * 1000); 619 var hours = date.getHours(); 620 var minutes = date.getMinutes(); 621 var ampm = hours >= 12 ? 'PM' : 'AM'; 622 hours = hours % 12; 623 hours = hours ? hours : 12; // the hour '0' should be '12' 624 minutes = minutes < 10 ? '0' + minutes : minutes; 625 var strTime = hours + ':' + minutes + ' ' + ampm; 626 return strTime; 627 } 628 ; 629 630 //Takes wind speed, direction in degrees and units 631 //and returns a string ex. (8.5, 270, "metric") returns "W 8.5 km/h" 632 function formatWind(speed, degrees, units) { 633 var wd = degrees; 634 if ((wd >= 0 && wd <= 11.25) || (wd > 348.75 && wd <= 360)) { 635 wd = "N"; 636 } else if (wd > 11.25 && wd <= 33.75) { 637 wd = "NNE"; 638 } else if (wd > 33.75 && wd <= 56.25) { 639 wd = "NE"; 640 } else if (wd > 56.25 && wd <= 78.75) { 641 wd = "ENE"; 642 } else if (wd > 78.75 && wd <= 101.25) { 643 wd = "E"; 644 } else if (wd > 101.25 && wd <= 123.75) { 645 wd = "ESE"; 646 } else if (wd > 123.75 && wd <= 146.25) { 647 wd = "SE"; 648 } else if (wd > 146.25 && wd <= 168.75) { 649 wd = "SSE"; 650 } else if (wd > 168.75 && wd <= 191.25) { 651 wd = "S"; 652 } else if (wd > 191.25 && wd <= 213.75) { 653 wd = "SSW"; 654 } else if (wd > 213.75 && wd <= 236.25) { 655 wd = "SW"; 656 } else if (wd > 236.25 && wd <= 258.75) { 657 wd = "WSW"; 658 } else if (wd > 258.75 && wd <= 281.25) { 659 wd = "W"; 660 } else if (wd > 281.25 && wd <= 303.75) { 661 wd = "WNW"; 662 } else if (wd > 303.75 && wd <= 326.25) { 663 wd = "NW"; 664 } else if (wd > 326.25 && wd <= 348.75) { 665 wd = "NNW"; 666 } 667 var speedUnits = (units == "metric") ? "km/h" : "mph"; 668 return wd + " " + speed + " " + speedUnits; 669 } 670 ; 671 672 673})(jQuery, window, document); 674 675 676
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.