diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index e46d5cc..60b5de3 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -11,9 +11,10 @@ on:
paths:
- '**/*.cs'
- '**/*.csproj'
+ workflow_dispatch:
env:
- DOTNET_VERSION: '6.x.x' # The .NET SDK version to use
+ DOTNET_VERSION: '8.x.x' # The .NET SDK version to use
jobs:
build-and-test:
@@ -22,7 +23,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, windows-latest, macOS-latest]
+ os: [ubuntu-latest]
steps:
- name: Checkout Code
diff --git a/.github/workflows/package-and-deploy.yml b/.github/workflows/package-and-deploy.yml
index 445631e..89874ef 100644
--- a/.github/workflows/package-and-deploy.yml
+++ b/.github/workflows/package-and-deploy.yml
@@ -51,5 +51,5 @@ jobs:
- name: Package nuget
run: dotnet pack ./OpenMeteo/OpenMeteo.csproj --configuration release -o:package /p:PackageVersion=${{ steps.gitversion.outputs.AssemblySemVer }}
- - name: Push generated package to NuGet.org
- run: dotnet nuget push ./package/*.nupkg --api-key ${{ secrets.API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
+ - name: Push generated package to Github Packages
+ run: dotnet nuget push ./package/*.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/colinnuk/index.json --skip-duplicate
diff --git a/.github/workflows/test-dev-build.yml b/.github/workflows/test-dev-build.yml
deleted file mode 100644
index a40ef54..0000000
--- a/.github/workflows/test-dev-build.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: Test Development Build
-
-on:
- push:
- branches-ignore:
- - 'master'
- paths:
- - '**/*.cs'
- - '**/*.csproj'
- pull_request:
- branches-ignore:
- - 'master'
- paths:
- - '**/*.cs'
- - '**/*.csproj'
-
-
-env:
- DOTNET_VERSION: '6.x.x' # The .NET SDK version to use
-
-jobs:
- test:
-
- name: test-ubuntu
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
- - name: Setup .NET Core
- uses: actions/setup-dotnet@v2
- with:
- dotnet-version: ${{ env.DOTNET_VERSION }}
-
- - name: Caching dependencies
- uses: actions/cache@v3
- with:
- path: ~/.nuget/packages
- key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- restore-keys: |
- ${{ runner.os }}-nuget
-
- - name: Install dependencies
- run: dotnet restore
-
- - name: Build
- run: dotnet build --configuration Release --no-restore
-
- - name: Test
- run: dotnet test --no-restore --verbosity normal
diff --git a/OpenMeteo/ElevationApiResponse.cs b/OpenMeteo/ElevationApiResponse.cs
new file mode 100644
index 0000000..91fee0d
--- /dev/null
+++ b/OpenMeteo/ElevationApiResponse.cs
@@ -0,0 +1,12 @@
+namespace OpenMeteo
+{
+ ///
+ /// Elevation API response
+ ///
+ public class ElevationApiResponse
+ {
+ ///
+ /// Elevation array in meters - this library currently only supports 1 input elevation so this will always be a single value array
+ public float[]? Elevation { get; set; }
+ }
+}
diff --git a/OpenMeteo/ElevationOptions.cs b/OpenMeteo/ElevationOptions.cs
new file mode 100644
index 0000000..70459f8
--- /dev/null
+++ b/OpenMeteo/ElevationOptions.cs
@@ -0,0 +1,21 @@
+namespace OpenMeteo
+{
+ internal class ElevationOptions
+ {
+ public ElevationOptions(float latitude, float longitude)
+ {
+ Latitude = latitude;
+ Longitude = longitude;
+ }
+
+ ///
+ /// Geographical WGS84 coordinate of the location
+ ///
+ public float Latitude { get; set; }
+
+ ///
+ /// Geographical WGS84 coordinate of the location
+ ///
+ public float Longitude { get; set; }
+ }
+}
diff --git a/OpenMeteo/ErrorResponse.cs b/OpenMeteo/ErrorResponse.cs
new file mode 100644
index 0000000..4de35a5
--- /dev/null
+++ b/OpenMeteo/ErrorResponse.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace OpenMeteo
+{
+ internal class ErrorResponse
+ {
+ [JsonPropertyName("reason")]
+ public string? Reason { get; set; }
+
+ [JsonPropertyName("error")]
+ public bool Error { get; set; }
+ }
+}
diff --git a/OpenMeteo/Hourly.cs b/OpenMeteo/Hourly.cs
index d25879e..321f6b9 100644
--- a/OpenMeteo/Hourly.cs
+++ b/OpenMeteo/Hourly.cs
@@ -67,10 +67,21 @@ public class Hourly
public float?[]? Temperature_950hPa { get; set; }
public float?[]? Temperature_925hPa { get; set; }
public float?[]? Temperature_900hPa { get; set; }
+ public float?[]? Temperature_875hPa { get; set; }
public float?[]? Temperature_850hPa { get; set; }
+ public float?[]? Temperature_825hPa { get; set; }
public float?[]? Temperature_800hPa { get; set; }
+ public float?[]? Temperature_775hPa { get; set; }
+ public float?[]? Temperature_750hPa { get; set; }
+ public float?[]? Temperature_725hPa { get; set; }
public float?[]? Temperature_700hPa { get; set; }
+ public float?[]? Temperature_675hPa { get; set; }
+ public float?[]? Temperature_650hPa { get; set; }
+ public float?[]? Temperature_625hPa { get; set; }
public float?[]? Temperature_600hPa { get; set; }
+ public float?[]? Temperature_575hPa { get; set; }
+ public float?[]? Temperature_550hPa { get; set; }
+ public float?[]? Temperature_525hPa { get; set; }
public float?[]? Temperature_500hPa { get; set; }
public float?[]? Temperature_400hPa { get; set; }
public float?[]? Temperature_300hPa { get; set; }
@@ -105,10 +116,21 @@ public class Hourly
public int?[]? Relativehumidity_950hPa { get; set; }
public int?[]? Relativehumidity_925hPa { get; set; }
public int?[]? Relativehumidity_900hPa { get; set; }
+ public int?[]? Relativehumidity_875hPa { get; set; }
public int?[]? Relativehumidity_850hPa { get; set; }
+ public int?[]? Relativehumidity_825hPa { get; set; }
public int?[]? Relativehumidity_800hPa { get; set; }
+ public int?[]? Relativehumidity_775hPa { get; set; }
+ public int?[]? Relativehumidity_750hPa { get; set; }
+ public int?[]? Relativehumidity_725hPa { get; set; }
public int?[]? Relativehumidity_700hPa { get; set; }
+ public int?[]? Relativehumidity_675hPa { get; set; }
+ public int?[]? Relativehumidity_650hPa { get; set; }
+ public int?[]? Relativehumidity_625hPa { get; set; }
public int?[]? Relativehumidity_600hPa { get; set; }
+ public int?[]? Relativehumidity_575hPa { get; set; }
+ public int?[]? Relativehumidity_550hPa { get; set; }
+ public int?[]? Relativehumidity_525hPa { get; set; }
public int?[]? Relativehumidity_500hPa { get; set; }
public int?[]? Relativehumidity_400hPa { get; set; }
public int?[]? Relativehumidity_300hPa { get; set; }
@@ -124,10 +146,21 @@ public class Hourly
public int?[]? Cloudcover_950hPa { get; set; }
public int?[]? Cloudcover_925hPa { get; set; }
public int?[]? Cloudcover_900hPa { get; set; }
+ public int?[]? Cloudcover_875hPa { get; set; }
public int?[]? Cloudcover_850hPa { get; set; }
+ public int?[]? Cloudcover_825hPa { get; set; }
public int?[]? Cloudcover_800hPa { get; set; }
+ public int?[]? Cloudcover_775hPa { get; set; }
+ public int?[]? Cloudcover_750hPa { get; set; }
+ public int?[]? Cloudcover_725hPa { get; set; }
public int?[]? Cloudcover_700hPa { get; set; }
+ public int?[]? Cloudcover_675hPa { get; set; }
+ public int?[]? Cloudcover_650hPa { get; set; }
+ public int?[]? Cloudcover_625hPa { get; set; }
public int?[]? Cloudcover_600hPa { get; set; }
+ public int?[]? Cloudcover_575hPa { get; set; }
+ public int?[]? Cloudcover_550hPa { get; set; }
+ public int?[]? Cloudcover_525hPa { get; set; }
public int?[]? Cloudcover_500hPa { get; set; }
public int?[]? Cloudcover_400hPa { get; set; }
public int?[]? Cloudcover_300hPa { get; set; }
@@ -181,10 +214,21 @@ public class Hourly
public float?[]? Geopotential_height_950hPa { get; set; }
public float?[]? Geopotential_height_925hPa { get; set; }
public float?[]? Geopotential_height_900hPa { get; set; }
+ public float?[]? Geopotential_height_875hPa { get; set; }
public float?[]? Geopotential_height_850hPa { get; set; }
+ public float?[]? Geopotential_height_825hPa { get; set; }
public float?[]? Geopotential_height_800hPa { get; set; }
+ public float?[]? Geopotential_height_775hPa { get; set; }
+ public float?[]? Geopotential_height_750hPa { get; set; }
+ public float?[]? Geopotential_height_725hPa { get; set; }
public float?[]? Geopotential_height_700hPa { get; set; }
+ public float?[]? Geopotential_height_675hPa { get; set; }
+ public float?[]? Geopotential_height_650hPa { get; set; }
+ public float?[]? Geopotential_height_625hPa { get; set; }
public float?[]? Geopotential_height_600hPa { get; set; }
+ public float?[]? Geopotential_height_575hPa { get; set; }
+ public float?[]? Geopotential_height_550hPa { get; set; }
+ public float?[]? Geopotential_height_525hPa { get; set; }
public float?[]? Geopotential_height_500hPa { get; set; }
public float?[]? Geopotential_height_400hPa { get; set; }
public float?[]? Geopotential_height_300hPa { get; set; }
diff --git a/OpenMeteo/HourlyOptions.cs b/OpenMeteo/HourlyOptions.cs
index 7d25e3b..d23e707 100644
--- a/OpenMeteo/HourlyOptions.cs
+++ b/OpenMeteo/HourlyOptions.cs
@@ -158,11 +158,25 @@ public enum HourlyOptionsParameter
temperature_950hPa,
temperature_925hPa,
temperature_900hPa,
+ temperature_875hPa,
temperature_850hPa,
+ temperature_825hPa,
temperature_800hPa,
+ temperature_775hPa,
+ temperature_750hPa,
+ temperature_725hPa,
temperature_700hPa,
+ temperature_675hPa,
+ temperature_650hPa,
+ temperature_625hPa,
temperature_600hPa,
+ temperature_575hPa,
+ temperature_550hPa,
+ temperature_525hPa,
temperature_500hPa,
+ temperature_475hPa,
+ temperature_450hPa,
+ temperature_425hPa,
temperature_400hPa,
temperature_300hPa,
temperature_250hPa,
@@ -177,11 +191,25 @@ public enum HourlyOptionsParameter
dewpoint_950hPa,
dewpoint_925hPa,
dewpoint_900hPa,
+ dewpoint_875hPa,
dewpoint_850hPa,
+ dewpoint_825hPa,
dewpoint_800hPa,
+ dewpoint_775hPa,
+ dewpoint_750hPa,
+ dewpoint_725hPa,
dewpoint_700hPa,
+ dewpoint_675hPa,
+ dewpoint_650hPa,
+ dewpoint_625hPa,
dewpoint_600hPa,
+ dewpoint_575hPa,
+ dewpoint_550hPa,
+ dewpoint_525hPa,
dewpoint_500hPa,
+ dewpoint_475hPa,
+ dewpoint_450hPa,
+ dewpoint_425hPa,
dewpoint_400hPa,
dewpoint_300hPa,
dewpoint_250hPa,
@@ -196,11 +224,25 @@ public enum HourlyOptionsParameter
relativehumidity_950hPa,
relativehumidity_925hPa,
relativehumidity_900hPa,
+ relativehumidity_875hPa,
relativehumidity_850hPa,
+ relativehumidity_825hPa,
relativehumidity_800hPa,
+ relativehumidity_775hPa,
+ relativehumidity_750hPa,
+ relativehumidity_725hPa,
relativehumidity_700hPa,
+ relativehumidity_675hPa,
+ relativehumidity_650hPa,
+ relativehumidity_625hPa,
relativehumidity_600hPa,
+ relativehumidity_575hPa,
+ relativehumidity_550hPa,
+ relativehumidity_525hPa,
relativehumidity_500hPa,
+ relativehumidity_475hPa,
+ relativehumidity_450hPa,
+ relativehumidity_425hPa,
relativehumidity_400hPa,
relativehumidity_300hPa,
relativehumidity_250hPa,
@@ -215,11 +257,25 @@ public enum HourlyOptionsParameter
cloudcover_950hPa,
cloudcover_925hPa,
cloudcover_900hPa,
+ cloudcover_875hPa,
cloudcover_850hPa,
+ cloudcover_825hPa,
cloudcover_800hPa,
+ cloudcover_775hPa,
+ cloudcover_750hPa,
+ cloudcover_725hPa,
cloudcover_700hPa,
+ cloudcover_675hPa,
+ cloudcover_650hPa,
+ cloudcover_625hPa,
cloudcover_600hPa,
+ cloudcover_575hPa,
+ cloudcover_550hPa,
+ cloudcover_525hPa,
cloudcover_500hPa,
+ cloudcover_475hPa,
+ cloudcover_450hPa,
+ cloudcover_425hPa,
cloudcover_400hPa,
cloudcover_300hPa,
cloudcover_250hPa,
@@ -234,11 +290,25 @@ public enum HourlyOptionsParameter
windspeed_950hPa,
windspeed_925hPa,
windspeed_900hPa,
+ windspeed_875hPa,
windspeed_850hPa,
+ windspeed_825hPa,
windspeed_800hPa,
+ windspeed_775hPa,
+ windspeed_750hPa,
+ windspeed_725hPa,
windspeed_700hPa,
+ windspeed_675hPa,
+ windspeed_650hPa,
+ windspeed_625hPa,
windspeed_600hPa,
+ windspeed_575hPa,
+ windspeed_550hPa,
+ windspeed_525hPa,
windspeed_500hPa,
+ windspeed_475hPa,
+ windspeed_450hPa,
+ windspeed_425hPa,
windspeed_400hPa,
windspeed_300hPa,
windspeed_250hPa,
@@ -253,11 +323,25 @@ public enum HourlyOptionsParameter
winddirection_950hPa,
winddirection_925hPa,
winddirection_900hPa,
+ winddirection_875hPa,
winddirection_850hPa,
+ winddirection_825hPa,
winddirection_800hPa,
+ winddirection_775hPa,
+ winddirection_750hPa,
+ winddirection_725hPa,
winddirection_700hPa,
+ winddirection_675hPa,
+ winddirection_650hPa,
+ winddirection_625hPa,
winddirection_600hPa,
+ winddirection_575hPa,
+ winddirection_550hPa,
+ winddirection_525hPa,
winddirection_500hPa,
+ winddirection_475hPa,
+ winddirection_450hPa,
+ winddirection_425hPa,
winddirection_400hPa,
winddirection_300hPa,
winddirection_250hPa,
@@ -272,11 +356,25 @@ public enum HourlyOptionsParameter
geopotential_height_950hPa,
geopotential_height_925hPa,
geopotential_height_900hPa,
+ geopotential_height_875hPa,
geopotential_height_850hPa,
+ geopotential_height_825hPa,
geopotential_height_800hPa,
+ geopotential_height_775hPa,
+ geopotential_height_750hPa,
+ geopotential_height_725hPa,
geopotential_height_700hPa,
+ geopotential_height_675hPa,
+ geopotential_height_650hPa,
+ geopotential_height_625hPa,
geopotential_height_600hPa,
+ geopotential_height_575hPa,
+ geopotential_height_550hPa,
+ geopotential_height_525hPa,
geopotential_height_500hPa,
+ geopotential_height_475hPa,
+ geopotential_height_450hPa,
+ geopotential_height_425hPa,
geopotential_height_400hPa,
geopotential_height_300hPa,
geopotential_height_250hPa,
diff --git a/OpenMeteo/HttpController.cs b/OpenMeteo/HttpController.cs
index d563ead..cd48cd9 100644
--- a/OpenMeteo/HttpController.cs
+++ b/OpenMeteo/HttpController.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Text;
using System.Net.Http;
namespace OpenMeteo
@@ -15,7 +13,12 @@ internal class HttpController
public HttpController()
{
- _httpClient = new HttpClient();
+ var socketHttpHandler = new SocketsHttpHandler()
+ {
+ PooledConnectionLifetime = TimeSpan.FromMinutes(4),
+ };
+ _httpClient = new HttpClient(socketHttpHandler);
+
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")
diff --git a/OpenMeteo/IOpenMeteoLogger.cs b/OpenMeteo/IOpenMeteoLogger.cs
new file mode 100644
index 0000000..b260c73
--- /dev/null
+++ b/OpenMeteo/IOpenMeteoLogger.cs
@@ -0,0 +1,16 @@
+namespace OpenMeteo
+{
+ ///
+ /// Specifies a simple interface for a logger.
+ /// Usage of this interface is optional.
+ /// To use this, create your own implementation of this interface which you can easily setup
+ /// to call your own logging framework.
+ ///
+ public interface IOpenMeteoLogger
+ {
+ void Information(string message);
+ void Warning(string message);
+ void Error(string message);
+ void Debug(string message);
+ }
+}
\ No newline at end of file
diff --git a/OpenMeteo/OpenMeteo.csproj b/OpenMeteo/OpenMeteo.csproj
index f56b1ee..e4900f0 100644
--- a/OpenMeteo/OpenMeteo.csproj
+++ b/OpenMeteo/OpenMeteo.csproj
@@ -1,10 +1,10 @@
- netstandard2.1
+ net8.0
enable
- $(AssemblyName).dotnet
- Open Meteo Dotnet Library
+ $(AssemblyName).colinnuk.dotnet
+ Open Meteo Dotnet 8 Library
0.0.3
AlienDwarf
@@ -17,7 +17,7 @@
0.0.1
0.0.1
LICENSE
- A .Net Standard 2.1 library for the Open-Meteo.com API.
+ A DotNet 8 library for the Open-Meteo.com API.
True
snupkg
False
diff --git a/OpenMeteo/OpenMeteoClient.cs b/OpenMeteo/OpenMeteoClient.cs
index 5d4123f..cb8d057 100644
--- a/OpenMeteo/OpenMeteoClient.cs
+++ b/OpenMeteo/OpenMeteoClient.cs
@@ -14,8 +14,12 @@ public class OpenMeteoClient
private readonly string _weatherApiUrl = "https://api.open-meteo.com/v1/forecast";
private readonly string _geocodeApiUrl = "https://geocoding-api.open-meteo.com/v1/search";
private readonly string _airQualityApiUrl = "https://air-quality-api.open-meteo.com/v1/air-quality";
+ private readonly string _elevationApiUrl = "https://api.open-meteo.com/v1/elevation";
private readonly HttpController httpController;
+ private readonly IOpenMeteoLogger? _logger = default!;
+ private readonly bool _rethrowExceptions = false;
+
///
/// Creates a new object and sets the neccessary variables (httpController, CultureInfo)
///
@@ -24,6 +28,16 @@ public OpenMeteoClient()
httpController = new HttpController();
}
+ ///
+ /// Creates a new object and sets the neccessary variables (httpController, CultureInfo)
+ ///
+ public OpenMeteoClient(bool rethrowExceptions, IOpenMeteoLogger? logger = null)
+ {
+ httpController = new HttpController();
+ _logger = logger;
+ _rethrowExceptions = rethrowExceptions;
+ }
+
///
/// Performs two GET-Requests (first geocoding api for latitude,longitude, then weather forecast)
///
@@ -79,14 +93,7 @@ public OpenMeteoClient()
/// Awaitable Task containing WeatherForecast or NULL
public async Task QueryAsync(WeatherForecastOptions options)
{
- try
- {
- return await GetWeatherForecastAsync(options);
- }
- catch (Exception)
- {
- return null;
- }
+ return await GetWeatherForecastAsync(options);
}
///
@@ -164,6 +171,19 @@ public OpenMeteoClient()
return (response.Locations[0].Latitude, response.Locations[0].Longitude);
}
+ ///
+ /// Performs one GET-Request to Open-Meteo Elevation API
+ ///
+ /// Latitude
+ /// Longitude
+ ///
+ public async Task QueryElevationAsync(float latitude, float longitude)
+ {
+ ElevationOptions elevationOptions = new ElevationOptions(latitude, longitude);
+
+ return await GetElevationAsync(elevationOptions);
+ }
+
public WeatherForecast? Query(WeatherForecastOptions options)
{
return QueryAsync(options).GetAwaiter().GetResult();
@@ -194,11 +214,18 @@ public OpenMeteoClient()
return QueryAsync(options).GetAwaiter().GetResult();
}
+ public ElevationApiResponse? QueryElevation(float latitude, float longitude)
+ {
+ return QueryElevationAsync(latitude, longitude).GetAwaiter().GetResult();
+ }
+
private async Task GetAirQualityAsync(AirQualityOptions options)
{
try
{
- HttpResponseMessage response = await httpController.Client.GetAsync(MergeUrlWithOptions(_airQualityApiUrl, options));
+ var url = MergeUrlWithOptions(_airQualityApiUrl, options);
+ _logger?.Debug($"{nameof(OpenMeteoClient)}.GetAirQualityAsync(). URL: {url}");
+ HttpResponseMessage response = await httpController.Client.GetAsync(url);
response.EnsureSuccessStatusCode();
AirQuality? airQuality = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
@@ -206,8 +233,9 @@ public OpenMeteoClient()
}
catch (HttpRequestException e)
{
- Console.WriteLine(e.Message);
- Console.WriteLine(e.StackTrace);
+ _logger?.Warning($"{nameof(OpenMeteoClient)}.GetAirQualityAsync(). Message: {e.Message} StackTrace: {e.StackTrace}");
+ if (_rethrowExceptions)
+ throw;
return null;
}
}
@@ -286,16 +314,35 @@ public string WeathercodeToString(int weathercode)
{
try
{
- HttpResponseMessage response = await httpController.Client.GetAsync(MergeUrlWithOptions(_weatherApiUrl, options));
- response.EnsureSuccessStatusCode();
+ var url = MergeUrlWithOptions(_weatherApiUrl, options);
+ _logger?.Debug($"{nameof(OpenMeteoClient)}.GetElevationAsync(). URL: {url}");
+ HttpResponseMessage response = await httpController.Client.GetAsync(url);
+ if(response.IsSuccessStatusCode)
+ {
+ WeatherForecast? weatherForecast = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
+ return weatherForecast;
+ }
- WeatherForecast? weatherForecast = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
- return weatherForecast;
+ ErrorResponse? error = null;
+ if((int)response.StatusCode >= 400 && (int)response.StatusCode < 500)
+ {
+ try
+ {
+ error = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
+ }
+ catch (Exception e)
+ {
+ _logger?.Error($"{nameof(OpenMeteoClient)}.GetWeatherForecastAsync(). Unable to deserialise error response. This exception will be thrown. Message: {e.Message} StackTrace: {e.StackTrace}");
+ }
+ }
+
+ throw new OpenMeteoClientException(error?.Reason ?? "Exception in OpenMeteoClient", response.StatusCode);
}
- catch (HttpRequestException e)
+ catch (Exception e)
{
- Console.WriteLine(e.Message);
- Console.WriteLine(e.StackTrace);
+ _logger?.Warning($"{nameof(OpenMeteoClient)}.GetWeatherForecastAsync(). Message: {e.Message} StackTrace: {e.StackTrace}");
+ if (_rethrowExceptions)
+ throw;
return null;
}
@@ -305,7 +352,10 @@ public string WeathercodeToString(int weathercode)
{
try
{
- HttpResponseMessage response = await httpController.Client.GetAsync(MergeUrlWithOptions(_geocodeApiUrl, options));
+
+ var url = MergeUrlWithOptions(_geocodeApiUrl, options);
+ _logger?.Debug($"{nameof(OpenMeteoClient)}.GetGeocodingDataAsync(). URL: {url}");
+ HttpResponseMessage response = await httpController.Client.GetAsync(url);
response.EnsureSuccessStatusCode();
GeocodingApiResponse? geocodingData = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
@@ -314,8 +364,32 @@ public string WeathercodeToString(int weathercode)
}
catch (HttpRequestException e)
{
- Console.WriteLine("Can't find " + options.Name + ". Please make sure that the name is valid.");
- Console.WriteLine(e.Message);
+ _logger?.Warning($"{nameof(OpenMeteoClient)}.GetGeocodingDataAsync(). Message: {e.Message} StackTrace: {e.StackTrace}");
+ if (_rethrowExceptions)
+ throw;
+ return null;
+ }
+ }
+
+ private async Task GetElevationAsync(ElevationOptions options)
+ {
+ try
+ {
+ var url = MergeUrlWithOptions(_elevationApiUrl, options);
+ _logger?.Debug($"{nameof(OpenMeteoClient)}.GetElevationAsync(). URL: {url}");
+ HttpResponseMessage response = await httpController.Client.GetAsync(url);
+ response.EnsureSuccessStatusCode();
+
+ ElevationApiResponse? elevationData = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
+
+ return elevationData;
+ }
+ catch (HttpRequestException e)
+ {
+ _logger?.Warning($"Can't find elevation for latitude {options.Latitude} & longitude {options.Longitude}. Please make sure that they are valid.");
+ _logger?.Warning($"Error in {nameof(OpenMeteoClient)}.GetElevationAsync(). Message: {e.Message} StackTrace: {e.StackTrace}");
+ if (_rethrowExceptions)
+ throw;
return null;
}
}
@@ -563,6 +637,18 @@ private string MergeUrlWithOptions(string url, AirQualityOptions options)
return uri.ToString();
}
+
+ private string MergeUrlWithOptions(string url, ElevationOptions options)
+ {
+ if (options == null) return url;
+
+ UriBuilder uri = new UriBuilder(url)
+ {
+ Query = $"?latitude={options.Latitude.ToString(CultureInfo.InvariantCulture)}&longitude={options.Longitude.ToString(CultureInfo.InvariantCulture)}"
+ };
+
+ return uri.ToString();
+ }
}
}
diff --git a/OpenMeteo/OpenMeteoClientException.cs b/OpenMeteo/OpenMeteoClientException.cs
new file mode 100644
index 0000000..e6cc822
--- /dev/null
+++ b/OpenMeteo/OpenMeteoClientException.cs
@@ -0,0 +1,9 @@
+using System.Net;
+using System.Net.Http;
+
+namespace OpenMeteo
+{
+ public class OpenMeteoClientException(string message, HttpStatusCode httpStatusCode) : HttpRequestException(message, null, httpStatusCode)
+ {
+ }
+}
diff --git a/OpenMeteo/WeatherModelOptions.cs b/OpenMeteo/WeatherModelOptions.cs
index 0d69688..604dd92 100644
--- a/OpenMeteo/WeatherModelOptions.cs
+++ b/OpenMeteo/WeatherModelOptions.cs
@@ -110,6 +110,7 @@ public enum WeatherModelOptionsParameter
gfs_seamless,
gfs_global,
gfs_hrrr,
+ gfs_graphcast025,
jma_seamless,
jma_msm,
jma_gsm,
@@ -125,6 +126,12 @@ public enum WeatherModelOptionsParameter
meteofrance_arpege_world,
meteofrance_arpege_europe,
meteofrance_arome_france,
- meteofrance_arome_france_hd
+ meteofrance_arome_france_hd,
+ bom_access_global,
+ arpae_cosmo_2i,
+ arpae_cosmo_2i_ruc,
+ arpae_cosmo_5m,
+ ukmo_global_deterministic_10km,
+ ukmo_global_deterministic_2km,
}
}
diff --git a/OpenMeteoTests/ElevationTests.cs b/OpenMeteoTests/ElevationTests.cs
new file mode 100644
index 0000000..1658731
--- /dev/null
+++ b/OpenMeteoTests/ElevationTests.cs
@@ -0,0 +1,33 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using OpenMeteo;
+using System.Threading.Tasks;
+
+namespace OpenMeteoTests
+{
+ [TestClass]
+ public class ElevationTests
+ {
+ private static readonly float Latitude = 52.5235f;
+ private static readonly float Longitude = 13.4115f;
+
+ [TestMethod]
+ public async Task Elevation_Async_Test()
+ {
+ OpenMeteoClient client = new();
+ var res = await client.QueryElevationAsync(Latitude, Longitude);
+
+ Assert.IsNotNull(res);
+ Assert.AreEqual(res.Elevation.Length, 1);
+ }
+
+ [TestMethod]
+ public void Elevation_Sync_Test()
+ {
+ OpenMeteoClient client = new();
+ var res = client.QueryElevation(Latitude, Longitude);
+
+ Assert.IsNotNull(res);
+ Assert.AreEqual(res.Elevation.Length, 1);
+ }
+ }
+}
diff --git a/OpenMeteoTests/OpenMeteoTests.csproj b/OpenMeteoTests/OpenMeteoTests.csproj
index 17432e8..15a682d 100644
--- a/OpenMeteoTests/OpenMeteoTests.csproj
+++ b/OpenMeteoTests/OpenMeteoTests.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net8.0
false
diff --git a/OpenMeteoTests/WeatherForecastTests.cs b/OpenMeteoTests/WeatherForecastTests.cs
index 54e8abe..0e3301a 100644
--- a/OpenMeteoTests/WeatherForecastTests.cs
+++ b/OpenMeteoTests/WeatherForecastTests.cs
@@ -1,4 +1,6 @@
using System.Globalization;
+using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -107,9 +109,8 @@ public async Task WeatherForecast_With_String_And_Options_Test()
}
[TestMethod]
- public async Task WeatherForecast_With_All_Options_Test()
+ public void WeatherForecast_With_All_Options_Test()
{
- OpenMeteoClient client = new();
WeatherForecastOptions options = new()
{
Hourly = HourlyOptions.All,
@@ -119,39 +120,28 @@ public async Task WeatherForecast_With_All_Options_Test()
Minutely15 = Minutely15Options.All
};
- var res = await client.QueryAsync(options);
-
- Assert.IsNotNull(res);
- Assert.IsNotNull(res.Hourly);
- Assert.IsNotNull(res.HourlyUnits);
- Assert.IsNotNull(res.Daily);
- Assert.IsNotNull(res.DailyUnits);
- Assert.IsNotNull(res.Hourly.Cloudcover_1000hPa_best_match);
- Assert.IsNotNull(res.Current);
- Assert.IsNotNull(res.Minutely15);
+ Assert.IsTrue(HourlyOptions.All.Parameter.All(p => options.Hourly.Parameter.Contains(p)));
+ Assert.IsTrue(DailyOptions.All.Parameter.All(p => options.Daily.Parameter.Contains(p)));
+ Assert.IsTrue(WeatherModelOptions.All.Parameter.All(p => options.Models.Parameter.Contains(p)));
+ Assert.IsTrue(CurrentOptions.All.Parameter.All(p => options.Current.Parameter.Contains(p)));
+ Assert.IsTrue(Minutely15Options.All.Parameter.All(p => options.Minutely15.Parameter.Contains(p)));
}
[TestMethod]
- public void WeatherForecast_With_All_Options_Sync_Test()
+ public async Task Latitude_Longitude_No_Data_For_Selected_Forecast_Rethrows_Test()
{
- OpenMeteoClient client = new();
- WeatherForecastOptions options = new()
+ OpenMeteoClient client = new(true);
+
+ WeatherForecastOptions options = new WeatherForecastOptions
{
- Hourly = HourlyOptions.All,
- Daily = DailyOptions.All,
- Current = CurrentOptions.All,
- Minutely15 = Minutely15Options.All,
+ Latitude = 1,
+ Longitude = 1,
+ Models = new WeatherModelOptions(WeatherModelOptionsParameter.gfs_hrrr),
};
- var res = client.Query(options);
-
- Assert.IsNotNull(res);
- Assert.IsNotNull(res.Hourly);
- Assert.IsNotNull(res.HourlyUnits);
- Assert.IsNotNull(res.Daily);
- Assert.IsNotNull(res.DailyUnits);
- Assert.IsNotNull(res.Current);
- Assert.IsNotNull(res.Minutely15);
+ var ex = await Assert.ThrowsExceptionAsync(async () => await client.QueryAsync(options));
+ Assert.AreEqual(System.Net.HttpStatusCode.BadRequest, ex.StatusCode);
+ Assert.AreEqual("No data is available for this location", ex.Message);
}
}
}