Interpolate option, also interpolate wind data

This commit is contained in:
Guntis Smaukstelis
2025-02-18 16:03:33 +02:00
parent cb9e6a88a0
commit 29ef46f4c7
6 changed files with 45 additions and 25 deletions
+9 -10
View File
@@ -2,7 +2,7 @@ import { Accessor, Component, createEffect, createSignal, Setter } from 'solid-j
import styles from './harmonie.module.css'
import { apiHost } from '../consts'
import { GribMessage } from './interfaces'
import { DrawOptions, GribMessage } from './interfaces'
import { fetchBuffer } from '../helpers/fetch'
import { drawGrib } from './draw/drawGrib'
import { fetchWindData, isCalculatedWindDirection } from './draw/windDirection'
@@ -16,8 +16,7 @@ export const GribFile: Component<{
setIsLoading: Setter<boolean>,
getFileGribList: Accessor<GribMessage[]> // specific reference and forecast time (in one file)
getAllGribLists: Accessor<GribMessage[]>
getIsCrop: Accessor<boolean>,
getIsContour: Accessor<boolean>,
options: DrawOptions;
onClick: (name: string) => void,
}> = ({
name,
@@ -25,8 +24,7 @@ export const GribFile: Component<{
setIsLoading,
getFileGribList,
getAllGribLists,
getIsCrop,
getIsContour,
options,
onClick,
}) => {
let cachedMessages: GribMessage[] = []
@@ -37,12 +35,13 @@ export const GribFile: Component<{
createEffect(async () => {
setIsLoading(true)
const cropBounds = getIsCrop() ? CROP_BOUNDS : undefined
const contour = getIsContour()
const cropBounds = options.getIsCrop() ? CROP_BOUNDS : undefined
const contour = options.getIsContour()
const isInterpolated = options.getIsInterpolated()
// hack to show loading spinner
await new Promise(resolve => setTimeout(resolve, 100))
if (cachedMessages.length === 0) return;
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds, contour)
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds, contour, isInterpolated)
setIsLoading(false)
})
@@ -74,8 +73,8 @@ export const GribFile: Component<{
cachedMessages = messages
cachedBuffers = binaryBuffers.map(b => new Uint8Array(b))
cachedBitmasks = bitmasks.map(b => new Uint8Array(b))
const cropBounds = getIsCrop() ? CROP_BOUNDS : undefined
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds, getIsContour())
const cropBounds = options.getIsCrop() ? CROP_BOUNDS : undefined
drawGrib(getCanvas()!, cachedMessages, cachedBuffers, cachedBitmasks, cropBounds, options.getIsContour(), options.getIsInterpolated())
})
.catch(err => console.warn(err.message))
.finally(() => setIsLoading(false))
+7 -2
View File
@@ -15,6 +15,7 @@ export const Harmonie: Component<{}> = () => {
const [getIsLoading, setIsLoading] = createSignal(true)
const [getIsCrop, setIsCrop] = createSignal(true)
const [getIsContour, setIsContour] = createSignal(true)
const [getIsInterpolated, setIsInterpolated] = createSignal(true)
const [getGribList, setGribList] = createSignal<GribMessage[]>([])
fetchJson(`${apiHost}/api/show/grib-list`)
@@ -65,6 +66,11 @@ export const Harmonie: Component<{}> = () => {
Contour
<input type='checkbox' checked={getIsContour()} onChange={()=>setIsContour(!getIsContour())} />
</label>
&nbsp;
<label>
Interpolate
<input type='checkbox' checked={getIsInterpolated()} onChange={()=>setIsInterpolated(!getIsInterpolated())} />
</label>
<ul class={styles.fileList}>
{getFileList().map(fileName =>
<GribFile
@@ -73,8 +79,7 @@ export const Harmonie: Component<{}> = () => {
setIsLoading={setIsLoading}
getFileGribList={() => getCurrentGribList(fileName)}
getAllGribLists={getGribList}
getIsCrop={getIsCrop}
getIsContour={getIsContour}
options={{ getIsCrop: getIsCrop, getIsContour: getIsContour, getIsInterpolated: getIsInterpolated }}
onClick={() => onGribMessageClick(fileName)}
/>
)}
+10 -8
View File
@@ -26,6 +26,7 @@ export function drawGrib(
bitmasks: Uint8Array[],
cropBounds: CropBounds | undefined,
isContour: boolean,
isInterpolated: boolean,
): void {
// normally we have one message/buffer/bitmask?. special cases have multiple like wind direction
const [grib] = messages
@@ -52,7 +53,7 @@ export function drawGrib(
const ctx = canvas.getContext('2d')!
let imgData = ctx.createImageData(cols, rows)
fillImageData(imgData, messages, modifiedBuffers)
fillImageData(imgData, messages, modifiedBuffers, isInterpolated)
if (isCalculatedWindDirection(grib)) {
imgData = windDirectionArrows(imgData, messages, modifiedBuffers)
}
@@ -90,6 +91,7 @@ function fillImageData(
imgData: ImageData,
messages: GribMessage[],
buffers: Uint8Array[],
isInterpolated: boolean,
) {
const [grib] = messages
const [buffer] = buffers
@@ -116,33 +118,33 @@ function fillImageData(
color = categoricalRainColors(firstByte)
}
else if (isMeteoEqual(meteo, TOTAL_PRECIPITATION)) {
color = precipitationColors(encodedValue, conversion)
color = precipitationColors(encodedValue, conversion, isInterpolated)
}
else if (isMeteoEqual(meteo, HOUR_PRECIPITATION)) {
const [, nowPrec, prevPrec] = buffers
const encodedValNow = toInt(nowPrec.slice(bufferI, bufferI+bitsPerDataPoint/8))
const encodedValPrev = toInt(prevPrec.slice(bufferI, bufferI+bitsPerDataPoint/8))
const [, metaNow, metaPrev] = messages
color = hourPrecipitationColors(encodedValNow, metaNow.conversion, encodedValPrev, metaPrev.conversion)
color = hourPrecipitationColors(encodedValNow, metaNow.conversion, encodedValPrev, metaPrev.conversion, isInterpolated)
}
else if (isMeteoEqual(meteo, RAIN_PRECIPITATION)) {
color = precipitationColors(encodedValue, conversion)
color = precipitationColors(encodedValue, conversion, isInterpolated)
}
else if (isMeteoEqual(meteo, TEMPERATURE)) {
color = temperatureColors(encodedValue, conversion)
color = temperatureColors(encodedValue, conversion, isInterpolated)
}
else if (isMeteoEqual(meteo, WIND_DIRECTION)) {
const [, bufferU, bufferV] = buffers
const encodedValU = toInt(bufferU.slice(bufferI, bufferI+bitsPerDataPoint/8))
const encodedValV = toInt(bufferV.slice(bufferI, bufferI+bitsPerDataPoint/8))
const [, metaU, metaV] = messages // first message fake one 0-2-192
color = windDirectionColors(encodedValU, encodedValV, metaU!.conversion, metaV!.conversion)
color = windDirectionColors(encodedValU, encodedValV, metaU!.conversion, metaV!.conversion, isInterpolated)
}
else if (isMeteoEqual(meteo, WIND_SPEED)) {
color = windSpeedColors(encodedValue, conversion)
color = windSpeedColors(encodedValue, conversion, isInterpolated)
}
else if (isMeteoEqual(meteo, WIND_SPEED_GUST)) {
color = windSpeedColors(encodedValue, conversion)
color = windSpeedColors(encodedValue, conversion, isInterpolated)
}
else {
color = interpolateColors(firstByte, fromColor, toColor)
+9 -3
View File
@@ -1,6 +1,6 @@
import { apiHost } from '../../consts'
import { fetchBuffer } from '../../helpers/fetch'
import { valueToColorInterpolated } from '../../helpers/interpolateColors'
import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors'
import { GribMessage, MeteoConversion } from '../interfaces'
import { WIND_SPEED } from './constants'
import { rotateWind } from './windRotate'
@@ -122,20 +122,26 @@ export function windDirectionColors(
encodedV: number,
convU: MeteoConversion,
convV: MeteoConversion,
isInterpolated: boolean,
) {
const windSpeedU = (convU.reference + encodedU * Math.pow(2, convU.binaryScale)) * Math.pow(10, -convU.decimalScale)
const windSpeedV = (convV.reference + encodedV * Math.pow(2, convV.binaryScale)) * Math.pow(10, -convV.decimalScale)
const windSpeed = Math.sqrt(Math.pow(windSpeedU, 2) + Math.pow(windSpeedV, 2))
return valueToColorInterpolated(windSpeed, WIND_SPEED)
return isInterpolated
? valueToColorInterpolated(windSpeed, WIND_SPEED)
: valueToColorThreshold(windSpeed, WIND_SPEED)
}
export function windSpeedColors(
encodedValue: number,
{ reference, binaryScale, decimalScale}: MeteoConversion,
isInterpolated: boolean,
) {
const windSpeed = (reference + encodedValue * Math.pow(2, binaryScale)) * Math.pow(10, -decimalScale)
return valueToColorInterpolated(windSpeed, WIND_SPEED)
return isInterpolated
? valueToColorInterpolated(windSpeed, WIND_SPEED)
: valueToColorThreshold(windSpeed, WIND_SPEED)
}
export function fetchWindData(
+9 -1
View File
@@ -1,3 +1,5 @@
import { Accessor } from 'solid-js'
export type MeteoParam = {
discipline: number, // 0=meteo, 1=hydro, 2=land surface, 3=space products
category: number, // 0=temperature, 1=moisture, 6=cloud, 19=atmospheric
@@ -42,4 +44,10 @@ export type GribSection = {
export type GribTime = {
referenceTime: string,
forecastTime: string,
}
}
export type DrawOptions = {
getIsCrop: Accessor<boolean>,
getIsContour: Accessor<boolean>,
getIsInterpolated: Accessor<boolean>,
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { ColorEntry } from '../grib/draw/constants'
import { ColorEntry } from '../harmonie/draw/constants'
type RGBu8 = [number, number, number]
type RGBAu8 = [number, number, number, number]