Xcalibur.Weather.Services 1.1.6

dotnet add package Xcalibur.Weather.Services --version 1.1.6
                    
NuGet\Install-Package Xcalibur.Weather.Services -Version 1.1.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Xcalibur.Weather.Services" Version="1.1.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Xcalibur.Weather.Services" Version="1.1.6" />
                    
Directory.Packages.props
<PackageReference Include="Xcalibur.Weather.Services" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Xcalibur.Weather.Services --version 1.1.6
                    
#r "nuget: Xcalibur.Weather.Services, 1.1.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Xcalibur.Weather.Services@1.1.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Xcalibur.Weather.Services&version=1.1.6
                    
Install as a Cake Addin
#tool nuget:?package=Xcalibur.Weather.Services&version=1.1.6
                    
Install as a Cake Tool

Xcalibur.Weather.Services

.NET 10 NuGet License

A comprehensive .NET library providing HTTP client services for weather-related APIs. Seamless integration with multiple weather data providers including Open-Meteo, Geocodio, IpGeolocation.io, Atmospore, SunriseSunset.io, and OpenStreetMap for weather forecasting, geocoding, air quality monitoring, pollen insights, multi-source weather alerts, and astronomical data.

Created by: Joshua Arzt | Company: Xcalibur Systems, LLC.

Purpose

Xcalibur.Weather.Services is designed to:

  • Provide production-ready HTTP client services for multiple weather, geocoding, astronomy, pollen, and alert APIs
  • Enable seamless integration with Open-Meteo, Geocodio, IpGeolocation.io, Atmospore, SunriseSunset.io, OpenStreetMap, and multi-source weather alert providers
  • Deliver async/await patterns with cancellation token support for responsive applications
  • Offer strongly-typed responses using Xcalibur.Weather.Models
  • Support both API-key and no-key service providers for flexible deployment scenarios
  • Centralize API communication logic with built-in error handling and logging

Latest Updates

  • Package version: 1.1.6
  • Models package dependency: 1.1.5
  • Target framework: .NET 10.0
  • Latest release: Expanded WeatherAlertService test suite with deserialization, guard-clause, and constructor null-check tests for all seven alert providers; total test count raised to 158
  • Migrated WeatherAlertService to current Meteoalarm, Environment Canada GeoMet, and BOM v1 alert APIs; refreshed alert models, examples, and regression coverage
  • Added AtmosporeService for pollen forecast data from Atmospore API
  • Added WeatherAlertService for multi-provider weather alerts (Meteoalarm, NWS, GDACS, Environment Canada, BOM Australia, EMSC, DWD)
  • Added WeatherObservationService orchestration with regional providers and METAR fallback
  • Added observation providers for NWS, ECCC, DWD, JMA, BOM fallback, Met Office fallback, Mรฉtรฉo-France fallback, and global METAR
  • Added source-generated NWS observation JSON handling for Native AOT compatibility
  • Added source-generated METAR observation JSON handling for Native AOT compatibility
  • Added METAR visibility handling for numeric, string, and null visib payloads
  • Optimized METAR widening-search logic and replaced magic numbers with named constants
  • All 158 unit tests passing with comprehensive coverage across all services

๐Ÿ“‹ Table of Contents

โœจ Features

  • Multiple Weather Providers: Integrated support for Open-Meteo, Geocodio, IpGeolocation.io, Atmospore, SunriseSunset.io, and OpenStreetMap APIs
  • Multi-Source Weather Alerts: Aggregated weather alerts from Meteoalarm, NWS, GDACS, Environment Canada, BOM Australia, EMSC, and DWD
  • Comprehensive Weather Data: Access current weather, forecasts, air quality, pollen forecasts, weather alerts, geocoding, and astronomical data
  • Modern .NET 10: Built with the latest .NET features and best practices
  • Async/Await: Full asynchronous API support with cancellation tokens
  • Logging Support: Built-in logging using Microsoft.Extensions.Logging
  • AOT-Ready: Source-generated JSON contexts for Native AOT compilation support
  • Error Handling: Robust error handling with detailed logging
  • Streaming Deserialization: Efficient memory usage with streaming JSON deserialization
  • Type-Safe: Strongly-typed responses using Xcalibur.Weather.Models
  • Flexible Provider Coverage: Includes both API key and no-key providers for geocoding, pollen, and astronomy data

๐Ÿ”ง Technology

  • Target Framework: .NET 10.0
  • Current Package Version: 1.1.6
  • Dependencies:
    • Microsoft.Extensions.Hosting (v10.0.11) - For logging and dependency injection abstractions
    • Xcalibur.Weather.Models (v1.1.5) - Shared models and DTOs
  • Features:
    • Implicit usings enabled
    • Nullable reference types enabled
    • Async/await throughout with CancellationToken support
    • Native AOT compilation support via source-generated JSON contexts
    • Streaming JSON deserialization for efficient memory usage
    • Built-in retry logic and error handling
    • Comprehensive logging via Microsoft.Extensions.Logging
    • NuGet package generation on Release build

๐Ÿ’ก Use Cases

