Basic slideshow functionality

This commit is contained in:
Guntis Smaukstelis
2025-02-21 23:07:49 +02:00
parent 832f723f39
commit 2893b5acff
7 changed files with 110 additions and 9 deletions
+3 -3
View File
@@ -4,23 +4,23 @@ import { LoadingSpinner } from '../components/LoadingSpinner'
import { DrawOptions, GribMessage } from './interfaces'
import { drawGrib } from './draw/drawGrib'
const CROP_BOUNDS = { x: 1906-1-440, y: 895, width: 440, height: 380 }
export const CROP_BOUNDS = { x: 1906-1-440, y: 895, width: 440, height: 380 }
export const DrawView: Component<{
isLoadingSignal: Signal<boolean>,
options: DrawOptions;
canvasSignal: Signal<HTMLCanvasElement | undefined>,
cachedMessagesSignal: Signal<GribMessage[]>,
cachedBuffersSignal: Signal<Uint8Array[]>,
cachedBitmasksSignal: Signal<Uint8Array[]>,
}> = ({
isLoadingSignal: [getIsLoading, setIsLoading],
options,
canvasSignal: [getCanvas, setCanvas],
cachedMessagesSignal: [getCachedMessages],
cachedBuffersSignal: [getCachedBuffers],
cachedBitmasksSignal: [getCachedBitmasks],
}) => {
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>()
createEffect(async () => {
setIsLoading(true)
const canvas = getCanvas()!
+2 -2
View File
@@ -32,8 +32,8 @@ export const GribFile: Component<{
fetchGribBinaries(grib, getAllGribLists()).then(([messages, binaryBuffers, bitmasks]) => {
batch(() => {
setCachedMessages(messages)
setCachedBuffers(binaryBuffers.map(b => new Uint8Array(b)))
setCachedBitmasks(bitmasks.map(b => new Uint8Array(b)))
setCachedBuffers(binaryBuffers)
setCachedBitmasks(bitmasks)
})
})
.catch(err => console.warn(err.message))
+12
View File
@@ -7,6 +7,7 @@ import { ReferenceTimes } from './ReferenceTimes'
import { fetchGribList, fetchGribListStructure } from './fetchGrib'
import styles from './harmonie.module.css'
import { SlideShow } from './SlideShow'
export const Harmonie: Component<{}> = () => {
const [getFileList, setFileList] = createSignal<string[]>([])
@@ -15,6 +16,8 @@ export const Harmonie: Component<{}> = () => {
const [getIsContour, setIsContour] = createSignal(true)
const [getIsInterpolated, setIsInterpolated] = createSignal(true)
const [getGribList, setGribList] = createSignal<GribMessage[]>([])
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>()
const [getImgList, setImgList] = createSignal<[string, ImageBitmap][]>([])
const cachedMessagesSignal = createSignal<GribMessage[]>([])
const cachedBuffersSignal = createSignal<Uint8Array[]>([])
@@ -62,8 +65,12 @@ export const Harmonie: Component<{}> = () => {
<input type='checkbox' checked={getIsInterpolated()} onChange={()=>setIsInterpolated(!getIsInterpolated())} />
</label>
<ReferenceTimes
setIsLoading={setIsLoading}
getFileList={getFileList}
getGribList={getGribList}
getCanvas={getCanvas}
options={{ getIsCrop: getIsCrop, getIsContour: getIsContour, getIsInterpolated: getIsInterpolated }}
setImgList={setImgList}
onClick={getAllGribStructure}
/>
<ul class={styles.fileList}>
@@ -82,9 +89,14 @@ export const Harmonie: Component<{}> = () => {
</ul>
</div>
<div class={styles.column}>
<SlideShow
getCanvas={getCanvas}
getImgList={getImgList}
/>
<DrawView
isLoadingSignal={[getIsLoading, setIsLoading]}
options={{ getIsCrop: getIsCrop, getIsContour: getIsContour, getIsInterpolated: getIsInterpolated }}
canvasSignal={[getCanvas, setCanvas]}
cachedMessagesSignal={cachedMessagesSignal}
cachedBuffersSignal={cachedBuffersSignal}
cachedBitmasksSignal={cachedBitmasksSignal}
+52 -3
View File
@@ -1,15 +1,27 @@
import { Accessor, Component, createSignal } from 'solid-js'
import { Accessor, Component, createSignal, resetErrorBoundaries, Setter } from 'solid-js'
import { DrawOptions, GribMessage } from './interfaces'
import { fetchGribBinaries } from './fetchGrib'
import { drawGrib } from './draw/drawGrib'
import { CROP_BOUNDS } from './DrawView'
import styles from './harmonie.module.css'
import { GribMessage } from './interfaces'
export const ReferenceTimes: Component<{
setIsLoading: Setter<boolean>,
getFileList: Accessor<string[]>,
getGribList: Accessor<GribMessage[]>,
getCanvas: Accessor<HTMLCanvasElement | undefined>,
options: DrawOptions,
setImgList: Setter<[string, ImageBitmap][]>,
onClick: () => void,
}> = ({
setIsLoading,
getFileList,
getGribList,
getCanvas,
options,
setImgList,
onClick,
}) => {
const [getActiveDate, setActiveDate] = createSignal('')
@@ -24,13 +36,50 @@ export const ReferenceTimes: Component<{
setActiveDate(newValue)
}
async function getImgList(dateStr: string) {
setIsLoading(true)
const canvas = getCanvas()!
const cropBounds = options.getIsCrop() ? CROP_BOUNDS : undefined
const contour = options.getIsContour()
const isInterpolated = options.getIsInterpolated()
const forecastList = getGribList()
.filter(g => g.time.referenceTime === dateStr)
.filter(g => g.meteo.discipline === 0 && g.meteo.category === 1 && g.meteo.product === 192)
// .slice(0, 5) // TODO remove this
const promiseList = forecastList.map(async (grib): Promise<[string, ImageBitmap]> => {
const [messages, buffers, bitmasks] = await fetchGribBinaries(grib, getGribList())
drawGrib(canvas, messages, buffers, bitmasks, cropBounds, contour, isInterpolated)
// just to be sure that draws
await new Promise(resolve => setTimeout(resolve))
const img = await createImageBitmap(canvas)
return [grib.time.forecastTime, img]
})
Promise.all(promiseList)
.then(imgList => {
imgList.sort((a, b) => a[0] > b[0] ? 1 : -1)
console.log(imgList)
setImgList(imgList)
})
.finally(() => {
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, canvas.width, canvas.height)
setIsLoading(false)
})
}
return <ul class={styles.dateList}>
{ dateList().map(dateStr =>
<li>
<div onClick={() => onActiveDate(dateStr)}>
{ dateStr }
</div>
<div class={styles.controls} style={{ display: getActiveDate() === dateStr ? 'block' : 'none'}}>
<div
class={styles.controls}
style={{ display: getActiveDate() === dateStr ? 'block' : 'none'}}
onClick={() => getImgList(dateStr)}
>
{
getGribList().some(g => g.time.referenceTime === dateStr)
&& <>TODO: download all forecasts</>
+30
View File
@@ -0,0 +1,30 @@
import { Accessor, Component } from 'solid-js'
import styles from './harmonie.module.css'
export const SlideShow: Component<{
getCanvas: Accessor<HTMLCanvasElement | undefined>,
getImgList: Accessor<[string, ImageBitmap][]>,
}> = ({
getCanvas,
getImgList
}) => {
function draw(img: ImageBitmap) {
const canvas = getCanvas()!
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0)
}
return <ul class={styles.slideShow}>
{ getImgList().map(([forecastDate, img]) =>
<li onClick={() => draw(img)}>
{ format(forecastDate) }
</li>)}
</ul>
}
function format(date: string) {
return date.slice(11, 13)
// return date
}
+6 -1
View File
@@ -27,7 +27,7 @@ export function fetchGribListStructure(): Promise<GribMessage[]> {
export function fetchGribBinaries(
grib: GribMessage,
gribList: GribMessage[],
) {
): Promise<[GribMessage[], Uint8Array[], Uint8Array[]]> {
const fileName = `harmonie_${grib.time.referenceTime}_${grib.time.forecastTime}.grib`
const bitmaskSection = grib.sections.find(section => section.id === 6)
const binarySection = grib.sections.find(section => section.id === 7)
@@ -51,4 +51,9 @@ export function fetchGribBinaries(
if(isCalculatedHourPrecipitation(grib)) fetchPromise = fetchHourPrecipitationData(grib, gribList)
return fetchPromise
.then(([messages, buffers, bitmasks]) => [
messages,
buffers.map(b => new Uint8Array(b)),
bitmasks.map(b => new Uint8Array(b)),
])
}
+5
View File
@@ -69,3 +69,8 @@
.dateList li .controls {
padding: 10px 3px;
}
.slideShow li {
display: inline;
padding: 3px 5px;
}