public async Task <CityData> GetCityDataAsync(string zip)
        {
            try
            {
                _logger.Information("Beginning to retrieve city data.");

                if (!string.IsNullOrEmpty(zip))
                {
                    string url  = string.Format(URL_FORMAT, zip);
                    string json = await WebRequests.GetCurlResponseAsync(url, _logger);

                    ZiptasticRoot root = await Json.ToObjectAsync <ZiptasticRoot>(json);

                    if (root != null)
                    {
                        return(new CityData
                        {
                            City = root.City?.ToDisplayName(),
                            State = root.State,
                            ZipCode = zip,
                            Country = root.Country
                        });
                    }
                }
            }
            catch (Exception e)
            {
                _logger.Error($"Failed to retrieve city data: {e.Message}");
                return(null);
            }

            _logger.Error("Failed to retrieve city data.");
            return(null);
        }
        public async Task <Coordinates> GetCoordinatesAsync(string zip)
        {
            try
            {
                _logger.Information("Beginning to retrieve coordinates.");

                if (!string.IsNullOrEmpty(zip))
                {
                    string url  = string.Format(URL_FORMAT, zip);
                    string json = await WebRequests.GetCurlResponseAsync(url, _logger);

                    FrostLineRoot root = await Json.ToObjectAsync <FrostLineRoot>(json);

                    if (root != null && root.Coordinates != null)
                    {
                        return(new Coordinates
                        {
                            Longitude = root.Coordinates.Lon.ToString(),
                            Latitude = root.Coordinates.Lat.ToString()
                        });
                    }
                }
            }
            catch (Exception e)
            {
                _logger.Error($"Failed to retrieve coordinates: {e.Message}");
                return(null);
            }

            _logger.Error("Failed to retrieve coordinates.");
            return(null);
        }
        private const string URL_FORMAT = "https://api.bigdatacloud.net/data/reverse-geocode-client?latitude={0}&longitude={1}&localityLanguage=en"; // {0}Latitude, {1}Longitude

        /// <summary>
        /// Use Policy: May only use current coordinates obtained from a client's web browser using getCurrentPosition() method or
        /// from client devices using their own current coordinates only.
        /// IMPORTANT! Using elsewhere obtained coordinates with BigDataCloud's free API services can lead to blacklisting.
        /// </summary>
        /// <param name="devicePosition">Client device's current position.</param>
        public static async Task <string> GetZipAsync(Windows.Devices.Geolocation.Geoposition devicePosition, AppLogger appLogger)
        {
            string zip = null;

            if (devicePosition != null && devicePosition.Coordinate != null && devicePosition.Coordinate.Point != null)
            {
                double latitude  = devicePosition.Coordinate.Point.Position.Latitude;
                double longitude = devicePosition.Coordinate.Point.Position.Longitude;

                string url  = string.Format(URL_FORMAT, latitude, longitude);
                string json = await WebRequests.GetCurlResponseAsync(url, appLogger.Logger);

                BigDataCloudRoot root = await Json.ToObjectAsync <BigDataCloudRoot>(json);

                zip = root?.Postcode;
            }

            return(zip);
        }
        public async Task <WeatherForecast> GetWeatherDataAsync(Coordinates coordinates)
        {
            try
            {
                _logger.Information("Beginning to retrieve weather forecast.");

                if (coordinates != null)
                {
                    Dictionary <string, string> requestHeaders = new Dictionary <string, string>
                    {
                        { "User-Agent", UserAgent }
                    };

                    string urlGrids  = string.Format(URL_FORMAT_GRIDS, coordinates.Latitude, coordinates.Longitude);
                    string jsonGrids = await WebRequests.GetCurlResponseAsync(urlGrids, _logger, requestHeaders);

                    Domains.WeatherGov.Grid.WeatherGovRoot rootGrid = await Json.ToObjectAsync <Domains.WeatherGov.Grid.WeatherGovRoot>(jsonGrids);

                    //TODO: Save coord/grid data so this request can be skipped in the future.

                    if (rootGrid?.Properties != null)
                    {
                        string urlForecast  = rootGrid.Properties.Forecast;
                        string jsonForecast = await WebRequests.GetCurlResponseAsync(urlForecast, _logger, requestHeaders);

                        WeatherGovRoot rootForecast = await Json.ToObjectAsync <WeatherGovRoot>(jsonForecast);

                        string urlForecastHourlyURL = rootGrid.Properties.ForecastHourly;
                        string jsonForecastHourly   = await WebRequests.GetCurlResponseAsync(urlForecastHourlyURL, _logger, requestHeaders);

                        WeatherGovRoot rootForecastHourly = await Json.ToObjectAsync <WeatherGovRoot>(jsonForecastHourly);

                        if (rootForecast != null && rootForecastHourly != null)
                        {
                            _attemptCounter = 1;
                            return(new WeatherForecast
                            {
                                Days = ConvertPeriods(rootForecast?.Properties?.Periods),
                                Hours = ConvertPeriods(rootForecastHourly?.Properties?.Periods, true)
                            });
                        }
                    }
                }
            }
            catch (Exception e)
            {
                _logger.Error($"Failed to retrieve weather forecast: {e.Message}");
                if (_attemptCounter >= ATTEMPT_LIMIT)
                {
                    _attemptCounter = 1;
                    return(null);
                }
                // Sometimes the first request fails even when everything is fine.
                _attemptCounter++;
                return(await GetWeatherDataAsync(coordinates));
            }

            _logger.Error("Failed to retrieve weather forecast.");
            _attemptCounter = 0;
            return(null);
        }