Skip to content

Commit adde240

Browse files
authored
Merge pull request #10 from tsjdev-apps/feature/airpolution
Add air pollution data models
2 parents 5450f7c + 91775fd commit adde240

11 files changed

Lines changed: 366 additions & 2 deletions

File tree

samples/OpenWeatherMapSharp.Console/Program.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,49 @@ OpenWeatherMapServiceResponse<WeatherRoot> weatherResponse
8787
};
8888
AnsiConsole.Write(weatherPanel);
8989

90+
// == AIR POLLUTION ==
91+
OpenWeatherMapServiceResponse<AirPolutionRoot> airPollutionResponse
92+
= await openWeatherMapService.GetAirPolutionAsync(geolocation.Latitude, geolocation.Longitude);
93+
94+
if (!airPollutionResponse.IsSuccess || airPollutionResponse.Response is not AirPolutionRoot airQuality || airQuality.Entries.Count == 0)
95+
{
96+
AnsiConsole.MarkupLine("[bold red]Unfortunately I can't retrieve air pollution data. Please try again.[/]");
97+
return;
98+
}
99+
100+
AirPolutionEntry pollution = airQuality.Entries.First();
101+
102+
// Map AQI to meaning
103+
string GetAqiMeaning(int aqi) => aqi switch
104+
{
105+
1 => "[green]Good[/]",
106+
2 => "[yellow]Fair[/]",
107+
3 => "[orange1]Moderate[/]",
108+
4 => "[red]Poor[/]",
109+
5 => "[maroon]Very Poor[/]",
110+
_ => "[grey]Unknown[/]"
111+
};
112+
113+
// == AIR POLLUTION PANEL ==
114+
List<Markup> pollutionMarkupList =
115+
[
116+
new($"[red]Air Quality Index (AQI): [/]{pollution.AQI.Index} ({GetAqiMeaning(pollution.AQI.Index)})"),
117+
new("-----"),
118+
new($"[red]PM2.5: [/]{pollution.Components.FineParticlesMatter:0.00} µg/m³"),
119+
new($"[red]PM10: [/]{pollution.Components.CoarseParticulateMatter:0.00} µg/m³"),
120+
new($"[red]O₃ (Ozone): [/]{pollution.Components.Ozone:0.00} µg/m³"),
121+
new($"[red]NO₂ (Nitrogen Dioxide): [/]{pollution.Components.NitrogenDioxide:0.00} µg/m³"),
122+
new($"[red]SO₂ (Sulfur Dioxide): [/]{pollution.Components.SulfurDioxide:0.00} µg/m³"),
123+
new($"[red]CO (Carbon Monoxide): [/]{pollution.Components.CarbonMonoxide:0.00} µg/m³"),
124+
new($"[red]NH₃ (Ammonia): [/]{pollution.Components.Ammonia:0.00} µg/m³")
125+
];
126+
127+
Panel pollutionPanel = new(new Rows(pollutionMarkupList))
128+
{
129+
Header = new PanelHeader("Air Pollution"),
130+
Width = 120
131+
};
132+
AnsiConsole.Write(pollutionPanel);
133+
134+
90135
Console.ReadLine();

