Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

envcanada.js 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /* global WeatherProvider, WeatherObject */
  2. /* Magic Mirror
  3. * Module: Weather
  4. * Provider: Environment Canada (EC)
  5. *
  6. * This class is a provider for Environment Canada MSC Datamart
  7. * Note that this is only for Canadian locations and does not require an API key (access is anonymous)
  8. *
  9. * EC Documentation at following links:
  10. * https://dd.weather.gc.ca/citypage_weather/schema/
  11. * https://eccc-msc.github.io/open-data/msc-datamart/readme_en/
  12. *
  13. * This module supports Canadian locations only and requires 2 additional config parms:
  14. *
  15. * siteCode - the city/town unique identifier for which weather is to be displayed. Format is 's0000000'.
  16. *
  17. * provCode - the 2-character province code for the selected city/town.
  18. *
  19. * Example: for Toronto, Ontario, the following parms would be used
  20. *
  21. * siteCode: 's0000458',
  22. * provCode: 'ON'
  23. *
  24. * To determine the siteCode and provCode values for a Canadian city/town, look at the Environment Canada document
  25. * at https://dd.weather.gc.ca/citypage_weather/docs/site_list_en.csv (or site_list_fr.csv). There you will find a table
  26. * with locations you can search under column B (English Names), with the corresponding siteCode under
  27. * column A (Codes) and provCode under column C (Province).
  28. *
  29. * Original by Kevin Godin
  30. *
  31. * License to use Environment Canada (EC) data is detailed here:
  32. * https://eccc-msc.github.io/open-data/licence/readme_en/
  33. *
  34. */
  35. WeatherProvider.register("envcanada", {
  36. // Set the name of the provider for debugging and alerting purposes (eg. provide eye-catcher)
  37. providerName: "Environment Canada",
  38. // Set the default config properties that is specific to this provider
  39. defaults: {
  40. siteCode: "s1234567",
  41. provCode: "ON"
  42. },
  43. //
  44. // Set config values (equates to weather module config values). Also set values pertaining to caching of
  45. // Today's temperature forecast (for use in the Forecast functions below)
  46. //
  47. setConfig: function (config) {
  48. this.config = config;
  49. this.todayTempCacheMin = 0;
  50. this.todayTempCacheMax = 0;
  51. this.todayCached = false;
  52. this.cacheCurrentTemp = 999;
  53. },
  54. //
  55. // Called when the weather provider is started
  56. //
  57. start: function () {
  58. Log.info(`Weather provider: ${this.providerName} started.`);
  59. this.setFetchedLocation(this.config.location);
  60. // Ensure kmH are ignored since these are custom-handled by this Provider
  61. this.config.useKmh = false;
  62. },
  63. //
  64. // Override the fetchCurrentWeather method to query EC and construct a Current weather object
  65. //
  66. fetchCurrentWeather() {
  67. this.fetchData(this.getUrl(), "GET")
  68. .then((data) => {
  69. if (!data) {
  70. // Did not receive usable new data.
  71. return;
  72. }
  73. const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
  74. this.setCurrentWeather(currentWeather);
  75. })
  76. .catch(function (request) {
  77. Log.error("Could not load EnvCanada site data ... ", request);
  78. })
  79. .finally(() => this.updateAvailable());
  80. },
  81. //
  82. // Override the fetchWeatherForecast method to query EC and construct Forecast weather objects
  83. //
  84. fetchWeatherForecast() {
  85. this.fetchData(this.getUrl(), "GET")
  86. .then((data) => {
  87. if (!data) {
  88. // Did not receive usable new data.
  89. return;
  90. }
  91. const forecastWeather = this.generateWeatherObjectsFromForecast(data);
  92. this.setWeatherForecast(forecastWeather);
  93. })
  94. .catch(function (request) {
  95. Log.error("Could not load EnvCanada forecast data ... ", request);
  96. })
  97. .finally(() => this.updateAvailable());
  98. },
  99. //
  100. // Override the fetchWeatherHourly method to query EC and construct Forecast weather objects
  101. //
  102. fetchWeatherHourly() {
  103. this.fetchData(this.getUrl(), "GET")
  104. .then((data) => {
  105. if (!data) {
  106. // Did not receive usable new data.
  107. return;
  108. }
  109. const hourlyWeather = this.generateWeatherObjectsFromHourly(data);
  110. this.setWeatherHourly(hourlyWeather);
  111. })
  112. .catch(function (request) {
  113. Log.error("Could not load EnvCanada hourly data ... ", request);
  114. })
  115. .finally(() => this.updateAvailable());
  116. },
  117. //
  118. // Override fetchData function to handle XML document (base function assumes JSON)
  119. //
  120. fetchData: function (url, method = "GET", data = null) {
  121. return new Promise(function (resolve, reject) {
  122. const request = new XMLHttpRequest();
  123. request.open(method, url, true);
  124. request.onreadystatechange = function () {
  125. if (this.readyState === 4) {
  126. if (this.status === 200) {
  127. resolve(this.responseXML);
  128. } else {
  129. reject(request);
  130. }
  131. }
  132. };
  133. request.send();
  134. });
  135. },
  136. //////////////////////////////////////////////////////////////////////////////////
  137. //
  138. // Environment Canada methods - not part of the standard Provider methods
  139. //
  140. //////////////////////////////////////////////////////////////////////////////////
  141. //
  142. // Build the EC URL based on the Site Code and Province Code specified in the config parms. Note that the
  143. // URL defaults to the Englsih version simply because there is no language dependancy in the data
  144. // being accessed. This is only pertinent when using the EC data elements that contain a textual forecast.
  145. //
  146. // Also note that access is supported through a proxy service (thingproxy.freeboard.io) to mitigate
  147. // CORS errors when accessing EC
  148. //
  149. getUrl() {
  150. return "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml";
  151. },
  152. //
  153. // Generate a WeatherObject based on current EC weather conditions
  154. //
  155. generateWeatherObjectFromCurrentWeather(ECdoc) {
  156. const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
  157. // There are instances where EC will update weather data and current temperature will not be
  158. // provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp
  159. // of NaN being displayed. Therefore... whenever we get a valid current temp from EC, we will cache
  160. // the value. Whenever EC data is missing current temp, we will provide the cached value
  161. // instead. This is reasonable since the cached value will typically be accurate within the previous
  162. // hour. The only time this does not work as expected is when MM is restarted and the first query to
  163. // EC finds no current temp. In this scenario, MM will end up displaying a current temp of null;
  164. if (ECdoc.querySelector("siteData currentConditions temperature").textContent) {
  165. currentWeather.temperature = this.convertTemp(ECdoc.querySelector("siteData currentConditions temperature").textContent);
  166. this.cacheCurrentTemp = currentWeather.temperature;
  167. } else {
  168. currentWeather.temperature = this.cacheCurrentTemp;
  169. }
  170. currentWeather.windSpeed = this.convertWind(ECdoc.querySelector("siteData currentConditions wind speed").textContent);
  171. currentWeather.windDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
  172. currentWeather.humidity = ECdoc.querySelector("siteData currentConditions relativeHumidity").textContent;
  173. // Ensure showPrecipitationAmount is forced to false. EC does not really provide POP for current day
  174. // and this feature for the weather module (current only) is sort of broken in that it wants
  175. // to say POP but will display precip as an accumulated amount vs. a percentage.
  176. this.config.showPrecipitationAmount = false;
  177. //
  178. // If the module config wants to showFeelsLike... default to the current temperature.
  179. // Check for EC wind chill and humidex values and overwrite the feelsLikeTemp value.
  180. // This assumes that the EC current conditions will never contain both a wind chill
  181. // and humidex temperature.
  182. //
  183. if (this.config.showFeelsLike) {
  184. currentWeather.feelsLikeTemp = currentWeather.temperature;
  185. if (ECdoc.querySelector("siteData currentConditions windChill")) {
  186. currentWeather.feelsLikeTemp = this.convertTemp(ECdoc.querySelector("siteData currentConditions windChill").textContent);
  187. }
  188. if (ECdoc.querySelector("siteData currentConditions humidex")) {
  189. currentWeather.feelsLikeTemp = this.convertTemp(ECdoc.querySelector("siteData currentConditions humidex").textContent);
  190. }
  191. }
  192. //
  193. // Need to map EC weather icon to MM weatherType values
  194. //
  195. currentWeather.weatherType = this.convertWeatherType(ECdoc.querySelector("siteData currentConditions iconCode").textContent);
  196. //
  197. // Capture the sunrise and sunset values from EC data
  198. //
  199. const sunList = ECdoc.querySelectorAll("siteData riseSet dateTime");
  200. currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
  201. currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
  202. return currentWeather;
  203. },
  204. //
  205. // Generate an array of WeatherObjects based on EC weather forecast
  206. //
  207. generateWeatherObjectsFromForecast(ECdoc) {
  208. // Declare an array to hold each day's forecast object
  209. const days = [];
  210. const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
  211. const foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime");
  212. const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent;
  213. weather.date = moment(baseDate, "YYYYMMDDhhmmss");
  214. const foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast");
  215. // For simplicity, we will only accumulate precipitation and will not try to break out
  216. // rain vs snow accumulations
  217. weather.rain = null;
  218. weather.snow = null;
  219. weather.precipitation = null;
  220. //
  221. // The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing
  222. // 2 elements. the first element for a day details the Today (daytime) forecast while the second
  223. // element details the Tonight (nightime) forecast. Element 0 is always for the current day.
  224. //
  225. // However... the forecast is somewhat 'rolling'.
  226. //
  227. // If the EC forecast is queried in the morning, then Element 0 will contain Current
  228. // Today and Element 1 will contain Current Tonight. From there, the next 5 days of forecast will be
  229. // contained in Elements 2/3, 4/5, 6/7, 8/9, and 10/11. This module will create a 6-day forecast using
  230. // all of these Elements.
  231. //
  232. // But, if the EC forecast is queried in late afternoon, the Current Today forecast will be rolled
  233. // off and Element 0 will contain Current Tonight. From there, the next 5 days will be contained in
  234. // Elements 1/2, 3/4, 5/6, 7/8, and 9/10. As well, Elelement 11 will contain a forecast for a 6th day,
  235. // but only for the Today portion (not Tonight). This module will create a 6-day forecast using
  236. // Elements 0 to 11, and will ignore the additional Todat forecast in Element 11.
  237. //
  238. // We need to determine if Element 0 is showing the forecast for Current Today or Current Tonight.
  239. // This is required to understand how Min and Max temperature will be determined, and to understand
  240. // where the next day's (aka Tomorrow's) forecast is located in the forecast array.
  241. //
  242. let nextDay = 0;
  243. let lastDay = 0;
  244. const currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent;
  245. //
  246. // If the first Element is Current Today, look at Current Today and Current Tonight for the current day.
  247. //
  248. if (foreGroup[0].querySelector("period[textForecastName='Today']")) {
  249. this.todaytempCacheMin = 0;
  250. this.todaytempCacheMax = 0;
  251. this.todayCached = true;
  252. this.setMinMaxTemps(weather, foreGroup, 0, true, currentTemp);
  253. this.setPrecipitation(weather, foreGroup, 0);
  254. //
  255. // Set the Element number that will reflect where the next day's forecast is located. Also set
  256. // the Element number where the end of the forecast will be. This is important because of the
  257. // rolling nature of the EC forecast. In the current scenario (Today and Tonight are present
  258. // in elements 0 and 11, we know that we will have 6 full days of forecasts and we will use
  259. // them. We will set lastDay such that we iterate through all 12 elements of the forecast.
  260. //
  261. nextDay = 2;
  262. lastDay = 12;
  263. }
  264. //
  265. // If the first Element is Current Tonight, look at Tonight only for the current day.
  266. //
  267. if (foreGroup[0].querySelector("period[textForecastName='Tonight']")) {
  268. this.setMinMaxTemps(weather, foreGroup, 0, false, currentTemp);
  269. this.setPrecipitation(weather, foreGroup, 0);
  270. //
  271. // Set the Element number that will reflect where the next day's forecast is located. Also set
  272. // the Element number where the end of the forecast will be. This is important because of the
  273. // rolling nature of the EC forecast. In the current scenario (only Current Tonight is present
  274. // in Element 0, we know that we will have 6 full days of forecasts PLUS a half-day and
  275. // forecast in the final element. Because we will only use full day forecasts, we set the
  276. // lastDay number to ensure we ignore that final half-day (in forecast Element 11).
  277. //
  278. nextDay = 1;
  279. lastDay = 11;
  280. }
  281. //
  282. // Need to map EC weather icon to MM weatherType values. Always pick the first Element's icon to
  283. // reflect either Today or Tonight depending on what the forecast is showing in Element 0.
  284. //
  285. weather.weatherType = this.convertWeatherType(foreGroup[0].querySelector("abbreviatedForecast iconCode").textContent);
  286. // Push the weather object into the forecast array.
  287. days.push(weather);
  288. //
  289. // Now do the the rest of the forecast starting at nextDay. We will process each day using 2 EC
  290. // forecast Elements. This will address the fact that the EC forecast always includes Today and
  291. // Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each
  292. // iteration looking at the current Element and the next Element.
  293. //
  294. let lastDate = moment(baseDate, "YYYYMMDDhhmmss");
  295. for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) {
  296. let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
  297. // Add 1 to the date to reflect the current forecast day we are building
  298. lastDate = lastDate.add(1, "day");
  299. weather.date = moment(lastDate, "X");
  300. // Capture the temperatures for the current Element and the next Element in order to set
  301. // the Min and Max temperatures for the forecast
  302. this.setMinMaxTemps(weather, foreGroup, stepDay, true, currentTemp);
  303. weather.rain = null;
  304. weather.snow = null;
  305. weather.precipitation = null;
  306. this.setPrecipitation(weather, foreGroup, stepDay);
  307. //
  308. // Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
  309. //
  310. weather.weatherType = this.convertWeatherType(foreGroup[stepDay].querySelector("abbreviatedForecast iconCode").textContent);
  311. // Push the weather object into the forecast array.
  312. days.push(weather);
  313. }
  314. return days;
  315. },
  316. //
  317. // Generate an array of WeatherObjects based on EC hourly weather forecast
  318. //
  319. generateWeatherObjectsFromHourly(ECdoc) {
  320. // Declare an array to hold each hour's forecast object
  321. const hours = [];
  322. // Get local timezone UTC offset so that each hourly time can be calculated properly
  323. const baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime");
  324. const hourOffset = baseHours[1].getAttribute("UTCOffset");
  325. //
  326. // The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding
  327. // the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours.
  328. //
  329. const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
  330. for (let stepHour = 0; stepHour < 24; stepHour += 1) {
  331. const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
  332. // Determine local time by applying UTC offset to the forecast timestamp
  333. const foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss");
  334. const currTime = foreTime.add(hourOffset, "hours");
  335. weather.date = moment(currTime, "X");
  336. // Capture the temperature
  337. weather.temperature = this.convertTemp(hourGroup[stepHour].querySelector("temperature").textContent);
  338. // Capture Likelihood of Precipitation (LOP) and unit-of-measure values
  339. const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0;
  340. if (precipLOP > 0) {
  341. weather.precipitation = precipLOP;
  342. weather.precipitationUnits = hourGroup[stepHour].querySelector("lop").getAttribute("units");
  343. }
  344. //
  345. // Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
  346. //
  347. weather.weatherType = this.convertWeatherType(hourGroup[stepHour].querySelector("iconCode").textContent);
  348. // Push the weather object into the forecast array.
  349. hours.push(weather);
  350. }
  351. return hours;
  352. },
  353. //
  354. // Determine Min and Max temp based on a supplied Forecast Element index and a boolen that denotes if
  355. // the next Forecast element should be considered - i.e. look at Today *and* Tonight vs.Tonight-only
  356. //
  357. setMinMaxTemps(weather, foreGroup, today, fullDay, currentTemp) {
  358. const todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent;
  359. const todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class");
  360. //
  361. // The following logic is largely aimed at accommodating the Current day's forecast whereby we
  362. // can have either Current Today+Current Tonight or only Current Tonight.
  363. //
  364. // If fullDay is false, then we only have Tonight for the current day's forecast - meaning we have
  365. // lost a min or max temp value for the day. Therefore, we will see if we were able to cache the the
  366. // Today forecast for the current day. If we have, we will use them. If we do not have the cached values,
  367. // it means that MM or the Computer has been restarted since the time EC rolled off Today from the
  368. // forecast. In this scenario, we will simply default to the Current Conditions temperature and then
  369. // check the Tonight temperature.
  370. //
  371. if (fullDay === false) {
  372. if (this.todayCached === true) {
  373. weather.minTemperature = this.todayTempCacheMin;
  374. weather.maxTemperature = this.todayTempCacheMax;
  375. } else {
  376. weather.minTemperature = this.convertTemp(currentTemp);
  377. weather.maxTemperature = weather.minTemperature;
  378. }
  379. }
  380. //
  381. // We will check to see if the current Element's temperature is Low or High and set weather values
  382. // accordingly. We will also check the condition where fullDay is true *and* we are looking at forecast
  383. // element 0. This is a special case where we will cache temperature values so that we have them later
  384. // in the current day when the Current Today element rolls off and we have Current Tonight only.
  385. //
  386. if (todayClass === "low") {
  387. weather.minTemperature = this.convertTemp(todayTemp);
  388. if (today === 0 && fullDay === true) {
  389. this.todayTempCacheMin = weather.minTemperature;
  390. }
  391. }
  392. if (todayClass === "high") {
  393. weather.maxTemperature = this.convertTemp(todayTemp);
  394. if (today === 0 && fullDay === true) {
  395. this.todayTempCacheMax = weather.maxTemperature;
  396. }
  397. }
  398. const nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent;
  399. const nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class");
  400. if (fullDay === true) {
  401. if (nextClass === "low") {
  402. weather.minTemperature = this.convertTemp(nextTemp);
  403. }
  404. if (nextClass === "high") {
  405. weather.maxTemperature = this.convertTemp(nextTemp);
  406. }
  407. }
  408. },
  409. //
  410. // Check for a Precipitation forecast. EC can provide a forecast in 2 ways: either an accumulation figure
  411. // or a POP percentage. If there is a POP, then that is what the module will show. If there is an accumulation,
  412. // then it will be displayed ONLY if no POP is present.
  413. //
  414. // POP Logic: By default, we want to show the POP for 'daytime' since we are presuming that is what
  415. // people are more interested in seeing. While EC provides a separate POP for daytime and nightime portions
  416. // of each day, the weather module does not really allow for that view of a daily forecast. There we will
  417. // ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
  418. // the nightime forecast after a certain point in the afternoon. As such, we will be showing the nightime POP
  419. // (if one exists) in that specific scenario.
  420. //
  421. // Accumulation Logic: Similar to POP, we want to show accumulation for 'daytime' since we presume that is what
  422. // people are interested in seeing. While EC provides a separate accumulation for daytime and nightime portions
  423. // of each day, the weather module does not really allow for that view of a daily forecast. There we will
  424. // ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
  425. // the nightime forecast after a certain point in that specific scenario.
  426. //
  427. setPrecipitation(weather, foreGroup, today) {
  428. if (foreGroup[today].querySelector("precipitation accumulation")) {
  429. weather.precipitation = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0;
  430. weather.precipitationUnits = " " + foreGroup[today].querySelector("precipitation accumulation amount").getAttribute("units");
  431. if (this.config.units === "imperial") {
  432. if (weather.precipitationUnits === " cm") {
  433. weather.precipitation = (weather.precipitation * 0.394).toFixed(2);
  434. weather.precipitationUnits = " in";
  435. }
  436. if (weather.precipitationUnits === " mm") {
  437. weather.precipitation = (weather.precipitation * 0.0394).toFixed(2);
  438. weather.precipitationUnits = " in";
  439. }
  440. }
  441. }
  442. // Check Today element for POP
  443. if (foreGroup[today].querySelector("abbreviatedForecast pop").textContent > 0) {
  444. weather.precipitation = foreGroup[today].querySelector("abbreviatedForecast pop").textContent;
  445. weather.precipitationUnits = foreGroup[today].querySelector("abbreviatedForecast pop").getAttribute("units");
  446. }
  447. },
  448. //
  449. // Unit conversions
  450. //
  451. //
  452. // Convert C to F temps
  453. //
  454. convertTemp(temp) {
  455. if (this.config.tempUnits === "imperial") {
  456. return 1.8 * temp + 32;
  457. } else {
  458. return temp;
  459. }
  460. },
  461. //
  462. // Convert km/h to mph
  463. //
  464. convertWind(kilo) {
  465. if (this.config.windUnits === "imperial") {
  466. return kilo / 1.609344;
  467. } else {
  468. return kilo;
  469. }
  470. },
  471. //
  472. // Convert the icons to a more usable name.
  473. //
  474. convertWeatherType(weatherType) {
  475. const weatherTypes = {
  476. "00": "day-sunny",
  477. "01": "day-sunny",
  478. "02": "day-sunny-overcast",
  479. "03": "day-cloudy",
  480. "04": "day-cloudy",
  481. "05": "day-cloudy",
  482. "06": "day-sprinkle",
  483. "07": "day-showers",
  484. "08": "day-snow",
  485. "09": "day-thunderstorm",
  486. 10: "cloud",
  487. 11: "showers",
  488. 12: "rain",
  489. 13: "rain",
  490. 14: "sleet",
  491. 15: "sleet",
  492. 16: "snow",
  493. 17: "snow",
  494. 18: "snow",
  495. 19: "thunderstorm",
  496. 20: "cloudy",
  497. 21: "cloudy",
  498. 22: "day-cloudy",
  499. 23: "day-haze",
  500. 24: "fog",
  501. 25: "snow-wind",
  502. 26: "sleet",
  503. 27: "sleet",
  504. 28: "rain",
  505. 29: "na",
  506. 30: "night-clear",
  507. 31: "night-clear",
  508. 32: "night-partly-cloudy",
  509. 33: "night-alt-cloudy",
  510. 34: "night-alt-cloudy",
  511. 35: "night-partly-cloudy",
  512. 36: "night-alt-showers",
  513. 37: "night-rain-mix",
  514. 38: "night-alt-snow",
  515. 39: "night-thunderstorm",
  516. 40: "snow-wind",
  517. 41: "tornado",
  518. 42: "tornado",
  519. 43: "windy",
  520. 44: "smoke",
  521. 45: "sandstorm",
  522. 46: "thunderstorm",
  523. 47: "thunderstorm",
  524. 48: "tornado"
  525. };
  526. return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
  527. }
  528. });