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.

weathergov.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /* global WeatherProvider, WeatherObject */
  2. /* Magic Mirror
  3. * Module: Weather
  4. * Provider: weather.gov
  5. * https://weather-gov.github.io/api/general-faqs
  6. *
  7. * Original by Vince Peri
  8. * MIT Licensed.
  9. *
  10. * This class is a provider for weather.gov.
  11. * Note that this is only for US locations (lat and lon) and does not require an API key
  12. * Since it is free, there are some items missing - like sunrise, sunset
  13. */
  14. WeatherProvider.register("weathergov", {
  15. // Set the name of the provider.
  16. // This isn't strictly necessary, since it will fallback to the provider identifier
  17. // But for debugging (and future alerts) it would be nice to have the real name.
  18. providerName: "Weather.gov",
  19. // Set the default config properties that is specific to this provider
  20. defaults: {
  21. apiBase: "https://api.weather.gov/points/",
  22. weatherEndpoint: "/forecast",
  23. lat: 0,
  24. lon: 0
  25. },
  26. // Flag all needed URLs availability
  27. configURLs: false,
  28. //This API has multiple urls involved
  29. forecastURL: "tbd",
  30. forecastHourlyURL: "tbd",
  31. forecastGridDataURL: "tbd",
  32. observationStationsURL: "tbd",
  33. stationObsURL: "tbd",
  34. // Called to set the config, this config is the same as the weather module's config.
  35. setConfig: function (config) {
  36. this.config = config;
  37. (this.config.apiBase = "https://api.weather.gov"), this.fetchWxGovURLs(this.config);
  38. },
  39. // Called when the weather provider is about to start.
  40. start: function () {
  41. Log.info(`Weather provider: ${this.providerName} started.`);
  42. },
  43. // This returns the name of the fetched location or an empty string.
  44. fetchedLocation: function () {
  45. return this.fetchedLocationName || "";
  46. },
  47. // Overwrite the fetchCurrentWeather method.
  48. fetchCurrentWeather() {
  49. if (!this.configURLs) {
  50. Log.info("fetch wx waiting on config URLs");
  51. return;
  52. }
  53. this.fetchData(this.stationObsURL)
  54. .then((data) => {
  55. if (!data || !data.properties) {
  56. // Did not receive usable new data.
  57. return;
  58. }
  59. const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties);
  60. this.setCurrentWeather(currentWeather);
  61. })
  62. .catch(function (request) {
  63. Log.error("Could not load station obs data ... ", request);
  64. })
  65. .finally(() => this.updateAvailable());
  66. },
  67. // Overwrite the fetchWeatherForecast method.
  68. fetchWeatherForecast() {
  69. if (!this.configURLs) {
  70. Log.info("fetch wx waiting on config URLs");
  71. return;
  72. }
  73. this.fetchData(this.forecastURL)
  74. .then((data) => {
  75. if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
  76. // Did not receive usable new data.
  77. return;
  78. }
  79. const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
  80. this.setWeatherForecast(forecast);
  81. })
  82. .catch(function (request) {
  83. Log.error("Could not load forecast hourly data ... ", request);
  84. })
  85. .finally(() => this.updateAvailable());
  86. },
  87. /** Weather.gov Specific Methods - These are not part of the default provider methods */
  88. /*
  89. * Get specific URLs
  90. */
  91. fetchWxGovURLs(config) {
  92. this.fetchData(`${config.apiBase}/points/${config.lat},${config.lon}`)
  93. .then((data) => {
  94. if (!data || !data.properties) {
  95. // points URL did not respond with usable data.
  96. return;
  97. }
  98. this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state;
  99. Log.log("Forecast location is " + this.fetchedLocationName);
  100. this.forecastURL = data.properties.forecast;
  101. this.forecastHourlyURL = data.properties.forecastHourly;
  102. this.forecastGridDataURL = data.properties.forecastGridData;
  103. this.observationStationsURL = data.properties.observationStations;
  104. // with this URL, we chain another promise for the station obs URL
  105. return this.fetchData(data.properties.observationStations);
  106. })
  107. .then((obsData) => {
  108. if (!obsData || !obsData.features) {
  109. // obs station URL did not respond with usable data.
  110. return;
  111. }
  112. this.stationObsURL = obsData.features[0].id + "/observations/latest";
  113. })
  114. .catch((err) => {
  115. Log.error(err);
  116. })
  117. .finally(() => {
  118. // excellent, let's fetch some actual wx data
  119. this.configURLs = true;
  120. // handle 'forecast' config, fall back to 'current'
  121. if (config.type === "forecast") {
  122. this.fetchWeatherForecast();
  123. } else {
  124. this.fetchCurrentWeather();
  125. }
  126. });
  127. },
  128. /*
  129. * Generate a WeatherObject based on currentWeatherInformation
  130. * Weather.gov API uses specific units; API does not include choice of units
  131. * ... object needs data in units based on config!
  132. */
  133. generateWeatherObjectFromCurrentWeather(currentWeatherData) {
  134. const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  135. currentWeather.date = moment(currentWeatherData.timestamp);
  136. currentWeather.temperature = this.convertTemp(currentWeatherData.temperature.value);
  137. currentWeather.windSpeed = this.convertSpeed(currentWeatherData.windSpeed.value);
  138. currentWeather.windDirection = currentWeatherData.windDirection.value;
  139. currentWeather.minTemperature = this.convertTemp(currentWeatherData.minTemperatureLast24Hours.value);
  140. currentWeather.maxTemperature = this.convertTemp(currentWeatherData.maxTemperatureLast24Hours.value);
  141. currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value);
  142. currentWeather.rain = null;
  143. currentWeather.snow = null;
  144. currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value);
  145. currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value);
  146. // determine the sunrise/sunset times - not supplied in weather.gov data
  147. currentWeather.updateSunTime(this.config.lat, this.config.lon);
  148. // update weatherType
  149. currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, currentWeather.isDayTime());
  150. return currentWeather;
  151. },
  152. /*
  153. * Generate WeatherObjects based on forecast information
  154. */
  155. generateWeatherObjectsFromForecast(forecasts) {
  156. return this.fetchForecastDaily(forecasts);
  157. },
  158. /*
  159. * fetch forecast information for daily forecast.
  160. */
  161. fetchForecastDaily(forecasts) {
  162. // initial variable declaration
  163. const days = [];
  164. // variables for temperature range and rain
  165. let minTemp = [];
  166. let maxTemp = [];
  167. // variable for date
  168. let date = "";
  169. let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  170. weather.precipitation = 0;
  171. for (const forecast of forecasts) {
  172. if (date !== moment(forecast.startTime).format("YYYY-MM-DD")) {
  173. // calculate minimum/maximum temperature, specify rain amount
  174. weather.minTemperature = Math.min.apply(null, minTemp);
  175. weather.maxTemperature = Math.max.apply(null, maxTemp);
  176. // push weather information to days array
  177. days.push(weather);
  178. // create new weather-object
  179. weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  180. minTemp = [];
  181. maxTemp = [];
  182. weather.precipitation = 0;
  183. // set new date
  184. date = moment(forecast.startTime).format("YYYY-MM-DD");
  185. // specify date
  186. weather.date = moment(forecast.startTime);
  187. // use the forecast isDayTime attribute to help build the weatherType label
  188. weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
  189. }
  190. if (moment(forecast.startTime).format("H") >= 8 && moment(forecast.startTime).format("H") <= 17) {
  191. weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
  192. }
  193. // the same day as before
  194. // add values from forecast to corresponding variables
  195. minTemp.push(forecast.temperature);
  196. maxTemp.push(forecast.temperature);
  197. }
  198. // last day
  199. // calculate minimum/maximum temperature
  200. weather.minTemperature = Math.min.apply(null, minTemp);
  201. weather.maxTemperature = Math.max.apply(null, maxTemp);
  202. // push weather information to days array
  203. days.push(weather);
  204. return days.slice(1);
  205. },
  206. /*
  207. * Unit conversions
  208. */
  209. // conversion to fahrenheit
  210. convertTemp(temp) {
  211. if (this.config.tempUnits === "imperial") {
  212. return (9 / 5) * temp + 32;
  213. } else {
  214. return temp;
  215. }
  216. },
  217. // conversion to mph or kmh
  218. convertSpeed(metSec) {
  219. if (this.config.windUnits === "imperial") {
  220. return metSec * 2.23694;
  221. } else {
  222. if (this.config.useKmh) {
  223. return metSec * 3.6;
  224. } else {
  225. return metSec;
  226. }
  227. }
  228. },
  229. // conversion to inches
  230. convertLength(meters) {
  231. if (this.config.units === "imperial") {
  232. return meters * 39.3701;
  233. } else {
  234. return meters;
  235. }
  236. },
  237. /*
  238. * Convert the icons to a more usable name.
  239. */
  240. convertWeatherType(weatherType, isDaytime) {
  241. //https://w1.weather.gov/xml/current_obs/weather.php
  242. // There are way too many types to create, so lets just look for certain strings
  243. if (weatherType.includes("Cloudy") || weatherType.includes("Partly")) {
  244. if (isDaytime) {
  245. return "day-cloudy";
  246. }
  247. return "night-cloudy";
  248. } else if (weatherType.includes("Overcast")) {
  249. if (isDaytime) {
  250. return "cloudy";
  251. }
  252. return "night-cloudy";
  253. } else if (weatherType.includes("Freezing") || weatherType.includes("Ice")) {
  254. return "rain-mix";
  255. } else if (weatherType.includes("Snow")) {
  256. if (isDaytime) {
  257. return "snow";
  258. }
  259. return "night-snow";
  260. } else if (weatherType.includes("Thunderstorm")) {
  261. if (isDaytime) {
  262. return "thunderstorm";
  263. }
  264. return "night-thunderstorm";
  265. } else if (weatherType.includes("Showers")) {
  266. if (isDaytime) {
  267. return "showers";
  268. }
  269. return "night-showers";
  270. } else if (weatherType.includes("Rain") || weatherType.includes("Drizzle")) {
  271. if (isDaytime) {
  272. return "rain";
  273. }
  274. return "night-rain";
  275. } else if (weatherType.includes("Breezy") || weatherType.includes("Windy")) {
  276. if (isDaytime) {
  277. return "cloudy-windy";
  278. }
  279. return "night-alt-cloudy-windy";
  280. } else if (weatherType.includes("Fair") || weatherType.includes("Clear") || weatherType.includes("Few") || weatherType.includes("Sunny")) {
  281. if (isDaytime) {
  282. return "day-sunny";
  283. }
  284. return "night-clear";
  285. } else if (weatherType.includes("Dust") || weatherType.includes("Sand")) {
  286. return "dust";
  287. } else if (weatherType.includes("Fog")) {
  288. return "fog";
  289. } else if (weatherType.includes("Smoke")) {
  290. return "smoke";
  291. } else if (weatherType.includes("Haze")) {
  292. return "day-haze";
  293. }
  294. return null;
  295. },
  296. /*
  297. Convert the direction into Degrees
  298. */
  299. convertWindDirection(windDirection) {
  300. const windCardinals = {
  301. N: 0,
  302. NNE: 22,
  303. NE: 45,
  304. ENE: 67,
  305. E: 90,
  306. ESE: 112,
  307. SE: 135,
  308. SSE: 157,
  309. S: 180,
  310. SSW: 202,
  311. SW: 225,
  312. WSW: 247,
  313. W: 270,
  314. WNW: 292,
  315. NW: 315,
  316. NNW: 337
  317. };
  318. return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
  319. }
  320. });