Calculate and draw hour precipitation rate
This commit is contained in:
@@ -5,7 +5,8 @@ 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'
|
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 }
|
const CROP_BOUNDS = { x: 1906-1-400, y: 950, width: 400, height: 300 }
|
||||||
|
|
||||||
@@ -13,14 +14,16 @@ export const GribFile: Component<{
|
|||||||
name: string,
|
name: string,
|
||||||
getCanvas: Accessor<HTMLCanvasElement | undefined>,
|
getCanvas: Accessor<HTMLCanvasElement | undefined>,
|
||||||
setIsLoading: Setter<boolean>,
|
setIsLoading: Setter<boolean>,
|
||||||
getGribList: Accessor<GribMessage[]>
|
getFileGribList: Accessor<GribMessage[]> // specific reference and forecast time (in one file)
|
||||||
|
getAllGribLists: Accessor<GribMessage[]>
|
||||||
getIsCrop: Accessor<boolean>,
|
getIsCrop: Accessor<boolean>,
|
||||||
onClick: (name: string) => void,
|
onClick: (name: string) => void,
|
||||||
}> = ({
|
}> = ({
|
||||||
name,
|
name,
|
||||||
getCanvas,
|
getCanvas,
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
getGribList,
|
getFileGribList,
|
||||||
|
getAllGribLists,
|
||||||
getIsCrop,
|
getIsCrop,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -42,7 +45,7 @@ export const GribFile: Component<{
|
|||||||
|
|
||||||
function onParamClick(paramId: number) {
|
function onParamClick(paramId: number) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const grib = getGribList()[paramId]
|
const grib = getFileGribList()[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;
|
||||||
@@ -55,14 +58,15 @@ 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[]]> = grib.meteo.discipline === 0 && grib.meteo.category === 2 && grib.meteo.product === 192
|
let fetchPromise: Promise<[GribMessage[], ArrayBuffer[], ArrayBuffer[]]> = Promise.all([
|
||||||
? fetchWindData(grib, getGribList(), name)
|
|
||||||
: Promise.all([
|
|
||||||
Promise.resolve([grib]),
|
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,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if(isCalculatedWindDirection(grib)) fetchPromise = fetchWindData(grib, getFileGribList(), name)
|
||||||
|
if(isCalculatedHourPrecipitation(grib)) fetchPromise = fetchHourPrecipitationData(grib, getAllGribLists())
|
||||||
|
|
||||||
fetchPromise.then(([messages, binaryBuffers, bitmasks]) => {
|
fetchPromise.then(([messages, binaryBuffers, bitmasks]) => {
|
||||||
cachedMessages = messages
|
cachedMessages = messages
|
||||||
cachedBuffers = binaryBuffers.map(b => new Uint8Array(b))
|
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>
|
<div class={styles.name} onClick={() => setIsActive(!getIsActive())}>{ trimName(name) }</div>
|
||||||
<ul class={styles.meteoParams}>
|
<ul class={styles.meteoParams}>
|
||||||
{ getGribList()
|
{ getFileGribList()
|
||||||
.map((grib, i) =>
|
.map((grib, i) =>
|
||||||
<li onClick={() => onParamClick(i)}>{ grib.title.replace('meteorology, ', '') }</li>
|
<li onClick={() => onParamClick(i)}>{ grib.title.replace('meteorology, ', '') }</li>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { LoadingSpinner } from '../components/LoadingSpinner'
|
|||||||
import { GribMessage } from './interfaces'
|
import { GribMessage } from './interfaces'
|
||||||
import { fetchJson } from '../helpers/fetch'
|
import { fetchJson } from '../helpers/fetch'
|
||||||
import { getFakeWindDirection, isWindSpeed } from './draw/windDirection'
|
import { getFakeWindDirection, isWindSpeed } from './draw/windDirection'
|
||||||
|
import { getFakeHourPrecipitation, isPrecipitation } from './draw/precipitation'
|
||||||
|
|
||||||
export const Harmonie: Component<{}> = () => {
|
export const Harmonie: Component<{}> = () => {
|
||||||
const [getFileList, setFileList] = createSignal<string[]>([])
|
const [getFileList, setFileList] = createSignal<string[]>([])
|
||||||
@@ -30,6 +31,7 @@ export const Harmonie: Component<{}> = () => {
|
|||||||
setGribList([
|
setGribList([
|
||||||
...gribList,
|
...gribList,
|
||||||
...gribList.filter(isWindSpeed).map(getFakeWindDirection),
|
...gribList.filter(isWindSpeed).map(getFakeWindDirection),
|
||||||
|
...gribList.filter(isPrecipitation).map(getFakeHourPrecipitation),
|
||||||
].sort((a, b) => a.title > b.title ? 1 : -1))
|
].sort((a, b) => a.title > b.title ? 1 : -1))
|
||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false))
|
.finally(() => setIsLoading(false))
|
||||||
@@ -60,7 +62,8 @@ export const Harmonie: Component<{}> = () => {
|
|||||||
name={fileName}
|
name={fileName}
|
||||||
getCanvas={getCanvas}
|
getCanvas={getCanvas}
|
||||||
setIsLoading={setIsLoading}
|
setIsLoading={setIsLoading}
|
||||||
getGribList={() => getCurrentGribList(fileName)}
|
getFileGribList={() => getCurrentGribList(fileName)}
|
||||||
|
getAllGribLists={getGribList}
|
||||||
getIsCrop={getIsCrop}
|
getIsCrop={getIsCrop}
|
||||||
onClick={() => onGribMessageClick(fileName)}
|
onClick={() => onGribMessageClick(fileName)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { GribMessage, MeteoParam } from '../interfaces'
|
|||||||
import { applyBitmask } from './bitmask'
|
import { applyBitmask } from './bitmask'
|
||||||
import { extractFromBounds } from './bounds'
|
import { extractFromBounds } from './bounds'
|
||||||
import { categoricalRainColors } from './categoricalRain'
|
import { categoricalRainColors } from './categoricalRain'
|
||||||
import { precipitationColors } from './precipitation'
|
import { hourPrecipitationColors, isCalculatedHourPrecipitation, precipitationColors } from './precipitation'
|
||||||
import { temperatureColors } from './temperature'
|
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 }
|
export type CropBounds = { x: number, y: number, width: number, height: number }
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export function drawGrib(
|
|||||||
let imgData = ctx.createImageData(cols, rows)
|
let imgData = ctx.createImageData(cols, rows)
|
||||||
|
|
||||||
fillImageData(imgData, messages, modifiedBuffers)
|
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)
|
imgData = windDirectionArrows(imgData, messages, modifiedBuffers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ export function drawGrib(
|
|||||||
|
|
||||||
const CATEGORICAL_RAIN = [0, 1, 192]
|
const CATEGORICAL_RAIN = [0, 1, 192]
|
||||||
const TOTAL_PRECIPITATION = [0, 1, 52]
|
const TOTAL_PRECIPITATION = [0, 1, 52]
|
||||||
|
const HOUR_PRECIPITATION = [0, 1, 236]
|
||||||
const RAIN_PRECIPITATION = [0, 1, 65]
|
const RAIN_PRECIPITATION = [0, 1, 65]
|
||||||
const TEMPERATURE = [0, 0, 0]
|
const TEMPERATURE = [0, 0, 0]
|
||||||
const WIND_DIRECTION = [0, 2, 192]
|
const WIND_DIRECTION = [0, 2, 192]
|
||||||
@@ -98,6 +99,13 @@ function fillImageData(
|
|||||||
else if (isMeteoEqual(meteo, TOTAL_PRECIPITATION)) {
|
else if (isMeteoEqual(meteo, TOTAL_PRECIPITATION)) {
|
||||||
color = precipitationColors(encodedValue, conversion)
|
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)) {
|
else if (isMeteoEqual(meteo, RAIN_PRECIPITATION)) {
|
||||||
color = precipitationColors(encodedValue, conversion)
|
color = precipitationColors(encodedValue, conversion)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import moment from 'moment'
|
||||||
import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors'
|
import { valueToColorInterpolated, valueToColorThreshold } from '../../helpers/interpolateColors'
|
||||||
import { MeteoConversion } from '../interfaces'
|
import { GribMessage, MeteoConversion } from '../interfaces'
|
||||||
import { PRECIPITATION } from './constants'
|
import { PRECIPITATION } from './constants'
|
||||||
|
import { fetchBuffer } from '../../helpers/fetch'
|
||||||
|
import { apiHost } from '../../consts'
|
||||||
|
|
||||||
|
|
||||||
export function precipitationColors(
|
export function precipitationColors(
|
||||||
@@ -8,10 +11,89 @@ export function precipitationColors(
|
|||||||
{ reference, binaryScale, decimalScale}: MeteoConversion,
|
{ reference, binaryScale, decimalScale}: MeteoConversion,
|
||||||
isInterpolated = true,
|
isInterpolated = true,
|
||||||
): [number, number, number, number] {
|
): [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)
|
const rainMM = (reference + encodedValue * Math.pow(2, binaryScale)) * Math.pow(10, -decimalScale)
|
||||||
|
|
||||||
return isInterpolated
|
return isInterpolated
|
||||||
? valueToColorInterpolated(rainMM, PRECIPITATION)
|
? valueToColorInterpolated(rainMM, PRECIPITATION)
|
||||||
: valueToColorThreshold(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 section7u = windU.sections.find(section => section.id === 7)
|
||||||
const section7v = windV.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 uBinaryOffset = section7u.offset + 5
|
||||||
const uBinaryLength = section7u.size - 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/${uBinaryOffset}/${uBinaryLength}/${fileName}`),
|
||||||
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${vBinaryOffset}/${vBinaryLength}/${fileName}`),
|
fetchBuffer(`${apiHost}/api/grib/binary-chunk/${vBinaryOffset}/${vBinaryLength}/${fileName}`),
|
||||||
]).then(([bufferU, bufferV]) => {
|
]).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 messages = [customMessage, windU, windV]
|
||||||
const buffers = [bufferU, bufferU, bufferV]
|
const buffers = [bufferU, bufferU, bufferV]
|
||||||
return [messages, buffers, []]
|
return [messages, buffers, []]
|
||||||
@@ -175,7 +172,11 @@ function toInt(bytes: Uint8Array): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isWindSpeed(grib: GribMessage): boolean {
|
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 {
|
export function getFakeWindDirection(windSpeed: GribMessage): GribMessage {
|
||||||
|
|||||||
Reference in New Issue
Block a user