This library is ideal for:

  • Weather Applications: Mobile and desktop apps requiring current conditions, forecasts, and air quality data
  • Smart Home Systems: IoT devices and home automation requiring weather-based triggers
  • Agricultural Solutions: Farm management systems needing weather, pollen, and environmental data
  • Travel & Navigation Apps: Applications requiring location-based weather and alerts
  • Health & Wellness Apps: Allergy tracking with pollen forecast integration
  • Emergency Management: Systems aggregating multi-source weather alerts and disaster notifications
  • Environmental Monitoring: Air quality dashboards and pollution tracking systems
  • Astronomy Applications: Sunrise/sunset tracking, moon phase, and astronomical event planning
  • Geocoding Services: Address validation, coordinate lookup, and location-based features
  • Web APIs & Microservices: Backend services needing weather data aggregation from multiple providers

๐Ÿ“ฆ Installation

Install via NuGet Package Manager:

dotnet add package Xcalibur.Weather.Services

Or via Package Manager Console:

Install-Package Xcalibur.Weather.Services

Or add to your project file:

<PackageReference Include="Xcalibur.Weather.Services" Version="1.1.5" />

๐ŸŒฆ๏ธ Services

OpenMeteoService

The OpenMeteoService provides access to Open-Meteo weather APIs, offering comprehensive weather data without requiring an API key.

Key Features:

  • Current weather conditions
  • Current air quality index (AQI)
  • Hourly forecasts with configurable forecastDays and pastDays
  • Multi-day daily forecasts
  • Historical weather data and configurable historic retrieval
  • Hourly air quality forecasts

Supported Data Points:

  • Temperature (current, apparent, min/max)
  • Humidity and dew point
  • Precipitation (rain, showers, snowfall)
  • Wind (speed, direction, gusts)
  • Atmospheric pressure
  • Cloud cover and visibility
  • Weather codes
  • Air quality metrics (PM2.5, PM10, CO, NO2, SO2, O3, etc.)
  • Pollen levels (alder, birch, grass, mugwort, olive, ragweed)
  • UV index
  • Sunrise/sunset and daylight duration

GeocodioService

The GeocodioService provides geocoding capabilities to convert addresses into geographic coordinates.

Key Features:

  • Forward geocoding (address to coordinates)
  • Country-specific searches
  • API key validation
  • Detailed location results with accuracy information

IpGeoService

The IpGeoService provides astronomical data for specific geographic locations.

Key Features:

  • Sunrise and sunset times
  • Moonrise and moonset times
  • Moon phase information
  • API key validation

AtmosporeService

The AtmosporeService provides pollen forecast data from the Atmospore API (pollenapi.com) and requires an API key.

Key Features:

  • Multi-day pollen forecast lookup by coordinates
  • Overall pollen risk assessment
  • Detailed species-level pollen data with risk levels
  • Display names and values for individual pollen species
  • Date-specific or current date forecasts
  • API key validation

WeatherAlertService

The WeatherAlertService aggregates weather alerts from multiple international sources without requiring an API key.

Key Features:

  • Meteoalarm: European weather alerts by country feed name
  • NWS (National Weather Service): US weather alerts by coordinates
  • GDACS (Global Disaster Alert and Coordination System): Global disaster alerts
  • Environment Canada: Canadian weather warnings from the GeoMet Current-Alerts layer
  • BOM Australia: Australian weather warnings from the BOM v1 national warnings API
  • EMSC (European-Mediterranean Seismological Centre): Earthquake alerts by coordinates and radius
  • DWD (Deutscher Wetterdienst): German weather warnings
  • Automatic User-Agent header management for provider compatibility

WeatherObservationService

The WeatherObservationService provides centralized nearest-observation lookup based on latitude and longitude, automatically selecting the appropriate regional service and falling back to METAR where needed.

Key Features:

  • Region-aware observation routing from coordinates
  • Unified WeatherObservation response model
  • Regional providers for United States, Canada, Germany, Japan, UK, France, Australia, and global fallback
  • Automatic METAR fallback when regional data is unavailable
  • Local timestamp normalization based on station longitude
  • Support for retrieving a single nearest observation or a list wrapper for future multi-station expansion

SunriseSunsetService

The SunriseSunsetService provides sunrise and sunset data from SunriseSunset.io without requiring an API key.

Key Features:

  • Sunrise and sunset times
  • Solar noon and day length data
  • No API key required
  • Lightweight astronomy lookups by coordinates

OpenStreetMapService

The OpenStreetMapService provides geocoding through OpenStreetMap Nominatim.

Key Features:

  • Forward geocoding (address to coordinates)
  • Address details in results
  • Country-filtered searches
  • No API key required
  • Built-in default User-Agent support for Nominatim requests

๐Ÿš€ Usage

Basic Setup

using Microsoft.Extensions.Logging;
using Xcalibur.Weather.Services;

var httpClient = new HttpClient();
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());

