← Zurück zum Blog
Development

Open-Meteo API Integration: Multi-Model Wettervorhersage für Segelflieger

Wie man icon_d2 und best_match Modelle kombiniert, mit pickField-Funktion Daten extrahiert, und Fallback-Mechanismen implementiert

🌦️ Das Problem: Welches Wettermodell ist das Beste?

Bei der Entwicklung von EDXE Thermik-X, einer Wettervorhersage für Segelflieger, stand ich vor einer Herausforderung: Open-Meteo bietet mehrere Wettermodelle (icon_d2, best_match, icon_eu, etc.), aber nicht alle Datenfelder sind in jedem Modell verfügbar.

Zum Beispiel: wind_direction_10m ist im icon_d2 Modell oft zuverlässiger, aber manchmal ist best_match der einzige Weg, um bestimmte Parameter wie CAPE (Konvektive Energie) zu erhalten.

Die Lösung? Eine flexible pickField() Funktion, die automatisch zwischen Modellen wechselt und Fallbacks implementiert.

⚙️ Die pickField() Funktion

Das Herzstück der Datenextraktion ist diese kleine, aber mächtige Funktion:

function pickField(obj, field) {
  // Priority order: icon_d2 > best_match > naked field
  if (obj[field + '_icon_d2'] !== undefined && obj[field + '_icon_d2'] !== null) {
    return obj[field + '_icon_d2'];
  }
  if (obj[field + '_best_match'] !== undefined && obj[field + '_best_match'] !== null) {
    return obj[field + '_best_match'];
  }
  if (obj[field] !== undefined && obj[field] !== null) {
    return obj[field];
  }
  return null;
}

So funktioniert's:

  • Schritt 1: Versuche zuerst, das Feld mit _icon_d2 Suffix zu finden (z.B. wind_speed_10m_icon_d2)
  • Schritt 2: Falls nicht vorhanden, greife auf _best_match zurück
  • Schritt 3: Als letztes Fallback: Verwende das "nackte" Feld ohne Suffix
  • Schritt 4: Gib null zurück, falls nichts gefunden wurde

📡 API-Call Setup: Dual-Model Request

Der Trick ist, Open-Meteo mit models=icon_d2,best_match zu konfigurieren. Das gibt uns beide Modelle in einer einzigen API-Response:

const API_URL = `https://api.open-meteo.com/v1/forecast?
  latitude=52.27669
  &longitude=7.4925
  &hourly=temperature_2m,wind_speed_10m,wind_direction_10m,wind_gusts_10m,cape
  &models=icon_d2,best_match
  &timezone=Europe/Berlin
  &forecast_days=3`;

const res = await fetch(API_URL);
const data = await res.json();

Die Response sieht dann so aus:

{
  "hourly": {
    "time": ["2025-01-22T00:00", "2025-01-22T01:00", ...],
    "temperature_2m_icon_d2": [5.2, 4.8, ...],
    "temperature_2m_best_match": [5.1, 4.9, ...],
    "wind_speed_10m_icon_d2": [12.5, 11.8, ...],
    "wind_speed_10m_best_match": [13.2, 12.1, ...],
    "cape_best_match": [120, 150, ...]
  }
}

Beachte: cape gibt es nur in _best_match, während wind_speed_10m in beiden Modellen vorhanden ist.

🔄 Praktische Anwendung: Datenextraktion

So verwenden wir pickField() in der Praxis, um stündliche Wetterdaten zu verarbeiten:

// Hourly-Daten nach Tagen gruppieren
const days = {};
hourly.time.forEach((t, i) => {
  const dateKey = t.slice(0, 10); // "2025-01-22"

  if (!days[dateKey]) {
    days[dateKey] = { date: dateKey, hours: [] };
  }

  // Erstelle Stunden-Objekt mit allen Feldern
  const hour = { time: t };
  Object.keys(hourly).forEach(key => {
    if (key !== 'time') {
      hour[key] = hourly[key][i];
    }
  });

  // Jetzt extrahieren wir die Werte mit pickField
  const temp = pickField(hour, 'temperature_2m');        // → icon_d2 bevorzugt
  const windSpeed = pickField(hour, 'wind_speed_10m');  // → icon_d2 bevorzugt
  const cape = pickField(hour, 'cape');                 // → best_match (nur dort vorhanden)

  console.log(`${t}: Temp ${temp}°C, Wind ${windSpeed} km/h, CAPE ${cape}`);

  days[dateKey].hours.push({ temp, windSpeed, cape });
});

