PageSourceSearch

https://www.ville-caromb.fr/typo3conf/ext/arc_projet/Resources/Pub…/js/jquery.flatWeatherPlugin.js?1677853379

js ville-caromb.fr collected 2026-10-02 11:10:16 UTC 30,854 bytes, 676 lines download raw bytes

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" ? "&#176;C" : "&#176;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.