Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions samples/OpenWeatherMapSharp.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,49 @@ OpenWeatherMapServiceResponse<WeatherRoot> weatherResponse
};
AnsiConsole.Write(weatherPanel);

// == AIR POLLUTION ==
OpenWeatherMapServiceResponse<AirPolutionRoot> airPollutionResponse
= await openWeatherMapService.GetAirPolutionAsync(geolocation.Latitude, geolocation.Longitude);

if (!airPollutionResponse.IsSuccess || airPollutionResponse.Response is not AirPolutionRoot airQuality || airQuality.Entries.Count == 0)
{
AnsiConsole.MarkupLine("[bold red]Unfortunately I can't retrieve air pollution data. Please try again.[/]");
return;
}

AirPolutionEntry pollution = airQuality.Entries.First();

// Map AQI to meaning
string GetAqiMeaning(int aqi) => aqi switch
{
1 => "[green]Good[/]",
2 => "[yellow]Fair[/]",
3 => "[orange1]Moderate[/]",
4 => "[red]Poor[/]",
5 => "[maroon]Very Poor[/]",
_ => "[grey]Unknown[/]"
};

// == AIR POLLUTION PANEL ==
List<Markup> pollutionMarkupList =
[
new($"[red]Air Quality Index (AQI): [/]{pollution.AQI.Index} ({GetAqiMeaning(pollution.AQI.Index)})"),
new("-----"),
new($"[red]PM2.5: [/]{pollution.Components.FineParticlesMatter:0.00} µg/m³"),
new($"[red]PM10: [/]{pollution.Components.CoarseParticulateMatter:0.00} µg/m³"),
new($"[red]O₃ (Ozone): [/]{pollution.Components.Ozone:0.00} µg/m³"),
new($"[red]NO₂ (Nitrogen Dioxide): [/]{pollution.Components.NitrogenDioxide:0.00} µg/m³"),
new($"[red]SO₂ (Sulfur Dioxide): [/]{pollution.Components.SulfurDioxide:0.00} µg/m³"),
new($"[red]CO (Carbon Monoxide): [/]{pollution.Components.CarbonMonoxide:0.00} µg/m³"),
new($"[red]NH₃ (Ammonia): [/]{pollution.Components.Ammonia:0.00} µg/m³")
];

Panel pollutionPanel = new(new Rows(pollutionMarkupList))
{
Header = new PanelHeader("Air Pollution"),
Width = 120
};
AnsiConsole.Write(pollutionPanel);


Console.ReadLine();
34 changes: 34 additions & 0 deletions src/OpenWeatherMapSharp/IOpenWeatherMapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,39 @@ Task<OpenWeatherMapServiceResponse<List<GeocodeInfo>>> GetLocationByLatLonAsync(
double latitude,
double longitude,
int limit = 5);

/// <summary>
/// Retrieves current air pollution data for a specific location.
/// </summary>
/// <param name="latitude">Latitude of the location.</param>
/// <param name="longitude">Longitude of the location.</param>
/// <returns>Current air pollution data wrapped in a service response.</returns>
Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionAsync(
double latitude,
double longitude);

/// <summary>
/// Retrieves forecasted air pollution data for the coming days for a specific location.
/// </summary>
/// <param name="latitude">Latitude of the location.</param>
/// <param name="longitude">Longitude of the location.</param>
/// <returns>Air pollution forecast data wrapped in a service response.</returns>
Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionForecastAsync(
double latitude,
double longitude);

/// <summary>
/// Retrieves historical air pollution data for a specific location and time range.
/// </summary>
/// <param name="latitude">Latitude of the location.</param>
/// <param name="longitude">Longitude of the location.</param>
/// <param name="start">Start of the time range (UTC).</param>
/// <param name="end">End of the time range (UTC).</param>
/// <returns>Historical air pollution data wrapped in a service response.</returns>
Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionHistoryAsync(
double latitude,
double longitude,
DateTime start,
DateTime end);
}
}
59 changes: 59 additions & 0 deletions src/OpenWeatherMapSharp/Models/AirPolutionComponents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Text.Json.Serialization;

