From 0b221bcfee072822b34c635d74ee905ecbd1f538 Mon Sep 17 00:00:00 2001 From: Guntis Smaukstelis Date: Fri, 14 Feb 2025 20:26:57 +0200 Subject: [PATCH] Download structure for all files in one go. For better UX --- src/main/scala/data/DataService.scala | 7 +++++ src/main/scala/server/Server.scala | 13 +++++---- web/src/harmonie/GribFile.tsx | 35 +++++++----------------- web/src/harmonie/Harmonie.tsx | 38 ++++++++++++++++++++++---- web/src/harmonie/draw/windDirection.ts | 4 +++ web/src/harmonie/interfaces.ts | 6 ++++ 6 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/main/scala/data/DataService.scala b/src/main/scala/data/DataService.scala index cf3a5c5..332b68b 100644 --- a/src/main/scala/data/DataService.scala +++ b/src/main/scala/data/DataService.scala @@ -54,6 +54,13 @@ class DataService(log: Logger[IO]) { } } + def getAllFileStructure(): IO[List[Grib]] = { + for { + fileList <- getFileList() + allStructure <- fileList.traverse(getGribStucture) + } yield allStructure.flatten + } + def getBinaryChunk(offset: Int, length: Int, fileName: String): IO[Array[Byte]] = { IO.blocking { val source = Source.fromFile(s"$GRIB_FOLDER/$fileName", "ISO-8859-1") diff --git a/src/main/scala/server/Server.scala b/src/main/scala/server/Server.scala index 2aacb33..071f765 100644 --- a/src/main/scala/server/Server.scala +++ b/src/main/scala/server/Server.scala @@ -51,17 +51,20 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch: private case class ResponseWrapper(result: Map[String, Option[Aggregate.AggregateValue]], query: UserQuery) private val apiRoutes = HttpRoutes.of[IO] { - // http://0.0.0.0:8080/api/show/grib-name/harmonie_2025-02-01T1500Z_2025-02-01T180000Z.grib - case GET -> Root / "show" / "grib" / fileName => -// val fileName = "data/HARMONIE_DINI_SF_2025-01-24T030000Z_2025-01-26T010000Z.grib" - dataService.getGribStucture(fileName).flatMap(response => Ok(response.asJson.pretty)) - case GET -> Root / "show" / "gribName" => Ok("{\"fileName\":\"TODO replace this fake name\"}") + // http://0.0.0.0:8080/api/show/grib-all-structure + case GET -> Root / "show" / "grib-all-structure" => + dataService.getAllFileStructure().flatMap(gribList => Ok(gribList.asJson)) + // http://0.0.0.0:8080/api/show/grib-list case GET -> Root / "show" / "grib-list" => dataService.getFileList().flatMap(fileList => Ok(fileList.asJson)) + // http://0.0.0.0:8080/api/show/grib-name/harmonie_2025-02-01T1500Z_2025-02-01T180000Z.grib + case GET -> Root / "show" / "grib" / fileName => + dataService.getGribStucture(fileName).flatMap(response => Ok(response.asJson.pretty)) + case GET -> Root / "grib" / "binary-chunk" / ValidateInt(binaryOffset) / ValidateInt(binaryLength) / fileName => dataService.getBinaryChunk(binaryOffset, binaryLength, fileName).flatMap(buffer => Ok(buffer)) diff --git a/web/src/harmonie/GribFile.tsx b/web/src/harmonie/GribFile.tsx index 420043b..ca4829c 100644 --- a/web/src/harmonie/GribFile.tsx +++ b/web/src/harmonie/GribFile.tsx @@ -1,49 +1,35 @@ -import { Accessor, Component, createEffect, createMemo, createSignal, Setter } from 'solid-js' +import { Accessor, Component, createEffect, createSignal, Setter } from 'solid-js' import styles from './harmonie.module.css' import { apiHost } from '../consts' import { GribMessage } from './interfaces' import { fetchBuffer } from '../helpers/fetch' import { drawGrib } from './draw/drawGrib' -import { fetchWindData, getFakeWindDirection } from './draw/windDirection' +import { fetchWindData } from './draw/windDirection' const CROP_BOUNDS = { x: 1906-1-400, y: 950, width: 400, height: 300 } export const GribFile: Component<{ name: string, - isActive: Accessor, getCanvas: Accessor, setIsLoading: Setter, + getGribList: Accessor getIsCrop: Accessor, onClick: (name: string) => void, }> = ({ name, - isActive, getCanvas, setIsLoading, + getGribList, getIsCrop, onClick, }) => { - const [getGribList, setGribList] = createSignal([]) - - function onFileClick() { - onClick(name) - - if (getGribList().length === 0) { - fetch(`${apiHost}/api/show/grib/${name}`) - .then(re => re.json()) - .then((gribList: GribMessage[]) => { - const windSpeed = gribList.find(m => m.meteo.discipline===0 && m.meteo.category===2 && m.meteo.product===1) - if (windSpeed) gribList.push(getFakeWindDirection(windSpeed)) - setGribList(gribList) - }) - } - } - let cachedMessages: GribMessage[] = [] let cachedBuffers: Uint8Array[] = [] let cachedBitmasks: Uint8Array[] = [] + const [getIsActive, setIsActive] = createSignal(false) + createEffect(async () => { setIsLoading(true) const cropBounds = getIsCrop() ? CROP_BOUNDS : undefined @@ -89,14 +75,13 @@ export const GribFile: Component<{ } return
  • onClick(name)} > -
    { trimName(name) }
    +
    setIsActive(!getIsActive())}>{ trimName(name) }
      { getGribList() - .sort((a, b) => a.title > b.title ? 1 : -1) - .map((grib, i) => + .map((grib, i) =>
    • onParamClick(i)}>{ grib.title.replace('meteorology, ', '') }
    • )}
    diff --git a/web/src/harmonie/Harmonie.tsx b/web/src/harmonie/Harmonie.tsx index e5b230f..1de2e6b 100644 --- a/web/src/harmonie/Harmonie.tsx +++ b/web/src/harmonie/Harmonie.tsx @@ -4,22 +4,50 @@ import { apiHost } from '../consts' import styles from './harmonie.module.css' import { GribFile } from './GribFile' import { LoadingSpinner } from '../components/LoadingSpinner' +import { GribMessage } from './interfaces' +import { fetchJson } from '../helpers/fetch' +import { getFakeWindDirection, isWindSpeed } from './draw/windDirection' export const Harmonie: Component<{}> = () => { const [getFileList, setFileList] = createSignal([]) - const [getActiveGrib, setActiveGrib] = createSignal('') const [getCanvas, setCanvas] = createSignal() const [getIsLoading, setIsLoading] = createSignal(true) const [getIsCrop, setIsCrop] = createSignal(true) + const [getGribList, setGribList] = createSignal([]) - fetch(`${apiHost}/api/show/grib-list`) - .then(re => re.json()) + fetchJson(`${apiHost}/api/show/grib-list`) .then(fileList => { fileList.sort((a: string, b: string) => a < b ? 1 : -1) setFileList(fileList) }) .finally(() => setIsLoading(false)) + function onGribMessageClick(fileName: string) { + if(!getGribList().length) { + setIsLoading(true) + fetchJson(`${apiHost}/api/show/grib-all-structure`) + .then((gribList: GribMessage[]) => { + setGribList([ + ...gribList, + ...gribList.filter(isWindSpeed).map(getFakeWindDirection), + ].sort((a, b) => a.title > b.title ? 1 : -1)) + }) + .finally(() => setIsLoading(false)) + } + } + + function getCurrentGribList(fileName: string): GribMessage[] { + const [referenceTime, forecastTime] = fileName.replace('harmonie_', '') + .replace('.grib', '') + .split('_') + if (!referenceTime || ! forecastTime) return [] + + return getGribList().filter(g => + g.time.referenceTime === referenceTime + && g.time.forecastTime === forecastTime + ) + } + return