using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using DCU.ADT.Domain.Entities;
using DCU.ADT.Domain.Enums;
using DCU.ADT.Domain.Exceptions;
using DCU.ADT.Domain.Interfaces;
using DCU.ADT.Domain.ViewModels;
using GoogleMapsApi.Entities.Common;
using GoogleMapsApi.Entities.Geocoding.Request;
using GoogleMapsApi.Entities.Geocoding.Response;
using WeatherNet.Clients;
namespace DCU.ADT.Application
public static void Main()
var cities = GetCities();
var cityForecasts = GetForecasts(cities, new GoogleMapsCityManager(), new OpenWeatherMap(), 5);
PrintResults(cityForecasts, 2);
private static IEnumerable<City> GetCities()
new City("Marlboro", EStateCodes.MA, 1),
new City("San Diego", EStateCodes.CA, 2),
new City("Cheyenne", EStateCodes.NY, 3),
new City("Anchorage", EStateCodes.AK, 4),
new City("Austin", EStateCodes.TX, 5),
new City("Orlando", EStateCodes.FL, 6),
new City("Seattle", EStateCodes.WA, 7),
new City("Cleveland", EStateCodes.OH, 8),
new City("Portland", EStateCodes.ME, 9),
new City("Honolulu", EStateCodes.HI, 10)
private static IEnumerable<CityForecast> GetForecasts(IEnumerable<City> cities, ICityManager cityManager, IForecastConsumer forecastConsumer, int numberOfDays)
var cityForecasts = new List<CityForecast>();
Parallel.ForEach(cities, city =>
var foreCastItem = new CityForecast { City = city };
cityManager.FillCityExtraInfo(city);
foreCastItem.Forecasts = forecastConsumer.GetForecast(city, numberOfDays);
cityForecasts.Add(foreCastItem);
catch (BusinessException b)
cityForecasts.Add(new CityForecast { City = city, ErrorMessage = b.Message });
cityForecasts.Add(new CityForecast
ErrorMessage = "General error when getting city's info"
private static void PrintResults(IEnumerable<CityForecast> cities, int decimalPlaces)
foreach (var cityForecast in cities.OrderBy(c => c.City.Position))
Console.WriteLine("___________________________");
Console.WriteLine(cityForecast.City.Name + ", " + cityForecast.City.StateCode + " (" + cityForecast.City.CenterPostalCode + ")");
if (cityForecast.ErrorMessage != null || cityForecast.Forecasts == null)
Console.WriteLine(cityForecast.ErrorMessage ?? "General error");
Console.WriteLine("Date Temp(F) Temp(C)");
foreach (var weatherInfo in cityForecast.Forecasts)
Console.WriteLine(weatherInfo.Date.ToString("dd/MM/yyyy") + (weatherInfo.ChanceOfPrecipitation ? " *" : " ") + " " + string.Format("{0:N2}", weatherInfo.GetTemperature(decimalPlaces)) + " " + string.Format("{0:N2}", weatherInfo.GetTemperature(decimalPlaces, ETemperatureType.Celsius)));
Console.WriteLine("* When some precipitation is expected");
public class CityForecast
public City City { get; set; }
public List<WeatherInfo> Forecasts { get; set; }
public string ErrorMessage { get; set; }
private readonly string _name;
private readonly EStateCodes _state;
public City(string cityName, EStateCodes stateCode, int pos)
public int Position { get; set; }
public string Name { get { return _name; } }
public EStateCodes StateCode { get { return _state; } }
public string CenterPostalCode { get; set; }
public double? CenterLatitude { get; set; }
public double? CenterLongitude { get; set; }
private double? _temperature;
private ETemperatureType _tempType;
private readonly DateTime _date;
public WeatherInfo(DateTime date)
public void SetTemperature(double temperature, ETemperatureType tempType)
_temperature = temperature;
public DateTime Date { get { return _date; } }
public bool ChanceOfPrecipitation { get; set; }
public double? GetTemperature(int decimalPlaces, ETemperatureType tempType = ETemperatureType.Fahrenheit)
if (_temperature == null) return null;
if (tempType == _tempType) return Math.Round(_temperature.Value, 2);
return Math.Round(tempType == ETemperatureType.Celsius ? ConvertTemp.ConvertFahrenheitToCelsius(_temperature.Value) : ConvertTemp.ConvertCelsiusToFahrenheit(_temperature.Value), 2);
public enum ETemperatureType
AL, AK, AS, AZ, AR, CA, CO, CT, DE, DC, FM, FL, GA, GU, HI, ID, IL, IN, IA, KS, KY, LA, ME, MH, MD, MA,
MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, MP, OH, OK, OR, PW, PA, PR, RI, SC, SD, TN, TX, UT,
VT, VI, VA, WA, WV, WI, WY
public interface ICityManager
void FillCityExtraInfo(City city);
public interface IForecastConsumer
List<WeatherInfo> GetForecast(City city, int days);
public class BusinessException : Exception
public BusinessException(string message) : base(message) { }
namespace DCU.ADT.Business
public class GoogleMapsCityManager : ICityManager
private readonly string googleMapsKey = "AIzaSyCTgrnIKGYri2D9_lCn7CAF6v3F6G6EwuU";
public void FillCityExtraInfo(City city)
var requestCenterLatLong = new GeocodingRequest
Address = city.Name + ", " + city.StateCode
var resultCenterLatLong = GoogleMaps.Geocode.Query(requestCenterLatLong);
if (resultCenterLatLong == null || resultCenterLatLong.Status != Status.OK) throw new BusinessException("Error consuming Google API.");
if (!resultCenterLatLong.Results.Any()) throw new BusinessException("City not found");
if (resultCenterLatLong.Results.First().Geometry == null || resultCenterLatLong.Results.First().Geometry.Location == null) throw new BusinessException("Geometry information not found");
city.CenterLatitude = resultCenterLatLong.Results.First().Geometry.Location.Latitude;
city.CenterLongitude = resultCenterLatLong.Results.First().Geometry.Location.Longitude;
if (resultCenterLatLong.Results.First().AddressComponents.All(ad => ad.ShortName != "postal_code"))
var postalCodeRequest = new GeocodingRequest()
Location = new Location(city.CenterLatitude.Value, city.CenterLongitude.Value)
var postalCodeResults = GoogleMaps.Geocode.Query(postalCodeRequest);
if (postalCodeResults.Status == Status.OK && postalCodeResults.Results.Any() && postalCodeResults.Results.First().AddressComponents != null)
var components = postalCodeResults.Results.First().AddressComponents;
var firstPostalCode = components.FirstOrDefault(ac => ac.Types != null && ac.Types.Any() && ac.Types.First() == "postal_code");
city.CenterPostalCode = firstPostalCode == null ? null : firstPostalCode.ShortName;
city.CenterPostalCode = resultCenterLatLong.Results.First().AddressComponents.First(ad => ad.ShortName == "postal_code").ShortName;
throw new BusinessException("There was a problem finding this city coordinates.");
public class OpenWeatherMap : IForecastConsumer
private const string appId = "7a38c16b76f67e9b53ee25b19fff4854";
public List<WeatherInfo> GetForecast(City city, int days)
ClientSettings.ApiKey = appId;
if (!(city.CenterLatitude.HasValue && city.CenterLongitude.HasValue)) return null;
var cityForecastInfo = FiveDaysForecast.GetByCoordinates(city.CenterLatitude.Value, city.CenterLongitude.Value, "en", "imperial");
if (!cityForecastInfo.Success || cityForecastInfo.Items == null || cityForecastInfo.Items.Count == 0)
throw new BusinessException("Could not load forecast from OpenWeatherMap.");
var results = new List<WeatherInfo>();
var timeUtc = DateTime.UtcNow;
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var easternTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, easternZone);
for (var i = 1; i <= days; i++)
results.Add(new WeatherInfo(easternTime.Date.AddDays(i)));
results = results.Select(r =>
if (cityForecastInfo.Items.All(t => t.Date.Date != r.Date)) return r;
r.SetTemperature(cityForecastInfo
.Where(f => f.Date.Date == r.Date)
.Select(f => (f.TempMin + f.TempMax) / 2)
.Average(), ETemperatureType.Fahrenheit);
r.ChanceOfPrecipitation = cityForecastInfo
.Any(f => f.Date.Date == r.Date && f.Title.IndexOf("rain", StringComparison.CurrentCultureIgnoreCase) >= 0);
if (e is BusinessException) throw;
throw new BusinessException("There was an error consuming OpenWeatherMap API");
internal static class ConvertTemp
public static double ConvertCelsiusToFahrenheit(double c)
return ((9.0 / 5.0) * c) + 32;
public static double ConvertFahrenheitToCelsius(double f)
return (5.0 / 9.0) * (f - 32);