var openMeteoService = new OpenMeteoService(httpClient, loggerFactory.CreateLogger<OpenMeteoService>());
var geocodioService = new GeocodioService(httpClient, "YOUR_GEOCODIO_API_KEY", loggerFactory.CreateLogger<GeocodioService>());
var ipGeoService = new IpGeoService(httpClient, "YOUR_IPGEO_API_KEY", loggerFactory.CreateLogger<IpGeoService>());
var atmosporeService = new AtmosporeService(httpClient, "YOUR_ATMOSPORE_API_KEY", loggerFactory.CreateLogger<AtmosporeService>());
var weatherAlertService = new WeatherAlertService(httpClient, loggerFactory.CreateLogger<WeatherAlertService>());
var sunriseSunsetService = new SunriseSunsetService(httpClient, loggerFactory.CreateLogger<SunriseSunsetService>());
var openStreetMapService = new OpenStreetMapService(httpClient, loggerFactory.CreateLogger<OpenStreetMapService>());

OpenMeteo Examples

Get Current Weather
var currentWeather = await openMeteoService.GetCurrentWeatherAsync("40.7128", "-74.0060", cancellationToken: CancellationToken.None);

if (currentWeather?.Current != null)
{
    Console.WriteLine($"Temperature: {currentWeather.Current.Temperature}ยฐC");
    Console.WriteLine($"Humidity: {currentWeather.Current.RelativeHumidity}%");
    Console.WriteLine($"Wind Speed: {currentWeather.Current.WindSpeed} km/h");
}
Get Current Air Quality
var airQuality = await openMeteoService.GetCurrentAirQualityAsync("40.7128", "-74.0060");

if (airQuality?.Current != null)
{
    Console.WriteLine($"US AQI: {airQuality.Current.UsAqi}");
    Console.WriteLine($"PM2.5: {airQuality.Current.Pm2_5}");
    Console.WriteLine($"PM10: {airQuality.Current.Pm10}");
}
Get Hourly Forecast
var hourlyForecast = await openMeteoService.GetHourlyForecastAsync("40.7128", "-74.0060", 2, 0, cancellationToken: CancellationToken.None);

if (hourlyForecast?.Hourly != null)
{
    for (int i = 0; i < hourlyForecast.Hourly.Time.Length; i++)
    {
        Console.WriteLine($"{hourlyForecast.Hourly.Time[i]}: {hourlyForecast.Hourly.Temperature2m[i]}ยฐC");
    }
}
Get Daily Forecast
// Get 7-day forecast
var dailyForecast = await openMeteoService.GetDailyForecastAsync("40.7128", "-74.0060", 7, 0, cancellationToken: CancellationToken.None);

if (dailyForecast?.Daily != null)
{
    for (int i = 0; i < dailyForecast.Daily.Time.Length; i++)
    {
        Console.WriteLine($"{dailyForecast.Daily.Time[i]}:");
        Console.WriteLine($"  High: {dailyForecast.Daily.Temperature2mMax[i]}ยฐC");
        Console.WriteLine($"  Low: {dailyForecast.Daily.Temperature2mMin[i]}ยฐC");
        Console.WriteLine($"  Precipitation: {dailyForecast.Daily.PrecipitationSum[i]}mm");
    }
}
Get Yesterday's Hourly Weather
var yesterday = DateTime.UtcNow.AddDays(-1);
var historicalWeather = await openMeteoService.GetYesterdayHourlyForecastAsync(
    "40.7128", 
    "-74.0060", 
    yesterday.ToString("yyyy-MM-dd"));

if (historicalWeather?.Hourly != null)
{
    Console.WriteLine($"Yesterday's temperatures:");
    for (int i = 0; i < historicalWeather.Hourly.Time.Length; i++)
    {
        Console.WriteLine($"{historicalWeather.Hourly.Time[i]}: {historicalWeather.Hourly.Temperature2m[i]}ยฐC");
    }
}
Get Yesterday's Daily Weather
var yesterday = DateTime.UtcNow.AddDays(-1);
var historicalDaily = await openMeteoService.GetYesterdayDailyForecastAsync(
    "40.7128", 
    "-74.0060", 
    yesterday.ToString("yyyy-MM-dd"),
    yesterday.ToString("yyyy-MM-dd"));

if (historicalDaily?.Daily != null)
{
    Console.WriteLine($"Yesterday's daily summary:");
    Console.WriteLine($"High: {historicalDaily.Daily.Temperature2mMax?[0]}ยฐC");
    Console.WriteLine($"Low: {historicalDaily.Daily.Temperature2mMin?[0]}ยฐC");
    Console.WriteLine($"Precipitation: {historicalDaily.Daily.PrecipitationSum?[0]}mm");
}

Geocodio Examples

Setup with API Key
var geocodioService = new GeocodioService(
    httpClient, 
    "YOUR_GEOCODIO_API_KEY", 
    loggerFactory.CreateLogger<GeocodioService>());
Test API Key
var isValid = await geocodioService.TestApiKey();
Console.WriteLine($"API Key is {(isValid ? "valid" : "invalid")}");
Geocode an Address
var locations = await geocodioService.GetLocationsAsync(
    "1600 Pennsylvania Avenue NW, Washington, DC", 
    "US");

if (locations?.Results != null)
{
    foreach (var result in locations.Results)
    {
        Console.WriteLine($"Location: {result.FormattedAddress}");
        Console.WriteLine($"Coordinates: {result.Location.Latitude}, {result.Location.Longitude}");
        Console.WriteLine($"Accuracy: {result.Accuracy}");
    }
}

