From 89fa9224feb9124f05840d2e24fda15378cdee2f Mon Sep 17 00:00:00 2001 From: Guntis Smaukstelis Date: Fri, 14 Mar 2025 15:20:25 +0200 Subject: [PATCH] Draw Table for Riga weather day and next day --- web/src/assets/bg-riga.webp | Bin 0 -> 9188 bytes web/src/components/map/windAngles.ts | 3 +- web/src/components/table/Table.tsx | 114 ++++++++++++++++++ web/src/pages/lvgmc-forecast/CsvMapData.tsx | 2 +- .../pages/lvgmc-forecast/LvgmcForecast.tsx | 8 ++ 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 web/src/assets/bg-riga.webp create mode 100644 web/src/components/table/Table.tsx diff --git a/web/src/assets/bg-riga.webp b/web/src/assets/bg-riga.webp new file mode 100644 index 0000000000000000000000000000000000000000..35d4c60ad4a292be5e00d4cb32f2ae7b865bd1bf GIT binary patch literal 9188 zcmeHMc|4R|-@mNcMUqgaAqitv%Zz;+CTj?3VayE1I%dY2Eo3PnTJ0gbqC!O^M6@7D zAxj}`qOxRruhDYf&-;0v_x
%9-h`Ch;CTfgU=>oarM5b*dRSpaY_Hn6t0)^Owl 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 literal 0 HcmV?d00001 diff --git a/web/src/components/map/windAngles.ts b/web/src/components/map/windAngles.ts index 165bfbe..c3e3bd7 100644 --- a/web/src/components/map/windAngles.ts +++ b/web/src/components/map/windAngles.ts @@ -18,7 +18,8 @@ export function drawRotatedImage( img: HTMLImageElement, x: number, y: number, - angleInDegrees: number): void { + angleInDegrees: number, +): void { const angleInRadians = angleInDegrees * (Math.PI / 180); ctx.translate(x + img.width / 2, y + img.height / 2); ctx.rotate(angleInRadians); diff --git a/web/src/components/table/Table.tsx b/web/src/components/table/Table.tsx new file mode 100644 index 0000000..09386e7 --- /dev/null +++ b/web/src/components/table/Table.tsx @@ -0,0 +1,114 @@ +import { Component } from 'solid-js' +import { fetchText } from '../../helpers/fetch' +import { apiHost } from '../../consts' +import { codeToIcon } from '../weatherIcons/iconConsts' +import rigaBgUrl from '../../assets/bg-riga.webp' +import arrowUrl from '../../assets/arrow.webp' +import { drawRotatedImage, getAngleFromString } from '../map/windAngles' + +type TableWeather = { + hour: string, + temperature: string, + windDirection: string, + windSpeed: string, + icon: string, +} + +export const Table: Component<{ + csvUrl: string, +}> = ({ + csvUrl, +}) => { + let canvas: HTMLCanvasElement | undefined + const rigaBg = new Image() + rigaBg.src = rigaBgUrl + const arrowImg = new Image() + arrowImg.src = arrowUrl + + fetchText(`${apiHost}/api/show/lvgmc-forecast/${csvUrl}`) + .then(csv => { + const csvLines = csv.split('\n') + const dateStr = csvLines[2].split(';')[0].split('.').reverse().join('.') + const weekDay = new Date(dateStr).getDay() + const weekDayLV = weekDaysLV.get(weekDay)?.toUpperCase() ?? '' + const dayOrNight = csvLines[2].split(';')[1] + const rigaOffset = dayOrNight === 'DIENA' ? 24 : 51 + const data: TableWeather[] = csvLines + .slice(rigaOffset, rigaOffset+4) + .map((line): TableWeather => { + const parts = line.split(';') + const hour = parts[0].slice(6, 11).replace(':', '.') + const temperature = parts[1] + const windDirection = parts[3] + const windSpeed = parts[5] + const iconCode = Number(parts[7]) + const icon = codeToIcon(iconCode) + + return { hour, temperature, windDirection, windSpeed, icon } + }) + drawTable(canvas!, rigaBg, arrowImg, data, weekDayLV) + }) + + return <> + + +} + + +function drawTable( + canvas: HTMLCanvasElement, + rigaBg: HTMLImageElement, + arrowImg: HTMLImageElement, + data: TableWeather[], + title: string, +) { + if (!data.length) throw new Error('Empty data for drawing table!') + canvas.width = rigaBg.width + canvas.height = rigaBg.height + const ctx = canvas.getContext('2d')! + ctx.drawImage(rigaBg, 0, 0) + + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.font = 'bold 30px Rubik' + ctx.fillStyle = '#000000' + ctx.letterSpacing = '10px' + ctx.fillText(title, rigaBg.width/2, rigaBg.height*0.065) + + ctx.letterSpacing = '0px' + const colWidth = rigaBg.width / data.length + data.forEach((weather, i) => { + const midX = colWidth * (i + 0.5) + ctx.textAlign = 'center' + ctx.font = 'bold 20px Rubik' + ctx.fillStyle = '#FFFFFF' + ctx.fillText(weather.hour, midX, rigaBg.height * 0.2) + + ctx.font = `normal 240px Daira by LTV grafika` + ctx.fillText(weather.icon, midX, rigaBg.height * 0.5) + + ctx.font = 'normal 50px Rubik' + ctx.fillText(weather.temperature, midX, rigaBg.height * 0.75) + + const bottomLineY = rigaBg.height * 0.9 + ctx.font = 'bold 30px Rubik' + ctx.textAlign = 'center' + ctx.fillText(weather.windDirection, midX, bottomLineY) + const dirWidth = ctx.measureText(weather.windDirection).width + const windAngle = getAngleFromString(weather.windDirection) + ctx.textAlign = 'right' + drawRotatedImage(ctx, arrowImg, midX-dirWidth/2-arrowImg.width-10, bottomLineY-arrowImg.height/2, windAngle) + ctx.textAlign = 'left' + ctx.fillText(weather.windSpeed, midX+dirWidth/2+10, bottomLineY) + }) +} + +const weekDaysLV = new Map([ + [1, 'pirmdiena'], + [2, 'otrdiena'], + [3, 'trešdiena'], + [4, 'ceturtdiena'], + [5, 'piektdiena'], + [6, 'sestdiena'], + [7, 'svētdiena'], +]) \ No newline at end of file diff --git a/web/src/pages/lvgmc-forecast/CsvMapData.tsx b/web/src/pages/lvgmc-forecast/CsvMapData.tsx index ae62f58..8c1ef94 100644 --- a/web/src/pages/lvgmc-forecast/CsvMapData.tsx +++ b/web/src/pages/lvgmc-forecast/CsvMapData.tsx @@ -23,7 +23,7 @@ export const CsvMapData: Component<{ .then(csv => { const csvLines = csv.split('\n') const dayOrNight = csvLines[2].split(';')[1] - console.log(csvLines) + // console.log(csvLines) const citiesOffset = 4 const cityData: CityData[] = csvCityListInOrder .map((city, i): CityData => { diff --git a/web/src/pages/lvgmc-forecast/LvgmcForecast.tsx b/web/src/pages/lvgmc-forecast/LvgmcForecast.tsx index 17153b3..e8c03c6 100644 --- a/web/src/pages/lvgmc-forecast/LvgmcForecast.tsx +++ b/web/src/pages/lvgmc-forecast/LvgmcForecast.tsx @@ -3,6 +3,7 @@ import { Component } from 'solid-js' import dayBgMap from '../../assets/map_3840x1440_wind.webp' import nightBgMap from '../../assets/map_night_3840x1440_wind.webp' import { CsvMapData } from './CsvMapData' +import { Table } from '../../components/table/Table' // Eiropa_LTV_pilsetas_nakama_dn.csv // Eiropa_LTV_pilsetas_tekosa_dn.csv @@ -12,6 +13,13 @@ import { CsvMapData } from './CsvMapData' export const LvgmcForecast: Component<{}> = () => { return <> + +
+

Night