From 29ef46f4c7032d429579b7b3b7941b5df99125c7 Mon Sep 17 00:00:00 2001 From: Guntis Smaukstelis Date: Tue, 18 Feb 2025 16:03:33 +0200 Subject: [PATCH] Interpolate option, also interpolate wind data --- web/src/harmonie/GribFile.tsx | 19 +++++++++---------- web/src/harmonie/Harmonie.tsx | 9 +++++++-- web/src/harmonie/draw/drawGrib.ts | 18 ++++++++++-------- web/src/harmonie/draw/windDirection.ts | 12 +++++++++--- web/src/harmonie/interfaces.ts | 10 +++++++++- web/src/helpers/interpolateColors.ts | 2 +- 6 files changed, 45 insertions(+), 25 deletions(-) diff --git a/web/src/harmonie/GribFile.tsx b/web/src/harmonie/GribFile.tsx index 5acf191..27523bf 100644 --- a/web/src/harmonie/GribFile.tsx +++ b/web/src/harmonie/GribFile.tsx @@ -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, getFileGribList: Accessor // specific reference and forecast time (in one file) getAllGribLists: Accessor - getIsCrop: Accessor, - getIsContour: Accessor, + 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)) diff --git a/web/src/harmonie/Harmonie.tsx b/web/src/harmonie/Harmonie.tsx index 16ffd65..8a34ee4 100644 --- a/web/src/harmonie/Harmonie.tsx +++ b/web/src/harmonie/Harmonie.tsx @@ -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([]) fetchJson(`${apiHost}/api/show/grib-list`) @@ -65,6 +66,11 @@ export const Harmonie: Component<{}> = () => { Contour setIsContour(!getIsContour())} /> +   +
    {getFileList().map(fileName => = () => { setIsLoading={setIsLoading} getFileGribList={() => getCurrentGribList(fileName)} getAllGribLists={getGribList} - getIsCrop={getIsCrop} - getIsContour={getIsContour} + options={{ getIsCrop: getIsCrop, getIsContour: getIsContour, getIsInterpolated: getIsInterpolated }} onClick={() => onGribMessageClick(fileName)} /> )} diff --git a/web/src/harmonie/draw/drawGrib.ts b/web/src/harmonie/draw/drawGrib.ts index e19e9d5..8533826 100644 --- a/web/src/harmonie/draw/drawGrib.ts +++ b/web/src/harmonie/draw/drawGrib.ts @@ -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) diff --git a/web/src/harmonie/draw/windDirection.ts b/web/src/harmonie/draw/windDirection.ts index f1da65a..b0ab0b1 100644 --- a/web/src/harmonie/draw/windDirection.ts +++ b/web/src/harmonie/draw/windDirection.ts @@ -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( diff --git a/web/src/harmonie/interfaces.ts b/web/src/harmonie/interfaces.ts index 5d8dafe..b86de46 100644 --- a/web/src/harmonie/interfaces.ts +++ b/web/src/harmonie/interfaces.ts @@ -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, -} \ No newline at end of file +} + +export type DrawOptions = { + getIsCrop: Accessor, + getIsContour: Accessor, + getIsInterpolated: Accessor, +} diff --git a/web/src/helpers/interpolateColors.ts b/web/src/helpers/interpolateColors.ts index af2523e..3139d1a 100644 --- a/web/src/helpers/interpolateColors.ts +++ b/web/src/helpers/interpolateColors.ts @@ -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]