|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- /* global WeatherProvider, WeatherObject */
-
- /* Magic Mirror
- * Module: Weather
- *
- * By Michael Teeuw https://michaelteeuw.nl
- * MIT Licensed.
- *
- * This class is the blueprint for a weather provider.
- */
- WeatherProvider.register("openweathermap", {
- // 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: "OpenWeatherMap",
-
- // Set the default config properties that is specific to this provider
- defaults: {
- apiVersion: "2.5",
- apiBase: "https://api.openweathermap.org/data/",
- weatherEndpoint: "", // can be "onecall", "forecast" or "weather" (for current)
- locationID: false,
- location: false,
- lat: 0, // the onecall endpoint needs lat / lon values, it doesn'T support the locationId
- lon: 0,
- apiKey: ""
- },
-
- // Overwrite the fetchCurrentWeather method.
- fetchCurrentWeather() {
- this.fetchData(this.getUrl())
- .then((data) => {
- if (this.config.weatherEndpoint === "/onecall") {
- const weatherData = this.generateWeatherObjectsFromOnecall(data);
- this.setCurrentWeather(weatherData.current);
- this.setFetchedLocation(`${data.timezone}`);
- } else {
- const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
- this.setCurrentWeather(currentWeather);
- }
- })
- .catch(function (request) {
- Log.error("Could not load data ... ", request);
- })
- .finally(() => this.updateAvailable());
- },
-
- // Overwrite the fetchWeatherForecast method.
- fetchWeatherForecast() {
- this.fetchData(this.getUrl())
- .then((data) => {
- if (this.config.weatherEndpoint === "/onecall") {
- const weatherData = this.generateWeatherObjectsFromOnecall(data);
- this.setWeatherForecast(weatherData.days);
- this.setFetchedLocation(`${data.timezone}`);
- } else {
- const forecast = this.generateWeatherObjectsFromForecast(data.list);
- this.setWeatherForecast(forecast);
- this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
- }
- })
- .catch(function (request) {
- Log.error("Could not load data ... ", request);
- })
- .finally(() => this.updateAvailable());
- },
-
- // Overwrite the fetchWeatherHourly method.
- fetchWeatherHourly() {
- this.fetchData(this.getUrl())
- .then((data) => {
- if (!data) {
- // Did not receive usable new data.
- // Maybe this needs a better check?
- return;
- }
-
- this.setFetchedLocation(`(${data.lat},${data.lon})`);
-
- const weatherData = this.generateWeatherObjectsFromOnecall(data);
- this.setWeatherHourly(weatherData.hours);
- })
- .catch(function (request) {
- Log.error("Could not load data ... ", request);
- })
- .finally(() => this.updateAvailable());
- },
-
- /**
- * Overrides method for setting config to check if endpoint is correct for hourly
- *
- * @param {object} config The configuration object
- */
- setConfig(config) {
- this.config = config;
- if (!this.config.weatherEndpoint) {
- switch (this.config.type) {
- case "hourly":
- this.config.weatherEndpoint = "/onecall";
- break;
- case "daily":
- case "forecast":
- this.config.weatherEndpoint = "/forecast";
- break;
- case "current":
- this.config.weatherEndpoint = "/weather";
- break;
- default:
- Log.error("weatherEndpoint not configured and could not resolve it based on type");
- }
- }
- },
-
- /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
- /*
- * Gets the complete url for the request
- */
- getUrl() {
- return this.config.apiBase + this.config.apiVersion + this.config.weatherEndpoint + this.getParams();
- },
-
- /*
- * Generate a WeatherObject based on currentWeatherInformation
- */
- generateWeatherObjectFromCurrentWeather(currentWeatherData) {
- const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
-
- currentWeather.humidity = currentWeatherData.main.humidity;
- currentWeather.temperature = currentWeatherData.main.temp;
- if (this.config.windUnits === "metric") {
- currentWeather.windSpeed = this.config.useKmh ? currentWeatherData.wind.speed * 3.6 : currentWeatherData.wind.speed;
- } else {
- currentWeather.windSpeed = currentWeatherData.wind.speed;
- }
- currentWeather.windDirection = currentWeatherData.wind.deg;
- currentWeather.weatherType = this.convertWeatherType(currentWeatherData.weather[0].icon);
- currentWeather.sunrise = moment(currentWeatherData.sys.sunrise, "X");
- currentWeather.sunset = moment(currentWeatherData.sys.sunset, "X");
-
- return currentWeather;
- },
-
- /*
- * Generate WeatherObjects based on forecast information
- */
- generateWeatherObjectsFromForecast(forecasts) {
- if (this.config.weatherEndpoint === "/forecast") {
- return this.fetchForecastHourly(forecasts);
- } else if (this.config.weatherEndpoint === "/forecast/daily") {
- return this.fetchForecastDaily(forecasts);
- }
- // if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
- const days = [new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh)];
- return days;
- },
-
- /*
- * Generate WeatherObjects based on One Call forecast information
- */
- generateWeatherObjectsFromOnecall(data) {
- if (this.config.weatherEndpoint === "/onecall") {
- return this.fetchOnecall(data);
- }
- // if weatherEndpoint does not match onecall, what should be returned?
- const weatherData = { current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh), hours: [], days: [] };
- return weatherData;
- },
-
- /*
- * fetch forecast information for 3-hourly forecast (available for free subscription).
- */
- fetchForecastHourly(forecasts) {
- // initial variable declaration
- const days = [];
- // variables for temperature range and rain
- let minTemp = [];
- let maxTemp = [];
- let rain = 0;
- let snow = 0;
- // variable for date
- let date = "";
- let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
-
- for (const forecast of forecasts) {
- if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) {
- // calculate minimum/maximum temperature, specify rain amount
- weather.minTemperature = Math.min.apply(null, minTemp);
- weather.maxTemperature = Math.max.apply(null, maxTemp);
- weather.rain = rain;
- weather.snow = snow;
- weather.precipitation = weather.rain + weather.snow;
- // push weather information to days array
- days.push(weather);
- // create new weather-object
- weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
-
- minTemp = [];
- maxTemp = [];
- rain = 0;
- snow = 0;
-
- // set new date
- date = moment(forecast.dt, "X").format("YYYY-MM-DD");
-
- // specify date
- weather.date = moment(forecast.dt, "X");
-
- // If the first value of today is later than 17:00, we have an icon at least!
- weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
- }
-
- if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
- weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
- }
-
- // the same day as before
- // add values from forecast to corresponding variables
- minTemp.push(forecast.main.temp_min);
- maxTemp.push(forecast.main.temp_max);
-
- if (forecast.hasOwnProperty("rain")) {
- if (this.config.units === "imperial" && !isNaN(forecast.rain["3h"])) {
- rain += forecast.rain["3h"] / 25.4;
- } else if (!isNaN(forecast.rain["3h"])) {
- rain += forecast.rain["3h"];
- }
- }
-
- if (forecast.hasOwnProperty("snow")) {
- if (this.config.units === "imperial" && !isNaN(forecast.snow["3h"])) {
- snow += forecast.snow["3h"] / 25.4;
- } else if (!isNaN(forecast.snow["3h"])) {
- snow += forecast.snow["3h"];
- }
- }
- }
-
- // last day
- // calculate minimum/maximum temperature, specify rain amount
- weather.minTemperature = Math.min.apply(null, minTemp);
- weather.maxTemperature = Math.max.apply(null, maxTemp);
- weather.rain = rain;
- weather.snow = snow;
- weather.precipitation = weather.rain + weather.snow;
- // push weather information to days array
- days.push(weather);
- return days.slice(1);
- },
-
- /*
- * fetch forecast information for daily forecast (available for paid subscription or old apiKey).
- */
- fetchForecastDaily(forecasts) {
- // initial variable declaration
- const days = [];
-
- for (const forecast of forecasts) {
- const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
-
- weather.date = moment(forecast.dt, "X");
- weather.minTemperature = forecast.temp.min;
- weather.maxTemperature = forecast.temp.max;
- weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
- weather.rain = 0;
- weather.snow = 0;
-
- // forecast.rain not available if amount is zero
- // The API always returns in millimeters
- if (forecast.hasOwnProperty("rain")) {
- if (this.config.units === "imperial" && !isNaN(forecast.rain)) {
- weather.rain = forecast.rain / 25.4;
- } else if (!isNaN(forecast.rain)) {
- weather.rain = forecast.rain;
- }
- }
-
- // forecast.snow not available if amount is zero
- // The API always returns in millimeters
- if (forecast.hasOwnProperty("snow")) {
- if (this.config.units === "imperial" && !isNaN(forecast.snow)) {
- weather.snow = forecast.snow / 25.4;
- } else if (!isNaN(forecast.snow)) {
- weather.snow = forecast.snow;
- }
- }
-
- weather.precipitation = weather.rain + weather.snow;
-
- days.push(weather);
- }
-
- return days;
- },
-
- /*
- * Fetch One Call forecast information (available for free subscription).
- * Factors in timezone offsets.
- * Minutely forecasts are excluded for the moment, see getParams().
- */
- fetchOnecall(data) {
- let precip = false;
-
- // get current weather, if requested
- const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
- if (data.hasOwnProperty("current")) {
- current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset / 60);
- current.windSpeed = data.current.wind_speed;
- current.windDirection = data.current.wind_deg;
- current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset / 60);
- current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset / 60);
- current.temperature = data.current.temp;
- current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
- current.humidity = data.current.humidity;
- if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) {
- if (this.config.units === "imperial") {
- current.rain = data.current["rain"]["1h"] / 25.4;
- } else {
- current.rain = data.current["rain"]["1h"];
- }
- precip = true;
- }
- if (data.current.hasOwnProperty("snow") && !isNaN(data.current["snow"]["1h"])) {
- if (this.config.units === "imperial") {
- current.snow = data.current["snow"]["1h"] / 25.4;
- } else {
- current.snow = data.current["snow"]["1h"];
- }
- precip = true;
- }
- if (precip) {
- current.precipitation = current.rain + current.snow;
- }
- current.feelsLikeTemp = data.current.feels_like;
- }
-
- let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
-
- // get hourly weather, if requested
- const hours = [];
- if (data.hasOwnProperty("hourly")) {
- for (const hour of data.hourly) {
- weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset / 60);
- // weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat);
- weather.temperature = hour.temp;
- weather.feelsLikeTemp = hour.feels_like;
- weather.humidity = hour.humidity;
- weather.windSpeed = hour.wind_speed;
- weather.windDirection = hour.wind_deg;
- weather.weatherType = this.convertWeatherType(hour.weather[0].icon);
- precip = false;
- if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) {
- if (this.config.units === "imperial") {
- weather.rain = hour.rain["1h"] / 25.4;
- } else {
- weather.rain = hour.rain["1h"];
- }
- precip = true;
- }
- if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) {
- if (this.config.units === "imperial") {
- weather.snow = hour.snow["1h"] / 25.4;
- } else {
- weather.snow = hour.snow["1h"];
- }
- precip = true;
- }
- if (precip) {
- weather.precipitation = weather.rain + weather.snow;
- }
-
- hours.push(weather);
- weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
- }
- }
-
- // get daily weather, if requested
- const days = [];
- if (data.hasOwnProperty("daily")) {
- for (const day of data.daily) {
- weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset / 60);
- weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset / 60);
- weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset / 60);
- weather.minTemperature = day.temp.min;
- weather.maxTemperature = day.temp.max;
- weather.humidity = day.humidity;
- weather.windSpeed = day.wind_speed;
- weather.windDirection = day.wind_deg;
- weather.weatherType = this.convertWeatherType(day.weather[0].icon);
- precip = false;
- if (!isNaN(day.rain)) {
- if (this.config.units === "imperial") {
- weather.rain = day.rain / 25.4;
- } else {
- weather.rain = day.rain;
- }
- precip = true;
- }
- if (!isNaN(day.snow)) {
- if (this.config.units === "imperial") {
- weather.snow = day.snow / 25.4;
- } else {
- weather.snow = day.snow;
- }
- precip = true;
- }
- if (precip) {
- weather.precipitation = weather.rain + weather.snow;
- }
-
- days.push(weather);
- weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
- }
- }
-
- return { current: current, hours: hours, days: days };
- },
-
- /*
- * Convert the OpenWeatherMap icons to a more usable name.
- */
- convertWeatherType(weatherType) {
- const weatherTypes = {
- "01d": "day-sunny",
- "02d": "day-cloudy",
- "03d": "cloudy",
- "04d": "cloudy-windy",
- "09d": "showers",
- "10d": "rain",
- "11d": "thunderstorm",
- "13d": "snow",
- "50d": "fog",
- "01n": "night-clear",
- "02n": "night-cloudy",
- "03n": "night-cloudy",
- "04n": "night-cloudy",
- "09n": "night-showers",
- "10n": "night-rain",
- "11n": "night-thunderstorm",
- "13n": "night-snow",
- "50n": "night-alt-cloudy-windy"
- };
-
- return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
- },
-
- /* getParams(compliments)
- * Generates an url with api parameters based on the config.
- *
- * return String - URL params.
- */
- getParams() {
- let params = "?";
- if (this.config.weatherEndpoint === "/onecall") {
- params += "lat=" + this.config.lat;
- params += "&lon=" + this.config.lon;
- if (this.config.type === "current") {
- params += "&exclude=minutely,hourly,daily";
- } else if (this.config.type === "hourly") {
- params += "&exclude=current,minutely,daily";
- } else if (this.config.type === "daily" || this.config.type === "forecast") {
- params += "&exclude=current,minutely,hourly";
- } else {
- params += "&exclude=minutely";
- }
- } else if (this.config.lat && this.config.lon) {
- params += "lat=" + this.config.lat + "&lon=" + this.config.lon;
- } else if (this.config.locationID) {
- params += "id=" + this.config.locationID;
- } else if (this.config.location) {
- params += "q=" + this.config.location;
- } else if (this.firstEvent && this.firstEvent.geo) {
- params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
- } else if (this.firstEvent && this.firstEvent.location) {
- params += "q=" + this.firstEvent.location;
- } else {
- this.hide(this.config.animationSpeed, { lockString: this.identifier });
- return;
- }
-
- params += "&units=" + this.config.units;
- params += "&lang=" + this.config.lang;
- params += "&APPID=" + this.config.apiKey;
-
- return params;
- }
- });
|