diff --git a/Nettify.Demo/Fixtures/Cases/Forecast.cs b/Nettify.Demo/Fixtures/Cases/Forecast.cs index 2b2d5c1..abbd7c2 100644 --- a/Nettify.Demo/Fixtures/Cases/Forecast.cs +++ b/Nettify.Demo/Fixtures/Cases/Forecast.cs @@ -28,32 +28,25 @@ internal class Forecast : IFixture public void RunFixture() { string ApiKey; - string StringID; + double latitude, longitude; WeatherForecastInfo forecastInfo; - bool IsNumeric; // ID or name - Console.Write("Enter city ID or name: "); - StringID = Console.ReadLine(); - IsNumeric = long.TryParse(StringID, out long FinalID); + Console.Write("Enter city latitude: "); + latitude = double.Parse(Console.ReadLine()); + Console.Write("Enter city longitude: "); + longitude = double.Parse(Console.ReadLine()); // API key - Console.Write("Enter API key: "); + Console.Write("Enter TWC API key: "); ApiKey = Console.ReadLine(); // Get weather info - if (IsNumeric) - forecastInfo = WeatherForecast.GetWeatherInfo(FinalID, ApiKey, UnitMeasurement.Metric); - else - forecastInfo = WeatherForecast.GetWeatherInfo(StringID, ApiKey, UnitMeasurement.Metric); + forecastInfo = WeatherForecast.GetWeatherInfo(latitude, longitude, ApiKey, UnitMeasurement.Metric); // Print the weather information - Console.WriteLine("City ID: " + forecastInfo.CityID); - Console.WriteLine("City Name: " + forecastInfo.CityName); Console.WriteLine("Weather State: " + forecastInfo.Weather); Console.WriteLine("Temperature: " + forecastInfo.Temperature); - Console.WriteLine("Feels Like: " + forecastInfo.FeelsLike); - Console.WriteLine("Pressure: " + forecastInfo.Pressure); Console.WriteLine("Humidity: " + forecastInfo.Humidity); Console.WriteLine("Wind Speed: " + forecastInfo.WindSpeed); Console.WriteLine("Wind Direction: " + forecastInfo.WindDirection); diff --git a/Nettify.Demo/Fixtures/Cases/ForecastList.cs b/Nettify.Demo/Fixtures/Cases/ForecastList.cs new file mode 100644 index 0000000..5434bd4 --- /dev/null +++ b/Nettify.Demo/Fixtures/Cases/ForecastList.cs @@ -0,0 +1,48 @@ +// +// Nettify Copyright (C) 2023-2024 Aptivi +// +// This file is part of Nettify +// +// Nettify is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Nettify is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using Nettify.Weather; +using System; + +namespace Nettify.Demo.Fixtures.Cases +{ + internal class ForecastList : IFixture + { + public string FixtureID => "ForecastList"; + public void RunFixture() + { + // City + Console.Write("Enter city name: "); + string city = Console.ReadLine(); + + // API key + Console.Write("Enter TWC API key: "); + string ApiKey = Console.ReadLine(); + + // List all cities + var longsLats = WeatherForecast.ListAllCities(city, ApiKey); + foreach (var longLat in longsLats) + { + string name = longLat.Key; + (double latitude, double longitude) = longLat.Value; + Console.WriteLine($"Name: {name,-55}\t\tlat: {latitude,-10}\tlng: {longitude,-10}"); + } + } + } +} diff --git a/Nettify.Demo/Fixtures/Cases/ForecastOwm.cs b/Nettify.Demo/Fixtures/Cases/ForecastOwm.cs new file mode 100644 index 0000000..02e96a1 --- /dev/null +++ b/Nettify.Demo/Fixtures/Cases/ForecastOwm.cs @@ -0,0 +1,58 @@ +// +// Nettify Copyright (C) 2023-2024 Aptivi +// +// This file is part of Nettify +// +// Nettify is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Nettify is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using Nettify.Weather; +using System; + +namespace Nettify.Demo.Fixtures.Cases +{ + internal class ForecastOwm : IFixture + { + public string FixtureID => "ForecastOwm"; + public void RunFixture() + { + string ApiKey; + string StringID; + WeatherForecastInfo forecastInfo; + bool IsNumeric; + + // ID or name + Console.Write("Enter city ID or name: "); + StringID = Console.ReadLine(); + IsNumeric = long.TryParse(StringID, out long FinalID); + + // API key + Console.Write("Enter API key: "); + ApiKey = Console.ReadLine(); + + // Get weather info + if (IsNumeric) + forecastInfo = WeatherForecastOwm.GetWeatherInfo(FinalID, ApiKey, UnitMeasurement.Metric); + else + forecastInfo = WeatherForecastOwm.GetWeatherInfo(StringID, ApiKey, UnitMeasurement.Metric); + + // Print the weather information + Console.WriteLine("Weather State: " + forecastInfo.Weather); + Console.WriteLine("Temperature: " + forecastInfo.Temperature); + Console.WriteLine("Humidity: " + forecastInfo.Humidity); + Console.WriteLine("Wind Speed: " + forecastInfo.WindSpeed); + Console.WriteLine("Wind Direction: " + forecastInfo.WindDirection); + } + } +} diff --git a/Nettify.Demo/Fixtures/FixtureManager.cs b/Nettify.Demo/Fixtures/FixtureManager.cs index 91e90c0..fa501c9 100644 --- a/Nettify.Demo/Fixtures/FixtureManager.cs +++ b/Nettify.Demo/Fixtures/FixtureManager.cs @@ -34,6 +34,8 @@ internal static class FixtureManager new Dictionary(), // Forecast + new ForecastOwm(), + new ForecastList(), new Forecast(), // RSS diff --git a/Nettify.Demo/Properties/launchSettings.json b/Nettify.Demo/Properties/launchSettings.json index 15b0734..e546dcd 100644 --- a/Nettify.Demo/Properties/launchSettings.json +++ b/Nettify.Demo/Properties/launchSettings.json @@ -16,6 +16,14 @@ "commandName": "Project", "commandLineArgs": "Forecast" }, + "Nettify.Demo - ForecastOwm": { + "commandName": "Project", + "commandLineArgs": "ForecastOwm" + }, + "Nettify.Demo - ForecastList": { + "commandName": "Project", + "commandLineArgs": "ForecastList" + }, "Nettify.Demo - RssFeedViewer": { "commandName": "Project", "commandLineArgs": "RssFeedViewer" diff --git a/Nettify/Weather/WeatherForecast.cs b/Nettify/Weather/WeatherForecast.cs index 1529a71..75bb083 100644 --- a/Nettify/Weather/WeatherForecast.cs +++ b/Nettify/Weather/WeatherForecast.cs @@ -17,6 +17,7 @@ // along with this program. If not, see . // +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -29,40 +30,28 @@ namespace Nettify.Weather { /// - /// The forecast tools + /// The forecast tools (The Weather Channel) /// public static class WeatherForecast { internal static HttpClient WeatherDownloader = new(); /// - /// Gets current weather info from OpenWeatherMap + /// Gets current weather info from The Weather Channel /// - /// City ID - /// API key - /// The preferred unit to use - /// A class containing properties of weather information - public static WeatherForecastInfo GetWeatherInfo(long CityID, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) - { - string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?id={CityID}&appid={APIKey}"; - return GetWeatherInfo(WeatherURL, Unit); - } - - /// - /// Gets current weather info from OpenWeatherMap - /// - /// City name + /// City latitude + /// City longitude /// API Key /// The preferred unit to use /// A class containing properties of weather information - public static WeatherForecastInfo GetWeatherInfo(string CityName, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) + public static WeatherForecastInfo GetWeatherInfo(double latitude, double longitude, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) { - string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?q={CityName}&appid={APIKey}"; - return GetWeatherInfo(WeatherURL, Unit); + string WeatherURL = $"http://api.weather.com/v3/wx/forecast/daily/15day?geocode={latitude},{longitude}&format=json&language=en-US&units={(Unit == UnitMeasurement.Metric ? 'm' : 'e')}&apiKey={APIKey}"; + return GetWeatherInfo(WeatherURL, Unit == UnitMeasurement.Kelvin ? UnitMeasurement.Imperial : Unit); } /// - /// Gets current weather info from OpenWeatherMap + /// Gets current weather info from The Weather Channel /// /// An URL to the weather API request /// The preferred unit to use @@ -74,10 +63,7 @@ internal static WeatherForecastInfo GetWeatherInfo(string WeatherURL, UnitMeasur Debug.WriteLine("Weather URL: {0} | Unit: {1}", WeatherURL, Unit); // Deal with measurements - if (Unit == UnitMeasurement.Imperial) - WeatherURL += "&units=imperial"; - else - WeatherURL += "&units=metric"; + Unit = Unit == UnitMeasurement.Kelvin ? UnitMeasurement.Imperial : Unit; // Download and parse JSON data WeatherData = WeatherDownloader.GetStringAsync(WeatherURL).Result; @@ -86,33 +72,21 @@ internal static WeatherForecastInfo GetWeatherInfo(string WeatherURL, UnitMeasur } /// - /// Gets current weather info from OpenWeatherMap + /// Gets current weather info from The Weather Channel /// - /// City ID + /// City latitude + /// City longitude /// API key /// The preferred unit to use /// A class containing properties of weather information - public static async Task GetWeatherInfoAsync(long CityID, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) + public static async Task GetWeatherInfoAsync(double latitude, double longitude, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) { - string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?id={CityID}&appid={APIKey}"; + string WeatherURL = $"http://api.weather.com/v3/wx/forecast/daily/15day?geocode={latitude},{longitude}&format=json&language=en-US&units={(Unit == UnitMeasurement.Metric ? 'm' : 'e')}&apiKey={APIKey}"; return await GetWeatherInfoAsync(WeatherURL, Unit); } /// - /// Gets current weather info from OpenWeatherMap - /// - /// City name - /// API Key - /// The preferred unit to use - /// A class containing properties of weather information - public static async Task GetWeatherInfoAsync(string CityName, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) - { - string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?q={CityName}&appid={APIKey}"; - return await GetWeatherInfoAsync(WeatherURL, Unit); - } - - /// - /// Gets current weather info from OpenWeatherMap + /// Gets current weather info from The Weather Channel /// /// An URL to the weather API request /// The preferred unit to use @@ -124,10 +98,7 @@ internal static async Task GetWeatherInfoAsync(string Weath Debug.WriteLine("Weather URL: {0} | Unit: {1}", WeatherURL, Unit); // Deal with measurements - if (Unit == UnitMeasurement.Imperial) - WeatherURL += "&units=imperial"; - else - WeatherURL += "&units=metric"; + Unit = Unit == UnitMeasurement.Kelvin ? UnitMeasurement.Imperial : Unit; // Download and parse JSON data WeatherData = await WeatherDownloader.GetStringAsync(WeatherURL); @@ -140,15 +111,12 @@ internal static WeatherForecastInfo FinalizeInstallation(JToken WeatherToken, Un WeatherForecastInfo WeatherInfo = new() { // Put needed data to the class + // TODO: Handle weather condition translation Weather = (WeatherCondition)WeatherToken.SelectToken("weather").First.SelectToken("id").ToObject(typeof(WeatherCondition)), - Temperature = (double)WeatherToken.SelectToken("main").SelectToken("temp").ToObject(typeof(double)), - FeelsLike = (double)WeatherToken.SelectToken("main").SelectToken("feels_like").ToObject(typeof(double)), - Pressure = (double)WeatherToken.SelectToken("main").SelectToken("pressure").ToObject(typeof(double)), - Humidity = (double)WeatherToken.SelectToken("main").SelectToken("humidity").ToObject(typeof(double)), - WindSpeed = (double)WeatherToken.SelectToken("wind").SelectToken("speed").ToObject(typeof(double)), - WindDirection = (double)WeatherToken.SelectToken("wind").SelectToken("deg").ToObject(typeof(double)), - CityID = (long)WeatherToken.SelectToken("id").ToObject(typeof(long)), - CityName = (string)WeatherToken.SelectToken("name").ToObject(typeof(string)), + Temperature = (double)WeatherToken["daypart"]["temperature"][1].ToObject(typeof(double)), + Humidity = (double)WeatherToken["daypart"]["humidity"][1].ToObject(typeof(double)), + WindSpeed = (double)WeatherToken["daypart"]["windSpeed"][1].ToObject(typeof(double)), + WindDirection = (double)WeatherToken["daypart"]["windDirection"][1].ToObject(typeof(double)), TemperatureMeasurement = Unit }; return WeatherInfo; @@ -157,9 +125,9 @@ internal static WeatherForecastInfo FinalizeInstallation(JToken WeatherToken, Un /// /// Lists all the available cities /// - public static Dictionary ListAllCities() + public static Dictionary ListAllCities(string city, string APIKey) { - string WeatherCityListURL = $"http://bulk.openweathermap.org/sample/city.list.json.gz"; + string WeatherCityListURL = $"http://api.weather.com/v3/location/search?language=en-US&query={city}&format=json&apiKey={APIKey}"; Stream WeatherCityListDataStream; Debug.WriteLine("Weather City List URL: {0}", WeatherCityListURL); @@ -171,9 +139,9 @@ public static Dictionary ListAllCities() /// /// Lists all the available cities /// - public static async Task> ListAllCitiesAsync() + public static async Task> ListAllCitiesAsync(string city, string APIKey) { - string WeatherCityListURL = $"http://bulk.openweathermap.org/sample/city.list.json.gz"; + string WeatherCityListURL = $"http://api.weather.com/v3/location/search?language=en-US&query={city}&format=json&apiKey={APIKey}"; Stream WeatherCityListDataStream; Debug.WriteLine("Weather City List URL: {0}", WeatherCityListURL); @@ -182,37 +150,32 @@ public static async Task> ListAllCitiesAsync() return FinalizeCityList(WeatherCityListDataStream); } - internal static Dictionary FinalizeCityList(Stream WeatherCityListDataStream) + internal static Dictionary FinalizeCityList(Stream WeatherCityListDataStream) { - GZipStream WeatherCityListData; - var WeatherCityListUncompressed = new List(); - int WeatherCityListReadByte = 0; - JToken WeatherCityListToken; - var WeatherCityList = new Dictionary(); - - // Parse the weather list JSON. Since the output is gzipped, we'll have to uncompress it using stream, since the city list - // is large anyways. This saves you from downloading full 45+ MB of text. - WeatherCityListData = new GZipStream(WeatherCityListDataStream, CompressionMode.Decompress, false); - while (WeatherCityListReadByte != -1) - { - WeatherCityListReadByte = WeatherCityListData.ReadByte(); - if (WeatherCityListReadByte != -1) - WeatherCityListUncompressed.Add((byte)WeatherCityListReadByte); - } - - WeatherCityListToken = JToken.Parse(Encoding.Default.GetString([.. WeatherCityListUncompressed])); - - // Put needed data to the class - foreach (JToken WeatherCityToken in WeatherCityListToken) + // Get the token + var reader = new StreamReader(WeatherCityListDataStream); + string json = reader.ReadToEnd(); + var token = JToken.Parse(json); + + // Get the addresses, the latitudes, and the longitudes + var loc = token["location"]; + var addresses = (JArray)loc["address"]; + var latitudes = (JArray)loc["latitude"]; + var longitudes = (JArray)loc["longitude"]; + Debug.Assert(addresses.Count == latitudes.Count && addresses.Count == longitudes.Count && latitudes.Count == longitudes.Count); + + // Put needed data + Dictionary cities = []; + for (int i = 0; i < addresses.Count; i++) { - long cityId = (long)WeatherCityToken["id"]; - string cityName = (string)WeatherCityToken["name"]; - if (!WeatherCityList.ContainsKey(cityId)) - WeatherCityList.Add(cityId, cityName); + var address = (string)addresses[i]; + var lat = (double)latitudes[i]; + var lng = (double)longitudes[i]; + cities.Add(address, (lat, lng)); } // Return list - return WeatherCityList; + return cities; } } } diff --git a/Nettify/Weather/WeatherForecastInfo.cs b/Nettify/Weather/WeatherForecastInfo.cs index be71478..0d73a31 100644 --- a/Nettify/Weather/WeatherForecastInfo.cs +++ b/Nettify/Weather/WeatherForecastInfo.cs @@ -24,17 +24,9 @@ namespace Nettify.Weather /// /// Forecast information /// - [DebuggerDisplay("{CityName} [{CityID}]: {Weather} @ {Temperature} [{TemperatureMeasurement}]")] + [DebuggerDisplay("{Weather} @ {Temperature} [{TemperatureMeasurement}]")] public partial class WeatherForecastInfo { - /// - /// City ID - /// - public long CityID { get; set; } - /// - /// City Name - /// - public string CityName { get; set; } /// /// Weather condition /// @@ -48,14 +40,6 @@ public partial class WeatherForecastInfo /// public double Temperature { get; set; } /// - /// Feels like - /// - public double FeelsLike { get; set; } - /// - /// Pressure in hPa - /// - public double Pressure { get; set; } - /// /// Humidity in percent /// public double Humidity { get; set; } diff --git a/Nettify/Weather/WeatherForecastOwm.cs b/Nettify/Weather/WeatherForecastOwm.cs new file mode 100644 index 0000000..d192366 --- /dev/null +++ b/Nettify/Weather/WeatherForecastOwm.cs @@ -0,0 +1,214 @@ +// +// Nettify Copyright (C) 2023-2024 Aptivi +// +// This file is part of Nettify +// +// Nettify is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Nettify is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace Nettify.Weather +{ + /// + /// The forecast tools (OpenWeatherMap) + /// + public static class WeatherForecastOwm + { + internal static HttpClient WeatherDownloader = new(); + + /// + /// Gets current weather info from OpenWeatherMap + /// + /// City ID + /// API key + /// The preferred unit to use + /// A class containing properties of weather information + public static WeatherForecastInfo GetWeatherInfo(long CityID, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) + { + string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?id={CityID}&appid={APIKey}"; + return GetWeatherInfo(WeatherURL, Unit); + } + + /// + /// Gets current weather info from OpenWeatherMap + /// + /// City name + /// API Key + /// The preferred unit to use + /// A class containing properties of weather information + public static WeatherForecastInfo GetWeatherInfo(string CityName, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) + { + string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?q={CityName}&appid={APIKey}"; + return GetWeatherInfo(WeatherURL, Unit); + } + + /// + /// Gets current weather info from OpenWeatherMap + /// + /// An URL to the weather API request + /// The preferred unit to use + /// A class containing properties of weather information + internal static WeatherForecastInfo GetWeatherInfo(string WeatherURL, UnitMeasurement Unit = UnitMeasurement.Metric) + { + string WeatherData; + JToken WeatherToken; + Debug.WriteLine("Weather URL: {0} | Unit: {1}", WeatherURL, Unit); + + // Deal with measurements + if (Unit == UnitMeasurement.Imperial) + WeatherURL += "&units=imperial"; + else + WeatherURL += "&units=metric"; + + // Download and parse JSON data + WeatherData = WeatherDownloader.GetStringAsync(WeatherURL).Result; + WeatherToken = JToken.Parse(WeatherData); + return FinalizeInstallation(WeatherToken, Unit); + } + + /// + /// Gets current weather info from OpenWeatherMap + /// + /// City ID + /// API key + /// The preferred unit to use + /// A class containing properties of weather information + public static async Task GetWeatherInfoAsync(long CityID, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) + { + string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?id={CityID}&appid={APIKey}"; + return await GetWeatherInfoAsync(WeatherURL, Unit); + } + + /// + /// Gets current weather info from OpenWeatherMap + /// + /// City name + /// API Key + /// The preferred unit to use + /// A class containing properties of weather information + public static async Task GetWeatherInfoAsync(string CityName, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric) + { + string WeatherURL = $"http://api.openweathermap.org/data/2.5/weather?q={CityName}&appid={APIKey}"; + return await GetWeatherInfoAsync(WeatherURL, Unit); + } + + /// + /// Gets current weather info from OpenWeatherMap + /// + /// An URL to the weather API request + /// The preferred unit to use + /// A class containing properties of weather information + internal static async Task GetWeatherInfoAsync(string WeatherURL, UnitMeasurement Unit = UnitMeasurement.Metric) + { + string WeatherData; + JToken WeatherToken; + Debug.WriteLine("Weather URL: {0} | Unit: {1}", WeatherURL, Unit); + + // Deal with measurements + if (Unit == UnitMeasurement.Imperial) + WeatherURL += "&units=imperial"; + else + WeatherURL += "&units=metric"; + + // Download and parse JSON data + WeatherData = await WeatherDownloader.GetStringAsync(WeatherURL); + WeatherToken = JToken.Parse(WeatherData); + return FinalizeInstallation(WeatherToken, Unit); + } + + internal static WeatherForecastInfo FinalizeInstallation(JToken WeatherToken, UnitMeasurement Unit = UnitMeasurement.Metric) + { + WeatherForecastInfo WeatherInfo = new() + { + // Put needed data to the class + Weather = (WeatherCondition)WeatherToken.SelectToken("weather").First.SelectToken("id").ToObject(typeof(WeatherCondition)), + Temperature = (double)WeatherToken.SelectToken("main").SelectToken("temp").ToObject(typeof(double)), + Humidity = (double)WeatherToken.SelectToken("main").SelectToken("humidity").ToObject(typeof(double)), + WindSpeed = (double)WeatherToken.SelectToken("wind").SelectToken("speed").ToObject(typeof(double)), + WindDirection = (double)WeatherToken.SelectToken("wind").SelectToken("deg").ToObject(typeof(double)), + TemperatureMeasurement = Unit + }; + return WeatherInfo; + } + + /// + /// Lists all the available cities + /// + public static Dictionary ListAllCities() + { + string WeatherCityListURL = $"http://bulk.openweathermap.org/sample/city.list.json.gz"; + Stream WeatherCityListDataStream; + Debug.WriteLine("Weather City List URL: {0}", WeatherCityListURL); + + // Open the stream to the city list URL + WeatherCityListDataStream = WeatherDownloader.GetStreamAsync(WeatherCityListURL).Result; + return FinalizeCityList(WeatherCityListDataStream); + } + + /// + /// Lists all the available cities + /// + public static async Task> ListAllCitiesAsync() + { + string WeatherCityListURL = $"http://bulk.openweathermap.org/sample/city.list.json.gz"; + Stream WeatherCityListDataStream; + Debug.WriteLine("Weather City List URL: {0}", WeatherCityListURL); + + // Open the stream to the city list URL + WeatherCityListDataStream = await WeatherDownloader.GetStreamAsync(WeatherCityListURL); + return FinalizeCityList(WeatherCityListDataStream); + } + + internal static Dictionary FinalizeCityList(Stream WeatherCityListDataStream) + { + GZipStream WeatherCityListData; + var WeatherCityListUncompressed = new List(); + int WeatherCityListReadByte = 0; + JToken WeatherCityListToken; + var WeatherCityList = new Dictionary(); + + // Parse the weather list JSON. Since the output is gzipped, we'll have to uncompress it using stream, since the city list + // is large anyways. This saves you from downloading full 45+ MB of text. + WeatherCityListData = new GZipStream(WeatherCityListDataStream, CompressionMode.Decompress, false); + while (WeatherCityListReadByte != -1) + { + WeatherCityListReadByte = WeatherCityListData.ReadByte(); + if (WeatherCityListReadByte != -1) + WeatherCityListUncompressed.Add((byte)WeatherCityListReadByte); + } + + WeatherCityListToken = JToken.Parse(Encoding.Default.GetString([.. WeatherCityListUncompressed])); + + // Put needed data to the class + foreach (JToken WeatherCityToken in WeatherCityListToken) + { + long cityId = (long)WeatherCityToken["id"]; + string cityName = (string)WeatherCityToken["name"]; + if (!WeatherCityList.ContainsKey(cityId)) + WeatherCityList.Add(cityId, cityName); + } + + // Return list + return WeatherCityList; + } + } +}