IpGeo Examples

Setup with API Key
var ipGeoService = new IpGeoService(
    httpClient, 
    "YOUR_IPGEO_API_KEY", 
    loggerFactory.CreateLogger<IpGeoService>());
Test API Key
var isValid = await ipGeoService.TestApiKey();
Console.WriteLine($"API Key is {(isValid ? "valid" : "invalid")}");
Get Astronomical Data
var sunMoonData = await ipGeoService.GetSunMoonDataAsync("40.7128", "-74.0060");

if (sunMoonData != null)
{
    Console.WriteLine($"Sunrise: {sunMoonData.Astronomy?.Sunrise}");
    Console.WriteLine($"Sunset: {sunMoonData.Astronomy?.Sunset}");
    Console.WriteLine($"Moonrise: {sunMoonData.Astronomy?.Moonrise}");
    Console.WriteLine($"Moonset: {sunMoonData.Astronomy?.Moonset}");
    Console.WriteLine($"Moon Phase: {sunMoonData.Astronomy?.MoonPhase}");
}

Atmospore Examples

Setup with API Key
var atmosporeService = new AtmosporeService(
    httpClient,
    "YOUR_ATMOSPORE_API_KEY",
    loggerFactory.CreateLogger<AtmosporeService>());
Test API Key
var isValid = await atmosporeService.TestApiKey();
Console.WriteLine($"API Key is {(isValid ? "valid" : "invalid")}");
Get Pollen Forecast for Today
var pollenForecast = await atmosporeService.GetPollenForecastAsync(
    "39.4300996", 
    "-77.804161", 
    null, // null = today's date
    1);   // 1 day forecast

if (pollenForecast?.Data != null && pollenForecast.Data.Count > 0)
{
    var daily = pollenForecast.Data[0];
    Console.WriteLine($"Date: {daily.Date}");
    Console.WriteLine($"Overall Risk: {daily.OverallRisk}");

    if (daily.Species != null)
    {
        foreach (var species in daily.Species)
        {
            Console.WriteLine($"{species.DisplayName}: {species.RiskLevel} (Value: {species.Value})");
        }
    }
}
Get Multi-Day Pollen Forecast
var multiDayForecast = await atmosporeService.GetPollenForecastAsync(
    "39.4300996", 
    "-77.804161", 
    "2026-05-27", // specific date
    3);           // 3 days

if (multiDayForecast?.Data != null)
{
    Console.WriteLine($"Location: {multiDayForecast.Meta?.Location?.Lat}, {multiDayForecast.Meta?.Location?.Lon}");

    foreach (var day in multiDayForecast.Data)
    {
        Console.WriteLine($"\n{day.Date}: Overall Risk = {day.OverallRisk}");
    }
}

WeatherAlert Examples

Setup
var weatherAlertService = new WeatherAlertService(
    httpClient,
    loggerFactory.CreateLogger<WeatherAlertService>());
Get Meteoalarm Alerts (European)
var meteoalarmAlerts = await weatherAlertService.GetMeteoalarmAlertsAsync(
    "france");

if (meteoalarmAlerts?.Warnings != null)
{
    Console.WriteLine($"Found {meteoalarmAlerts.Warnings.Count} Meteoalarm alerts");
}
Get NWS Alerts (US)
var nwsAlerts = await weatherAlertService.GetNwsAlertsAsync(
    "40.7128",  // NYC latitude
    "-74.0060"); // NYC longitude

if (nwsAlerts?.Features != null)
{
    foreach (var feature in nwsAlerts.Features)
    {
        Console.WriteLine($"Event: {feature.Properties?.Event}");
        Console.WriteLine($"Severity: {feature.Properties?.Severity}");
    }
}
Get Environment Canada Alerts
var canadaAlerts = await weatherAlertService.GetEnvironmentCanadaAlertsAsync();

if (canadaAlerts?.Features != null)
{
    foreach (var feature in canadaAlerts.Features)
    {
        Console.WriteLine($"Title: {feature.Properties?.AlertNameEn}");
        Console.WriteLine($"Province: {feature.Properties?.ProvinceCode}");
    }
}
Get BOM Australia Alerts
var bomAlerts = await weatherAlertService.GetBomAlertsAsync();

if (bomAlerts?.Data != null)
{
    foreach (var warning in bomAlerts.Data.Where(w => w.State == "NSW"))
    {
        Console.WriteLine($"Warning: {warning.Title}");
    }
}

Weather Observation Examples

Setup
using Xcalibur.Weather.Services.Observation;

