Fix drawing of temperature, precipitation, wind

This commit is contained in:
Guntis Smaukstelis
2025-02-04 19:42:35 +02:00
parent df8d16ed4a
commit 2b1e348dde
7 changed files with 43 additions and 32 deletions
+1 -1
View File
@@ -136,7 +136,7 @@ object GribParser {
bytes <- readBytes(path, ptr, 64) bytes <- readBytes(path, ptr, 64)
length = ByteBuffer.wrap(bytes.slice(0, 4)).getInt length = ByteBuffer.wrap(bytes.slice(0, 4)).getInt
bitsPerDataPoint = bytes(19).toInt bitsPerDataPoint = bytes(19).toInt
reference = ByteBuffer.wrap(bytes.slice(11, 15)).getInt reference = ByteBuffer.wrap(bytes.slice(11, 15)).getFloat
binaryScale = -(bytes(16) & 0xFF) binaryScale = -(bytes(16) & 0xFF)
decimalScale = bytes(18) & 0xFF decimalScale = bytes(18) & 0xFF
conversion = MeteoConversion(reference, binaryScale, decimalScale) conversion = MeteoConversion(reference, binaryScale, decimalScale)
+22 -15
View File
@@ -1,10 +1,11 @@
import { Accessor, Component, createSignal } from 'solid-js' import { Accessor, Component, createSignal, Setter } from 'solid-js'
import styles from './harmonie.module.css' import styles from './harmonie.module.css'
import { apiHost } from '../consts' import { apiHost } from '../consts'
import { GribMessage } from './interfaces' import { GribMessage } from './interfaces'
import { fetchBuffer } from '../helpers/fetch' import { fetchBuffer } from '../helpers/fetch'
import { drawGrib } from './draw/drawGrib' import { drawGrib } from './draw/drawGrib'
import { fetchWindData } from './draw/windDirection'
const CROP_BOUNDS = { x: 1906-1-400, y: 950, width: 400, height: 300 } const CROP_BOUNDS = { x: 1906-1-400, y: 950, width: 400, height: 300 }
@@ -12,18 +13,25 @@ export const GribFile: Component<{
name: string, name: string,
isActive: Accessor<boolean>, isActive: Accessor<boolean>,
getCanvas: Accessor<HTMLCanvasElement | undefined>, getCanvas: Accessor<HTMLCanvasElement | undefined>,
setIsLoading: Setter<boolean>,
onClick: (name: string) => void, onClick: (name: string) => void,
}> = ({ name, isActive, getCanvas, onClick }) => { }> = ({
const [getStructure, setStructure] = createSignal<GribMessage[]>([]) name,
isActive,
getCanvas,
setIsLoading,
onClick,
}) => {
const [getGribList, setGribList] = createSignal<GribMessage[]>([])
const isCrop = createSignal(false) const isCrop = createSignal(false)
function onFileClick() { function onFileClick() {
onClick(name) onClick(name)
if (getStructure().length === 0) { if (getGribList().length === 0) {
fetch(`${apiHost}/api/show/grib/${name}`) fetch(`${apiHost}/api/show/grib/${name}`)
.then(re => re.json()) .then(re => re.json())
.then(setStructure) .then(setGribList)
} }
} }
@@ -32,12 +40,11 @@ export const GribFile: Component<{
let cachedBitmasks: Uint8Array[] = [] let cachedBitmasks: Uint8Array[] = []
function onParamClick(paramId: number) { function onParamClick(paramId: number) {
const grib = getStructure()[paramId] setIsLoading(true);
console.log("onParamClick", grib) const grib = getGribList()[paramId]
const bitmaskSection = grib.sections.find(section => section.id === 6) const bitmaskSection = grib.sections.find(section => section.id === 6)
const binarySection = grib.sections.find(section => section.id === 7) const binarySection = grib.sections.find(section => section.id === 7)
if (!bitmaskSection || !binarySection) return; if (!bitmaskSection || !binarySection) return;
console.log("sections 6,7")
const bitmaskOffset = bitmaskSection.offset + 6 const bitmaskOffset = bitmaskSection.offset + 6
const bitmaskLength = bitmaskSection.size - 6 const bitmaskLength = bitmaskSection.size - 6
@@ -47,9 +54,10 @@ export const GribFile: Component<{
const binaryOffset = binarySection.offset + 5 const binaryOffset = binarySection.offset + 5
const binaryLength = binarySection.size - 5 const binaryLength = binarySection.size - 5
const fetchPromise: Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> = const fetchPromise: Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> = grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192
Promise.all([ ? fetchWindData(grib, getGribList())
[grib], : Promise.all([
Promise.resolve([grib]),
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${binaryOffset}/${binaryLength}/${name}`).then(b=>[b]), fetchBuffer(`${apiHost}/api/grib/binary-chunk/${binaryOffset}/${binaryLength}/${name}`).then(b=>[b]),
bitmaskPromise, bitmaskPromise,
]) ])
@@ -58,12 +66,11 @@ export const GribFile: Component<{
cachedMessages = messages cachedMessages = messages
cachedBuffers = binaryBuffers.map(b => new Uint8Array(b)) cachedBuffers = binaryBuffers.map(b => new Uint8Array(b))
cachedBitmasks = bitmasks.map(b => new Uint8Array(b)) cachedBitmasks = bitmasks.map(b => new Uint8Array(b))
const colors: [string, string] = ['#0000ff', '#ffff00']
const cropBounds = isCrop[0]() ? CROP_BOUNDS : undefined const cropBounds = isCrop[0]() ? CROP_BOUNDS : undefined
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, colors, cropBounds) drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds)
}) })
.catch(err => console.warn(err.message)) .catch(err => console.warn(err.message))
// .finally(() => setIsLoading(false)) .finally(() => setIsLoading(false))
} }
return <li return <li
@@ -72,7 +79,7 @@ export const GribFile: Component<{
> >
<div class={styles.name}>{ trimName(name) }</div> <div class={styles.name}>{ trimName(name) }</div>
<ul class={styles.meteoParams}> <ul class={styles.meteoParams}>
{ getStructure() { getGribList()
.sort((a, b) => a.title > b.title ? 1 : -1) .sort((a, b) => a.title > b.title ? 1 : -1)
.map((grib, i) => .map((grib, i) =>
<li onClick={() => onParamClick(i)}>{ grib.title }</li> <li onClick={() => onParamClick(i)}>{ grib.title }</li>
+7 -2
View File
@@ -1,13 +1,15 @@
import { Component, createSignal } from 'solid-js' import { Component, createSignal } from 'solid-js'
import { apiHost } from '../consts'
import { apiHost } from '../consts'
import styles from './harmonie.module.css' import styles from './harmonie.module.css'
import { GribFile } from './GribFile' import { GribFile } from './GribFile'
import { LoadingSpinner } from '../components/LoadingSpinner'
export const Harmonie: Component<{}> = () => { export const Harmonie: Component<{}> = () => {
const [getFileList, setFileList] = createSignal<string[]>([]) const [getFileList, setFileList] = createSignal<string[]>([])
const [getActiveGrib, setActiveGrib] = createSignal('') const [getActiveGrib, setActiveGrib] = createSignal('')
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>() const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>()
const [getIsLoading, setIsLoading] = createSignal(true)
fetch(`${apiHost}/api/show/grib-list`) fetch(`${apiHost}/api/show/grib-list`)
.then(re => re.json()) .then(re => re.json())
@@ -15,6 +17,7 @@ export const Harmonie: Component<{}> = () => {
fileList.sort((a: string, b: string) => a < b ? 1 : -1) fileList.sort((a: string, b: string) => a < b ? 1 : -1)
setFileList(fileList) setFileList(fileList)
}) })
.finally(() => setIsLoading(false))
return <div class={styles.container}> return <div class={styles.container}>
<div class={styles.column}> <div class={styles.column}>
@@ -24,13 +27,15 @@ export const Harmonie: Component<{}> = () => {
name={fileName} name={fileName}
isActive={() => getActiveGrib() === fileName} isActive={() => getActiveGrib() === fileName}
getCanvas={getCanvas} getCanvas={getCanvas}
setIsLoading={setIsLoading}
onClick={() => setActiveGrib(fileName)} onClick={() => setActiveGrib(fileName)}
/> />
)} )}
</ul> </ul>
</div> </div>
<div class={styles.column}> <div class={styles.column}>
<canvas ref={setCanvas} /> { getIsLoading() && <LoadingSpinner text='' />}
<canvas ref={setCanvas} style={{ display: getIsLoading() ? 'none' : 'block' }} />
</div> </div>
</div> </div>
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { GribMessage } from '../../interfaces/interfaces' import { GribMessage } from '../interfaces'
import { CropBounds } from './drawGrib' import { CropBounds } from './drawGrib'
export function extractFromBounds( export function extractFromBounds(
+10 -11
View File
@@ -1,11 +1,11 @@
import { interpolateColors } from '../../helpers/interpolateColors.ts' import { interpolateColors } from '../../helpers/interpolateColors'
import { GribMessage, MeteoParam } from '../../interfaces/interfaces.ts' import { GribMessage, MeteoParam } from '../interfaces'
import { applyBitmask } from './bitmask.ts' import { applyBitmask } from './bitmask'
import { extractFromBounds } from './bounds.ts' import { extractFromBounds } from './bounds'
import { categoricalRainColors } from './categoricalRain.ts' import { categoricalRainColors } from './categoricalRain'
import { precipitationColors } from './precipitation.ts' import { precipitationColors } from './precipitation'
import { temperatureColors } from './temperature.ts' import { temperatureColors } from './temperature'
import { windDirectionArrows, windDirectionColors, windSpeedColors } from './windDirection.ts' import { windDirectionArrows, windDirectionColors, windSpeedColors } from './windDirection'
export type CropBounds = { x: number, y: number, width: number, height: number } export type CropBounds = { x: number, y: number, width: number, height: number }
@@ -14,7 +14,6 @@ export function drawGrib(
messages: GribMessage[], messages: GribMessage[],
buffers: Uint8Array[], buffers: Uint8Array[],
bitmasks: Uint8Array[], bitmasks: Uint8Array[],
colors: [string, string],
cropBounds: CropBounds | undefined, cropBounds: CropBounds | undefined,
): void { ): void {
// normally we have one message/buffer/bitmask?. special cases have multiple like wind direction // normally we have one message/buffer/bitmask?. special cases have multiple like wind direction
@@ -41,7 +40,7 @@ export function drawGrib(
const ctx = canvas.getContext('2d')! const ctx = canvas.getContext('2d')!
let imgData = ctx.createImageData(cols, rows) let imgData = ctx.createImageData(cols, rows)
fillImageData(imgData, messages, modifiedBuffers, colors) fillImageData(imgData, messages, modifiedBuffers)
if (grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192) { if (grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192) {
imgData = windDirectionArrows(imgData, messages, modifiedBuffers) imgData = windDirectionArrows(imgData, messages, modifiedBuffers)
} }
@@ -71,10 +70,10 @@ function fillImageData(
imgData: ImageData, imgData: ImageData,
messages: GribMessage[], messages: GribMessage[],
buffers: Uint8Array[], buffers: Uint8Array[],
colors: [string, string],
) { ) {
const [grib] = messages const [grib] = messages
const [buffer] = buffers const [buffer] = buffers
const colors: [string, string] = ['#0000ff', '#ffff00']
const { meteo, conversion, bitsPerDataPoint } = grib const { meteo, conversion, bitsPerDataPoint } = grib
const bytesPerPoint = grib.bitsPerDataPoint / 8 const bytesPerPoint = grib.bitsPerDataPoint / 8
+1 -1
View File
@@ -1,5 +1,5 @@
import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors' import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors'
import { MeteoConversion } from '../../interfaces/interfaces' import { MeteoConversion } from '../interfaces'
import { PRECIPITATION } from './constants' import { PRECIPITATION } from './constants'
+1 -1
View File
@@ -1,5 +1,5 @@
import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors' import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors'
import { MeteoConversion } from '../../interfaces/interfaces' import { MeteoConversion } from '../interfaces'
import { TEMPERATURES } from './constants' import { TEMPERATURES } from './constants'