diff --git a/src/data_flights.py b/src/data_flights.py index fa3680f..a219720 100644 --- a/src/data_flights.py +++ b/src/data_flights.py @@ -1,4 +1,4 @@ -"""Flugverkehr: OpenSky (primary) + airplanes.live (fallback).""" +"""Flugverkehr: adsb.lol (primary, 10.000+) + adsb.one (fallback).""" import asyncio import logging import time @@ -9,82 +9,71 @@ from fastapi import APIRouter logger = logging.getLogger("globe.flights") router = APIRouter() -_OPENSKY_URL = "https://opensky-network.org/api/states/all" - -# Fallback: airplanes.live Stuetzpunkte (wenige, wichtigste) -_FALLBACK_GRID = [ - (48, 10), (52, 13), (40, -4), (41, 12), (55, 10), # Europa - (40, -74), (34, -118), (42, -88), # USA - (33, 36), (26, 56), (25, 45), # Nahost - (35, 140), (31, 121), (22, 114), # Asien -] - _cache: dict = {"data": None, "ts": 0} _lock = asyncio.Lock() _task = None - -async def _fetch_opensky(client): - """Versucht OpenSky — liefert alle Flugzeuge oder None bei Fehler.""" - try: - resp = await client.get(_OPENSKY_URL) - if resp.status_code == 429: - logger.info("OpenSky: Rate-Limited (429), nutze Fallback") - return None - resp.raise_for_status() - raw = resp.json() - states = raw.get("states", []) - ac = [] - for s in states: - if not s or len(s) < 12 or s[5] is None or s[6] is None: - continue - if s[8]: # on_ground - continue - ac.append({ - "hex": s[0] or "", "flight": (s[1] or "").strip(), - "lat": s[6], "lon": s[5], - "alt_baro": round(s[7] * 3.281) if s[7] else None, - "gs": round(s[9] * 1.944) if s[9] else None, - "track": s[10], "origin": s[2] or "", - }) - logger.info(f"OpenSky: {len(ac)} Flugzeuge") - return ac - except Exception as e: - logger.warning(f"OpenSky: {e}") - return None +# adsb.lol: Ein Call, 10.000+ Flugzeuge, kein Rate-Limit +_PRIMARY_URL = "https://api.adsb.lol/v2/point/30/0/10000" +# adsb.one: Fallback, ~7.000 Flugzeuge +_FALLBACK_URL = "https://api.adsb.one/v2/point/30/0/10000" -async def _fetch_fallback(client): - """Fallback: airplanes.live mit wenigen Stuetzpunkten.""" - seen = {} - for lat, lon in _FALLBACK_GRID: - try: - resp = await client.get(f"https://api.airplanes.live/v2/point/{lat:.0f}/{lon:.0f}/250") - if resp.status_code == 200: - for a in resp.json().get("ac", []): - h = a.get("hex") - if h and h not in seen: - seen[h] = a - except Exception: - pass - await asyncio.sleep(2) # Langsam um Rate-Limits zu vermeiden - logger.info(f"airplanes.live Fallback: {len(seen)} Flugzeuge") - return list(seen.values()) +def _parse_ac(data): + """Parst Flugzeuge aus adsb.lol/adsb.one Format.""" + ac = [] + for a in data.get("ac", []): + lat = a.get("lat") + lon = a.get("lon") + if lat is None or lon is None: + continue + ac.append({ + "hex": a.get("hex", ""), + "flight": (a.get("flight") or "").strip(), + "reg": a.get("r", ""), + "type": a.get("t", ""), + "lat": lat, + "lon": lon, + "alt_baro": a.get("alt_baro"), + "alt_geom": a.get("alt_geom"), + "gs": a.get("gs"), + "track": a.get("track"), + "squawk": a.get("squawk", ""), + }) + return ac async def _fetch_all(): now = time.time() - if _cache["data"] and now - _cache["ts"] < 55: + if _cache["data"] and now - _cache["ts"] < 12: return _cache["data"] async with _lock: - if _cache["data"] and time.time() - _cache["ts"] < 55: + if _cache["data"] and time.time() - _cache["ts"] < 12: return _cache["data"] + ac = None async with httpx.AsyncClient(timeout=20) as client: - ac = await _fetch_opensky(client) - if ac is None: - ac = await _fetch_fallback(client) + # Primary: adsb.lol + try: + resp = await client.get(_PRIMARY_URL) + if resp.status_code == 200: + ac = _parse_ac(resp.json()) + if ac: + logger.info(f"adsb.lol: {len(ac)} Flugzeuge") + except Exception as e: + logger.warning(f"adsb.lol: {e}") + + # Fallback: adsb.one + if not ac: + try: + resp = await client.get(_FALLBACK_URL) + if resp.status_code == 200: + ac = _parse_ac(resp.json()) + if ac: + logger.info(f"adsb.one Fallback: {len(ac)} Flugzeuge") + except Exception as e: + logger.warning(f"adsb.one: {e}") if ac: _cache["data"] = {"ac": ac, "total": len(ac)} @@ -96,20 +85,20 @@ async def _fetch_all(): async def _collector_loop(): - await asyncio.sleep(5) + await asyncio.sleep(3) while True: try: await _fetch_all() except Exception as e: logger.warning(f"Flight collector: {e}") - await asyncio.sleep(60) + await asyncio.sleep(15) def start_flight_collector(): global _task if _task is None or _task.done(): _task = asyncio.create_task(_collector_loop()) - logger.info("Flight collector gestartet (OpenSky + Fallback)") + logger.info("Flight collector gestartet (adsb.lol + adsb.one)") @router.get("/flights")