the_paragliding_app

Weather Station APIs

Overview

The Paragliding App integrates with multiple weather station providers to display real-time wind data on the map. Each provider has different API capabilities, coverage areas, and data retrieval strategies.

Provider Comparison

Provider Coverage API Key bbox Support Strategy Update Frequency
Aviation Weather Center Global airports No ✅ Yes Direct bbox query Varies (1-60min)
Bureau of Meteorology (BOM) Australia No ❌ No State-based fetch 10 minutes
FFVL Beacons France/Europe Yes ❌ No Global fetch-all 5 minutes
Pioupiou (OpenWindMap) Global No ❌ No Global fetch-all 20 minutes
NWS Observations US only No ❌ No Grid-based lookup 10 minutes

Bounding Box Support

✅ Providers with Direct bbox Support

Aviation Weather Center (METAR)

Implementation: aviation_weather_center_provider.dart:159

final url = Uri.parse(
  'https://aviationweather.gov/api/data/metar?bbox=$bbox&format=json',
);

bbox Format: minLat,minLon,maxLat,maxLon

Benefits:

Example Request:

https://aviationweather.gov/api/data/metar?bbox=45.50,-73.60,45.60,-73.50&format=json

❌ Providers Without bbox Support

These providers use alternative strategies with client-side filtering.

1. Bureau of Meteorology (BOM) - State-Based Fetching

Implementation: bom_weather_provider.dart:66

Strategy:

Code Reference:

// Determine overlapping states
final overlappingStates = _determineOverlappingStates(bounds);

// Fetch each state in parallel
final futures = overlappingStates.map((state) => _fetchStateStations(state, bounds));
final results = await Future.wait(futures);

// In-memory filtering (line 488-504)
final filtered = stations.where((station) {
  return bounds.contains(LatLng(station.latitude, station.longitude));
}).toList();

Coverage Areas:

Caching Strategy:

Performance:

2. FFVL Beacons - Global Fetch-All

Implementation: ffvl_weather_provider.dart:208

Strategy:

Code Reference:

// Fetch all beacons
final beaconListUrl = Uri.parse(
  '$_baseUrl?base=balises&r=list&mode=json&key=$apiKey',
);

// Calculate bbox from stations (line 706-729)
final cachedBounds = _calculateBoundsFromStations(stations);

// Early exit optimization (line 66-76)
if (_globalCache != null && !_globalCache!.beaconListExpired) {
  if (!_boundsOverlap(bounds, _globalCache!.bounds)) {
    return []; // Skip this provider - no overlap
  }
}

// In-memory filtering (line 688-704)
final filtered = stations.where((station) {
  return bounds.contains(LatLng(station.latitude, station.longitude));
}).toList();

API Endpoints:

Caching Strategy:

Performance:

Special Features:

3. Pioupiou (OpenWindMap) - Global Fetch-All

Implementation: pioupiou_weather_provider.dart:199

Strategy:

Code Reference:

// Fetch all stations
final url = Uri.parse('$_baseUrl/live-with-meta/all');

// Calculate bbox from stations (line 419-442)
final cachedBounds = _calculateBoundsFromStations(stations);

// Early exit optimization (line 66-76)
if (_globalCache != null && !_globalCache!.stationListExpired) {
  if (!_boundsOverlap(bounds, _globalCache!.bounds)) {
    return []; // Skip this provider - no overlap
  }
}

// In-memory filtering (line 401-416)
final filtered = stations.where((station) {
  return bounds.contains(LatLng(station.latitude, station.longitude));
}).toList();

API Endpoint:

Caching Strategy:

Performance:

Special Features:

4. NWS (National Weather Service) - Grid-Based Lookup

Implementation: nws_weather_provider.dart:297

Strategy:

Code Reference:

// Calculate bbox center for grid lookup (line 218-220)
final centerLat = (requestedBounds.north + requestedBounds.south) / 2;
final centerLon = (requestedBounds.east + requestedBounds.west) / 2;

// Step 1: Get grid stations URL
final gridUrl = await _getGridStationsUrl(centerLat, centerLon);
// Returns: https://api.weather.gov/gridpoints/{office}/{x},{y}/stations

// Step 2: Fetch stations for grid
final allStations = await _fetchGridStations(gridUrl);

// Calculate containing bbox (line 646-665)
final containingBbox = _calculateContainingBbox(allStations);

// In-memory filtering (line 263-266)
final filtered = allStations.where((station) {
  return requestedBounds.contains(LatLng(station.latitude, station.longitude));
}).toList();

Coverage Areas:

// Continental US
'CONUS': [-125.0, 24.5, -66.9, 49.6]

// Alaska (split to handle International Date Line)
'Alaska_Main': [-180.0, 51.2, -130.0, 71.4]
'Alaska_West_Aleutians': [172.0, 51.2, 180.0, 71.4]

// Hawaii
'Hawaii': [-178.3, 18.9, -154.8, 28.4]

// Caribbean territories
'Puerto_Rico': [-67.9, 17.9, -65.2, 18.5]
'US_Virgin_Islands': [-65.1, 17.7, -64.6, 18.4]