🛡️ Fehlerbehandlung und Fallbacks

Was passiert, wenn ein Modell ausfällt oder Daten fehlen?

// Beispiel: Wind-Richtung mit Fallback
const windDir = pickField(hour, 'wind_direction_10m');

if (windDir == null) {
  console.warn('Keine Windrichtung verfügbar für', t);
  // Fallback: Verwende vorherigen Wert oder Default
  windDir = previousWindDir || 0;
}

// Dominante Wind-Richtung für den ganzen Tag berechnen
const windDirDom = pickField(dayObj, 'wind_direction_10m_dominant');
const windDirText = windDirDom != null ? degToDir(windDirDom) : '?';

// degToDir() konvertiert Grad in Himmelsrichtung
function degToDir(deg) {
  const dirs = ['N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO',
                'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
  return dirs[Math.round(deg / 22.5) % 16];
}

🚀 Best Practices für Multi-Model APIs

  • Bevorzuge regionale Modelle: icon_d2 ist für Deutschland/Europa präziser als globale Modelle
  • Verwende best_match als Fallback: Wenn spezielle Parameter (wie CAPE) nur dort verfügbar sind
  • Immer null-checks: API-Daten können unvollständig sein – defensiv programmieren!
  • Log die verwendeten Modelle: Für Debugging ist es hilfreich zu wissen, welches Modell für welches Feld verwendet wurde
  • Cache API-Responses: Open-Meteo hat Rate Limits – caching spart Requests

💡 Erweiterte Techniken

Dual-Location Requests

Für das Projekt laden wir gleichzeitig Daten von zwei Standorten (EDXE Flugplatz + Porta Westfalica Hangflug):

// Parallele Fetches
const [resEDXE, resPorta] = await Promise.all([
  fetch(EDXE_API_URL),
  fetch(PORTA_API_URL)
]);

const edxeData = await resEDXE.json();
const portaData = await resPorta.json();

// Verarbeite beide Datasets separat
processEDXEData(edxeData);
processPortaData(portaData);

Höhenwind-Berechnung aus Hourly-Daten

Ein häufiges Problem: wind_speed_120m_max ist keine gültige daily-Variable in Open-Meteo. Die Lösung? Berechne das Tages-Maximum selbst aus hourly-Daten:

// Höhenwind-Maximum für den Tag berechnen
const hourlyByDay = {};

portaHourly.time.forEach((t, i) => {
  const dateKey = t.slice(0, 10);
  if (!hourlyByDay[dateKey]) {
    hourlyByDay[dateKey] = { wind_120m: [], wind_180m: [] };
  }

  const hourObj = {...};  // Erstelle Objekt aus allen hourly-Feldern
  const ws120 = pickField(hourObj, 'wind_speed_120m');
  const ws180 = pickField(hourObj, 'wind_speed_180m');

  if (ws120 != null) hourlyByDay[dateKey].wind_120m.push(ws120);
  if (ws180 != null) hourlyByDay[dateKey].wind_180m.push(ws180);
});

// Tages-Maximum ermitteln
const max120 = Math.max(...hourlyByDay[dateKey].wind_120m);
const max180 = Math.max(...hourlyByDay[dateKey].wind_180m);
const windSpeedAloft = Math.max(max120, max180);

console.log(`Höhenwind Maximum: ${windSpeedAloft} km/h`);

🎯 Zusammenfassung

Mit der pickField() Funktion und Multi-Model Requests kannst du:

  • ✅ Mehrere Wettermodelle gleichzeitig nutzen
  • ✅ Automatisch das beste verfügbare Modell pro Datenfeld wählen
  • ✅ Robuste Fallback-Mechanismen implementieren
  • ✅ Fehlende Daten elegant behandeln
  • ✅ API-Responses flexibel verarbeiten

Der vollständige Code ist im EDXE Thermik-X Projekt zu finden. Schau dir auch die anderen Blog-Artikel zum Projekt an!