Calculate and draw hour precipitation rate

This commit is contained in:
Guntis Smaukstelis
2025-02-14 22:35:02 +02:00
parent 0b221bcfee
commit 28d0ab2267
5 changed files with 117 additions and 19 deletions
+11 -3
View File
@@ -3,9 +3,9 @@ import { GribMessage, MeteoParam } from '../interfaces'
import { applyBitmask } from './bitmask'
import { extractFromBounds } from './bounds'
import { categoricalRainColors } from './categoricalRain'
import { precipitationColors } from './precipitation'
import { hourPrecipitationColors, isCalculatedHourPrecipitation, precipitationColors } from './precipitation'
import { temperatureColors } from './temperature'
import { windDirectionArrows, windDirectionColors, windSpeedColors } from './windDirection'
import { isCalculatedWindDirection, windDirectionArrows, windDirectionColors, windSpeedColors } from './windDirection'
export type CropBounds = { x: number, y: number, width: number, height: number }
@@ -41,7 +41,7 @@ export function drawGrib(
let imgData = ctx.createImageData(cols, rows)
fillImageData(imgData, messages, modifiedBuffers)
if (grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192) {
if (isCalculatedWindDirection(grib)) {
imgData = windDirectionArrows(imgData, messages, modifiedBuffers)
}
@@ -60,6 +60,7 @@ export function drawGrib(
const CATEGORICAL_RAIN = [0, 1, 192]
const TOTAL_PRECIPITATION = [0, 1, 52]
const HOUR_PRECIPITATION = [0, 1, 236]
const RAIN_PRECIPITATION = [0, 1, 65]
const TEMPERATURE = [0, 0, 0]
const WIND_DIRECTION = [0, 2, 192]
@@ -98,6 +99,13 @@ function fillImageData(
else if (isMeteoEqual(meteo, TOTAL_PRECIPITATION)) {
color = precipitationColors(encodedValue, conversion)
}
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)
}
else if (isMeteoEqual(meteo, RAIN_PRECIPITATION)) {
color = precipitationColors(encodedValue, conversion)
}
+84 -2
View File
@@ -1,6 +1,9 @@
import moment from 'moment'
import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors'
import { MeteoConversion } from '../interfaces'
import { GribMessage, MeteoConversion } from '../interfaces'
import { PRECIPITATION } from './constants'
import { fetchBuffer } from '../../helpers/fetch'
import { apiHost } from '../../consts'
export function precipitationColors(
@@ -8,10 +11,89 @@ export function precipitationColors(
{ reference, binaryScale, decimalScale}: MeteoConversion,
isInterpolated = true,
): [number, number, number, number] {
// const rainMM = (reference + encodedValue * Math.pow(2, binaryScale)) * Math.pow(10, -decimalScale) / 1000
const rainMM = (reference + encodedValue * Math.pow(2, binaryScale)) * Math.pow(10, -decimalScale)
return isInterpolated
? valueToColorInterpolated(rainMM, PRECIPITATION)
: valueToColorThreshold(rainMM, PRECIPITATION)
}
export function hourPrecipitationColors(
encodedValNow: number,
convNow: MeteoConversion,
encodedValPrev: number,
convPrev: MeteoConversion,
isInterpolated = true,
): [number, number, number, number] {
const nowVal = (convNow.reference + encodedValNow * Math.pow(2, convNow.binaryScale)) * Math.pow(10, -convNow.decimalScale)
const prevVal = (convPrev.reference + encodedValPrev * Math.pow(2, convPrev.binaryScale)) * Math.pow(10, -convPrev.decimalScale)
const rainMM = nowVal - prevVal
return isInterpolated
? valueToColorInterpolated(rainMM, PRECIPITATION)
: valueToColorThreshold(rainMM, PRECIPITATION)
}
export function fetchHourPrecipitationData(
customMessage: GribMessage,
gribArr: GribMessage[],
): Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> {
const totalPrecipitation = gribArr.find(g => isPrecipitation(g)
&& g.time.referenceTime === customMessage.time.referenceTime
&& g.time.forecastTime === customMessage.time.forecastTime
)
if (!totalPrecipitation) throw new Error('Not found total precipitation')
const { forecastTime } = totalPrecipitation.time
const prevForecastTime = moment(forecastTime.replace(/(\d{2})(\d{2})Z/, '$1:$2:00Z'))
.subtract(1, 'hours')
.utc()
.format("YYYY-MM-DDTHHmm")+'Z'
const prevTotalPrecipitation = gribArr.find(g => isPrecipitation(g)
&& g.time.referenceTime === customMessage.time.referenceTime
&& g.time.forecastTime === prevForecastTime
)
const section7now = totalPrecipitation.sections.find(section => section.id === 7)
const section7prev = prevTotalPrecipitation?.sections.find(section => section.id === 7)
if (!section7now) throw new Error('Didnt found binary section for total precipitation')
const nowBinaryOffset = section7now.offset + 5
const nowBinaryLength = section7now.size - 5
const nowFileName = `harmonie_${totalPrecipitation.time.referenceTime}_${totalPrecipitation.time.forecastTime}.grib`
// for oldest message there is no more -1h message
let prevPromise: Promise<ArrayBuffer> | undefined
if (prevTotalPrecipitation && section7prev) {
const prevBinaryOffset = section7prev.offset + 5
const prevBinaryLength = section7prev.size - 5
const prevFileName = `harmonie_${prevTotalPrecipitation.time.referenceTime}_${prevTotalPrecipitation.time.forecastTime}.grib`
prevPromise = fetchBuffer(`${apiHost}/api/grib/binary-chunk/${prevBinaryOffset}/${prevBinaryLength}/${prevFileName}`)
}
if (!prevPromise) prevPromise = new Promise(resolve => resolve(new Uint8Array(nowBinaryLength).buffer))
return Promise.all([
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${nowBinaryOffset}/${nowBinaryLength}/${nowFileName}`),
prevPromise,
]).then(([bufferNow, bufferPrev]) => {
const messages = [customMessage, totalPrecipitation, prevTotalPrecipitation ?? totalPrecipitation]
const buffers = [bufferNow, bufferNow, bufferPrev]
return [messages, buffers, []]
})
}
export function isPrecipitation(grib: GribMessage): boolean {
return grib.meteo.discipline === 0 && grib.meteo.category === 1 && grib.meteo.product === 52
}
export function isCalculatedHourPrecipitation(grib: GribMessage): boolean {
return grib.meteo.discipline === 0 && grib.meteo.category === 1 && grib.meteo.product === 236
}
export function getFakeHourPrecipitation(totalPrecipitation: GribMessage): GribMessage {
const modifiedPrecipitation = structuredClone(totalPrecipitation)
modifiedPrecipitation.meteo = {...modifiedPrecipitation.meteo, product: 236}
modifiedPrecipitation.title = 'meteorology, moisture, hour precipitation rate'
return modifiedPrecipitation
}
+6 -5
View File
@@ -149,7 +149,7 @@ export function fetchWindData(
const section7u = windU.sections.find(section => section.id === 7)
const section7v = windV.sections.find(section => section.id === 7)
if (!section7u || !section7v) throw new Error('Didnt found binaru section for wind u/v')
if (!section7u || !section7v) throw new Error('Didnt found binary section for wind u/v')
const uBinaryOffset = section7u.offset + 5
const uBinaryLength = section7u.size - 5
@@ -161,9 +161,6 @@ export function fetchWindData(
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${uBinaryOffset}/${uBinaryLength}/${fileName}`),
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${vBinaryOffset}/${vBinaryLength}/${fileName}`),
]).then(([bufferU, bufferV]) => {
const buffer = new Uint8Array(bufferU.byteLength + bufferV.byteLength)
buffer.set(new Uint8Array(bufferU))
buffer.set(new Uint8Array(bufferV), bufferU.byteLength)
const messages = [customMessage, windU, windV]
const buffers = [bufferU, bufferU, bufferV]
return [messages, buffers, []]
@@ -175,7 +172,11 @@ function toInt(bytes: Uint8Array): number {
}
export function isWindSpeed(grib: GribMessage): boolean {
return grib.meteo.discipline===0 && grib.meteo.category===2 && grib.meteo.product===1
return grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 1
}
export function isCalculatedWindDirection(grib: GribMessage): boolean {
return grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192
}
export function getFakeWindDirection(windSpeed: GribMessage): GribMessage {