namespace OpenWeatherMapSharp.Models
{
/// <summary>
/// Represents the concentration values
/// of various air pollutants (in μg/m³).
/// </summary>
public class AirPolutionComponents
{
/// <summary>
/// Carbon monoxide concentration.
/// </summary>
[JsonPropertyName("co")]
public double CarbonMonoxide { get; set; }

/// <summary>
/// Nitric oxide concentration.
/// </summary>
[JsonPropertyName("no")]
public double NitrogenMonoxide { get; set; }

/// <summary>
/// Nitrogen dioxide concentration.
/// </summary>
[JsonPropertyName("no2")]
public double NitrogenDioxide { get; set; }

/// <summary>
/// Ozone concentration.
/// </summary>
[JsonPropertyName("o3")]
public double Ozone { get; set; }

/// <summary>
/// Sulfur dioxide concentration.
/// </summary>
[JsonPropertyName("so2")]
public double SulfurDioxide { get; set; }

/// <summary>
/// Fine particulate matter (PM2.5) concentration.
/// </summary>
[JsonPropertyName("pm2_5")]
public double FineParticlesMatter { get; set; }

/// <summary>
/// Coarse particulate matter (PM10) concentration.
/// </summary>
[JsonPropertyName("pm10")]
public double CoarseParticulateMatter { get; set; }

/// <summary>
/// Ammonia concentration.
/// </summary>
[JsonPropertyName("nh3")]
public double Ammonia { get; set; }
}
}
39 changes: 39 additions & 0 deletions src/OpenWeatherMapSharp/Models/AirPolutionEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using OpenWeatherMapSharp.Utils;
using System;
using System.Text.Json.Serialization;

namespace OpenWeatherMapSharp.Models
{
/// <summary>
/// Represents a single entry of air quality data,
/// including AQI, components, and timestamp.
/// </summary>
public class AirPolutionEntry
{
/// <summary>
/// Main air quality index (AQI).
/// </summary>
[JsonPropertyName("main")]
public AirPolutionIndex AQI { get; set; }

/// <summary>
/// Concentrations of individual air components.
/// </summary>
[JsonPropertyName("components")]
public AirPolutionComponents Components { get; set; }

/// <summary>
/// Timestamp of the measurement (Unix time in seconds).
/// </summary>
[JsonPropertyName("dt")]
public long DateUnix { get; set; }

/// <summary>
/// Timestamp of the weather data as a
/// UTC <see cref="DateTime"/>.
/// </summary>
[JsonIgnore]
public DateTime Date
=> DateUnix.ToDateTime();
}
}
16 changes: 16 additions & 0 deletions src/OpenWeatherMapSharp/Models/AirPolutionIndex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;

namespace OpenWeatherMapSharp.Models
{
/// <summary>
/// Represents the air quality index (AQI) value.
/// </summary>
public class AirPolutionIndex
{
/// <summary>
/// Air Quality Index (1 = Good, 5 = Very Poor).
/// </summary>
[JsonPropertyName("aqi")]
public int Index { get; set; }
}
}
23 changes: 23 additions & 0 deletions src/OpenWeatherMapSharp/Models/AirPolutionRoot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace OpenWeatherMapSharp.Models
{
/// <summary>
/// Root object representing the air quality response from the API.
/// </summary>
public class AirPolutionRoot
{
/// <summary>
/// Geographic coordinates of the measurement location.
/// </summary>
[JsonPropertyName("coord")]
public Coordinates Coordinates { get; set; }

/// <summary>
/// List of air polution measurements.
/// </summary>
[JsonPropertyName("list")]
public List<AirPolutionEntry> Entries { get; set; }
}
}
21 changes: 21 additions & 0 deletions src/OpenWeatherMapSharp/OpenWeatherMapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,26 @@ public async Task<OpenWeatherMapServiceResponse<List<GeocodeInfo>>> GetLocationB
string url = string.Format(Statics.GeocodeReverseUri, latitude, longitude, limit, _apiKey);
return await HttpService.GetDataAsync<List<GeocodeInfo>>(url);
}