// Pacific territories
'Guam', 'Northern_Mariana_Islands', 'American_Samoa'

Caching Strategy:

Performance:

Special Features:

Caching Architecture

Dual-Timestamp Caching

Providers use separate TTLs for station lists vs. measurements:

class _CacheEntry {
  final List<WeatherStation> stations;
  final DateTime stationListTimestamp;    // 24-hour TTL
  final DateTime measurementsTimestamp;   // 5-20 minute TTL

  bool get stationListExpired { /* ... */ }
  bool get measurementsExpired { /* ... */ }
}

Benefits:

Cache TTL Summary

Provider Station List TTL Measurements TTL
Aviation Weather Center 1 hour 1 hour
BOM 24 hours 10 minutes
FFVL 24 hours 5 minutes
Pioupiou 24 hours 20 minutes
NWS 24 hours 10 minutes

Performance Optimizations

1. Early Exit for Non-Overlapping Regions

Global providers (FFVL, Pioupiou) calculate bbox from cached stations and exit early if view doesn’t overlap:

// Check if cached bbox overlaps with requested bounds
if (_globalCache != null && !_globalCache!.beaconListExpired) {
  if (!_boundsOverlap(bounds, _globalCache!.bounds)) {
    return []; // Skip this provider - no overlap with view
  }
}

2. Skip Measurement Refresh if No Stations in View

// Check if ANY stations exist in current view
final hasStationsInView = _globalCache!.stations.any((s) =>
  bounds.contains(LatLng(s.latitude, s.longitude))
);

if (!hasStationsInView) {
  return []; // Skip API call - no stations to show
}

3. Parallel Fetching

BOM fetches multiple states in parallel when they overlap with view:

final futures = overlappingStates.map((state) =>
  _fetchStateStations(state, bounds)
);
final results = await Future.wait(futures);

4. Request Deduplication

All providers prevent duplicate simultaneous requests:

if (_pendingRequests.containsKey(cacheKey)) {
  return _pendingRequests[cacheKey]!;
}

final future = _fetchFromApi();
_pendingRequests[cacheKey] = future;
try {
  return await future;
} finally {
  _pendingRequests.remove(cacheKey);
}

Wind Data Format

All providers normalize wind data to a common format:

class WindData {
  final double speedKmh;           // Wind speed in km/h
  final double directionDegrees;   // 0-360, where 0 = North
  final double? gustsKmh;          // Peak gusts in km/h (optional)
  final double? precipitationMm;   // Rainfall (BOM only)
  final DateTime timestamp;        // Observation time (UTC)
}

Unit Conversions

Provider Native Unit Conversion
Aviation Weather Center Knots × 1.852 → km/h
BOM km/h No conversion
FFVL km/h No conversion
Pioupiou km/h No conversion
NWS km/h No conversion

API Authentication

Provider Requires API Key Configuration
Aviation Weather Center No N/A
BOM No N/A
FFVL Yes ApiKeys.ffvlApiKey
Pioupiou No N/A
NWS No N/A

Error Handling

Non-Coverage Areas

Providers handle requests outside their coverage area gracefully:

Aviation Weather Center: Returns 204 No Content (empty list) BOM: Returns empty list if no states overlap FFVL: Early exit if cached bbox doesn’t overlap Pioupiou: Early exit if cached bbox doesn’t overlap NWS: Returns 404 from /points endpoint, cached as empty

Timeout Handling

All providers use 30-second timeouts with structured logging:

final response = await http.get(url).timeout(
  const Duration(seconds: 30),
  onTimeout: () {
    LoggingService.structured('PROVIDER_TIMEOUT', {
      'duration_ms': stopwatch.elapsedMilliseconds,
    });
    return http.Response('{"error": "Request timeout"}', 408);
  },
);

Implementation Files

Provider File Path
Aviation Weather Center lib/services/weather_providers/aviation_weather_center_provider.dart
BOM lib/services/weather_providers/bom_weather_provider.dart
FFVL lib/services/weather_providers/ffvl_weather_provider.dart
Pioupiou lib/services/weather_providers/pioupiou_weather_provider.dart
NWS lib/services/weather_providers/nws_weather_provider.dart
Base Interface lib/services/weather_providers/weather_station_provider.dart
Registry lib/services/weather_providers/weather_station_provider_registry.dart

Adding New Providers

To add a new weather station provider:

  1. Implement the interface (WeatherStationProvider)
  2. Choose a caching strategy:
    • Has bbox API? → Use Aviation Weather Center pattern
    • Regional API? → Use BOM state-based pattern
    • Small global dataset? → Use FFVL/Pioupiou fetch-all pattern
    • Grid/point lookup? → Use NWS grid-based pattern
  3. Handle coverage area (return empty list outside coverage)
  4. Normalize wind data (convert to km/h if needed)
  5. Add to registry (WeatherStationProviderRegistry)
  6. Test caching (station list + measurements)
  7. Verify performance (logging with structured data)

Best Practices

Caching

API Calls

Performance

Error Handling