Calculate and draw hour precipitation rate
This commit is contained in:
@@ -5,7 +5,8 @@ import { apiHost } from '../consts'
|
||||
import { GribMessage } from './interfaces'
|
||||
import { fetchBuffer } from '../helpers/fetch'
|
||||
import { drawGrib } from './draw/drawGrib'
|
||||
import { fetchWindData } from './draw/windDirection'
|
||||
import { fetchWindData, isCalculatedWindDirection } from './draw/windDirection'
|
||||
import { fetchHourPrecipitationData, isCalculatedHourPrecipitation } from './draw/precipitation'
|
||||
|
||||
const CROP_BOUNDS = { x: 1906-1-400, y: 950, width: 400, height: 300 }
|
||||
|
||||
@@ -13,14 +14,16 @@ export const GribFile: Component<{
|
||||
name: string,
|
||||
getCanvas: Accessor<HTMLCanvasElement | undefined>,
|
||||
setIsLoading: Setter<boolean>,
|
||||
getGribList: Accessor<GribMessage[]>
|
||||
getFileGribList: Accessor<GribMessage[]> // specific reference and forecast time (in one file)
|
||||
getAllGribLists: Accessor<GribMessage[]>
|
||||
getIsCrop: Accessor<boolean>,
|
||||
onClick: (name: string) => void,
|
||||
}> = ({
|
||||
name,
|
||||
getCanvas,
|
||||
setIsLoading,
|
||||
getGribList,
|
||||
getFileGribList,
|
||||
getAllGribLists,
|
||||
getIsCrop,
|
||||
onClick,
|
||||
}) => {
|
||||
@@ -42,7 +45,7 @@ export const GribFile: Component<{
|
||||
|
||||
function onParamClick(paramId: number) {
|
||||
setIsLoading(true);
|
||||
const grib = getGribList()[paramId]
|
||||
const grib = getFileGribList()[paramId]
|
||||
const bitmaskSection = grib.sections.find(section => section.id === 6)
|
||||
const binarySection = grib.sections.find(section => section.id === 7)
|
||||
if (!bitmaskSection || !binarySection) return;
|
||||
@@ -55,14 +58,15 @@ export const GribFile: Component<{
|
||||
|
||||
const binaryOffset = binarySection.offset + 5
|
||||
const binaryLength = binarySection.size - 5
|
||||
const fetchPromise: Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> = grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192
|
||||
? fetchWindData(grib, getGribList(), name)
|
||||
: Promise.all([
|
||||
let fetchPromise: Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> = Promise.all([
|
||||
Promise.resolve([grib]),
|
||||
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${binaryOffset}/${binaryLength}/${name}`).then(b=>[b]),
|
||||
bitmaskPromise,
|
||||
])
|
||||
|
||||
if(isCalculatedWindDirection(grib)) fetchPromise = fetchWindData(grib, getFileGribList(), name)
|
||||
if(isCalculatedHourPrecipitation(grib)) fetchPromise = fetchHourPrecipitationData(grib, getAllGribLists())
|
||||
|
||||
fetchPromise.then(([messages, binaryBuffers, bitmasks]) => {
|
||||
cachedMessages = messages
|
||||
cachedBuffers = binaryBuffers.map(b => new Uint8Array(b))
|
||||
@@ -80,7 +84,7 @@ export const GribFile: Component<{
|
||||
>
|
||||
<div class={styles.name} onClick={() => setIsActive(!getIsActive())}>{ trimName(name) }</div>
|
||||
<ul class={styles.meteoParams}>
|
||||
{ getGribList()
|
||||
{ getFileGribList()
|
||||
.map((grib, i) =>
|
||||
<li onClick={() => onParamClick(i)}>{ grib.title.replace('meteorology, ', '') }</li>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||
import { GribMessage } from './interfaces'
|
||||
import { fetchJson } from '../helpers/fetch'
|
||||
import { getFakeWindDirection, isWindSpeed } from './draw/windDirection'
|
||||
import { getFakeHourPrecipitation, isPrecipitation } from './draw/precipitation'
|
||||
|
||||
export const Harmonie: Component<{}> = () => {
|
||||
const [getFileList, setFileList] = createSignal<string[]>([])
|
||||
@@ -30,6 +31,7 @@ export const Harmonie: Component<{}> = () => {
|
||||
setGribList([
|
||||
...gribList,
|
||||
...gribList.filter(isWindSpeed).map(getFakeWindDirection),
|
||||
...gribList.filter(isPrecipitation).map(getFakeHourPrecipitation),
|
||||
].sort((a, b) => a.title > b.title ? 1 : -1))
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
@@ -60,7 +62,8 @@ export const Harmonie: Component<{}> = () => {
|
||||
name={fileName}
|
||||
getCanvas={getCanvas}
|
||||
setIsLoading={setIsLoading}
|
||||
getGribList={() => getCurrentGribList(fileName)}
|
||||
getFileGribList={() => getCurrentGribList(fileName)}
|
||||
getAllGribLists={getGribList}
|
||||
getIsCrop={getIsCrop}
|
||||
onClick={() => onGribMessageClick(fileName)}
|
||||
/>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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, []]
|
||||
@@ -178,6 +175,10 @@ export function isWindSpeed(grib: GribMessage): boolean {
|
||||
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 {
|
||||
const modifiedWindSpeed = structuredClone(windSpeed)
|
||||
modifiedWindSpeed.meteo = {...modifiedWindSpeed.meteo, product: 192}
|
||||
|
||||
Reference in New Issue
Block a user