diff --git a/server/scripts/modules/hourly.mjs b/server/scripts/modules/hourly.mjs index cb566bb..a898134 100644 --- a/server/scripts/modules/hourly.mjs +++ b/server/scripts/modules/hourly.mjs @@ -141,6 +141,7 @@ const parseForecast = async (data) => { const iceAccumulation = expand(data.iceAccumulation.values); // ice icon const probabilityOfPrecipitation = expand(data.probabilityOfPrecipitation.values); // rain icon const snowfallAmount = expand(data.snowfallAmount.values); // snow icon + const waveHeight = expand(data.waveHeight.values); const icons = await determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed); @@ -152,6 +153,7 @@ const parseForecast = async (data) => { probabilityOfPrecipitation: probabilityOfPrecipitation[idx], skyCover: skyCover[idx], icon: icons[idx], + waveHeight: waveHeight[idx], })); }; diff --git a/server/scripts/modules/marineforecast.mjs b/server/scripts/modules/marineforecast.mjs new file mode 100644 index 0000000..c869552 --- /dev/null +++ b/server/scripts/modules/marineforecast.mjs @@ -0,0 +1,139 @@ +// display extended forecast graphically +// technically uses the same data as the local forecast, we'll let the browser do the caching of that + +import STATUS from './status.mjs'; +import WeatherDisplay from './weatherdisplay.mjs'; +import { registerDisplay } from './navigation.mjs'; +import getExtendedForecast from './extendedforecast.mjs'; +import getHourlyForecast from './hourly.mjs'; + +class MarineForecast extends WeatherDisplay { + constructor(navId, elemId) { + super(navId, elemId, 'Marine Forecast', false); + // this.showOnProgress = false; + + // set timings + this.timing.totalScreens = 1; + } + + async getData() { + if (!super.getData()) return; + + const [extendedForecast, hourlyForecast] = await Promise.all([ + getExtendedForecast(() => this.stillWaiting()), + getHourlyForecast(() => this.stillWaiting()), + ]); + if (extendedForecast === undefined || hourlyForecast === undefined) { + this.setStatus(STATUS.failed); + return; + } + + // test for all wave heights = 0, no data for wave heights + if (hourlyForecast.every((value) => !value.waveHeight)) { + // total screens = 0 to skip this display + this.totalScreens = 0; + this.setStatus(STATUS.noData); + return; + } + + this.data = { + extendedForecast, + hourlyForecast, + }; + this.screenIndex = 0; + this.setStatus(STATUS.loaded); + } + + async drawCanvas() { + super.drawCanvas(); + + // determine bounds + // grab the first three or second set of three array elements + const forecast = this.data.slice(0, 2); + + // create each day template + const days = forecast.map((Day) => { + const fill = {}; + fill.date = Day.dayName; + fill['wind-dir'] = 'NW'; + fill['wind-speed'] = '10 - 15kts'; + fill['wave-height'] = '1\''; + fill['wave-desc'] = ''; + + const { low } = Day; + if (low !== undefined) { + fill['value-lo'] = Math.round(low); + } + const { high } = Day; + fill['value-hi'] = Math.round(high); + fill.condition = Day.text; + + // draw the icon + fill['wave-icon'] = { type: 'img', src: waveImage('') }; + + // return the filled template + return this.fillTemplate('day', fill); + }); + + // empty and update the container + const dayContainer = this.elem.querySelector('.day-container'); + dayContainer.innerHTML = ''; + dayContainer.append(...days); + this.finishDraw(); + } +} + +const waveImage = (conditions) => { + const color = 'rgb(172, 165, 251)'; + const canvas = document.createElement('canvas'); + canvas.width = 150; + canvas.height = 20; + const context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + + let y = 0; + let r = 35; + let arc1 = Math.PI * 0.3; + let arc2 = Math.PI * 0.7; + + switch (conditions) { + case 'CHOPPY': + y = -10; + arc1 = Math.PI * 0.2; + arc2 = Math.PI * 0.8; + r = 25; + break; + + case 'ROUGH': + y = -5; + arc1 = Math.PI * 0.1; + arc2 = Math.PI * 0.9; + r = 20; + break; + + case 'LIGHT': + default: + y = -20; + arc1 = Math.PI * 0.3; + arc2 = Math.PI * 0.7; + r = 35; + break; + } + + context.beginPath(); + context.arc(35, y, r, arc1, arc2); + context.strokeStyle = color; + context.lineWidth = 4; + context.stroke(); + context.beginPath(); + context.arc(75, y, r, arc1, arc2); + context.stroke(); + context.beginPath(); + context.arc(115, y, r, arc1, arc2); + context.stroke(); + + return canvas.toDataURL(); +}; + +// register display +registerDisplay(new MarineForecast(11, 'marine-forecast')); diff --git a/server/styles/scss/_marine-forecast.scss b/server/styles/scss/_marine-forecast.scss new file mode 100644 index 0000000..a6e12d9 --- /dev/null +++ b/server/styles/scss/_marine-forecast.scss @@ -0,0 +1,98 @@ +@use 'shared/_colors'as c; +@use 'shared/_utils'as u; + +#marine-forecast-html.weather-display { + background-image: url('../images/BackGround8_1.png'); +} + +.weather-display .main.marine-forecast { + font-family: 'Star4000'; + font-size: 24pt; + @include u.text-shadow(); + + .advisory { + width: 100%; + height: 90px; + overflow: hidden; + + .advisory-text { + border: 4px solid black; + width: 75%; + margin-left: auto; + margin-right: auto; + text-align: center; + margin-top: 40px; + ; + } + } + + .headers { + display: inline-block; + vertical-align: top; + + .winds { + text-align: right; + width: 150px; + margin-top: 42px; + margin-bottom: 60px; + } + } + + .day-container { + display: inline-block; + } + + .day { + padding: 5px; + width: 165px; + display: inline-block; + margin: 0px 15px; + text-align: center; + + .date { + color: c.$title-color; + } + + .wave { + border: 4px solid #b09ffb; + + .wave-icon { + height: 20px; + + img { + display: block; + margin-left: auto; + margin-right: auto; + } + } + } + + .temperatures { + width: 100%; + margin-top: 5px; + + .temperature-block { + display: inline-block; + width: 44%; + vertical-align: top; + + >div { + text-align: center; + } + + .value { + font-family: 'Star4000 Large'; + margin-top: 4px; + } + + &.lo .label { + color: c.$extended-low; + } + + &.hi .label { + color: c.$title-color; + } + } + } + } +} \ No newline at end of file diff --git a/server/styles/scss/main.scss b/server/styles/scss/main.scss index 2b8d5c2..f2cc422 100644 --- a/server/styles/scss/main.scss +++ b/server/styles/scss/main.scss @@ -11,4 +11,5 @@ @import 'radar'; @import 'regional-forecast'; @import 'almanac'; -@import 'hazards'; \ No newline at end of file +@import 'hazards'; +@import 'marine-forecast'; \ No newline at end of file diff --git a/views/partials/marine-forecast.ejs b/views/partials/marine-forecast.ejs new file mode 100644 index 0000000..1483cb1 --- /dev/null +++ b/views/partials/marine-forecast.ejs @@ -0,0 +1,23 @@ +<%- include('header.ejs', { title: 'Marine Forecast' , hasTime: true }) %> +