/// <inheritdoc/>
public async Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionAsync(double latitude, double longitude)
{
string url = string.Format(Statics.AirPollutionCoordinatesUri, latitude, longitude, _apiKey);
return await HttpService.GetDataAsync<AirPolutionRoot>(url);
}

/// <inheritdoc/>
public async Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionForecastAsync(double latitude, double longitude)
{
string url = string.Format(Statics.AirPollutionCoordinatesForecastUri, latitude, longitude, _apiKey);
return await HttpService.GetDataAsync<AirPolutionRoot>(url);
}

/// <inheritdoc/>
public async Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionHistoryAsync(double latitude, double longitude, DateTime start, DateTime end)
{
string url = string.Format(Statics.AirPollutionCoordinatesHistoryUri, latitude, longitude, start.ToUnixTimestamp(), end.ToUnixTimestamp(), _apiKey);
return await HttpService.GetDataAsync<AirPolutionRoot>(url);
}
}
}
27 changes: 27 additions & 0 deletions src/OpenWeatherMapSharp/Utils/DateTimeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace OpenWeatherMapSharp.Utils
{
/// <summary>
/// Provides extension methods for converting
/// a <see cref="DateTime"/> to a Unix timestamps.
/// </summary>
internal static class DateTimeExtensions
{
/// <summary>
/// Converts a DateTime object to a Unix timestamp (seconds since 1970-01-01T00:00:00Z).
/// </summary>
/// <param name="dateTime">The DateTime to convert.
/// Should be in UTC or convertible to UTC.</param>
/// <returns>The Unix timestamp in seconds.</returns>
public static long ToUnixTimestamp(this DateTime dateTime)
{
// Ensure the DateTime is in UTC
var utcDateTime = dateTime.Kind == DateTimeKind.Utc
? dateTime
: dateTime.ToUniversalTime();

return new DateTimeOffset(utcDateTime).ToUnixTimeSeconds();
}
}
}
4 changes: 2 additions & 2 deletions src/OpenWeatherMapSharp/Utils/LongExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ internal static class LongExtensions
/// The Unix timestamp to convert.</param>
/// <returns>
/// A <see cref="DateTime"/> object representing
/// the local time.
/// the universal time.
/// </returns>
internal static DateTime ToDateTime(this long unixTimeStamp)
{
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return dateTime.AddSeconds(unixTimeStamp).ToLocalTime();
return dateTime.AddSeconds(unixTimeStamp).ToUniversalTime();
}
}
}
24 changes: 24 additions & 0 deletions src/OpenWeatherMapSharp/Utils/Statics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ private static readonly string ForecastBaseUri
private static readonly string GeocodeBaseUri
= $"{BaseUri}/geo/1.0";

private static readonly string AirPollutionBaseUri
= $"{BaseUri}/data/2.5/air_pollution";


/// <summary>
/// Weather by geographic coordinates (latitude, longitude).
Expand Down Expand Up @@ -80,5 +83,26 @@ public static readonly string GeocodeZipUri
/// </summary>
public static readonly string GeocodeReverseUri
= GeocodeBaseUri + "/reverse?lat={0}&lon={1}&limit={2}&appid={3}";

/// <summary>
/// Air pollution data by geographic coordinates.
/// Format: lat={0}&lon={1}&appid={2}
/// </summary>
public static readonly string AirPollutionCoordinatesUri
= AirPollutionBaseUri + "?lat={0}&lon={1}&appid={2}";

/// <summary>
/// Air pollution forecast by geographic coordinates.
/// Format: lat={0}&lon={1}&start={2}&end={3}&appid={4}
/// </summary>
public static readonly string AirPollutionCoordinatesForecastUri
= AirPollutionBaseUri + "/forecast?lat={0}&lon={1}&appid={2}";

/// <summary>
/// Air pollution history by geographic coordinates.
/// Format: lat={0}&lon={1}&start={2}&end={3}&appid={4}
/// </summary>
public static readonly string AirPollutionCoordinatesHistoryUri
= AirPollutionBaseUri + "/history?lat={0}&lon={1}&start={2}&end={3}&appid={4}";
}
}
Loading