Skip to content

Commit

Permalink
add - doc|prt - Added initial support for Weather.com
Browse files Browse the repository at this point in the history
---

We've added initial support for Weather.com.

---

Type: add
Breaking: False
Doc Required: True
Part: 1/2
  • Loading branch information
AptiviCEO committed Jun 8, 2024
1 parent c7d61fd commit 67ce166
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 115 deletions.
21 changes: 7 additions & 14 deletions Nettify.Demo/Fixtures/Cases/Forecast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
48 changes: 48 additions & 0 deletions Nettify.Demo/Fixtures/Cases/ForecastList.cs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
//

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}");
}
}
}
}
58 changes: 58 additions & 0 deletions Nettify.Demo/Fixtures/Cases/ForecastOwm.cs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
//

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);
}
}
}
2 changes: 2 additions & 0 deletions Nettify.Demo/Fixtures/FixtureManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ internal static class FixtureManager
new Dictionary(),

// Forecast
new ForecastOwm(),
new ForecastList(),
new Forecast(),

// RSS
Expand Down
8 changes: 8 additions & 0 deletions Nettify.Demo/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
131 changes: 47 additions & 84 deletions Nettify/Weather/WeatherForecast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand All @@ -29,40 +30,28 @@
namespace Nettify.Weather
{
/// <summary>
/// The forecast tools
/// The forecast tools (The Weather Channel)
/// </summary>
public static class WeatherForecast
{
internal static HttpClient WeatherDownloader = new();

/// <summary>
/// Gets current weather info from OpenWeatherMap
/// Gets current weather info from The Weather Channel
/// </summary>
/// <param name="CityID">City ID</param>
/// <param name="APIKey">API key</param>
/// <param name="Unit">The preferred unit to use</param>
/// <returns>A class containing properties of weather information</returns>
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);
}

/// <summary>
/// Gets current weather info from OpenWeatherMap
/// </summary>
/// <param name="CityName">City name</param>
/// <param name="latitude">City latitude</param>
/// <param name="longitude">City longitude</param>
/// <param name="APIKey">API Key</param>
/// <param name="Unit">The preferred unit to use</param>
/// <returns>A class containing properties of weather information</returns>
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);
}

/// <summary>
/// Gets current weather info from OpenWeatherMap
/// Gets current weather info from The Weather Channel
/// </summary>
/// <param name="WeatherURL">An URL to the weather API request</param>
/// <param name="Unit">The preferred unit to use</param>
Expand All @@ -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;
Expand All @@ -86,33 +72,21 @@ internal static WeatherForecastInfo GetWeatherInfo(string WeatherURL, UnitMeasur
}

/// <summary>
/// Gets current weather info from OpenWeatherMap
/// Gets current weather info from The Weather Channel
/// </summary>
/// <param name="CityID">City ID</param>
/// <param name="latitude">City latitude</param>
/// <param name="longitude">City longitude</param>
/// <param name="APIKey">API key</param>
/// <param name="Unit">The preferred unit to use</param>
/// <returns>A class containing properties of weather information</returns>
public static async Task<WeatherForecastInfo> GetWeatherInfoAsync(long CityID, string APIKey, UnitMeasurement Unit = UnitMeasurement.Metric)
public static async Task<WeatherForecastInfo> 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);
}

/// <summary>
/// Gets current weather info from OpenWeatherMap
/// </summary>
/// <param name="CityName">City name</param>
/// <param name="APIKey">API Key</param>
/// <param name="Unit">The preferred unit to use</param>
/// <returns>A class containing properties of weather information</returns>
public static async Task<WeatherForecastInfo> 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);
}

/// <summary>
/// Gets current weather info from OpenWeatherMap
/// Gets current weather info from The Weather Channel
/// </summary>
/// <param name="WeatherURL">An URL to the weather API request</param>
/// <param name="Unit">The preferred unit to use</param>
Expand All @@ -124,10 +98,7 @@ internal static async Task<WeatherForecastInfo> 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);
Expand All @@ -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;
Expand All @@ -157,9 +125,9 @@ internal static WeatherForecastInfo FinalizeInstallation(JToken WeatherToken, Un
/// <summary>
/// Lists all the available cities
/// </summary>
public static Dictionary<long, string> ListAllCities()
public static Dictionary<string, (double, double)> 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);

Expand All @@ -171,9 +139,9 @@ public static Dictionary<long, string> ListAllCities()
/// <summary>
/// Lists all the available cities
/// </summary>
public static async Task<Dictionary<long, string>> ListAllCitiesAsync()
public static async Task<Dictionary<string, (double, double)>> 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);

Expand All @@ -182,37 +150,32 @@ public static async Task<Dictionary<long, string>> ListAllCitiesAsync()
return FinalizeCityList(WeatherCityListDataStream);
}

internal static Dictionary<long, string> FinalizeCityList(Stream WeatherCityListDataStream)
internal static Dictionary<string, (double, double)> FinalizeCityList(Stream WeatherCityListDataStream)
{
GZipStream WeatherCityListData;
var WeatherCityListUncompressed = new List<byte>();
int WeatherCityListReadByte = 0;
JToken WeatherCityListToken;
var WeatherCityList = new Dictionary<long, string>();

// 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<string, (double, double)> 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;
}
}
}
Loading

0 comments on commit 67ce166

Please sign in to comment.