mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 03:39:55 +00:00
## [2.26.0] - 01-01-2024 Thanks to: @bnitkin, @bugsounet, @dependabot, @jkriegshauser, @kaennchenstruggle, @KristjanESPERANTO and @Ybbet. Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you guys! You are awesome! This release also marks the latest release by Michael Teeuw. For more info, please read the following post: [A New Chapter for MagicMirror: The Community Takes the Lead](https://forum.magicmirror.builders/topic/18329/a-new-chapter-for-magicmirror-the-community-takes-the-lead). ### Added - Added update notification updater (for 3rd party modules) - Added node 21 to the test matrix - Added transform object to calendar:customEvents - Added ESLint rules for jest (including jest/expect-expect and jest/no-done-callback) ### Removed - Removed Codecov workflow (not working anymore, other workflow required) (#3107) - Removed titleReplace from calendar, replaced + extended by customEvents (backward compatibility included) (#3249) - Removed failing unit test (#3254) - Removed some unused variables ### Updated - Update electron to v27 and update other dependencies as well as github actions - Update newsfeed: Use `html-to-text` instead of regex for transform description - Review ESLint config (#3269) - Updated dependencies - Clock module: optionally display current moon phase in addition to rise/set times - electron is now per default started without gpu, if needed it must be enabled with new env var `ELECTRON_ENABLE_GPU=1` on startup (#3226) - Replace prettier by stylistic in ESLint config to lint JavaScript (and disable some rules for `config/config.js*` files) - Update node-ical to v0.17.1 and fix tests ### Fixed - Avoid fade out/in on updateDom when many calendars are used - Fix the option eventClass on customEvents. - Fix yr API version in locationforecast and sunrise call (#3227) - Fix cloneObject() function to respect RegExp (#3237) - Fix newsfeed module for feeds using "a10:updated" tag (#3238) - Fix issue template (#3167) - Fix #3256 filter out bad results from rrule.between - Fix calendar events sometimes not respecting deleted events (#3250) - Fix electron loadurl locally on Windows when address "0.0.0.0" (#2550) - Fix updatanotification (update_helper.js): catch error if reponse is not an JSON format (check PM2) - Fix missing typeof in calendar module - Fix style issues after prettier update - Fix calendar test (#3291) by moving "Exdate check" from e2e to electron to run on a Thursday - Fix calendar config params `fetchInterval` and `excludedEvents` were never used from single calendar config (#3297) - Fix MM_PORT variable not used in electron and allow full path for MM_CONFIG_FILE variable (#3302) --------- Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Malte Hallström <46646495+SkySails@users.noreply.github.com> Co-authored-by: Veeck <github@veeck.de> Co-authored-by: veeck <michael@veeck.de> Co-authored-by: dWoolridge <dwoolridge@charter.net> Co-authored-by: Johan <jojjepersson@yahoo.se> Co-authored-by: Dario Mratovich <dario_mratovich@hotmail.com> Co-authored-by: Dario Mratovich <dario.mratovich@outlook.com> Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com> Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com> Co-authored-by: buxxi <buxxi@omfilm.net> Co-authored-by: Thomas Hirschberger <47733292+Tom-Hirschberger@users.noreply.github.com> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com> Co-authored-by: Dave Child <dave@addedbytes.com> Co-authored-by: grenagit <46225780+grenagit@users.noreply.github.com> Co-authored-by: Grena <grena@grenabox.fr> Co-authored-by: Magnus Marthinsen <magmar@online.no> Co-authored-by: Patrick <psieg@users.noreply.github.com> Co-authored-by: Piotr Rajnisz <56397164+rajniszp@users.noreply.github.com> Co-authored-by: Suthep Yonphimai <tomzt@users.noreply.github.com> Co-authored-by: CarJem Generations (Carter Wallace) <cwallacecs@gmail.com> Co-authored-by: Nicholas Fogal <nfogal.misc@gmail.com> Co-authored-by: JakeBinney <126349119+JakeBinney@users.noreply.github.com> Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com> Co-authored-by: Oscar Björkman <17575446+oscarb@users.noreply.github.com> Co-authored-by: Ismar Slomic <ismar@slomic.no> Co-authored-by: Jørgen Veum-Wahlberg <jorgen.wahlberg@amedia.no> Co-authored-by: Eddie Hung <6740044+eddiehung@users.noreply.github.com> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: bugsounet <bugsounet@bugsounet.fr> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Knapoc <Knapoc@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: veeck <michael.veeck@nebenan.de> Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com> Co-authored-by: NolanKingdon <27908974+NolanKingdon@users.noreply.github.com> Co-authored-by: J. Kenzal Hunter <kenzal.hunter@gmail.com> Co-authored-by: Teddy <teddy.payet@gmail.com> Co-authored-by: TeddyStarinvest <teddy.payet@starinvest.com> Co-authored-by: martingron <61826403+martingron@users.noreply.github.com> Co-authored-by: dgoth <132394363+dgoth@users.noreply.github.com> Co-authored-by: kaennchenstruggle <54073894+kaennchenstruggle@users.noreply.github.com> Co-authored-by: jkriegshauser <jkriegshauser@gmail.com> Co-authored-by: Ben Nitkin <ben@nitkin.net>
573 lines
21 KiB
JavaScript
573 lines
21 KiB
JavaScript
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
|
|
|
/* MagicMirror²
|
|
* Module: Weather
|
|
* Provider: Environment Canada (EC)
|
|
*
|
|
* This class is a provider for Environment Canada MSC Datamart
|
|
* Note that this is only for Canadian locations and does not require an API key (access is anonymous)
|
|
*
|
|
* EC Documentation at following links:
|
|
* https://dd.weather.gc.ca/citypage_weather/schema/
|
|
* https://eccc-msc.github.io/open-data/msc-datamart/readme_en/
|
|
*
|
|
* This module supports Canadian locations only and requires 2 additional config parameters:
|
|
*
|
|
* siteCode - the city/town unique identifier for which weather is to be displayed. Format is 's0000000'.
|
|
*
|
|
* provCode - the 2-character province code for the selected city/town.
|
|
*
|
|
* Example: for Toronto, Ontario, the following parameters would be used
|
|
*
|
|
* siteCode: 's0000458',
|
|
* provCode: 'ON'
|
|
*
|
|
* To determine the siteCode and provCode values for a Canadian city/town, look at the Environment Canada document
|
|
* at https://dd.weather.gc.ca/citypage_weather/docs/site_list_en.csv (or site_list_fr.csv). There you will find a table
|
|
* with locations you can search under column B (English Names), with the corresponding siteCode under
|
|
* column A (Codes) and provCode under column C (Province).
|
|
*
|
|
* Original by Kevin Godin
|
|
*
|
|
* License to use Environment Canada (EC) data is detailed here:
|
|
* https://eccc-msc.github.io/open-data/licence/readme_en/
|
|
*
|
|
*/
|
|
|
|
WeatherProvider.register("envcanada", {
|
|
// Set the name of the provider for debugging and alerting purposes (eg. provide eye-catcher)
|
|
providerName: "Environment Canada",
|
|
|
|
// Set the default config properties that is specific to this provider
|
|
defaults: {
|
|
useCorsProxy: true,
|
|
siteCode: "s1234567",
|
|
provCode: "ON"
|
|
},
|
|
|
|
//
|
|
// Set config values (equates to weather module config values). Also set values pertaining to caching of
|
|
// Today's temperature forecast (for use in the Forecast functions below)
|
|
//
|
|
setConfig (config) {
|
|
this.config = config;
|
|
|
|
this.todayTempCacheMin = 0;
|
|
this.todayTempCacheMax = 0;
|
|
this.todayCached = false;
|
|
this.cacheCurrentTemp = 999;
|
|
},
|
|
|
|
//
|
|
// Called when the weather provider is started
|
|
//
|
|
start () {
|
|
Log.info(`Weather provider: ${this.providerName} started.`);
|
|
this.setFetchedLocation(this.config.location);
|
|
},
|
|
|
|
//
|
|
// Override the fetchCurrentWeather method to query EC and construct a Current weather object
|
|
//
|
|
fetchCurrentWeather () {
|
|
this.fetchData(this.getUrl(), "xml")
|
|
.then((data) => {
|
|
if (!data) {
|
|
// Did not receive usable new data.
|
|
return;
|
|
}
|
|
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
|
|
|
this.setCurrentWeather(currentWeather);
|
|
})
|
|
.catch(function (request) {
|
|
Log.error("Could not load EnvCanada site data ... ", request);
|
|
})
|
|
.finally(() => this.updateAvailable());
|
|
},
|
|
|
|
//
|
|
// Override the fetchWeatherForecast method to query EC and construct Forecast weather objects
|
|
//
|
|
fetchWeatherForecast () {
|
|
this.fetchData(this.getUrl(), "xml")
|
|
.then((data) => {
|
|
if (!data) {
|
|
// Did not receive usable new data.
|
|
return;
|
|
}
|
|
const forecastWeather = this.generateWeatherObjectsFromForecast(data);
|
|
|
|
this.setWeatherForecast(forecastWeather);
|
|
})
|
|
.catch(function (request) {
|
|
Log.error("Could not load EnvCanada forecast data ... ", request);
|
|
})
|
|
.finally(() => this.updateAvailable());
|
|
},
|
|
|
|
//
|
|
// Override the fetchWeatherHourly method to query EC and construct Forecast weather objects
|
|
//
|
|
fetchWeatherHourly () {
|
|
this.fetchData(this.getUrl(), "xml")
|
|
.then((data) => {
|
|
if (!data) {
|
|
// Did not receive usable new data.
|
|
return;
|
|
}
|
|
const hourlyWeather = this.generateWeatherObjectsFromHourly(data);
|
|
|
|
this.setWeatherHourly(hourlyWeather);
|
|
})
|
|
.catch(function (request) {
|
|
Log.error("Could not load EnvCanada hourly data ... ", request);
|
|
})
|
|
.finally(() => this.updateAvailable());
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Environment Canada methods - not part of the standard Provider methods
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Build the EC URL based on the Site Code and Province Code specified in the config params. Note that the
|
|
// URL defaults to the English version simply because there is no language dependency in the data
|
|
// being accessed. This is only pertinent when using the EC data elements that contain a textual forecast.
|
|
//
|
|
getUrl () {
|
|
return `https://dd.weather.gc.ca/citypage_weather/xml/${this.config.provCode}/${this.config.siteCode}_e.xml`;
|
|
},
|
|
|
|
//
|
|
// Generate a WeatherObject based on current EC weather conditions
|
|
//
|
|
|
|
generateWeatherObjectFromCurrentWeather (ECdoc) {
|
|
const currentWeather = new WeatherObject();
|
|
|
|
// There are instances where EC will update weather data and current temperature will not be
|
|
// provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp
|
|
// of NaN being displayed. Therefore... whenever we get a valid current temp from EC, we will cache
|
|
// the value. Whenever EC data is missing current temp, we will provide the cached value
|
|
// instead. This is reasonable since the cached value will typically be accurate within the previous
|
|
// hour. The only time this does not work as expected is when MM is restarted and the first query to
|
|
// EC finds no current temp. In this scenario, MM will end up displaying a current temp of null;
|
|
|
|
if (ECdoc.querySelector("siteData currentConditions temperature").textContent) {
|
|
currentWeather.temperature = ECdoc.querySelector("siteData currentConditions temperature").textContent;
|
|
this.cacheCurrentTemp = currentWeather.temperature;
|
|
} else {
|
|
currentWeather.temperature = this.cacheCurrentTemp;
|
|
}
|
|
|
|
currentWeather.windSpeed = WeatherUtils.convertWindToMs(ECdoc.querySelector("siteData currentConditions wind speed").textContent);
|
|
|
|
currentWeather.windFromDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
|
|
|
|
currentWeather.humidity = ECdoc.querySelector("siteData currentConditions relativeHumidity").textContent;
|
|
|
|
// Ensure showPrecipitationAmount is forced to false. EC does not really provide POP for current day
|
|
// and this feature for the weather module (current only) is sort of broken in that it wants
|
|
// to say POP but will display precip as an accumulated amount vs. a percentage.
|
|
|
|
this.config.showPrecipitationAmount = false;
|
|
|
|
//
|
|
// If the module config wants to showFeelsLike... default to the current temperature.
|
|
// Check for EC wind chill and humidex values and overwrite the feelsLikeTemp value.
|
|
// This assumes that the EC current conditions will never contain both a wind chill
|
|
// and humidex temperature.
|
|
//
|
|
|
|
if (this.config.showFeelsLike) {
|
|
currentWeather.feelsLikeTemp = currentWeather.temperature;
|
|
|
|
if (ECdoc.querySelector("siteData currentConditions windChill")) {
|
|
currentWeather.feelsLikeTemp = ECdoc.querySelector("siteData currentConditions windChill").textContent;
|
|
}
|
|
|
|
if (ECdoc.querySelector("siteData currentConditions humidex")) {
|
|
currentWeather.feelsLikeTemp = ECdoc.querySelector("siteData currentConditions humidex").textContent;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Need to map EC weather icon to MM weatherType values
|
|
//
|
|
|
|
currentWeather.weatherType = this.convertWeatherType(ECdoc.querySelector("siteData currentConditions iconCode").textContent);
|
|
|
|
//
|
|
// Capture the sunrise and sunset values from EC data
|
|
//
|
|
|
|
const sunList = ECdoc.querySelectorAll("siteData riseSet dateTime");
|
|
|
|
currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
|
|
currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
|
|
|
|
return currentWeather;
|
|
},
|
|
|
|
//
|
|
// Generate an array of WeatherObjects based on EC weather forecast
|
|
//
|
|
|
|
generateWeatherObjectsFromForecast (ECdoc) {
|
|
// Declare an array to hold each day's forecast object
|
|
|
|
const days = [];
|
|
|
|
const weather = new WeatherObject();
|
|
|
|
const foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime");
|
|
const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent;
|
|
|
|
weather.date = moment(baseDate, "YYYYMMDDhhmmss");
|
|
|
|
const foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast");
|
|
|
|
weather.precipitationAmount = null;
|
|
|
|
//
|
|
// The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing
|
|
// 2 elements. the first element for a day details the Today (daytime) forecast while the second
|
|
// element details the Tonight (nightime) forecast. Element 0 is always for the current day.
|
|
//
|
|
// However... the forecast is somewhat 'rolling'.
|
|
//
|
|
// If the EC forecast is queried in the morning, then Element 0 will contain Current
|
|
// Today and Element 1 will contain Current Tonight. From there, the next 5 days of forecast will be
|
|
// contained in Elements 2/3, 4/5, 6/7, 8/9, and 10/11. This module will create a 6-day forecast using
|
|
// all of these Elements.
|
|
//
|
|
// But, if the EC forecast is queried in late afternoon, the Current Today forecast will be rolled
|
|
// off and Element 0 will contain Current Tonight. From there, the next 5 days will be contained in
|
|
// Elements 1/2, 3/4, 5/6, 7/8, and 9/10. As well, Elelement 11 will contain a forecast for a 6th day,
|
|
// but only for the Today portion (not Tonight). This module will create a 6-day forecast using
|
|
// Elements 0 to 11, and will ignore the additional Todat forecast in Element 11.
|
|
//
|
|
// We need to determine if Element 0 is showing the forecast for Current Today or Current Tonight.
|
|
// This is required to understand how Min and Max temperature will be determined, and to understand
|
|
// where the next day's (aka Tomorrow's) forecast is located in the forecast array.
|
|
//
|
|
|
|
let nextDay = 0;
|
|
let lastDay = 0;
|
|
const currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent;
|
|
|
|
//
|
|
// If the first Element is Current Today, look at Current Today and Current Tonight for the current day.
|
|
//
|
|
|
|
if (foreGroup[0].querySelector("period[textForecastName='Today']")) {
|
|
this.todaytempCacheMin = 0;
|
|
this.todaytempCacheMax = 0;
|
|
this.todayCached = true;
|
|
|
|
this.setMinMaxTemps(weather, foreGroup, 0, true, currentTemp);
|
|
|
|
this.setPrecipitation(weather, foreGroup, 0);
|
|
|
|
//
|
|
// Set the Element number that will reflect where the next day's forecast is located. Also set
|
|
// the Element number where the end of the forecast will be. This is important because of the
|
|
// rolling nature of the EC forecast. In the current scenario (Today and Tonight are present
|
|
// in elements 0 and 11, we know that we will have 6 full days of forecasts and we will use
|
|
// them. We will set lastDay such that we iterate through all 12 elements of the forecast.
|
|
//
|
|
|
|
nextDay = 2;
|
|
lastDay = 12;
|
|
}
|
|
|
|
//
|
|
// If the first Element is Current Tonight, look at Tonight only for the current day.
|
|
//
|
|
if (foreGroup[0].querySelector("period[textForecastName='Tonight']")) {
|
|
this.setMinMaxTemps(weather, foreGroup, 0, false, currentTemp);
|
|
|
|
this.setPrecipitation(weather, foreGroup, 0);
|
|
|
|
//
|
|
// Set the Element number that will reflect where the next day's forecast is located. Also set
|
|
// the Element number where the end of the forecast will be. This is important because of the
|
|
// rolling nature of the EC forecast. In the current scenario (only Current Tonight is present
|
|
// in Element 0, we know that we will have 6 full days of forecasts PLUS a half-day and
|
|
// forecast in the final element. Because we will only use full day forecasts, we set the
|
|
// lastDay number to ensure we ignore that final half-day (in forecast Element 11).
|
|
//
|
|
|
|
nextDay = 1;
|
|
lastDay = 11;
|
|
}
|
|
|
|
//
|
|
// Need to map EC weather icon to MM weatherType values. Always pick the first Element's icon to
|
|
// reflect either Today or Tonight depending on what the forecast is showing in Element 0.
|
|
//
|
|
|
|
weather.weatherType = this.convertWeatherType(foreGroup[0].querySelector("abbreviatedForecast iconCode").textContent);
|
|
|
|
// Push the weather object into the forecast array.
|
|
|
|
days.push(weather);
|
|
|
|
//
|
|
// Now do the rest of the forecast starting at nextDay. We will process each day using 2 EC
|
|
// forecast Elements. This will address the fact that the EC forecast always includes Today and
|
|
// Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each
|
|
// iteration looking at the current Element and the next Element.
|
|
//
|
|
|
|
let lastDate = moment(baseDate, "YYYYMMDDhhmmss");
|
|
|
|
for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) {
|
|
let weather = new WeatherObject();
|
|
|
|
// Add 1 to the date to reflect the current forecast day we are building
|
|
|
|
lastDate = lastDate.add(1, "day");
|
|
weather.date = moment(lastDate);
|
|
|
|
// Capture the temperatures for the current Element and the next Element in order to set
|
|
// the Min and Max temperatures for the forecast
|
|
|
|
this.setMinMaxTemps(weather, foreGroup, stepDay, true, currentTemp);
|
|
|
|
weather.precipitationAmount = null;
|
|
|
|
this.setPrecipitation(weather, foreGroup, stepDay);
|
|
|
|
//
|
|
// Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
|
|
//
|
|
|
|
weather.weatherType = this.convertWeatherType(foreGroup[stepDay].querySelector("abbreviatedForecast iconCode").textContent);
|
|
|
|
// Push the weather object into the forecast array.
|
|
|
|
days.push(weather);
|
|
}
|
|
|
|
return days;
|
|
},
|
|
|
|
//
|
|
// Generate an array of WeatherObjects based on EC hourly weather forecast
|
|
//
|
|
|
|
generateWeatherObjectsFromHourly (ECdoc) {
|
|
// Declare an array to hold each hour's forecast object
|
|
|
|
const hours = [];
|
|
|
|
// Get local timezone UTC offset so that each hourly time can be calculated properly
|
|
|
|
const baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime");
|
|
const hourOffset = baseHours[1].getAttribute("UTCOffset");
|
|
|
|
//
|
|
// The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding
|
|
// the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours.
|
|
//
|
|
|
|
const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
|
|
|
|
for (let stepHour = 0; stepHour < 24; stepHour += 1) {
|
|
const weather = new WeatherObject();
|
|
|
|
// Determine local time by applying UTC offset to the forecast timestamp
|
|
|
|
const foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss");
|
|
const currTime = foreTime.add(hourOffset, "hours");
|
|
weather.date = moment(currTime);
|
|
|
|
// Capture the temperature
|
|
|
|
weather.temperature = hourGroup[stepHour].querySelector("temperature").textContent;
|
|
|
|
// Capture Likelihood of Precipitation (LOP) and unit-of-measure values
|
|
|
|
const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0;
|
|
|
|
if (precipLOP > 0) {
|
|
weather.precipitationProbability = precipLOP;
|
|
}
|
|
|
|
//
|
|
// Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
|
|
//
|
|
|
|
weather.weatherType = this.convertWeatherType(hourGroup[stepHour].querySelector("iconCode").textContent);
|
|
|
|
// Push the weather object into the forecast array.
|
|
|
|
hours.push(weather);
|
|
}
|
|
|
|
return hours;
|
|
},
|
|
//
|
|
// Determine Min and Max temp based on a supplied Forecast Element index and a boolen that denotes if
|
|
// the next Forecast element should be considered - i.e. look at Today *and* Tonight vs.Tonight-only
|
|
//
|
|
|
|
setMinMaxTemps (weather, foreGroup, today, fullDay, currentTemp) {
|
|
const todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent;
|
|
|
|
const todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class");
|
|
|
|
//
|
|
// The following logic is largely aimed at accommodating the Current day's forecast whereby we
|
|
// can have either Current Today+Current Tonight or only Current Tonight.
|
|
//
|
|
// If fullDay is false, then we only have Tonight for the current day's forecast - meaning we have
|
|
// lost a min or max temp value for the day. Therefore, we will see if we were able to cache the the
|
|
// Today forecast for the current day. If we have, we will use them. If we do not have the cached values,
|
|
// it means that MM or the Computer has been restarted since the time EC rolled off Today from the
|
|
// forecast. In this scenario, we will simply default to the Current Conditions temperature and then
|
|
// check the Tonight temperature.
|
|
//
|
|
|
|
if (fullDay === false) {
|
|
if (this.todayCached === true) {
|
|
weather.minTemperature = this.todayTempCacheMin;
|
|
weather.maxTemperature = this.todayTempCacheMax;
|
|
} else {
|
|
weather.minTemperature = currentTemp;
|
|
weather.maxTemperature = weather.minTemperature;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We will check to see if the current Element's temperature is Low or High and set weather values
|
|
// accordingly. We will also check the condition where fullDay is true *and* we are looking at forecast
|
|
// element 0. This is a special case where we will cache temperature values so that we have them later
|
|
// in the current day when the Current Today element rolls off and we have Current Tonight only.
|
|
//
|
|
|
|
if (todayClass === "low") {
|
|
weather.minTemperature = todayTemp;
|
|
if (today === 0 && fullDay === true) {
|
|
this.todayTempCacheMin = weather.minTemperature;
|
|
}
|
|
}
|
|
|
|
if (todayClass === "high") {
|
|
weather.maxTemperature = todayTemp;
|
|
if (today === 0 && fullDay === true) {
|
|
this.todayTempCacheMax = weather.maxTemperature;
|
|
}
|
|
}
|
|
|
|
const nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent;
|
|
|
|
const nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class");
|
|
|
|
if (fullDay === true) {
|
|
if (nextClass === "low") {
|
|
weather.minTemperature = nextTemp;
|
|
}
|
|
|
|
if (nextClass === "high") {
|
|
weather.maxTemperature = nextTemp;
|
|
}
|
|
}
|
|
},
|
|
|
|
//
|
|
// Check for a Precipitation forecast. EC can provide a forecast in 2 ways: either an accumulation figure
|
|
// or a POP percentage. If there is a POP, then that is what the module will show. If there is an accumulation,
|
|
// then it will be displayed ONLY if no POP is present.
|
|
//
|
|
// POP Logic: By default, we want to show the POP for 'daytime' since we are presuming that is what
|
|
// people are more interested in seeing. While EC provides a separate POP for daytime and nightime portions
|
|
// of each day, the weather module does not really allow for that view of a daily forecast. There we will
|
|
// ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
|
|
// the nightime forecast after a certain point in the afternoon. As such, we will be showing the nightime POP
|
|
// (if one exists) in that specific scenario.
|
|
//
|
|
// Accumulation Logic: Similar to POP, we want to show accumulation for 'daytime' since we presume that is what
|
|
// people are interested in seeing. While EC provides a separate accumulation for daytime and nightime portions
|
|
// of each day, the weather module does not really allow for that view of a daily forecast. There we will
|
|
// ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
|
|
// the nightime forecast after a certain point in that specific scenario.
|
|
//
|
|
|
|
setPrecipitation (weather, foreGroup, today) {
|
|
if (foreGroup[today].querySelector("precipitation accumulation")) {
|
|
weather.precipitationAmount = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0;
|
|
weather.precipitationUnits = foreGroup[today].querySelector("precipitation accumulation amount").getAttribute("units");
|
|
}
|
|
|
|
// Check Today element for POP
|
|
const precipPOP = foreGroup[today].querySelector("abbreviatedForecast pop").textContent * 1.0;
|
|
if (precipPOP > 0) {
|
|
weather.precipitationProbability = precipPOP;
|
|
}
|
|
},
|
|
|
|
//
|
|
// Convert the icons to a more usable name.
|
|
//
|
|
convertWeatherType (weatherType) {
|
|
const weatherTypes = {
|
|
"00": "day-sunny",
|
|
"01": "day-sunny",
|
|
"02": "day-sunny-overcast",
|
|
"03": "day-cloudy",
|
|
"04": "day-cloudy",
|
|
"05": "day-cloudy",
|
|
"06": "day-sprinkle",
|
|
"07": "day-showers",
|
|
"08": "day-snow",
|
|
"09": "day-thunderstorm",
|
|
10: "cloud",
|
|
11: "showers",
|
|
12: "rain",
|
|
13: "rain",
|
|
14: "sleet",
|
|
15: "sleet",
|
|
16: "snow",
|
|
17: "snow",
|
|
18: "snow",
|
|
19: "thunderstorm",
|
|
20: "cloudy",
|
|
21: "cloudy",
|
|
22: "day-cloudy",
|
|
23: "day-haze",
|
|
24: "fog",
|
|
25: "snow-wind",
|
|
26: "sleet",
|
|
27: "sleet",
|
|
28: "rain",
|
|
29: "na",
|
|
30: "night-clear",
|
|
31: "night-clear",
|
|
32: "night-partly-cloudy",
|
|
33: "night-alt-cloudy",
|
|
34: "night-alt-cloudy",
|
|
35: "night-partly-cloudy",
|
|
36: "night-alt-showers",
|
|
37: "night-rain-mix",
|
|
38: "night-alt-snow",
|
|
39: "night-thunderstorm",
|
|
40: "snow-wind",
|
|
41: "tornado",
|
|
42: "tornado",
|
|
43: "windy",
|
|
44: "smoke",
|
|
45: "sandstorm",
|
|
46: "thunderstorm",
|
|
47: "thunderstorm",
|
|
48: "tornado"
|
|
};
|
|
|
|
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
|
|
}
|
|
});
|