src/OpenWeatherMapSharp/IOpenWeatherMapService.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,5 +121,39 @@ Task<OpenWeatherMapServiceResponse<List<GeocodeInfo>>> GetLocationByLatLonAsync(
121121
double latitude,
122122
double longitude,
123123
int limit = 5);
124+
125+
/// <summary>
126+
/// Retrieves current air pollution data for a specific location.
127+
/// </summary>
128+
/// <param name="latitude">Latitude of the location.</param>
129+
/// <param name="longitude">Longitude of the location.</param>
130+
/// <returns>Current air pollution data wrapped in a service response.</returns>
131+
Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionAsync(
132+
double latitude,
133+
double longitude);
134+
135+
/// <summary>
136+
/// Retrieves forecasted air pollution data for the coming days for a specific location.
137+
/// </summary>
138+
/// <param name="latitude">Latitude of the location.</param>
139+
/// <param name="longitude">Longitude of the location.</param>
140+
/// <returns>Air pollution forecast data wrapped in a service response.</returns>
141+
Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionForecastAsync(
142+
double latitude,
143+
double longitude);
144+
145+
/// <summary>
146+
/// Retrieves historical air pollution data for a specific location and time range.
147+
/// </summary>
148+
/// <param name="latitude">Latitude of the location.</param>
149+
/// <param name="longitude">Longitude of the location.</param>
150+
/// <param name="start">Start of the time range (UTC).</param>
151+
/// <param name="end">End of the time range (UTC).</param>
152+
/// <returns>Historical air pollution data wrapped in a service response.</returns>
153+
Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionHistoryAsync(
154+
double latitude,
155+
double longitude,
156+
DateTime start,
157+
DateTime end);
124158
}
125159
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace OpenWeatherMapSharp.Models
4+
{
5+
/// <summary>
6+
/// Represents the concentration values
7+
/// of various air pollutants (in μg/m³).
8+
/// </summary>
9+
public class AirPolutionComponents
10+
{
11+
/// <summary>
12+
/// Carbon monoxide concentration.
13+
/// </summary>
14+
[JsonPropertyName("co")]
15+
public double CarbonMonoxide { get; set; }
16+
17+
/// <summary>
18+
/// Nitric oxide concentration.
19+
/// </summary>
20+
[JsonPropertyName("no")]
21+
public double NitrogenMonoxide { get; set; }
22+
23+
/// <summary>
24+
/// Nitrogen dioxide concentration.
25+
/// </summary>
26+
[JsonPropertyName("no2")]
27+
public double NitrogenDioxide { get; set; }
28+
29+
/// <summary>
30+
/// Ozone concentration.
31+
/// </summary>
32+
[JsonPropertyName("o3")]
33+
public double Ozone { get; set; }
34+
35+
/// <summary>
36+
/// Sulfur dioxide concentration.
37+
/// </summary>
38+
[JsonPropertyName("so2")]
39+
public double SulfurDioxide { get; set; }
40+
41+
/// <summary>
42+
/// Fine particulate matter (PM2.5) concentration.
43+
/// </summary>
44+
[JsonPropertyName("pm2_5")]
45+
public double FineParticlesMatter { get; set; }
46+
47+
/// <summary>
48+
/// Coarse particulate matter (PM10) concentration.
49+
/// </summary>
50+
[JsonPropertyName("pm10")]
51+
public double CoarseParticulateMatter { get; set; }
52+
53+
/// <summary>
54+
/// Ammonia concentration.
55+
/// </summary>
56+
[JsonPropertyName("nh3")]
57+
public double Ammonia { get; set; }
58+
}
59+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using OpenWeatherMapSharp.Utils;
2+
using System;
3+
using System.Text.Json.Serialization;
4+
5+
namespace OpenWeatherMapSharp.Models
6+
{
7+
/// <summary>
8+
/// Represents a single entry of air quality data,
9+
/// including AQI, components, and timestamp.
10+
/// </summary>
11+
public class AirPolutionEntry
12+
{
13+
/// <summary>
14+
/// Main air quality index (AQI).
15+
/// </summary>
16+
[JsonPropertyName("main")]
17+
public AirPolutionIndex AQI { get; set; }
18+
19+
/// <summary>
20+
/// Concentrations of individual air components.
21+
/// </summary>
22+
[JsonPropertyName("components")]
23+
public AirPolutionComponents Components { get; set; }
24+
25+
/// <summary>
26+
/// Timestamp of the measurement (Unix time in seconds).
27+
/// </summary>
28+
[JsonPropertyName("dt")]
29+
public long DateUnix { get; set; }
30+
31+
/// <summary>
32+
/// Timestamp of the weather data as a
33+
/// UTC <see cref="DateTime"/>.
34+
/// </summary>
35+
[JsonIgnore]
36+
public DateTime Date
37+
=> DateUnix.ToDateTime();
38+
}
39+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace OpenWeatherMapSharp.Models
4+
{
5+
/// <summary>
6+
/// Represents the air quality index (AQI) value.
7+
/// </summary>
8+
public class AirPolutionIndex
9+
{
10+
/// <summary>
11+
/// Air Quality Index (1 = Good, 5 = Very Poor).
12+
/// </summary>
13+
[JsonPropertyName("aqi")]
14+
public int Index { get; set; }
15+
}
16+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json.Serialization;
3+
4+
namespace OpenWeatherMapSharp.Models
5+
{
6+
/// <summary>
7+
/// Root object representing the air quality response from the API.
8+
/// </summary>
9+
public class AirPolutionRoot
10+
{
11+
/// <summary>
12+
/// Geographic coordinates of the measurement location.
13+
/// </summary>
14+
[JsonPropertyName("coord")]
15+
public Coordinates Coordinates { get; set; }
16+
17+
/// <summary>
18+
/// List of air polution measurements.
19+
/// </summary>
20+
[JsonPropertyName("list")]
21+
public List<AirPolutionEntry> Entries { get; set; }
22+
}
23+
}

src/OpenWeatherMapSharp/OpenWeatherMapService.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,26 @@ public async Task<OpenWeatherMapServiceResponse<List<GeocodeInfo>>> GetLocationB
148148
string url = string.Format(Statics.GeocodeReverseUri, latitude, longitude, limit, _apiKey);
149149
return await HttpService.GetDataAsync<List<GeocodeInfo>>(url);
150150
}
151+
152+
/// <inheritdoc/>
153+
public async Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionAsync(double latitude, double longitude)
154+
{
155+
string url = string.Format(Statics.AirPollutionCoordinatesUri, latitude, longitude, _apiKey);
156+
return await HttpService.GetDataAsync<AirPolutionRoot>(url);
157+
}
158+
159+
/// <inheritdoc/>
160+
public async Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionForecastAsync(double latitude, double longitude)
161+
{
162+
string url = string.Format(Statics.AirPollutionCoordinatesForecastUri, latitude, longitude, _apiKey);
163+
return await HttpService.GetDataAsync<AirPolutionRoot>(url);
164+
}
165+
166+
/// <inheritdoc/>
167+
public async Task<OpenWeatherMapServiceResponse<AirPolutionRoot>> GetAirPolutionHistoryAsync(double latitude, double longitude, DateTime start, DateTime end)
168+
{
169+
string url = string.Format(Statics.AirPollutionCoordinatesHistoryUri, latitude, longitude, start.ToUnixTimestamp(), end.ToUnixTimestamp(), _apiKey);
170+
return await HttpService.GetDataAsync<AirPolutionRoot>(url);
171+
}
151172
}
152173
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
3+
namespace OpenWeatherMapSharp.Utils
4+
{
5+
/// <summary>
6+
/// Provides extension methods for converting
7+
/// a <see cref="DateTime"/> to a Unix timestamps.
8+
/// </summary>
9+
internal static class DateTimeExtensions
10+
{
11+
/// <summary>
12+
/// Converts a DateTime object to a Unix timestamp (seconds since 1970-01-01T00:00:00Z).
13+
/// </summary>
14+
/// <param name="dateTime">The DateTime to convert.
15+
/// Should be in UTC or convertible to UTC.</param>
16+
/// <returns>The Unix timestamp in seconds.</returns>
17+
public static long ToUnixTimestamp(this DateTime dateTime)
18+
{
19+
// Ensure the DateTime is in UTC
20+
var utcDateTime = dateTime.Kind == DateTimeKind.Utc
21+
? dateTime
22+
: dateTime.ToUniversalTime();
23+
24+
return new DateTimeOffset(utcDateTime).ToUnixTimeSeconds();
25+
}
26+
}
27+
}

src/OpenWeatherMapSharp/Utils/LongExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ internal static class LongExtensions
1717
/// The Unix timestamp to convert.</param>
1818
/// <returns>
1919
/// A <see cref="DateTime"/> object representing
20-
/// the local time.
20+
/// the universal time.
2121
/// </returns>
2222
internal static DateTime ToDateTime(this long unixTimeStamp)
2323
{
2424
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
25-
return dateTime.AddSeconds(unixTimeStamp).ToLocalTime();
25+
return dateTime.AddSeconds(unixTimeStamp).ToUniversalTime();
2626
}
2727
}
2828
}

