Skip to content

Weather Integration

Joël Deffner edited this page Jul 17, 2026 · 1 revision

Weather Integration

FlightMeet uses Open-Meteo (no API key required) for geocoding and forecasts. Wind data is first-class because the audience is paraglider pilots.

The OpenMeteo library

app/Libraries/OpenMeteo.php is a thin wrapper shared by the Weather controller and the meets weather endpoint:

  • geocode(string $name) calls the geocoding API (v1/search, first match, English) and returns { name, country, latitude, longitude } or null when the place is unresolvable or the service fails. Failures are logged, never thrown.
  • forecast(float $lat, float $lon) fetches current conditions plus the daily forecast, returning null on failure.

Forecast variables include, for the current conditions: wind_speed_10m, wind_gusts_10m, wind_direction_10m; daily: wind_speed_10m_max, wind_gusts_10m_max, wind_direction_10m_dominant, alongside temperature and weather codes (rendered via a WMO code map on the frontend).

Endpoints

Method Path Auth Purpose
GET /api/weather?city=... public current + 7-day forecast for a searched city
GET /api/weather/reports public every cached forecast report
POST /api/weather/reports/{id}/refresh public re-fetch a cached report from Open-Meteo
GET /api/meets/{id}/weather public forecast for a meet's stored coordinates

GET /api/weather?city=...200 { "status": "ok", "location": { name, country, latitude, longitude }, "forecast": {...} }. Unknown city → 404; weather service down → 502 (both as { "status": "error", "message": "..." }).

Report caching

When a city search resolves and the forecast succeeds, a weather_reports row is upserted, keyed by (latitude, longitude):

// WeatherReport
{
  "id": 1, "name": "Trier", "country": "Germany",
  "latitude": 49.75565, "longitude": 6.63935,
  "fetchedAt": "2026-07-17T12:00:00+02:00",
  "stale": false,               // true when fetched_at is older than 6 hours
  "forecast": { "current": {...}, "current_units": {...},
                "daily": {...}, "daily_units": {...} }
}
  • Staleness: a report is flagged stale after 6 hours; the UI offers a refresh.
  • Purging: rows older than 4 days are deleted whenever the table is touched.
  • Refresh: POST /api/weather/reports/{id}/refresh re-fetches from Open-Meteo, updates payload and fetched_at, and returns 200 { "report": WeatherReport } (404 unknown id, 502 service down).

These cached reports double as pilot-submitted weather reports: every successful city search leaves a pin, and the Weather page renders them on a Leaflet map with marker clustering.

Meets and geocoding

  • Create: when a meet is created without coordinates, the backend geocodes the spot, falling back to the region. An unresolvable spot leaves latitude/longitude null; this is deliberate and never blocks meet creation.
  • Edit: explicit coordinates win; empty coordinates keep the stored values when spot and region are unchanged, otherwise the spot is re-geocoded.
  • Per-meet forecast: GET /api/meets/{id}/weather uses the stored coordinates; when they are null it returns 409 { "error": "This meet has no coordinates." }, and the frontend's MeetWeatherPanel shows a friendly "no forecast" note instead of an error.
  • On the create/edit form, users can also pick the spot directly on an interactive map (LocationPickerMap), which supplies explicit coordinates and skips geocoding.

Frontend pieces

  • pages/WeatherPage.tsx: city search, current conditions, forecast, and the clustered report map.
  • pages/weather/: ForecastStrip (7-day strip), ReportDetail, WindArrow (direction visualization), wmo.ts (WMO weather-code → label/icon map), wind.ts and format.ts helpers.
  • pages/meets/MeetWeatherPanel.tsx: the per-meet forecast panel on the detail page.
  • components/map/: BaseMap, MarkerClusterLayer, and markers.ts shared between weather and meet location picking.

Clone this wiki locally