var metarService = new MetarObservationService(httpClient, loggerFactory.CreateLogger<MetarObservationService>());
var weatherObservationService = new WeatherObservationService(
    loggerFactory.CreateLogger<WeatherObservationService>(),
    new NwsObservationService(httpClient, loggerFactory.CreateLogger<NwsObservationService>()),
    new EcccObservationService(httpClient, loggerFactory.CreateLogger<EcccObservationService>()),
    new MetOfficeObservationService(httpClient, loggerFactory.CreateLogger<MetOfficeObservationService>(), metarService),
    new DwdObservationService(httpClient, loggerFactory.CreateLogger<DwdObservationService>(), metarService),
    new MeteoFranceObservationService(httpClient, loggerFactory.CreateLogger<MeteoFranceObservationService>(), metarService),
    new JmaObservationService(httpClient, loggerFactory.CreateLogger<JmaObservationService>(), metarService),
    new BomObservationService(httpClient, loggerFactory.CreateLogger<BomObservationService>(), metarService),
    metarService);
Get Nearest Observation
var observation = await weatherObservationService.GetObservationAsync(39.4300996, -77.804161);

if (observation != null)
{
    Console.WriteLine($"Station: {observation.StationId} - {observation.StationName}");
    Console.WriteLine($"Region: {observation.Region}");
    Console.WriteLine($"Source: {observation.Source}");
    Console.WriteLine($"Temperature: {observation.TemperatureCelsius}ยฐC");
}
Get Earthquake Alerts (EMSC)
var earthquakeAlerts = await weatherAlertService.GetEmscAlertsAsync(
    "35.6762",  // Tokyo latitude
    "139.6503", // Tokyo longitude
    500);       // 500 km radius

if (earthquakeAlerts?.Features != null)
{
    foreach (var earthquake in earthquakeAlerts.Features)
    {
        Console.WriteLine($"Magnitude: {earthquake.Properties?.Mag}");
        Console.WriteLine($"Location: {earthquake.Properties?.Flynn_region}");
        Console.WriteLine($"Time: {earthquake.Properties?.Time}");
    }
}

SunriseSunset Examples

Setup
var sunriseSunsetService = new SunriseSunsetService(
    httpClient,
    loggerFactory.CreateLogger<SunriseSunsetService>());
Get Sunrise and Sunset Data
var astronomy = await sunriseSunsetService.GetSunriseSunsetAsync("40.7128", "-74.0060");

if (astronomy?.Results != null)
{
    Console.WriteLine($"Sunrise: {astronomy.Results.Sunrise}");
    Console.WriteLine($"Sunset: {astronomy.Results.Sunset}");
    Console.WriteLine($"Day Length: {astronomy.Results.DayLength}");
    Console.WriteLine($"Solar Noon: {astronomy.Results.SolarNoon}");
}

OpenStreetMap Examples

Setup
var openStreetMapService = new OpenStreetMapService(
    httpClient,
    loggerFactory.CreateLogger<OpenStreetMapService>());
Search for Locations
var locations = await openStreetMapService.GetLocationsAsync(
    "1600 Pennsylvania Avenue NW, Washington, DC",
    "en-US",
    "us");

if (locations != null)
{
    foreach (var location in locations)
    {
        Console.WriteLine($"Display Name: {location.DisplayName}");
        Console.WriteLine($"Coordinates: {location.Lat}, {location.Lon}");
    }
}

๐ŸŒ API Endpoints

Open-Meteo

  • Current Weather: https://api.open-meteo.com/v1/forecast
  • Air Quality: https://air-quality-api.open-meteo.com/v1/air-quality
  • Historical: https://archive-api.open-meteo.com/v1/archive
  • API Key: Not required (free and open)
  • Documentation: Open-Meteo API Docs

Geocodio

IpGeolocation.io

Atmospore API

  • Base URL: https://pollenapi.com/v1/pollen
  • API Key: Required (via x-api-key header)
  • Sign Up: Atmospore
  • Documentation: Atmospore API Docs

Weather Alert Sources

  • Meteoalarm: https://feeds.meteoalarm.org/api/v1/warnings/feeds-{countryName} (European country feed alerts)
  • NWS: https://api.weather.gov/alerts/active (US National Weather Service)
  • GDACS: https://www.gdacs.org/gdacsapi/api/events/geteventlist/MAP (Global disasters)
  • Environment Canada: https://geo.weather.gc.ca/geomet?service=WFS&version=2.0.0&request=GetFeature&typeNames=Current-Alerts&outputFormat=application/json (Canadian GeoMet alerts)
  • BOM: https://api.weather.bom.gov.au/v1/warnings (Australian Bureau of Meteorology national warnings)
  • EMSC: https://www.seismicportal.eu/fdsnws/event/1/query (European earthquake alerts)
  • DWD: https://www.dwd.de/DWD/warnungen/warnapp/json/warnings.json (German weather service)
  • API Key: Not required
  • Note: NWS requires a User-Agent header (automatically added by the service); Meteoalarm now uses country feed names, while Environment Canada and BOM return national datasets that can be filtered client-side

SunriseSunset.io

OpenStreetMap Nominatim

  • Base URL: https://nominatim.openstreetmap.org/search
  • API Key: Not required
  • Requirement: A descriptive User-Agent header is required
  • Documentation: Nominatim Search API

๐Ÿ“š Dependencies

  • .NET 10.0: Target framework
  • Microsoft.Extensions.Hosting (v10.0.11): For hosting, logging, and dependency injection abstractions
  • Xcalibur.Weather.Models (v1.1.5): Shared models and DTOs for weather data