src/OpenWeatherMapSharp/Utils/Statics.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ private static readonly string ForecastBaseUri
1717
private static readonly string GeocodeBaseUri
1818
= $"{BaseUri}/geo/1.0";
1919

20+
private static readonly string AirPollutionBaseUri
21+
= $"{BaseUri}/data/2.5/air_pollution";
22+
2023

2124
/// <summary>
2225
/// Weather by geographic coordinates (latitude, longitude).
@@ -80,5 +83,26 @@ public static readonly string GeocodeZipUri
8083
/// </summary>
8184
public static readonly string GeocodeReverseUri
8285
= GeocodeBaseUri + "/reverse?lat={0}&lon={1}&limit={2}&appid={3}";
86+
87+
/// <summary>
88+
/// Air pollution data by geographic coordinates.
89+
/// Format: lat={0}&lon={1}&appid={2}
90+
/// </summary>
91+
public static readonly string AirPollutionCoordinatesUri
92+
= AirPollutionBaseUri + "?lat={0}&lon={1}&appid={2}";
93+
94+
/// <summary>
95+
/// Air pollution forecast by geographic coordinates.
96+
/// Format: lat={0}&lon={1}&start={2}&end={3}&appid={4}
97+
/// </summary>
98+
public static readonly string AirPollutionCoordinatesForecastUri
99+
= AirPollutionBaseUri + "/forecast?lat={0}&lon={1}&appid={2}";
100+
101+
/// <summary>
102+
/// Air pollution history by geographic coordinates.
103+
/// Format: lat={0}&lon={1}&start={2}&end={3}&appid={4}
104+
/// </summary>
105+
public static readonly string AirPollutionCoordinatesHistoryUri
106+
= AirPollutionBaseUri + "/history?lat={0}&lon={1}&start={2}&end={3}&appid={4}";
83107
}
84108
}

0 commit comments

Comments
 (0)