From 94d0612d800e56360109120a0e8934ce7a40c230 Mon Sep 17 00:00:00 2001 From: Guntis Smaukstelis Date: Sat, 1 Feb 2025 20:19:58 +0200 Subject: [PATCH] Fetching from harmonie servers --- src/main/scala/fetchDMI/FetchService.scala | 73 +++++++++++++++++++ .../scala/fetchDMI/FetchServiceTest.scala | 19 +++++ src/main/scala/fetchDMI/FileName.scala | 36 +++++++++ 3 files changed, 128 insertions(+) create mode 100644 src/main/scala/fetchDMI/FetchService.scala create mode 100644 src/main/scala/fetchDMI/FetchServiceTest.scala create mode 100644 src/main/scala/fetchDMI/FileName.scala diff --git a/src/main/scala/fetchDMI/FetchService.scala b/src/main/scala/fetchDMI/FetchService.scala new file mode 100644 index 0000000..1d0ba32 --- /dev/null +++ b/src/main/scala/fetchDMI/FetchService.scala @@ -0,0 +1,73 @@ +package fetchDMI + +import cats.effect._ +import fs2.io.file.{Files, Path} +import org.http4s._ +import org.http4s.client.Client +import org.http4s.ember.client.EmberClientBuilder +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger + +import java.time.ZonedDateTime + + +final case class HarmonieServerConfig( + api_key: String, + url: String, +) + +object FetchService { + def of: IO[FetchService] = { + Slf4jLogger.create[IO].map(logger => new FetchService(logger)) + } +} + +class FetchService(log: Logger[IO]) { + private val harmonieServerConfig: HarmonieServerConfig = ( + sys.env.get("HARMONIE_EDR_API_KEY"), + sys.env.get("HARMONIE_EDR_URL"), + ) match { + case (Some(api_key), Some(url)) => + HarmonieServerConfig(api_key, url) + case _ => + throw new RuntimeException("Unable to load harmonie config: Missing required environment variables") + } + + /* + https://dmigw.govcloud.dk/v1/forecastedr/collections/harmonie_dini_sf/grib + ?parameter-name=temperature-2m + &datetime=2025-01-28T00:00:00Z + &api-key=b12d36c7-d7ba-4dca-9bc9-de0c9c27435f + */ + + private val baseUrl: IO[Uri] = IO(Uri.unsafeFromString(harmonieServerConfig.url)) + private def makeRequest(client: Client[IO], url: Uri) = { + + } + + def fetchFromDateTime(time: ZonedDateTime) = { + EmberClientBuilder.default[IO].build.use { client => + for { + base <- baseUrl + queryParams = Query.fromPairs( + "parameter-name" -> "temperature-2m", + "datetime" -> time.toString, + "api-key" -> harmonieServerConfig.api_key, + ) + urlWithParams = Uri.unsafeFromString(s"${base.toString}?${queryParams.toString}") + _ <- IO.println(urlWithParams) + + request = Request[IO](Method.GET, urlWithParams) + + _ <- Files[IO].createDirectories(Path("data")) + + _ <- client.stream(request) + .flatMap(_.body) + .through(Files[IO].writeAll(Path("data/tmp.grib"))) + .compile + .drain + } yield () + } + } + +} \ No newline at end of file diff --git a/src/main/scala/fetchDMI/FetchServiceTest.scala b/src/main/scala/fetchDMI/FetchServiceTest.scala new file mode 100644 index 0000000..68268fc --- /dev/null +++ b/src/main/scala/fetchDMI/FetchServiceTest.scala @@ -0,0 +1,19 @@ +package fetchDMI; + +import cats.effect.IO +import cats.effect.unsafe.implicits.global + +import java.time.{ZoneOffset, ZonedDateTime} + +object FetchServiceTest { + def main(args: Array[String]): Unit = { + val program = for { + nowUTC <- IO(ZonedDateTime.now(ZoneOffset.UTC)) + referenceTime = FileName.getClosestReferenceTime(nowUTC) + fetch <- FetchService.of + _ <- fetch.fetchFromDateTime(referenceTime) + } yield () + + program.unsafeRunSync() + } +} diff --git a/src/main/scala/fetchDMI/FileName.scala b/src/main/scala/fetchDMI/FileName.scala new file mode 100644 index 0000000..9a80d43 --- /dev/null +++ b/src/main/scala/fetchDMI/FileName.scala @@ -0,0 +1,36 @@ +package fetchDMI + +import java.time.{ZoneOffset, ZonedDateTime} + +object FileName { + def main(args: Array[String]): Unit = { + println("-------- FileName") + + val nowUTC = ZonedDateTime.now(ZoneOffset.UTC) + val referenceTime = getClosestReferenceTime(nowUTC) + val timeList = generateTimeList(referenceTime) + + timeList.foreach(println) + + } + + def getClosestReferenceTime(time: ZonedDateTime): ZonedDateTime = { + val referenceHours = Vector(0, 3, 6, 9, 12, 15, 18, 21) + val currentHour = time.getHour + val closestHour = referenceHours + .filter(h => h <= currentHour) + .maxOption + .getOrElse(21) // actually should not be such case + + time + .withHour(closestHour) + .withMinute(0) + .withSecond(0) + .withNano(0) + } + + // TODO make default count to 62 or 60 + def generateTimeList(initialTime: ZonedDateTime, interval: Int = 1, count: Int = 5): List[ZonedDateTime] = { + (0 until count).map(id => initialTime.plusHours(id * interval)).toList + } +}