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
<PackageReference Include="Xcalibur.Weather.Services" Version="1.1.6" />
<PackageVersion Include="Xcalibur.Weather.Services" Version="1.1.6" />
<PackageReference Include="Xcalibur.Weather.Services" />
paket add Xcalibur.Weather.Services --version 1.1.6
#r "nuget: Xcalibur.Weather.Services, 1.1.6"
#:package Xcalibur.Weather.Services@1.1.6
#addin nuget:?package=Xcalibur.Weather.Services&version=1.1.6
#tool nuget:?package=Xcalibur.Weather.Services&version=1.1.6
Xcalibur.Weather.Services
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
WeatherAlertServicetest 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
AtmosporeServicefor pollen forecast data from Atmospore API - Added
WeatherAlertServicefor multi-provider weather alerts (Meteoalarm, NWS, GDACS, Environment Canada, BOM Australia, EMSC, DWD) - Added
WeatherObservationServiceorchestration 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
visibpayloads - 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
- Purpose
- Latest Updates
- Features
- Technology
- Use Cases
- Installation
- Services
- Usage
- API Endpoints
- Dependencies
- Testing
- API Key Management
- Best Practices
- Advanced Configuration
- Project Structure
- Version History
- License
- Related Projects
- Contributing
โจ 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
forecastDaysandpastDays - 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-Alertslayer - 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
WeatherObservationresponse 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-Agentsupport 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
- Base URL:
https://api.geocod.io/v1.9/geocode - API Key: Required
- Sign Up: Geocodio
- Documentation: Geocodio API Docs
IpGeolocation.io
- Base URL:
https://api.ipgeolocation.io/v2/astronomy - API Key: Required
- Sign Up: IpGeolocation.io
- Documentation: IpGeo API Docs
Atmospore API
- Base URL:
https://pollenapi.com/v1/pollen - API Key: Required (via
x-api-keyheader) - 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
- Base URL:
https://api.sunrisesunset.io/json - API Key: Not required
- Documentation: SunriseSunset.io Docs
OpenStreetMap Nominatim
- Base URL:
https://nominatim.openstreetmap.org/search - API Key: Not required
- Requirement: A descriptive
User-Agentheader 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.Testingfor 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
nullwithout making an HTTP request [Theory]coverage for null, empty, and whitespace latitude/longitude on NWS- Constructor
ArgumentNullExceptionfor nullHttpClientand nullILogger - 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
visibregression 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
- Reuse HttpClient: Create a single
HttpClientinstance and reuse it across service instances - Use Dependency Injection: Register services in your DI container for better testability
- Handle Nulls: All service methods return nullable types; always check for null responses
- Monitor Logs: Enable debug logging to troubleshoot API issues
- Respect Provider Policies: Be mindful of rate limits and usage policies for Geocodio, IpGeo, Atmospore, SunriseSunset.io, and OpenStreetMap Nominatim
- Secure API Keys: Store API keys in secure configuration (Azure Key Vault, user secrets, etc.)
- Set a User-Agent When Needed: OpenStreetMap Nominatim and NWS require a meaningful
User-Agent; services set defaults if one is not already present - Route Observation Queries by Region: Use
WeatherObservationServiceto 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
nullimmediately without making an HTTP request - Added
[Theory]test covering null, empty, and whitespace latitude/longitude forGetNwsAlertsAsync - Added constructor
ArgumentNullExceptiontests for nullHttpClientand nullILogger
v1.1.5
- Migrated
WeatherAlertServiceMeteoalarm integration from the retired coordinate-based API to the current country-feed warnings API - Migrated
WeatherAlertServiceEnvironment Canada integration from the retired RSS/XML feed to the GeoMetCurrent-AlertsGeoJSON endpoint - Migrated
WeatherAlertServiceBOM 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
datapayloads - 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
MetarObservationServiceby 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
WeatherObservationServicefor 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
forecastDaysandpastDaysparameters - Enhanced flexibility for weather forecast queries with historic data support
- Improved consistency across OpenMeteo API methods
v1.0.22
GetHourlyAirQualityAsyncnow supports historic data retrieval- Added support for specifying
forecastDaysandpastDaysparameters - Improved hourly air quality query flexibility
v1.0.21
- AQI enhancements to account for US and EU metrics
- Added
GetHourlyAirQualityAsyncmethod 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
๐ Related Projects
- Xcalibur.Weather.Models - Core weather data models and DTOs (GitHub)
- Xcalibur.Weather.Helpers - Utility functions and conversion helpers (GitHub)
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.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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 | Versions 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. |
-
net10.0
- Microsoft.Extensions.Hosting (>= 10.0.11)
- Xcalibur.Weather.Models (>= 1.1.5)
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 |
Added deserialization tests for all seven alert providers (NWS, GDACS, EMSC, DWD, Meteoalarm, Environment Canada, BOM)