๐Ÿงช Testing

The project includes comprehensive unit tests in the Xcalibur.Weather.Services.Tests project.

Current test status: 158 passing tests across the solution, including full deserialization, guard-clause, and constructor null-check coverage for WeatherAlertService and updated regression coverage for endpoint migrations and observation-service behavior.

Running Tests

dotnet test

All tests use:

  • xUnit as the testing framework
  • FluentAssertions for readable assertions
  • DelegatingHandlerStub from Xcalibur.Weather.Models.Testing for HTTP mocking
  • NullLogger for lightweight test logging

Test Statistics

  • Total Tests: 158
  • Status: โœ… All passing
  • Coverage: Comprehensive unit tests for all services

Current Test Coverage

OpenMeteoServiceTests

  • Current weather deserialization and validation
  • Current and hourly air quality data retrieval (US and EU AQI metrics)
  • Hourly air quality forecasts with configurable time ranges
  • Hourly and daily forecast operations
  • Historical weather data (yesterday)
  • Model selection for different geographic regions
  • Error handling for non-success HTTP responses
  • Invalid JSON response handling

GeocodioServiceTests (5 tests)

  • API key validation (valid, invalid, forbidden)
  • Address-to-coordinate geocoding
  • Error handling for bad requests
  • Invalid JSON response handling

IpGeoServiceTests (5 tests)

  • API key validation (valid, unauthorized)
  • Astronomical data retrieval (sun/moon)
  • Error handling for non-success responses
  • Invalid JSON response handling

AtmosporeServiceTests (9 tests)

  • API key validation (OK, unauthorized, forbidden)
  • Pollen forecast data deserialization
  • URL generation with explicit and null dates
  • API key header verification
  • Request parameter validation
  • Error handling for bad requests
  • Invalid JSON response handling

WeatherAlertServiceTests (33 tests)

  • URL construction for all seven providers (Meteoalarm, NWS, GDACS, Environment Canada, BOM, EMSC, DWD)
  • Response deserialization for all seven providers with realistic JSON payloads
  • Guard-clause validation: unknown Meteoalarm country returns null without making an HTTP request
  • [Theory] coverage for null, empty, and whitespace latitude/longitude on NWS
  • Constructor ArgumentNullException for null HttpClient and null ILogger
  • Non-success HTTP status handling for all seven providers
  • User-Agent header preservation

ObservationServicesTests

  • Region resolution by latitude/longitude
  • METAR nearest observation selection, widening search behavior, and numeric visib regression coverage
  • Direct NWS observation deserialization and NWS-backed nearest observation routing
  • METAR non-success, empty-result, and null-visibility handling
  • NWS points, stations, and observation endpoint failure handling
  • Regional fallback to METAR when provider requests fail
  • Station-local timestamp normalization

SunriseSunsetServiceTests

  • Sunrise and sunset data deserialization
  • Error handling for non-success responses
  • Invalid JSON response handling

OpenStreetMapServiceTests

  • Location search and geocoding
  • User-Agent header management (default, preservation)
  • Error handling for bad requests
  • Invalid JSON response handling

ErrorHandlingTests

  • Comprehensive HTTP exception handling across all services
  • Network error resilience validation
  • Timeout and cancellation behavior
  • Service-level error recovery verification

๐Ÿ—๏ธ Project Structure

Xcalibur.Weather.Services/
โ”œโ”€โ”€ AtmosporeService.cs
โ”œโ”€โ”€ GeocodioService.cs
โ”œโ”€โ”€ IpGeoService.cs
โ”œโ”€โ”€ Observation/
โ”‚   โ”œโ”€โ”€ BomObservationService.cs
โ”‚   โ”œโ”€โ”€ DwdObservationService.cs
โ”‚   โ”œโ”€โ”€ EcccObservationService.cs
โ”‚   โ”œโ”€โ”€ JmaObservationService.cs
โ”‚   โ”œโ”€โ”€ MetarObservationService.cs
โ”‚   โ”œโ”€โ”€ MeteoFranceObservationService.cs
โ”‚   โ”œโ”€โ”€ MetOfficeObservationService.cs
โ”‚   โ”œโ”€โ”€ NwsObservationService.cs
โ”‚   โ”œโ”€โ”€ ObservationHelper.cs
โ”‚   โ”œโ”€โ”€ ObservationRegionResolver.cs
โ”‚   โ”œโ”€โ”€ ObservationServiceBase.cs
โ”‚   โ”œโ”€โ”€ WeatherObservationService.cs
โ”‚   โ””โ”€โ”€ ARCHITECTURE.md
โ”œโ”€โ”€ OpenMeteoService.cs
โ”œโ”€โ”€ OpenStreetMapService.cs
โ”œโ”€โ”€ SunriseSunsetService.cs
โ”œโ”€โ”€ WeatherAlertService.cs
โ””โ”€โ”€ Xcalibur.Weather.Services.csproj

