2025-02-14 20:26:57 +02:00
|
|
|
import { Accessor, Component, createEffect, createSignal, Setter } from 'solid-js'
|
2025-02-02 23:33:15 +02:00
|
|
|
|
|
|
|
|
import styles from './harmonie.module.css'
|
|
|
|
|
import { apiHost } from '../consts'
|
2025-02-03 23:33:28 +02:00
|
|
|
import { GribMessage } from './interfaces'
|
|
|
|
|
import { fetchBuffer } from '../helpers/fetch'
|
|
|
|
|
import { drawGrib } from './draw/drawGrib'
|
2025-02-14 20:26:57 +02:00
|
|
|
import { fetchWindData } from './draw/windDirection'
|
2025-02-03 23:33:28 +02:00
|
|
|
|
|
|
|
|
const CROP_BOUNDS = { x: 1906-1-400, y: 950, width: 400, height: 300 }
|
2025-02-02 23:33:15 +02:00
|
|
|
|
|
|
|
|
export const GribFile: Component<{
|
|
|
|
|
name: string,
|
2025-02-03 23:33:28 +02:00
|
|
|
getCanvas: Accessor<HTMLCanvasElement | undefined>,
|
2025-02-04 19:42:35 +02:00
|
|
|
setIsLoading: Setter<boolean>,
|
2025-02-14 20:26:57 +02:00
|
|
|
getGribList: Accessor<GribMessage[]>
|
2025-02-04 22:12:03 +02:00
|
|
|
getIsCrop: Accessor<boolean>,
|
2025-02-02 23:33:15 +02:00
|
|
|
onClick: (name: string) => void,
|
2025-02-04 19:42:35 +02:00
|
|
|
}> = ({
|
|
|
|
|
name,
|
|
|
|
|
getCanvas,
|
|
|
|
|
setIsLoading,
|
2025-02-14 20:26:57 +02:00
|
|
|
getGribList,
|
2025-02-04 22:12:03 +02:00
|
|
|
getIsCrop,
|
2025-02-04 19:42:35 +02:00
|
|
|
onClick,
|
|
|
|
|
}) => {
|
2025-02-03 23:33:28 +02:00
|
|
|
let cachedMessages: GribMessage[] = []
|
|
|
|
|
let cachedBuffers: Uint8Array[] = []
|
|
|
|
|
let cachedBitmasks: Uint8Array[] = []
|
|
|
|
|
|
2025-02-14 20:26:57 +02:00
|
|
|
const [getIsActive, setIsActive] = createSignal(false)
|
|
|
|
|
|
2025-02-04 22:12:03 +02:00
|
|
|
createEffect(async () => {
|
|
|
|
|
setIsLoading(true)
|
|
|
|
|
const cropBounds = getIsCrop() ? CROP_BOUNDS : undefined
|
|
|
|
|
// hack to show loading spinner
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
|
|
|
if (cachedMessages.length === 0) return;
|
|
|
|
|
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds)
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
})
|
|
|
|
|
|
2025-02-03 23:33:28 +02:00
|
|
|
function onParamClick(paramId: number) {
|
2025-02-04 19:42:35 +02:00
|
|
|
setIsLoading(true);
|
|
|
|
|
const grib = getGribList()[paramId]
|
2025-02-03 23:33:28 +02:00
|
|
|
const bitmaskSection = grib.sections.find(section => section.id === 6)
|
|
|
|
|
const binarySection = grib.sections.find(section => section.id === 7)
|
|
|
|
|
if (!bitmaskSection || !binarySection) return;
|
|
|
|
|
|
|
|
|
|
const bitmaskOffset = bitmaskSection.offset + 6
|
|
|
|
|
const bitmaskLength = bitmaskSection.size - 6
|
|
|
|
|
const bitmaskPromise = bitmaskSection.size > 6
|
|
|
|
|
? fetchBuffer(`${apiHost}/api/grib/binary-chunk/${bitmaskOffset}/${bitmaskLength}/${name}`).then(b=>[b])
|
|
|
|
|
: Promise.resolve([])
|
|
|
|
|
|
|
|
|
|
const binaryOffset = binarySection.offset + 5
|
|
|
|
|
const binaryLength = binarySection.size - 5
|
2025-02-04 19:42:35 +02:00
|
|
|
const fetchPromise: Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> = grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192
|
2025-02-04 22:47:01 +02:00
|
|
|
? fetchWindData(grib, getGribList(), name)
|
2025-02-04 19:42:35 +02:00
|
|
|
: Promise.all([
|
|
|
|
|
Promise.resolve([grib]),
|
2025-02-03 23:33:28 +02:00
|
|
|
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${binaryOffset}/${binaryLength}/${name}`).then(b=>[b]),
|
|
|
|
|
bitmaskPromise,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
fetchPromise.then(([messages, binaryBuffers, bitmasks]) => {
|
|
|
|
|
cachedMessages = messages
|
|
|
|
|
cachedBuffers = binaryBuffers.map(b => new Uint8Array(b))
|
|
|
|
|
cachedBitmasks = bitmasks.map(b => new Uint8Array(b))
|
2025-02-04 22:12:03 +02:00
|
|
|
const cropBounds = getIsCrop() ? CROP_BOUNDS : undefined
|
2025-02-04 19:42:35 +02:00
|
|
|
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds)
|
2025-02-03 23:33:28 +02:00
|
|
|
})
|
|
|
|
|
.catch(err => console.warn(err.message))
|
2025-02-04 19:42:35 +02:00
|
|
|
.finally(() => setIsLoading(false))
|
2025-02-03 23:33:28 +02:00
|
|
|
}
|
|
|
|
|
|
2025-02-02 23:33:15 +02:00
|
|
|
return <li
|
2025-02-14 20:26:57 +02:00
|
|
|
class={getIsActive() ? styles.active : ''}
|
|
|
|
|
onClick={() => onClick(name)}
|
2025-02-02 23:33:15 +02:00
|
|
|
>
|
2025-02-14 20:26:57 +02:00
|
|
|
<div class={styles.name} onClick={() => setIsActive(!getIsActive())}>{ trimName(name) }</div>
|
2025-02-02 23:33:15 +02:00
|
|
|
<ul class={styles.meteoParams}>
|
2025-02-04 19:42:35 +02:00
|
|
|
{ getGribList()
|
2025-02-14 20:26:57 +02:00
|
|
|
.map((grib, i) =>
|
2025-02-04 21:59:39 +02:00
|
|
|
<li onClick={() => onParamClick(i)}>{ grib.title.replace('meteorology, ', '') }</li>
|
2025-02-02 23:33:15 +02:00
|
|
|
)}
|
|
|
|
|
</ul>
|
|
|
|
|
</li>
|
|
|
|
|
|
2025-02-03 20:24:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function trimName(title: string): string {
|
|
|
|
|
let result = title
|
|
|
|
|
result = result.replace('harmonie_', '')
|
|
|
|
|
result = result.replace('.grib', '')
|
|
|
|
|
return result
|
|
|
|
|
}
|