// current weather conditions display import STATUS from './status.mjs'; import { preloadImg } from './utils/image.mjs'; import { directionToNSEW } from './utils/calc.mjs'; import WeatherDisplay from './weatherdisplay.mjs'; import { registerDisplay } from './navigation.mjs'; import { temperature, windSpeed, pressure, distanceKilometers, distanceMeters, } from './utils/units.mjs'; import { getConditionText } from './utils/weather.mjs'; import { getLargeIconFromWmoCode } from './icons.mjs'; class CurrentWeather extends WeatherDisplay { constructor(navId, elemId) { super(navId, elemId, 'Current Conditions', true); } async getData(weatherParameters, refresh) { const superResult = super.getData(weatherParameters, refresh); this.data = parseData(this.weatherParameters); if (!this.data) { if (this.isEnabled) this.setStatus(STATUS.failed); this.getDataCallback(undefined); return; } this.getDataCallback(); if (!superResult) return; this.timing.totalScreens = 1; preloadImg(this.data.Icon); this.setStatus(STATUS.loaded); } async drawCanvas() { super.drawCanvas(); let condition = getConditionText(this.data.TextConditions); if (condition.length > 15) { condition = shortConditions(condition); } const wind = this.data.WindSpeed > 0 ? this.data.WindDirection.padEnd(3, '') + this.data.WindSpeed.toString().padStart(3, ' ') : 'Calm'; const fill = { temp: this.data.Temperature + String.fromCharCode(176), condition, wind, location: this.data.city.substr(0, 20), humidity: `${this.data.Humidity}%`, dewpoint: this.data.DewPoint + String.fromCharCode(176), ceiling: this.data.Ceiling === 0 ? 'Unlimited' : `${this.data.Ceiling}${this.data.CeilingUnit}`, visibility: `${this.data.Visibility}${this.data.VisibilityUnit}`, pressure: `${this.data.Pressure} ${this.data.PressureDirection}`, icon: { type: 'img', src: this.data.Icon }, }; if (this.data.WindGust > 0) fill['wind-gusts'] = `Gusts to ${this.data.WindGust}`; const area = this.elem.querySelector('.main'); area.innerHTML = ''; area.append(this.fillTemplate('weather', fill)); this.finishDraw(); } async getCurrentWeather(stillWaiting) { this.setAutoReload(); if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting); return new Promise((resolve) => { if (this.data) resolve({ data: this.data, parameters: this.weatherParameters }); this.getDataCallbacks.push(() => resolve({ data: this.data, parameters: this.weatherParameters })); }); } } const shortConditions = (_condition) => { let condition = _condition; condition = condition.replace(/Light/g, 'L'); condition = condition.replace(/Heavy/g, 'H'); condition = condition.replace(/Partly/g, 'P'); condition = condition.replace(/Mostly/g, 'M'); condition = condition.replace(/Thunderstorm/g, 'T\'storm'); condition = condition.replace(/ and /g, ' '); condition = condition.replace(/Freezing Rain/g, 'Frz Rn'); condition = condition.replace(/Freezing/g, 'Frz'); condition = condition.replace(/ with /g, '/'); return condition; }; const getCurrentWeatherByHourFromTime = (data) => { const currentTime = new Date(); const currentDateKey = currentTime.toLocaleDateString('en-CA', { timeZone: data.timeZone }); const availableTimes = data.forecast[currentDateKey]?.hours ?? Object.values(data.forecast)[0]?.hours ?? []; if (availableTimes.length === 0) return null; const closestTime = availableTimes.reduce((prev, curr) => { const prevDiff = Math.abs(new Date(prev.time) - currentTime); const currDiff = Math.abs(new Date(curr.time) - currentTime); return currDiff < prevDiff ? curr : prev; }); const threeHoursAgo = new Date(currentTime.getTime() - 3 * 60 * 60 * 1000); const previousHour = availableTimes .filter((entry) => new Date(entry.time) <= currentTime && new Date(entry.time) >= threeHoursAgo) .reduce((prev, curr) => { const prevDiff = Math.abs(new Date(prev.time) - threeHoursAgo); const currDiff = Math.abs(new Date(curr.time) - threeHoursAgo); return currDiff < prevDiff ? curr : prev; }, availableTimes[0]); const diff = (closestTime.pressure_msl ?? 0) - (previousHour.pressure_msl ?? 0); let pressureTrend = 'Steady'; if (diff > 0.5) pressureTrend = 'Rising'; if (diff < -0.5) pressureTrend = 'Falling'; closestTime.pressureTrend = pressureTrend; closestTime.uv_index_max = data.forecast[currentDateKey]?.uv_index_max ?? closestTime.uv_index ?? 0; return closestTime; }; const parseData = (weatherParameters) => { const currentForecast = getCurrentWeatherByHourFromTime(weatherParameters); if (!currentForecast) return null; const temperatureConverter = temperature(); const windConverter = windSpeed(); const pressureConverter = pressure(); const ceilingConverter = distanceMeters(); const visibilityConverter = distanceKilometers(); const ceilingMeters = Math.max(0, ((currentForecast.temperature_2m ?? 0) - (currentForecast.dew_point_2m ?? 0)) * 68); return { city: weatherParameters.city, timeZone: weatherParameters.timeZone, Temperature: temperatureConverter(currentForecast.temperature_2m), TemperatureUnit: temperatureConverter.units, DewPoint: temperatureConverter(currentForecast.dew_point_2m), Ceiling: ceilingConverter(ceilingMeters), CeilingUnit: ceilingConverter.units, Visibility: visibilityConverter(currentForecast.visibility), VisibilityUnit: visibilityConverter.units, WindSpeed: windConverter(currentForecast.wind_speed_10m), WindDirection: directionToNSEW(currentForecast.wind_direction_10m ?? 0), Pressure: pressureConverter((currentForecast.pressure_msl ?? 0) * 100), PressureDirection: currentForecast.pressureTrend, Humidity: Math.round(currentForecast.relative_humidity_2m ?? 0), WindGust: windConverter(currentForecast.wind_gusts_10m), WindUnit: windConverter.units, TextConditions: Number(currentForecast.weather_code ?? 0), Icon: getLargeIconFromWmoCode(currentForecast.weather_code, Boolean(currentForecast.is_day)), }; }; const display = new CurrentWeather(1, 'current-weather'); registerDisplay(display); export default display.getCurrentWeather.bind(display);