Xcalibur.Weather.Services.Tests/
โ”œโ”€โ”€ AtmosporeServiceTests.cs
โ”œโ”€โ”€ ErrorHandlingTests.cs
โ”œโ”€โ”€ GeocodioServiceTests.cs
โ”œโ”€โ”€ IpGeoServiceTests.cs
โ”œโ”€โ”€ OpenMeteoServiceTests.cs
โ”œโ”€โ”€ ObservationServicesTests.cs
โ”œโ”€โ”€ OpenStreetMapServiceTests.cs
โ”œโ”€โ”€ SunriseSunsetServiceTests.cs
โ”œโ”€โ”€ WeatherAlertServiceTests.cs
โ””โ”€โ”€ Xcalibur.Weather.Services.Tests.csproj

๐Ÿ”ง Advanced Configuration

Custom HttpClient Configuration

var httpClient = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(30)
};

httpClient.DefaultRequestHeaders.Add("User-Agent", "YourApp/1.0");

var service = new OpenMeteoService(httpClient, logger);

Cancellation Token Support

All async methods support cancellation tokens for graceful shutdown:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

try
{
    var weather = await openMeteoService.GetCurrentWeatherAsync(
        "40.7128", 
        "-74.0060", 
        cancellationToken: cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Request was cancelled");
}

๐Ÿ“ Best Practices

  1. Reuse HttpClient: Create a single HttpClient instance and reuse it across service instances
  2. Use Dependency Injection: Register services in your DI container for better testability
  3. Handle Nulls: All service methods return nullable types; always check for null responses
  4. Monitor Logs: Enable debug logging to troubleshoot API issues
  5. Respect Provider Policies: Be mindful of rate limits and usage policies for Geocodio, IpGeo, Atmospore, SunriseSunset.io, and OpenStreetMap Nominatim
  6. Secure API Keys: Store API keys in secure configuration (Azure Key Vault, user secrets, etc.)
  7. Set a User-Agent When Needed: OpenStreetMap Nominatim and NWS require a meaningful User-Agent; services set defaults if one is not already present
  8. Route Observation Queries by Region: Use WeatherObservationService to select the best regional observation provider from coordinates and fall back to METAR when necessary

๐Ÿ” API Key Management

User Secrets (Development)

dotnet user-secrets init
dotnet user-secrets set "Geocodio:ApiKey" "YOUR_API_KEY"
dotnet user-secrets set "IpGeo:ApiKey" "YOUR_API_KEY"
dotnet user-secrets set "Atmospore:ApiKey" "YOUR_API_KEY"

appsettings.json

{
  "Geocodio": {
    "ApiKey": "YOUR_GEOCODIO_API_KEY"
  },
  "IpGeo": {
    "ApiKey": "YOUR_IPGEO_API_KEY"
  },
  "Atmospore": {
    "ApiKey": "YOUR_ATMOSPORE_API_KEY"
  }
}

Environment Variables

set GEOCODIO_API_KEY=your_key_here
set IPGEO_API_KEY=your_key_here
set ATMOSPORE_API_KEY=your_key_here

๐Ÿ“œ Version History

v1.1.6 (Current)

  • Added deserialization tests for all seven alert providers (NWS, GDACS, EMSC, DWD, Meteoalarm, Environment Canada, BOM)
  • Added guard-clause test: unknown Meteoalarm country returns null immediately without making an HTTP request
  • Added [Theory] test covering null, empty, and whitespace latitude/longitude for GetNwsAlertsAsync
  • Added constructor ArgumentNullException tests for null HttpClient and null ILogger

v1.1.5

  • Migrated WeatherAlertService Meteoalarm integration from the retired coordinate-based API to the current country-feed warnings API
  • Migrated WeatherAlertService Environment Canada integration from the retired RSS/XML feed to the GeoMet Current-Alerts GeoJSON endpoint
  • Migrated WeatherAlertService BOM integration from the retired FWO warnings endpoint to the BOM v1 national warnings API
  • Updated weather alert models and combined alert mapping for Meteoalarm CAP responses, Environment Canada GeoJSON features, and BOM data payloads
  • Expanded WeatherAlertService regression coverage for updated URLs, payload deserialization, and non-success handling

v1.1.4

  • Hardened NWS observation deserialization with explicit source-generated JSON metadata coverage for Native AOT compatibility
  • Hardened METAR observation deserialization with source-generated JSON metadata and support for numeric, string, and null visibility payloads
  • Optimized MetarObservationService by replacing magic numbers with named constants and correcting widening-search response handling
  • Expanded observation regression coverage for METAR non-success, empty-result, and null-visibility scenarios
  • Expanded NWS regression coverage for points, stations, and latest-observation failure paths
  • Updated package metadata and documentation to current versions
  • Added and updated tests with 121 passing tests across the solution

v1.1.3

  • Added WeatherObservationService for centralized nearest-observation routing by latitude/longitude
  • Added regional observation providers for NWS, ECCC, DWD, JMA, and METAR-based global fallback support
  • Added observation fallback patterns for UK, France, and Australia through METAR-backed regional services
  • Expanded OpenMeteo support for historic forecast retrieval and hourly air quality scenarios
  • Updated package dependencies and documentation to current versions
  • Added and updated tests with 115 passing tests across the solution

v1.0.24

  • OpenMeteo functions allow for historic data retrieval by specifying forecastDays and pastDays parameters
  • Enhanced flexibility for weather forecast queries with historic data support
  • Improved consistency across OpenMeteo API methods

v1.0.22

  • GetHourlyAirQualityAsync now supports historic data retrieval
  • Added support for specifying forecastDays and pastDays parameters
  • Improved hourly air quality query flexibility

v1.0.21

  • AQI enhancements to account for US and EU metrics
  • Added GetHourlyAirQualityAsync method for hourly air quality forecasts
  • Enhanced air quality monitoring with comprehensive US and European AQI values
  • Added support for hourly pollutant concentrations (PM2.5, PM10, O3, NO2, SO2, CO)
  • Improved test coverage with 101 passing tests

v1.0.19

  • Added more OpenMeteo functionality
  • Bug fixes and improvements

v1.0.18

  • Several improvements and bug fixes
  • Updated dependencies to latest stable versions

v1.0.17

  • Several improvements and bug fixes
  • Updated dependencies to latest stable versions

v1.0.16

  • Several improvements and bug fixes
  • Updated dependencies to latest stable versions

v1.0.12

  • Performance improvements and property descriptions for Weather Alerts
  • Enhanced documentation and XML comments
  • Updated dependencies to latest stable versions

v1.0.10

  • Additional property descriptions and documentation improvements
  • Bug fixes and stability enhancements

v1.0.9

  • Minor bug fixes and refinements
  • Improved error handling across services

v1.0.8

  • Internal improvements and updates
  • Dependency version updates

v1.0.7

  • Added multi-source weather alert aggregation support
  • Enhanced alert service provider coverage

v1.0.6

  • Major Refactoring: Services moved to flat namespace structure (Xcalibur.Weather.Services)
  • Added AtmosporeService: Pollen forecast data from Atmospore API (replaced Google Pollen)
  • Added WeatherAlertService: Multi-provider weather alerts
    • Meteoalarm (European severe weather)
    • NWS (US National Weather Service)
    • GDACS (Global disasters)
    • Environment Canada (Canadian alerts)
    • BOM (Australian Bureau of Meteorology)
    • EMSC (European earthquake data)
    • DWD (German Weather Service)
  • Updated to use Xcalibur.Weather.Models v1.0.6 with refactored structure

v1.0.5

  • Added support for Google Weather Alerts API
  • Introduced weather alert retrieval capabilities
  • Updated model dependencies

v1.0.4

  • Added pollen forecast support (Google Pollen API)
  • Enhanced geocoding functionality
  • Improved error handling

v1.0.3

  • Added SunriseSunsetService for astronomy data
  • Added OpenStreetMapService for geocoding
  • Expanded API provider coverage

v1.0.2

  • Improved logging and error handling
  • Performance optimizations
  • Documentation updates

v1.0.1

  • Initial bug fixes
  • Improved API key validation

v1.0.0

  • Initial release
  • OpenMeteoService for weather and air quality
  • GeocodioService for geocoding
  • IpGeoService for astronomy data
  • Comprehensive async/await support
  • Built-in logging and error handling

๐Ÿ“„ License

This project is licensed under the Apache License 2.0. See the LICENSE-2.0.txt file for details.

Copyright ยฉ 2006 - 2026, Xcalibur Systems, LLC - All Rights Reserved

Part of the Xcalibur Weather ecosystem for comprehensive weather data integration.


๐Ÿ‘ฅ Contributing

Contributions are welcome! Please feel free to submit issues or pull requests to improve the library.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Part of the Xcalibur Weather ecosystem for comprehensive weather data integration.

Author

Joshua Arzt
Xcalibur Systems, LLC


Note: This library requires API keys for Geocodio, IpGeolocation.io, and Atmospore services. Open-Meteo, SunriseSunset.io, OpenStreetMap Nominatim, and all Weather Alert sources (Meteoalarm, NWS, GDACS, Environment Canada, BOM, EMSC, DWD) do not require API keys.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Xcalibur.Weather.Services:

Package Downloads
Xcalibur.Weather.Helpers

A comprehensive .NET helper library providing utility functions for weather-related operations. Includes conversion helpers for temperature, wind speed, length, and pressure, along with specialized helpers for Open-Meteo, Geocodio, and IpGeolocation.io weather data processing and transformation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.6 70 8/16/2026
1.1.5 56 8/16/2026
1.1.4 66 8/15/2026
1.1.3 56 8/15/2026
1.1.0 77 8/13/2026
1.0.24 103 8/7/2026
1.0.23 93 8/5/2026
1.0.22 101 8/5/2026
1.0.21 112 8/4/2026
1.0.20 101 8/4/2026
1.0.19 116 8/3/2026
1.0.18 108 7/31/2026
1.0.17 109 7/31/2026
1.0.16 118 7/28/2026
1.0.15 112 7/28/2026
1.0.13 173 7/7/2026
1.0.12 127 7/6/2026
1.0.11 150 7/3/2026
1.0.8 145 7/2/2026
1.0.7 164 6/18/2026
Loading failed

Added deserialization tests for all seven alert providers (NWS, GDACS, EMSC, DWD, Meteoalarm, Environment Canada, BOM)