2021-08-31 21:41:27 +02:00

248 lines
7.2 KiB
JavaScript
Executable File

/* global WeatherProvider, WeatherObject, SunCalc */
/* Magic Mirror
* Module: Weather
*
* By Malcolm Oakes https://github.com/maloakes
* MIT Licensed.
*
* This class is a provider for UK Met Office Datapoint.
*/
WeatherProvider.register("ukmetoffice", {
// Set the name of the provider.
// This isn't strictly necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name.
providerName: "UK Met Office",
// Set the default config properties that is specific to this provider
defaults: {
apiBase: "http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/",
locationID: false,
apiKey: ""
},
units: {
imperial: "us",
metric: "si"
},
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl("3hourly"))
.then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`);
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl("daily"))
.then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`);
const forecast = this.generateWeatherObjectsFromForecast(data);
this.setWeatherForecast(forecast);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
},
/** UK Met Office Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
*/
getUrl(forecastType) {
return this.config.apiBase + this.config.locationID + this.getParams(forecastType);
},
/*
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
const location = currentWeatherData.SiteRep.DV.Location;
// data times are always UTC
let nowUtc = moment.utc();
let midnightUtc = nowUtc.clone().startOf("day");
let timeInMins = nowUtc.diff(midnightUtc, "minutes");
// loop round each of the (5) periods, look for today (the first period may be yesterday)
for (const period of location.Period) {
const periodDate = moment.utc(period.value.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// check this is the period we want, after today the diff will be -ve
if (moment().diff(periodDate, "minutes") > 0) {
// loop round the reports looking for the one we are in
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
for (const rep of period.Rep) {
const p = rep.$;
if (timeInMins >= p && timeInMins - 180 < p) {
// finally got the one we want, so populate weather object
currentWeather.humidity = rep.H;
currentWeather.temperature = this.convertTemp(rep.T);
currentWeather.feelsLikeTemp = this.convertTemp(rep.F);
currentWeather.precipitation = parseInt(rep.Pp);
currentWeather.windSpeed = this.convertWindSpeed(rep.S);
currentWeather.windDirection = this.convertWindDirection(rep.D);
currentWeather.weatherType = this.convertWeatherType(rep.W);
}
}
}
}
}
// determine the sunrise/sunset times - not supplied in UK Met Office data
currentWeather.updateSunTime(location.lat, location.lon);
return currentWeather;
},
/*
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
const days = [];
// loop round the (5) periods getting the data
// for each period array, Day is [0], Night is [1]
for (const period of forecasts.SiteRep.DV.Location.Period) {
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
// data times are always UTC
const dateStr = period.value;
let periodDate = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object
weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
weather.minTemperature = this.convertTemp(period.Rep[1].Nm);
weather.maxTemperature = this.convertTemp(period.Rep[0].Dm);
weather.weatherType = this.convertWeatherType(period.Rep[0].W);
weather.precipitation = parseInt(period.Rep[0].PPd);
days.push(weather);
}
}
return days;
},
/*
* Convert the Met Office icons to a more usable name.
*/
convertWeatherType(weatherType) {
const weatherTypes = {
0: "night-clear",
1: "day-sunny",
2: "night-alt-cloudy",
3: "day-cloudy",
5: "fog",
6: "fog",
7: "cloudy",
8: "cloud",
9: "night-sprinkle",
10: "day-sprinkle",
11: "raindrops",
12: "sprinkle",
13: "night-alt-showers",
14: "day-showers",
15: "rain",
16: "night-alt-sleet",
17: "day-sleet",
18: "sleet",
19: "night-alt-hail",
20: "day-hail",
21: "hail",
22: "night-alt-snow",
23: "day-snow",
24: "snow",
25: "night-alt-snow",
26: "day-snow",
27: "snow",
28: "night-alt-thunderstorm",
29: "day-thunderstorm",
30: "thunderstorm"
};
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
},
/*
* Convert temp (from degrees C) if required
*/
convertTemp(tempInC) {
return this.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
},
/*
* Convert wind speed (from mph to m/s or km/h) if required
*/
convertWindSpeed(windInMph) {
return this.windUnits === "metric" ? (this.useKmh ? windInMph * 1.60934 : windInMph / 2.23694) : windInMph;
},
/*
* Convert the wind direction cardinal to value
*/
convertWindDirection(windDirection) {
const windCardinals = {
N: 0,
NNE: 22,
NE: 45,
ENE: 67,
E: 90,
ESE: 112,
SE: 135,
SSE: 157,
S: 180,
SSW: 202,
SW: 225,
WSW: 247,
W: 270,
WNW: 292,
NW: 315,
NNW: 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
},
/*
* Generates an url with api parameters based on the config.
*
* return String - URL params.
*/
getParams(forecastType) {
let params = "?";
params += "res=" + forecastType;
params += "&key=" + this.config.apiKey;
return params;
}
});